From 6bf20c78f5b69d40bcc4931df93d29198435ab67 Mon Sep 17 00:00:00 2001 From: zakk Date: Fri, 26 Aug 2005 17:39:27 +0000 Subject: newlines fixed git-svn-id: svn://svn.icculus.org/quake3/trunk@6 edf5b092-35ff-0310-97b2-ce42778d08ea --- code/game/Conscript | 280 +- code/game/ai_chat.c | 2452 +++++----- code/game/ai_chat.h | 122 +- code/game/ai_cmd.c | 3984 ++++++++--------- code/game/ai_cmd.h | 74 +- code/game/ai_dmnet.c | 5220 +++++++++++----------- code/game/ai_dmnet.h | 122 +- code/game/ai_dmq3.c | 10922 ++++++++++++++++++++++----------------------- code/game/ai_dmq3.h | 412 +- code/game/ai_main.c | 3390 +++++++------- code/game/ai_main.h | 598 +-- code/game/ai_team.c | 4160 ++++++++--------- code/game/ai_team.h | 78 +- code/game/ai_vcmd.c | 1100 ++--- code/game/ai_vcmd.h | 72 +- code/game/be_aas.h | 442 +- code/game/be_ai_char.h | 96 +- code/game/be_ai_chat.h | 226 +- code/game/be_ai_gen.h | 66 +- code/game/be_ai_goal.h | 236 +- code/game/be_ai_move.h | 284 +- code/game/be_ai_weap.h | 208 +- code/game/be_ea.h | 132 +- code/game/bg_lib.c | 2648 +++++------ code/game/bg_lib.h | 182 +- code/game/bg_local.h | 166 +- code/game/bg_misc.c | 3208 ++++++------- code/game/bg_pmove.c | 4138 ++++++++--------- code/game/bg_public.h | 1476 +++--- code/game/bg_slidemove.c | 650 +-- code/game/botlib.h | 1032 ++--- code/game/chars.h | 268 +- code/game/g_active.c | 2382 +++++----- code/game/g_arenas.c | 752 ++-- code/game/g_bot.c | 2034 ++++----- code/game/g_client.c | 2688 +++++------ code/game/g_cmds.c | 3402 +++++++------- code/game/g_combat.c | 2390 +++++----- code/game/g_items.c | 2020 ++++----- code/game/g_local.h | 1942 ++++---- code/game/g_main.c | 3664 +++++++-------- code/game/g_mem.c | 122 +- code/game/g_misc.c | 964 ++-- code/game/g_missile.c | 1616 +++---- code/game/g_mover.c | 3224 ++++++------- code/game/g_public.h | 858 ++-- code/game/g_rankings.c | 2270 +++++----- code/game/g_rankings.h | 792 ++-- code/game/g_session.c | 386 +- code/game/g_spawn.c | 1286 +++--- code/game/g_svcmds.c | 1016 ++--- code/game/g_syscalls.asm | 450 +- code/game/g_syscalls.c | 1580 +++---- code/game/g_target.c | 934 ++-- code/game/g_team.c | 2966 ++++++------ code/game/g_team.h | 176 +- code/game/g_trigger.c | 930 ++-- code/game/g_utils.c | 1332 +++--- code/game/g_weapon.c | 2290 +++++----- code/game/game.bat | 170 +- code/game/game.def | 6 +- code/game/game.q3asm | 70 +- code/game/game.sh | 96 +- code/game/game.vcproj | 4264 +++++++++--------- code/game/game_ta.bat | 172 +- code/game/game_ta.q3asm | 70 +- code/game/game_ta.sh | 96 +- code/game/inv.h | 332 +- code/game/match.h | 268 +- code/game/q_math.c | 2616 +++++------ code/game/q_shared.c | 2516 +++++------ code/game/q_shared.h | 2864 ++++++------ code/game/surfaceflags.h | 160 +- code/game/syn.h | 68 +- 74 files changed, 53339 insertions(+), 53339 deletions(-) (limited to 'code/game') diff --git a/code/game/Conscript b/code/game/Conscript index f823cef..5fdf815 100755 --- a/code/game/Conscript +++ b/code/game/Conscript @@ -1,140 +1,140 @@ -# game building -# builds the game for vanilla Q3 and TA - -# there are slight differences between Q3 and TA build: -# -DMISSIONPACK -# the config is passed in the imported variable TARGET_DIR - -# qvm building against native: -# only native has g_syscalls.c -# only qvm has ../game/bg_lib.c -# qvm uses a custom g_syscalls.asm with equ stubs - -Import qw( BASE_CFLAGS TARGET_DIR INSTALL_DIR NO_VM NO_SO CC CXX LINK ); - -$env = new cons( - # the code has the very bad habit of doing things like #include "../ui/ui_shared.h" - # this seems to confuse the dependency analysis, explicit toplevel includes seem to fix - CPPPATH => '#cgame:#game:#q3_ui', - CC => $CC, - CXX => $CXX, - LINK => $LINK, - ENV => { PATH => $ENV{PATH}, HOME => $ENV{HOME} }, - CFLAGS => $BASE_CFLAGS . '-fPIC', - LDFLAGS => '-shared -ldl -lm' -); - -# for TA, use -DMISSIONPACK -%ta_env_hash = $env->copy( - CPPPATH => '#cgame:#game:#ui' - ); -$ta_env_hash{CFLAGS} = '-DMISSIONPACK ' . $ta_env_hash{CFLAGS}; -$ta_env = new cons(%ta_env_hash); - -# qvm building -# we heavily customize the cons environment -$vm_env = new cons( - # the code has the very bad habit of doing things like #include "../ui/ui_shared.h" - # this seems to confuse the dependency analysis, explicit toplevel includes seem to fix - CPPPATH => '#cgame:#game:#q3_ui', - CC => 'q3lcc', - CCCOM => '%CC %CFLAGS %_IFLAGS -c %< -o %>', - SUFOBJ => '.asm', - LINK => 'q3asm', - CFLAGS => '-DQ3_VM -S -Wf-target=bytecode -Wf-g', - # need to know where to find the compiler tools - ENV => { PATH => $ENV{PATH} . ":./qvmtools", }, -); - -# TA qvm building -%vm_ta_env_hash = $vm_env->copy( - CPPPATH => '#cgame:#game:#ui' - ); -$vm_ta_env_hash{CFLAGS} = '-DMISSIONPACK ' . $vm_ta_env_hash{CFLAGS}; -$vm_ta_env = new cons(%vm_ta_env_hash); - -# the file with vmMain function MUST be the first one of the list -@FILES = qw( - g_main.c - ai_chat.c - ai_cmd.c - ai_dmnet.c - ai_dmq3.c - ai_main.c - ai_team.c - ai_vcmd.c - bg_misc.c - bg_pmove.c - bg_slidemove.c - g_active.c - g_arenas.c - g_bot.c - g_client.c - g_cmds.c - g_combat.c - g_items.c - g_mem.c - g_misc.c - g_missile.c - g_mover.c - g_session.c - g_spawn.c - g_svcmds.c - g_target.c - g_team.c - g_trigger.c - g_utils.c - g_weapon.c - q_math.c - q_shared.c - ); -$FILESREF = \@FILES; - -# only in .so -# (VM uses a custom .asm with equ stubs) -@SO_FILES = qw( - g_syscalls.c - ); -$SO_FILESREF = \@SO_FILES; - -# only for VM -@VM_FILES = qw( - bg_lib.c - g_syscalls.asm - ); -$VM_FILESREF = \@VM_FILES; - -# FIXME CPU string? -# NOTE: $env $ta_env and $vm_env $vm_ta_env may not be necessary -# we could alter the $env and $ta_env based on $TARGET_DIR -# doing it this way to ensure homogeneity with cgame building -if ($TARGET_DIR eq 'Q3') -{ - if ($NO_SO eq 0) - { - Program $env 'qagamei386.so', @$FILESREF, @$SO_FILESREF; - Install $env $INSTALL_DIR, 'qagamei386.so'; - } - if ($NO_VM eq 0) - { - Depends $vm_env 'qagame.qvm', '#qvmtools/q3lcc'; - Depends $vm_env 'qagame.qvm', '#qvmtools/q3asm'; - Program $vm_env 'qagame.qvm', @$FILESREF, @$VM_FILESREF; - Install $vm_env $INSTALL_DIR . '/vm', 'qagame.qvm'; - } -} -else -{ - if ($NO_SO eq 0) - { - Program $ta_env 'qagamei386.so', @$FILESREF, @$SO_FILESREF; - Install $ta_env $INSTALL_DIR, 'qagamei386.so'; - } - if ($NO_VM eq 0) - { - Depends $vm_env 'qagame.qvm', '#qvmtools/q3lcc'; - Depends $vm_env 'qagame.qvm', '#qvmtools/q3asm'; - Program $vm_ta_env 'qagame.qvm', @$FILESREF, @$VM_FILESREF; - Install $vm_ta_env $INSTALL_DIR . '/vm', 'qagame.qvm'; - } -} +# game building +# builds the game for vanilla Q3 and TA + +# there are slight differences between Q3 and TA build: +# -DMISSIONPACK +# the config is passed in the imported variable TARGET_DIR + +# qvm building against native: +# only native has g_syscalls.c +# only qvm has ../game/bg_lib.c +# qvm uses a custom g_syscalls.asm with equ stubs + +Import qw( BASE_CFLAGS TARGET_DIR INSTALL_DIR NO_VM NO_SO CC CXX LINK ); + +$env = new cons( + # the code has the very bad habit of doing things like #include "../ui/ui_shared.h" + # this seems to confuse the dependency analysis, explicit toplevel includes seem to fix + CPPPATH => '#cgame:#game:#q3_ui', + CC => $CC, + CXX => $CXX, + LINK => $LINK, + ENV => { PATH => $ENV{PATH}, HOME => $ENV{HOME} }, + CFLAGS => $BASE_CFLAGS . '-fPIC', + LDFLAGS => '-shared -ldl -lm' +); + +# for TA, use -DMISSIONPACK +%ta_env_hash = $env->copy( + CPPPATH => '#cgame:#game:#ui' + ); +$ta_env_hash{CFLAGS} = '-DMISSIONPACK ' . $ta_env_hash{CFLAGS}; +$ta_env = new cons(%ta_env_hash); + +# qvm building +# we heavily customize the cons environment +$vm_env = new cons( + # the code has the very bad habit of doing things like #include "../ui/ui_shared.h" + # this seems to confuse the dependency analysis, explicit toplevel includes seem to fix + CPPPATH => '#cgame:#game:#q3_ui', + CC => 'q3lcc', + CCCOM => '%CC %CFLAGS %_IFLAGS -c %< -o %>', + SUFOBJ => '.asm', + LINK => 'q3asm', + CFLAGS => '-DQ3_VM -S -Wf-target=bytecode -Wf-g', + # need to know where to find the compiler tools + ENV => { PATH => $ENV{PATH} . ":./qvmtools", }, +); + +# TA qvm building +%vm_ta_env_hash = $vm_env->copy( + CPPPATH => '#cgame:#game:#ui' + ); +$vm_ta_env_hash{CFLAGS} = '-DMISSIONPACK ' . $vm_ta_env_hash{CFLAGS}; +$vm_ta_env = new cons(%vm_ta_env_hash); + +# the file with vmMain function MUST be the first one of the list +@FILES = qw( + g_main.c + ai_chat.c + ai_cmd.c + ai_dmnet.c + ai_dmq3.c + ai_main.c + ai_team.c + ai_vcmd.c + bg_misc.c + bg_pmove.c + bg_slidemove.c + g_active.c + g_arenas.c + g_bot.c + g_client.c + g_cmds.c + g_combat.c + g_items.c + g_mem.c + g_misc.c + g_missile.c + g_mover.c + g_session.c + g_spawn.c + g_svcmds.c + g_target.c + g_team.c + g_trigger.c + g_utils.c + g_weapon.c + q_math.c + q_shared.c + ); +$FILESREF = \@FILES; + +# only in .so +# (VM uses a custom .asm with equ stubs) +@SO_FILES = qw( + g_syscalls.c + ); +$SO_FILESREF = \@SO_FILES; + +# only for VM +@VM_FILES = qw( + bg_lib.c + g_syscalls.asm + ); +$VM_FILESREF = \@VM_FILES; + +# FIXME CPU string? +# NOTE: $env $ta_env and $vm_env $vm_ta_env may not be necessary +# we could alter the $env and $ta_env based on $TARGET_DIR +# doing it this way to ensure homogeneity with cgame building +if ($TARGET_DIR eq 'Q3') +{ + if ($NO_SO eq 0) + { + Program $env 'qagamei386.so', @$FILESREF, @$SO_FILESREF; + Install $env $INSTALL_DIR, 'qagamei386.so'; + } + if ($NO_VM eq 0) + { + Depends $vm_env 'qagame.qvm', '#qvmtools/q3lcc'; + Depends $vm_env 'qagame.qvm', '#qvmtools/q3asm'; + Program $vm_env 'qagame.qvm', @$FILESREF, @$VM_FILESREF; + Install $vm_env $INSTALL_DIR . '/vm', 'qagame.qvm'; + } +} +else +{ + if ($NO_SO eq 0) + { + Program $ta_env 'qagamei386.so', @$FILESREF, @$SO_FILESREF; + Install $ta_env $INSTALL_DIR, 'qagamei386.so'; + } + if ($NO_VM eq 0) + { + Depends $vm_env 'qagame.qvm', '#qvmtools/q3lcc'; + Depends $vm_env 'qagame.qvm', '#qvmtools/q3asm'; + Program $vm_ta_env 'qagame.qvm', @$FILESREF, @$VM_FILESREF; + Install $vm_ta_env $INSTALL_DIR . '/vm', 'qagame.qvm'; + } +} diff --git a/code/game/ai_chat.c b/code/game/ai_chat.c index b5485e9..ae2554e 100755 --- a/code/game/ai_chat.c +++ b/code/game/ai_chat.c @@ -1,1226 +1,1226 @@ -/* -=========================================================================== -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 Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// - -/***************************************************************************** - * name: ai_chat.c - * - * desc: Quake3 bot AI - * - * $Archive: /MissionPack/code/game/ai_chat.c $ - * - *****************************************************************************/ - -#include "g_local.h" -#include "botlib.h" -#include "be_aas.h" -#include "be_ea.h" -#include "be_ai_char.h" -#include "be_ai_chat.h" -#include "be_ai_gen.h" -#include "be_ai_goal.h" -#include "be_ai_move.h" -#include "be_ai_weap.h" -// -#include "ai_main.h" -#include "ai_dmq3.h" -#include "ai_chat.h" -#include "ai_cmd.h" -#include "ai_dmnet.h" -// -#include "chars.h" //characteristics -#include "inv.h" //indexes into the inventory -#include "syn.h" //synonyms -#include "match.h" //string matching types and vars - -// for the voice chats -#ifdef MISSIONPACK // bk001205 -#include "../../ui/menudef.h" -#endif - -#define TIME_BETWEENCHATTING 25 - - -/* -================== -BotNumActivePlayers -================== -*/ -int BotNumActivePlayers(void) { - int i, num; - char buf[MAX_INFO_STRING]; - static int maxclients; - - if (!maxclients) - maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); - - num = 0; - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { - trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); - //if no config string or no name - if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue; - //skip spectators - if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue; - // - num++; - } - return num; -} - -/* -================== -BotIsFirstInRankings -================== -*/ -int BotIsFirstInRankings(bot_state_t *bs) { - int i, score; - char buf[MAX_INFO_STRING]; - static int maxclients; - playerState_t ps; - - if (!maxclients) - maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); - - score = bs->cur_ps.persistant[PERS_SCORE]; - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { - trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); - //if no config string or no name - if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue; - //skip spectators - if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue; - // - BotAI_GetClientState(i, &ps); - if (score < ps.persistant[PERS_SCORE]) return qfalse; - } - return qtrue; -} - -/* -================== -BotIsLastInRankings -================== -*/ -int BotIsLastInRankings(bot_state_t *bs) { - int i, score; - char buf[MAX_INFO_STRING]; - static int maxclients; - playerState_t ps; - - if (!maxclients) - maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); - - score = bs->cur_ps.persistant[PERS_SCORE]; - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { - trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); - //if no config string or no name - if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue; - //skip spectators - if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue; - // - BotAI_GetClientState(i, &ps); - if (score > ps.persistant[PERS_SCORE]) return qfalse; - } - return qtrue; -} - -/* -================== -BotFirstClientInRankings -================== -*/ -char *BotFirstClientInRankings(void) { - int i, bestscore, bestclient; - char buf[MAX_INFO_STRING]; - static char name[32]; - static int maxclients; - playerState_t ps; - - if (!maxclients) - maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); - - bestscore = -999999; - bestclient = 0; - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { - trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); - //if no config string or no name - if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue; - //skip spectators - if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue; - // - BotAI_GetClientState(i, &ps); - if (ps.persistant[PERS_SCORE] > bestscore) { - bestscore = ps.persistant[PERS_SCORE]; - bestclient = i; - } - } - EasyClientName(bestclient, name, 32); - return name; -} - -/* -================== -BotLastClientInRankings -================== -*/ -char *BotLastClientInRankings(void) { - int i, worstscore, bestclient; - char buf[MAX_INFO_STRING]; - static char name[32]; - static int maxclients; - playerState_t ps; - - if (!maxclients) - maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); - - worstscore = 999999; - bestclient = 0; - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { - trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); - //if no config string or no name - if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue; - //skip spectators - if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue; - // - BotAI_GetClientState(i, &ps); - if (ps.persistant[PERS_SCORE] < worstscore) { - worstscore = ps.persistant[PERS_SCORE]; - bestclient = i; - } - } - EasyClientName(bestclient, name, 32); - return name; -} - -/* -================== -BotRandomOpponentName -================== -*/ -char *BotRandomOpponentName(bot_state_t *bs) { - int i, count; - char buf[MAX_INFO_STRING]; - int opponents[MAX_CLIENTS], numopponents; - static int maxclients; - static char name[32]; - - if (!maxclients) - maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); - - numopponents = 0; - opponents[0] = 0; - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { - if (i == bs->client) continue; - // - trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); - //if no config string or no name - if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue; - //skip spectators - if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue; - //skip team mates - if (BotSameTeam(bs, i)) continue; - // - opponents[numopponents] = i; - numopponents++; - } - count = random() * numopponents; - for (i = 0; i < numopponents; i++) { - count--; - if (count <= 0) { - EasyClientName(opponents[i], name, sizeof(name)); - return name; - } - } - EasyClientName(opponents[0], name, sizeof(name)); - return name; -} - -/* -================== -BotMapTitle -================== -*/ - -char *BotMapTitle(void) { - char info[1024]; - static char mapname[128]; - - trap_GetServerinfo(info, sizeof(info)); - - strncpy(mapname, Info_ValueForKey( info, "mapname" ), sizeof(mapname)-1); - mapname[sizeof(mapname)-1] = '\0'; - - return mapname; -} - - -/* -================== -BotWeaponNameForMeansOfDeath -================== -*/ - -char *BotWeaponNameForMeansOfDeath(int mod) { - switch(mod) { - case MOD_SHOTGUN: return "Shotgun"; - case MOD_GAUNTLET: return "Gauntlet"; - case MOD_MACHINEGUN: return "Machinegun"; - case MOD_GRENADE: - case MOD_GRENADE_SPLASH: return "Grenade Launcher"; - case MOD_ROCKET: - case MOD_ROCKET_SPLASH: return "Rocket Launcher"; - case MOD_PLASMA: - case MOD_PLASMA_SPLASH: return "Plasmagun"; - case MOD_RAILGUN: return "Railgun"; - case MOD_LIGHTNING: return "Lightning Gun"; - case MOD_BFG: - case MOD_BFG_SPLASH: return "BFG10K"; -#ifdef MISSIONPACK - case MOD_NAIL: return "Nailgun"; - case MOD_CHAINGUN: return "Chaingun"; - case MOD_PROXIMITY_MINE: return "Proximity Launcher"; - case MOD_KAMIKAZE: return "Kamikaze"; - case MOD_JUICED: return "Prox mine"; -#endif - case MOD_GRAPPLE: return "Grapple"; - default: return "[unknown weapon]"; - } -} - -/* -================== -BotRandomWeaponName -================== -*/ -char *BotRandomWeaponName(void) { - int rnd; - -#ifdef MISSIONPACK - rnd = random() * 11.9; -#else - rnd = random() * 8.9; -#endif - switch(rnd) { - case 0: return "Gauntlet"; - case 1: return "Shotgun"; - case 2: return "Machinegun"; - case 3: return "Grenade Launcher"; - case 4: return "Rocket Launcher"; - case 5: return "Plasmagun"; - case 6: return "Railgun"; - case 7: return "Lightning Gun"; -#ifdef MISSIONPACK - case 8: return "Nailgun"; - case 9: return "Chaingun"; - case 10: return "Proximity Launcher"; -#endif - default: return "BFG10K"; - } -} - -/* -================== -BotVisibleEnemies -================== -*/ -int BotVisibleEnemies(bot_state_t *bs) { - float vis; - int i; - aas_entityinfo_t entinfo; - - for (i = 0; i < MAX_CLIENTS; i++) { - - if (i == bs->client) continue; - // - BotEntityInfo(i, &entinfo); - // - if (!entinfo.valid) continue; - //if the enemy isn't dead and the enemy isn't the bot self - if (EntityIsDead(&entinfo) || entinfo.number == bs->entitynum) continue; - //if the enemy is invisible and not shooting - if (EntityIsInvisible(&entinfo) && !EntityIsShooting(&entinfo)) { - continue; - } - //if on the same team - if (BotSameTeam(bs, i)) continue; - //check if the enemy is visible - vis = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, i); - if (vis > 0) return qtrue; - } - return qfalse; -} - -/* -================== -BotValidChatPosition -================== -*/ -int BotValidChatPosition(bot_state_t *bs) { - vec3_t point, start, end, mins, maxs; - bsp_trace_t trace; - - //if the bot is dead all positions are valid - if (BotIsDead(bs)) return qtrue; - //never start chatting with a powerup - if (bs->inventory[INVENTORY_QUAD] || - bs->inventory[INVENTORY_HASTE] || - bs->inventory[INVENTORY_INVISIBILITY] || - bs->inventory[INVENTORY_REGEN] || - bs->inventory[INVENTORY_FLIGHT]) return qfalse; - //must be on the ground - //if (bs->cur_ps.groundEntityNum != ENTITYNUM_NONE) return qfalse; - //do not chat if in lava or slime - VectorCopy(bs->origin, point); - point[2] -= 24; - if (trap_PointContents(point,bs->entitynum) & (CONTENTS_LAVA|CONTENTS_SLIME)) return qfalse; - //do not chat if under water - VectorCopy(bs->origin, point); - point[2] += 32; - if (trap_PointContents(point,bs->entitynum) & MASK_WATER) return qfalse; - //must be standing on the world entity - VectorCopy(bs->origin, start); - VectorCopy(bs->origin, end); - start[2] += 1; - end[2] -= 10; - trap_AAS_PresenceTypeBoundingBox(PRESENCE_CROUCH, mins, maxs); - BotAI_Trace(&trace, start, mins, maxs, end, bs->client, MASK_SOLID); - if (trace.ent != ENTITYNUM_WORLD) return qfalse; - //the bot is in a position where it can chat - return qtrue; -} - -/* -================== -BotChat_EnterGame -================== -*/ -int BotChat_EnterGame(bot_state_t *bs) { - char name[32]; - float rnd; - - if (bot_nochat.integer) return qfalse; - if (bs->lastchat_time > FloatTime() - TIME_BETWEENCHATTING) return qfalse; - //don't chat in teamplay - if (TeamPlayIsOn()) return qfalse; - // don't chat in tournament mode - if (gametype == GT_TOURNAMENT) return qfalse; - rnd = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_ENTEREXITGAME, 0, 1); - if (!bot_fastchat.integer) { - if (random() > rnd) return qfalse; - } - if (BotNumActivePlayers() <= 1) return qfalse; - if (!BotValidChatPosition(bs)) return qfalse; - BotAI_BotInitialChat(bs, "game_enter", - EasyClientName(bs->client, name, 32), // 0 - BotRandomOpponentName(bs), // 1 - "[invalid var]", // 2 - "[invalid var]", // 3 - BotMapTitle(), // 4 - NULL); - bs->lastchat_time = FloatTime(); - bs->chatto = CHAT_ALL; - return qtrue; -} - -/* -================== -BotChat_ExitGame -================== -*/ -int BotChat_ExitGame(bot_state_t *bs) { - char name[32]; - float rnd; - - if (bot_nochat.integer) return qfalse; - if (bs->lastchat_time > FloatTime() - TIME_BETWEENCHATTING) return qfalse; - //don't chat in teamplay - if (TeamPlayIsOn()) return qfalse; - // don't chat in tournament mode - if (gametype == GT_TOURNAMENT) return qfalse; - rnd = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_ENTEREXITGAME, 0, 1); - if (!bot_fastchat.integer) { - if (random() > rnd) return qfalse; - } - if (BotNumActivePlayers() <= 1) return qfalse; - // - BotAI_BotInitialChat(bs, "game_exit", - EasyClientName(bs->client, name, 32), // 0 - BotRandomOpponentName(bs), // 1 - "[invalid var]", // 2 - "[invalid var]", // 3 - BotMapTitle(), // 4 - NULL); - bs->lastchat_time = FloatTime(); - bs->chatto = CHAT_ALL; - return qtrue; -} - -/* -================== -BotChat_StartLevel -================== -*/ -int BotChat_StartLevel(bot_state_t *bs) { - char name[32]; - float rnd; - - if (bot_nochat.integer) return qfalse; - if (BotIsObserver(bs)) return qfalse; - if (bs->lastchat_time > FloatTime() - TIME_BETWEENCHATTING) return qfalse; - //don't chat in teamplay - if (TeamPlayIsOn()) { - trap_EA_Command(bs->client, "vtaunt"); - return qfalse; - } - // don't chat in tournament mode - if (gametype == GT_TOURNAMENT) return qfalse; - rnd = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_STARTENDLEVEL, 0, 1); - if (!bot_fastchat.integer) { - if (random() > rnd) return qfalse; - } - if (BotNumActivePlayers() <= 1) return qfalse; - BotAI_BotInitialChat(bs, "level_start", - EasyClientName(bs->client, name, 32), // 0 - NULL); - bs->lastchat_time = FloatTime(); - bs->chatto = CHAT_ALL; - return qtrue; -} - -/* -================== -BotChat_EndLevel -================== -*/ -int BotChat_EndLevel(bot_state_t *bs) { - char name[32]; - float rnd; - - if (bot_nochat.integer) return qfalse; - if (BotIsObserver(bs)) return qfalse; - if (bs->lastchat_time > FloatTime() - TIME_BETWEENCHATTING) return qfalse; - // teamplay - if (TeamPlayIsOn()) - { - if (BotIsFirstInRankings(bs)) { - trap_EA_Command(bs->client, "vtaunt"); - } - return qtrue; - } - // don't chat in tournament mode - if (gametype == GT_TOURNAMENT) return qfalse; - rnd = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_STARTENDLEVEL, 0, 1); - if (!bot_fastchat.integer) { - if (random() > rnd) return qfalse; - } - if (BotNumActivePlayers() <= 1) return qfalse; - // - if (BotIsFirstInRankings(bs)) { - BotAI_BotInitialChat(bs, "level_end_victory", - EasyClientName(bs->client, name, 32), // 0 - BotRandomOpponentName(bs), // 1 - "[invalid var]", // 2 - BotLastClientInRankings(), // 3 - BotMapTitle(), // 4 - NULL); - } - else if (BotIsLastInRankings(bs)) { - BotAI_BotInitialChat(bs, "level_end_lose", - EasyClientName(bs->client, name, 32), // 0 - BotRandomOpponentName(bs), // 1 - BotFirstClientInRankings(), // 2 - "[invalid var]", // 3 - BotMapTitle(), // 4 - NULL); - } - else { - BotAI_BotInitialChat(bs, "level_end", - EasyClientName(bs->client, name, 32), // 0 - BotRandomOpponentName(bs), // 1 - BotFirstClientInRankings(), // 2 - BotLastClientInRankings(), // 3 - BotMapTitle(), // 4 - NULL); - } - bs->lastchat_time = FloatTime(); - bs->chatto = CHAT_ALL; - return qtrue; -} - -/* -================== -BotChat_Death -================== -*/ -int BotChat_Death(bot_state_t *bs) { - char name[32]; - float rnd; - - if (bot_nochat.integer) return qfalse; - if (bs->lastchat_time > FloatTime() - TIME_BETWEENCHATTING) return qfalse; - rnd = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_DEATH, 0, 1); - // don't chat in tournament mode - if (gametype == GT_TOURNAMENT) return qfalse; - //if fast chatting is off - if (!bot_fastchat.integer) { - if (random() > rnd) return qfalse; - } - if (BotNumActivePlayers() <= 1) return qfalse; - // - if (bs->lastkilledby >= 0 && bs->lastkilledby < MAX_CLIENTS) - EasyClientName(bs->lastkilledby, name, 32); - else - strcpy(name, "[world]"); - // - if (TeamPlayIsOn() && BotSameTeam(bs, bs->lastkilledby)) { - if (bs->lastkilledby == bs->client) return qfalse; - BotAI_BotInitialChat(bs, "death_teammate", name, NULL); - bs->chatto = CHAT_TEAM; - } - else - { - //teamplay - if (TeamPlayIsOn()) { - trap_EA_Command(bs->client, "vtaunt"); - return qtrue; - } - // - if (bs->botdeathtype == MOD_WATER) - BotAI_BotInitialChat(bs, "death_drown", BotRandomOpponentName(bs), NULL); - else if (bs->botdeathtype == MOD_SLIME) - BotAI_BotInitialChat(bs, "death_slime", BotRandomOpponentName(bs), NULL); - else if (bs->botdeathtype == MOD_LAVA) - BotAI_BotInitialChat(bs, "death_lava", BotRandomOpponentName(bs), NULL); - else if (bs->botdeathtype == MOD_FALLING) - BotAI_BotInitialChat(bs, "death_cratered", BotRandomOpponentName(bs), NULL); - else if (bs->botsuicide || //all other suicides by own weapon - bs->botdeathtype == MOD_CRUSH || - bs->botdeathtype == MOD_SUICIDE || - bs->botdeathtype == MOD_TARGET_LASER || - bs->botdeathtype == MOD_TRIGGER_HURT || - bs->botdeathtype == MOD_UNKNOWN) - BotAI_BotInitialChat(bs, "death_suicide", BotRandomOpponentName(bs), NULL); - else if (bs->botdeathtype == MOD_TELEFRAG) - BotAI_BotInitialChat(bs, "death_telefrag", name, NULL); -#ifdef MISSIONPACK - else if (bs->botdeathtype == MOD_KAMIKAZE && trap_BotNumInitialChats(bs->cs, "death_kamikaze")) - BotAI_BotInitialChat(bs, "death_kamikaze", name, NULL); -#endif - else { - if ((bs->botdeathtype == MOD_GAUNTLET || - bs->botdeathtype == MOD_RAILGUN || - bs->botdeathtype == MOD_BFG || - bs->botdeathtype == MOD_BFG_SPLASH) && random() < 0.5) { - - if (bs->botdeathtype == MOD_GAUNTLET) - BotAI_BotInitialChat(bs, "death_gauntlet", - name, // 0 - BotWeaponNameForMeansOfDeath(bs->botdeathtype), // 1 - NULL); - else if (bs->botdeathtype == MOD_RAILGUN) - BotAI_BotInitialChat(bs, "death_rail", - name, // 0 - BotWeaponNameForMeansOfDeath(bs->botdeathtype), // 1 - NULL); - else - BotAI_BotInitialChat(bs, "death_bfg", - name, // 0 - BotWeaponNameForMeansOfDeath(bs->botdeathtype), // 1 - NULL); - } - //choose between insult and praise - else if (random() < trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_INSULT, 0, 1)) { - BotAI_BotInitialChat(bs, "death_insult", - name, // 0 - BotWeaponNameForMeansOfDeath(bs->botdeathtype), // 1 - NULL); - } - else { - BotAI_BotInitialChat(bs, "death_praise", - name, // 0 - BotWeaponNameForMeansOfDeath(bs->botdeathtype), // 1 - NULL); - } - } - bs->chatto = CHAT_ALL; - } - bs->lastchat_time = FloatTime(); - return qtrue; -} - -/* -================== -BotChat_Kill -================== -*/ -int BotChat_Kill(bot_state_t *bs) { - char name[32]; - float rnd; - - if (bot_nochat.integer) return qfalse; - if (bs->lastchat_time > FloatTime() - TIME_BETWEENCHATTING) return qfalse; - rnd = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_KILL, 0, 1); - // don't chat in tournament mode - if (gametype == GT_TOURNAMENT) return qfalse; - //if fast chat is off - if (!bot_fastchat.integer) { - if (random() > rnd) return qfalse; - } - if (bs->lastkilledplayer == bs->client) return qfalse; - if (BotNumActivePlayers() <= 1) return qfalse; - if (!BotValidChatPosition(bs)) return qfalse; - // - if (BotVisibleEnemies(bs)) return qfalse; - // - EasyClientName(bs->lastkilledplayer, name, 32); - // - bs->chatto = CHAT_ALL; - if (TeamPlayIsOn() && BotSameTeam(bs, bs->lastkilledplayer)) { - BotAI_BotInitialChat(bs, "kill_teammate", name, NULL); - bs->chatto = CHAT_TEAM; - } - else - { - //don't chat in teamplay - if (TeamPlayIsOn()) { - trap_EA_Command(bs->client, "vtaunt"); - return qfalse; // don't wait - } - // - if (bs->enemydeathtype == MOD_GAUNTLET) { - BotAI_BotInitialChat(bs, "kill_gauntlet", name, NULL); - } - else if (bs->enemydeathtype == MOD_RAILGUN) { - BotAI_BotInitialChat(bs, "kill_rail", name, NULL); - } - else if (bs->enemydeathtype == MOD_TELEFRAG) { - BotAI_BotInitialChat(bs, "kill_telefrag", name, NULL); - } -#ifdef MISSIONPACK - else if (bs->botdeathtype == MOD_KAMIKAZE && trap_BotNumInitialChats(bs->cs, "kill_kamikaze")) - BotAI_BotInitialChat(bs, "kill_kamikaze", name, NULL); -#endif - //choose between insult and praise - else if (random() < trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_INSULT, 0, 1)) { - BotAI_BotInitialChat(bs, "kill_insult", name, NULL); - } - else { - BotAI_BotInitialChat(bs, "kill_praise", name, NULL); - } - } - bs->lastchat_time = FloatTime(); - return qtrue; -} - -/* -================== -BotChat_EnemySuicide -================== -*/ -int BotChat_EnemySuicide(bot_state_t *bs) { - char name[32]; - float rnd; - - if (bot_nochat.integer) return qfalse; - if (bs->lastchat_time > FloatTime() - TIME_BETWEENCHATTING) return qfalse; - if (BotNumActivePlayers() <= 1) return qfalse; - // - rnd = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_KILL, 0, 1); - //don't chat in teamplay - if (TeamPlayIsOn()) return qfalse; - // don't chat in tournament mode - if (gametype == GT_TOURNAMENT) return qfalse; - //if fast chat is off - if (!bot_fastchat.integer) { - if (random() > rnd) return qfalse; - } - if (!BotValidChatPosition(bs)) return qfalse; - // - if (BotVisibleEnemies(bs)) return qfalse; - // - if (bs->enemy >= 0) EasyClientName(bs->enemy, name, 32); - else strcpy(name, ""); - BotAI_BotInitialChat(bs, "enemy_suicide", name, NULL); - bs->lastchat_time = FloatTime(); - bs->chatto = CHAT_ALL; - return qtrue; -} - -/* -================== -BotChat_HitTalking -================== -*/ -int BotChat_HitTalking(bot_state_t *bs) { - char name[32], *weap; - int lasthurt_client; - float rnd; - - if (bot_nochat.integer) return qfalse; - if (bs->lastchat_time > FloatTime() - TIME_BETWEENCHATTING) return qfalse; - if (BotNumActivePlayers() <= 1) return qfalse; - lasthurt_client = g_entities[bs->client].client->lasthurt_client; - if (!lasthurt_client) return qfalse; - if (lasthurt_client == bs->client) return qfalse; - // - if (lasthurt_client < 0 || lasthurt_client >= MAX_CLIENTS) return qfalse; - // - rnd = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_HITTALKING, 0, 1); - //don't chat in teamplay - if (TeamPlayIsOn()) return qfalse; - // don't chat in tournament mode - if (gametype == GT_TOURNAMENT) return qfalse; - //if fast chat is off - if (!bot_fastchat.integer) { - if (random() > rnd * 0.5) return qfalse; - } - if (!BotValidChatPosition(bs)) return qfalse; - // - ClientName(g_entities[bs->client].client->lasthurt_client, name, sizeof(name)); - weap = BotWeaponNameForMeansOfDeath(g_entities[bs->client].client->lasthurt_client); - // - BotAI_BotInitialChat(bs, "hit_talking", name, weap, NULL); - bs->lastchat_time = FloatTime(); - bs->chatto = CHAT_ALL; - return qtrue; -} - -/* -================== -BotChat_HitNoDeath -================== -*/ -int BotChat_HitNoDeath(bot_state_t *bs) { - char name[32], *weap; - float rnd; - int lasthurt_client; - aas_entityinfo_t entinfo; - - lasthurt_client = g_entities[bs->client].client->lasthurt_client; - if (!lasthurt_client) return qfalse; - if (lasthurt_client == bs->client) return qfalse; - // - if (lasthurt_client < 0 || lasthurt_client >= MAX_CLIENTS) return qfalse; - // - if (bot_nochat.integer) return qfalse; - if (bs->lastchat_time > FloatTime() - TIME_BETWEENCHATTING) return qfalse; - if (BotNumActivePlayers() <= 1) return qfalse; - rnd = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_HITNODEATH, 0, 1); - //don't chat in teamplay - if (TeamPlayIsOn()) return qfalse; - // don't chat in tournament mode - if (gametype == GT_TOURNAMENT) return qfalse; - //if fast chat is off - if (!bot_fastchat.integer) { - if (random() > rnd * 0.5) return qfalse; - } - if (!BotValidChatPosition(bs)) return qfalse; - // - if (BotVisibleEnemies(bs)) return qfalse; - // - BotEntityInfo(bs->enemy, &entinfo); - if (EntityIsShooting(&entinfo)) return qfalse; - // - ClientName(lasthurt_client, name, sizeof(name)); - weap = BotWeaponNameForMeansOfDeath(g_entities[bs->client].client->lasthurt_mod); - // - BotAI_BotInitialChat(bs, "hit_nodeath", name, weap, NULL); - bs->lastchat_time = FloatTime(); - bs->chatto = CHAT_ALL; - return qtrue; -} - -/* -================== -BotChat_HitNoKill -================== -*/ -int BotChat_HitNoKill(bot_state_t *bs) { - char name[32], *weap; - float rnd; - aas_entityinfo_t entinfo; - - if (bot_nochat.integer) return qfalse; - if (bs->lastchat_time > FloatTime() - TIME_BETWEENCHATTING) return qfalse; - if (BotNumActivePlayers() <= 1) return qfalse; - rnd = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_HITNOKILL, 0, 1); - //don't chat in teamplay - if (TeamPlayIsOn()) return qfalse; - // don't chat in tournament mode - if (gametype == GT_TOURNAMENT) return qfalse; - //if fast chat is off - if (!bot_fastchat.integer) { - if (random() > rnd * 0.5) return qfalse; - } - if (!BotValidChatPosition(bs)) return qfalse; - // - if (BotVisibleEnemies(bs)) return qfalse; - // - BotEntityInfo(bs->enemy, &entinfo); - if (EntityIsShooting(&entinfo)) return qfalse; - // - ClientName(bs->enemy, name, sizeof(name)); - weap = BotWeaponNameForMeansOfDeath(g_entities[bs->enemy].client->lasthurt_mod); - // - BotAI_BotInitialChat(bs, "hit_nokill", name, weap, NULL); - bs->lastchat_time = FloatTime(); - bs->chatto = CHAT_ALL; - return qtrue; -} - -/* -================== -BotChat_Random -================== -*/ -int BotChat_Random(bot_state_t *bs) { - float rnd; - char name[32]; - - if (bot_nochat.integer) return qfalse; - if (BotIsObserver(bs)) return qfalse; - if (bs->lastchat_time > FloatTime() - TIME_BETWEENCHATTING) return qfalse; - // don't chat in tournament mode - if (gametype == GT_TOURNAMENT) return qfalse; - //don't chat when doing something important :) - if (bs->ltgtype == LTG_TEAMHELP || - bs->ltgtype == LTG_TEAMACCOMPANY || - bs->ltgtype == LTG_RUSHBASE) return qfalse; - // - rnd = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_RANDOM, 0, 1); - if (random() > bs->thinktime * 0.1) return qfalse; - if (!bot_fastchat.integer) { - if (random() > rnd) return qfalse; - if (random() > 0.25) return qfalse; - } - if (BotNumActivePlayers() <= 1) return qfalse; - // - if (!BotValidChatPosition(bs)) return qfalse; - // - if (BotVisibleEnemies(bs)) return qfalse; - // - if (bs->lastkilledplayer == bs->client) { - strcpy(name, BotRandomOpponentName(bs)); - } - else { - EasyClientName(bs->lastkilledplayer, name, sizeof(name)); - } - if (TeamPlayIsOn()) { - trap_EA_Command(bs->client, "vtaunt"); - return qfalse; // don't wait - } - // - if (random() < trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_MISC, 0, 1)) { - BotAI_BotInitialChat(bs, "random_misc", - BotRandomOpponentName(bs), // 0 - name, // 1 - "[invalid var]", // 2 - "[invalid var]", // 3 - BotMapTitle(), // 4 - BotRandomWeaponName(), // 5 - NULL); - } - else { - BotAI_BotInitialChat(bs, "random_insult", - BotRandomOpponentName(bs), // 0 - name, // 1 - "[invalid var]", // 2 - "[invalid var]", // 3 - BotMapTitle(), // 4 - BotRandomWeaponName(), // 5 - NULL); - } - bs->lastchat_time = FloatTime(); - bs->chatto = CHAT_ALL; - return qtrue; -} - -/* -================== -BotChatTime -================== -*/ -float BotChatTime(bot_state_t *bs) { - int cpm; - - cpm = trap_Characteristic_BInteger(bs->character, CHARACTERISTIC_CHAT_CPM, 1, 4000); - - return 2.0; //(float) trap_BotChatLength(bs->cs) * 30 / cpm; -} - -/* -================== -BotChatTest -================== -*/ -void BotChatTest(bot_state_t *bs) { - - char name[32]; - char *weap; - int num, i; - - num = trap_BotNumInitialChats(bs->cs, "game_enter"); - for (i = 0; i < num; i++) - { - BotAI_BotInitialChat(bs, "game_enter", - EasyClientName(bs->client, name, 32), // 0 - BotRandomOpponentName(bs), // 1 - "[invalid var]", // 2 - "[invalid var]", // 3 - BotMapTitle(), // 4 - NULL); - trap_BotEnterChat(bs->cs, 0, CHAT_ALL); - } - num = trap_BotNumInitialChats(bs->cs, "game_exit"); - for (i = 0; i < num; i++) - { - BotAI_BotInitialChat(bs, "game_exit", - EasyClientName(bs->client, name, 32), // 0 - BotRandomOpponentName(bs), // 1 - "[invalid var]", // 2 - "[invalid var]", // 3 - BotMapTitle(), // 4 - NULL); - trap_BotEnterChat(bs->cs, 0, CHAT_ALL); - } - num = trap_BotNumInitialChats(bs->cs, "level_start"); - for (i = 0; i < num; i++) - { - BotAI_BotInitialChat(bs, "level_start", - EasyClientName(bs->client, name, 32), // 0 - NULL); - trap_BotEnterChat(bs->cs, 0, CHAT_ALL); - } - num = trap_BotNumInitialChats(bs->cs, "level_end_victory"); - for (i = 0; i < num; i++) - { - BotAI_BotInitialChat(bs, "level_end_victory", - EasyClientName(bs->client, name, 32), // 0 - BotRandomOpponentName(bs), // 1 - BotFirstClientInRankings(), // 2 - BotLastClientInRankings(), // 3 - BotMapTitle(), // 4 - NULL); - trap_BotEnterChat(bs->cs, 0, CHAT_ALL); - } - num = trap_BotNumInitialChats(bs->cs, "level_end_lose"); - for (i = 0; i < num; i++) - { - BotAI_BotInitialChat(bs, "level_end_lose", - EasyClientName(bs->client, name, 32), // 0 - BotRandomOpponentName(bs), // 1 - BotFirstClientInRankings(), // 2 - BotLastClientInRankings(), // 3 - BotMapTitle(), // 4 - NULL); - trap_BotEnterChat(bs->cs, 0, CHAT_ALL); - } - num = trap_BotNumInitialChats(bs->cs, "level_end"); - for (i = 0; i < num; i++) - { - BotAI_BotInitialChat(bs, "level_end", - EasyClientName(bs->client, name, 32), // 0 - BotRandomOpponentName(bs), // 1 - BotFirstClientInRankings(), // 2 - BotLastClientInRankings(), // 3 - BotMapTitle(), // 4 - NULL); - trap_BotEnterChat(bs->cs, 0, CHAT_ALL); - } - EasyClientName(bs->lastkilledby, name, sizeof(name)); - num = trap_BotNumInitialChats(bs->cs, "death_drown"); - for (i = 0; i < num; i++) - { - // - BotAI_BotInitialChat(bs, "death_drown", name, NULL); - trap_BotEnterChat(bs->cs, 0, CHAT_ALL); - } - num = trap_BotNumInitialChats(bs->cs, "death_slime"); - for (i = 0; i < num; i++) - { - BotAI_BotInitialChat(bs, "death_slime", name, NULL); - trap_BotEnterChat(bs->cs, 0, CHAT_ALL); - } - num = trap_BotNumInitialChats(bs->cs, "death_lava"); - for (i = 0; i < num; i++) - { - BotAI_BotInitialChat(bs, "death_lava", name, NULL); - trap_BotEnterChat(bs->cs, 0, CHAT_ALL); - } - num = trap_BotNumInitialChats(bs->cs, "death_cratered"); - for (i = 0; i < num; i++) - { - BotAI_BotInitialChat(bs, "death_cratered", name, NULL); - trap_BotEnterChat(bs->cs, 0, CHAT_ALL); - } - num = trap_BotNumInitialChats(bs->cs, "death_suicide"); - for (i = 0; i < num; i++) - { - BotAI_BotInitialChat(bs, "death_suicide", name, NULL); - trap_BotEnterChat(bs->cs, 0, CHAT_ALL); - } - num = trap_BotNumInitialChats(bs->cs, "death_telefrag"); - for (i = 0; i < num; i++) - { - BotAI_BotInitialChat(bs, "death_telefrag", name, NULL); - trap_BotEnterChat(bs->cs, 0, CHAT_ALL); - } - num = trap_BotNumInitialChats(bs->cs, "death_gauntlet"); - for (i = 0; i < num; i++) - { - BotAI_BotInitialChat(bs, "death_gauntlet", - name, // 0 - BotWeaponNameForMeansOfDeath(bs->botdeathtype), // 1 - NULL); - trap_BotEnterChat(bs->cs, 0, CHAT_ALL); - } - num = trap_BotNumInitialChats(bs->cs, "death_rail"); - for (i = 0; i < num; i++) - { - BotAI_BotInitialChat(bs, "death_rail", - name, // 0 - BotWeaponNameForMeansOfDeath(bs->botdeathtype), // 1 - NULL); - trap_BotEnterChat(bs->cs, 0, CHAT_ALL); - } - num = trap_BotNumInitialChats(bs->cs, "death_bfg"); - for (i = 0; i < num; i++) - { - BotAI_BotInitialChat(bs, "death_bfg", - name, // 0 - BotWeaponNameForMeansOfDeath(bs->botdeathtype), // 1 - NULL); - trap_BotEnterChat(bs->cs, 0, CHAT_ALL); - } - num = trap_BotNumInitialChats(bs->cs, "death_insult"); - for (i = 0; i < num; i++) - { - BotAI_BotInitialChat(bs, "death_insult", - name, // 0 - BotWeaponNameForMeansOfDeath(bs->botdeathtype), // 1 - NULL); - trap_BotEnterChat(bs->cs, 0, CHAT_ALL); - } - num = trap_BotNumInitialChats(bs->cs, "death_praise"); - for (i = 0; i < num; i++) - { - BotAI_BotInitialChat(bs, "death_praise", - name, // 0 - BotWeaponNameForMeansOfDeath(bs->botdeathtype), // 1 - NULL); - trap_BotEnterChat(bs->cs, 0, CHAT_ALL); - } - // - EasyClientName(bs->lastkilledplayer, name, 32); - // - num = trap_BotNumInitialChats(bs->cs, "kill_gauntlet"); - for (i = 0; i < num; i++) - { - // - BotAI_BotInitialChat(bs, "kill_gauntlet", name, NULL); - trap_BotEnterChat(bs->cs, 0, CHAT_ALL); - } - num = trap_BotNumInitialChats(bs->cs, "kill_rail"); - for (i = 0; i < num; i++) - { - BotAI_BotInitialChat(bs, "kill_rail", name, NULL); - trap_BotEnterChat(bs->cs, 0, CHAT_ALL); - } - num = trap_BotNumInitialChats(bs->cs, "kill_telefrag"); - for (i = 0; i < num; i++) - { - BotAI_BotInitialChat(bs, "kill_telefrag", name, NULL); - trap_BotEnterChat(bs->cs, 0, CHAT_ALL); - } - num = trap_BotNumInitialChats(bs->cs, "kill_insult"); - for (i = 0; i < num; i++) - { - BotAI_BotInitialChat(bs, "kill_insult", name, NULL); - trap_BotEnterChat(bs->cs, 0, CHAT_ALL); - } - num = trap_BotNumInitialChats(bs->cs, "kill_praise"); - for (i = 0; i < num; i++) - { - BotAI_BotInitialChat(bs, "kill_praise", name, NULL); - trap_BotEnterChat(bs->cs, 0, CHAT_ALL); - } - num = trap_BotNumInitialChats(bs->cs, "enemy_suicide"); - for (i = 0; i < num; i++) - { - BotAI_BotInitialChat(bs, "enemy_suicide", name, NULL); - trap_BotEnterChat(bs->cs, 0, CHAT_ALL); - } - ClientName(g_entities[bs->client].client->lasthurt_client, name, sizeof(name)); - weap = BotWeaponNameForMeansOfDeath(g_entities[bs->client].client->lasthurt_client); - num = trap_BotNumInitialChats(bs->cs, "hit_talking"); - for (i = 0; i < num; i++) - { - BotAI_BotInitialChat(bs, "hit_talking", name, weap, NULL); - trap_BotEnterChat(bs->cs, 0, CHAT_ALL); - } - num = trap_BotNumInitialChats(bs->cs, "hit_nodeath"); - for (i = 0; i < num; i++) - { - BotAI_BotInitialChat(bs, "hit_nodeath", name, weap, NULL); - trap_BotEnterChat(bs->cs, 0, CHAT_ALL); - } - num = trap_BotNumInitialChats(bs->cs, "hit_nokill"); - for (i = 0; i < num; i++) - { - BotAI_BotInitialChat(bs, "hit_nokill", name, weap, NULL); - trap_BotEnterChat(bs->cs, 0, CHAT_ALL); - } - // - if (bs->lastkilledplayer == bs->client) { - strcpy(name, BotRandomOpponentName(bs)); - } - else { - EasyClientName(bs->lastkilledplayer, name, sizeof(name)); - } - // - num = trap_BotNumInitialChats(bs->cs, "random_misc"); - for (i = 0; i < num; i++) - { - // - BotAI_BotInitialChat(bs, "random_misc", - BotRandomOpponentName(bs), // 0 - name, // 1 - "[invalid var]", // 2 - "[invalid var]", // 3 - BotMapTitle(), // 4 - BotRandomWeaponName(), // 5 - NULL); - trap_BotEnterChat(bs->cs, 0, CHAT_ALL); - } - num = trap_BotNumInitialChats(bs->cs, "random_insult"); - for (i = 0; i < num; i++) - { - BotAI_BotInitialChat(bs, "random_insult", - BotRandomOpponentName(bs), // 0 - name, // 1 - "[invalid var]", // 2 - "[invalid var]", // 3 - BotMapTitle(), // 4 - BotRandomWeaponName(), // 5 - NULL); - trap_BotEnterChat(bs->cs, 0, CHAT_ALL); - } -} +/* +=========================================================================== +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 Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// + +/***************************************************************************** + * name: ai_chat.c + * + * desc: Quake3 bot AI + * + * $Archive: /MissionPack/code/game/ai_chat.c $ + * + *****************************************************************************/ + +#include "g_local.h" +#include "botlib.h" +#include "be_aas.h" +#include "be_ea.h" +#include "be_ai_char.h" +#include "be_ai_chat.h" +#include "be_ai_gen.h" +#include "be_ai_goal.h" +#include "be_ai_move.h" +#include "be_ai_weap.h" +// +#include "ai_main.h" +#include "ai_dmq3.h" +#include "ai_chat.h" +#include "ai_cmd.h" +#include "ai_dmnet.h" +// +#include "chars.h" //characteristics +#include "inv.h" //indexes into the inventory +#include "syn.h" //synonyms +#include "match.h" //string matching types and vars + +// for the voice chats +#ifdef MISSIONPACK // bk001205 +#include "../../ui/menudef.h" +#endif + +#define TIME_BETWEENCHATTING 25 + + +/* +================== +BotNumActivePlayers +================== +*/ +int BotNumActivePlayers(void) { + int i, num; + char buf[MAX_INFO_STRING]; + static int maxclients; + + if (!maxclients) + maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); + + num = 0; + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); + //if no config string or no name + if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue; + //skip spectators + if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue; + // + num++; + } + return num; +} + +/* +================== +BotIsFirstInRankings +================== +*/ +int BotIsFirstInRankings(bot_state_t *bs) { + int i, score; + char buf[MAX_INFO_STRING]; + static int maxclients; + playerState_t ps; + + if (!maxclients) + maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); + + score = bs->cur_ps.persistant[PERS_SCORE]; + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); + //if no config string or no name + if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue; + //skip spectators + if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue; + // + BotAI_GetClientState(i, &ps); + if (score < ps.persistant[PERS_SCORE]) return qfalse; + } + return qtrue; +} + +/* +================== +BotIsLastInRankings +================== +*/ +int BotIsLastInRankings(bot_state_t *bs) { + int i, score; + char buf[MAX_INFO_STRING]; + static int maxclients; + playerState_t ps; + + if (!maxclients) + maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); + + score = bs->cur_ps.persistant[PERS_SCORE]; + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); + //if no config string or no name + if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue; + //skip spectators + if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue; + // + BotAI_GetClientState(i, &ps); + if (score > ps.persistant[PERS_SCORE]) return qfalse; + } + return qtrue; +} + +/* +================== +BotFirstClientInRankings +================== +*/ +char *BotFirstClientInRankings(void) { + int i, bestscore, bestclient; + char buf[MAX_INFO_STRING]; + static char name[32]; + static int maxclients; + playerState_t ps; + + if (!maxclients) + maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); + + bestscore = -999999; + bestclient = 0; + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); + //if no config string or no name + if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue; + //skip spectators + if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue; + // + BotAI_GetClientState(i, &ps); + if (ps.persistant[PERS_SCORE] > bestscore) { + bestscore = ps.persistant[PERS_SCORE]; + bestclient = i; + } + } + EasyClientName(bestclient, name, 32); + return name; +} + +/* +================== +BotLastClientInRankings +================== +*/ +char *BotLastClientInRankings(void) { + int i, worstscore, bestclient; + char buf[MAX_INFO_STRING]; + static char name[32]; + static int maxclients; + playerState_t ps; + + if (!maxclients) + maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); + + worstscore = 999999; + bestclient = 0; + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); + //if no config string or no name + if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue; + //skip spectators + if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue; + // + BotAI_GetClientState(i, &ps); + if (ps.persistant[PERS_SCORE] < worstscore) { + worstscore = ps.persistant[PERS_SCORE]; + bestclient = i; + } + } + EasyClientName(bestclient, name, 32); + return name; +} + +/* +================== +BotRandomOpponentName +================== +*/ +char *BotRandomOpponentName(bot_state_t *bs) { + int i, count; + char buf[MAX_INFO_STRING]; + int opponents[MAX_CLIENTS], numopponents; + static int maxclients; + static char name[32]; + + if (!maxclients) + maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); + + numopponents = 0; + opponents[0] = 0; + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + if (i == bs->client) continue; + // + trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); + //if no config string or no name + if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue; + //skip spectators + if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue; + //skip team mates + if (BotSameTeam(bs, i)) continue; + // + opponents[numopponents] = i; + numopponents++; + } + count = random() * numopponents; + for (i = 0; i < numopponents; i++) { + count--; + if (count <= 0) { + EasyClientName(opponents[i], name, sizeof(name)); + return name; + } + } + EasyClientName(opponents[0], name, sizeof(name)); + return name; +} + +/* +================== +BotMapTitle +================== +*/ + +char *BotMapTitle(void) { + char info[1024]; + static char mapname[128]; + + trap_GetServerinfo(info, sizeof(info)); + + strncpy(mapname, Info_ValueForKey( info, "mapname" ), sizeof(mapname)-1); + mapname[sizeof(mapname)-1] = '\0'; + + return mapname; +} + + +/* +================== +BotWeaponNameForMeansOfDeath +================== +*/ + +char *BotWeaponNameForMeansOfDeath(int mod) { + switch(mod) { + case MOD_SHOTGUN: return "Shotgun"; + case MOD_GAUNTLET: return "Gauntlet"; + case MOD_MACHINEGUN: return "Machinegun"; + case MOD_GRENADE: + case MOD_GRENADE_SPLASH: return "Grenade Launcher"; + case MOD_ROCKET: + case MOD_ROCKET_SPLASH: return "Rocket Launcher"; + case MOD_PLASMA: + case MOD_PLASMA_SPLASH: return "Plasmagun"; + case MOD_RAILGUN: return "Railgun"; + case MOD_LIGHTNING: return "Lightning Gun"; + case MOD_BFG: + case MOD_BFG_SPLASH: return "BFG10K"; +#ifdef MISSIONPACK + case MOD_NAIL: return "Nailgun"; + case MOD_CHAINGUN: return "Chaingun"; + case MOD_PROXIMITY_MINE: return "Proximity Launcher"; + case MOD_KAMIKAZE: return "Kamikaze"; + case MOD_JUICED: return "Prox mine"; +#endif + case MOD_GRAPPLE: return "Grapple"; + default: return "[unknown weapon]"; + } +} + +/* +================== +BotRandomWeaponName +================== +*/ +char *BotRandomWeaponName(void) { + int rnd; + +#ifdef MISSIONPACK + rnd = random() * 11.9; +#else + rnd = random() * 8.9; +#endif + switch(rnd) { + case 0: return "Gauntlet"; + case 1: return "Shotgun"; + case 2: return "Machinegun"; + case 3: return "Grenade Launcher"; + case 4: return "Rocket Launcher"; + case 5: return "Plasmagun"; + case 6: return "Railgun"; + case 7: return "Lightning Gun"; +#ifdef MISSIONPACK + case 8: return "Nailgun"; + case 9: return "Chaingun"; + case 10: return "Proximity Launcher"; +#endif + default: return "BFG10K"; + } +} + +/* +================== +BotVisibleEnemies +================== +*/ +int BotVisibleEnemies(bot_state_t *bs) { + float vis; + int i; + aas_entityinfo_t entinfo; + + for (i = 0; i < MAX_CLIENTS; i++) { + + if (i == bs->client) continue; + // + BotEntityInfo(i, &entinfo); + // + if (!entinfo.valid) continue; + //if the enemy isn't dead and the enemy isn't the bot self + if (EntityIsDead(&entinfo) || entinfo.number == bs->entitynum) continue; + //if the enemy is invisible and not shooting + if (EntityIsInvisible(&entinfo) && !EntityIsShooting(&entinfo)) { + continue; + } + //if on the same team + if (BotSameTeam(bs, i)) continue; + //check if the enemy is visible + vis = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, i); + if (vis > 0) return qtrue; + } + return qfalse; +} + +/* +================== +BotValidChatPosition +================== +*/ +int BotValidChatPosition(bot_state_t *bs) { + vec3_t point, start, end, mins, maxs; + bsp_trace_t trace; + + //if the bot is dead all positions are valid + if (BotIsDead(bs)) return qtrue; + //never start chatting with a powerup + if (bs->inventory[INVENTORY_QUAD] || + bs->inventory[INVENTORY_HASTE] || + bs->inventory[INVENTORY_INVISIBILITY] || + bs->inventory[INVENTORY_REGEN] || + bs->inventory[INVENTORY_FLIGHT]) return qfalse; + //must be on the ground + //if (bs->cur_ps.groundEntityNum != ENTITYNUM_NONE) return qfalse; + //do not chat if in lava or slime + VectorCopy(bs->origin, point); + point[2] -= 24; + if (trap_PointContents(point,bs->entitynum) & (CONTENTS_LAVA|CONTENTS_SLIME)) return qfalse; + //do not chat if under water + VectorCopy(bs->origin, point); + point[2] += 32; + if (trap_PointContents(point,bs->entitynum) & MASK_WATER) return qfalse; + //must be standing on the world entity + VectorCopy(bs->origin, start); + VectorCopy(bs->origin, end); + start[2] += 1; + end[2] -= 10; + trap_AAS_PresenceTypeBoundingBox(PRESENCE_CROUCH, mins, maxs); + BotAI_Trace(&trace, start, mins, maxs, end, bs->client, MASK_SOLID); + if (trace.ent != ENTITYNUM_WORLD) return qfalse; + //the bot is in a position where it can chat + return qtrue; +} + +/* +================== +BotChat_EnterGame +================== +*/ +int BotChat_EnterGame(bot_state_t *bs) { + char name[32]; + float rnd; + + if (bot_nochat.integer) return qfalse; + if (bs->lastchat_time > FloatTime() - TIME_BETWEENCHATTING) return qfalse; + //don't chat in teamplay + if (TeamPlayIsOn()) return qfalse; + // don't chat in tournament mode + if (gametype == GT_TOURNAMENT) return qfalse; + rnd = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_ENTEREXITGAME, 0, 1); + if (!bot_fastchat.integer) { + if (random() > rnd) return qfalse; + } + if (BotNumActivePlayers() <= 1) return qfalse; + if (!BotValidChatPosition(bs)) return qfalse; + BotAI_BotInitialChat(bs, "game_enter", + EasyClientName(bs->client, name, 32), // 0 + BotRandomOpponentName(bs), // 1 + "[invalid var]", // 2 + "[invalid var]", // 3 + BotMapTitle(), // 4 + NULL); + bs->lastchat_time = FloatTime(); + bs->chatto = CHAT_ALL; + return qtrue; +} + +/* +================== +BotChat_ExitGame +================== +*/ +int BotChat_ExitGame(bot_state_t *bs) { + char name[32]; + float rnd; + + if (bot_nochat.integer) return qfalse; + if (bs->lastchat_time > FloatTime() - TIME_BETWEENCHATTING) return qfalse; + //don't chat in teamplay + if (TeamPlayIsOn()) return qfalse; + // don't chat in tournament mode + if (gametype == GT_TOURNAMENT) return qfalse; + rnd = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_ENTEREXITGAME, 0, 1); + if (!bot_fastchat.integer) { + if (random() > rnd) return qfalse; + } + if (BotNumActivePlayers() <= 1) return qfalse; + // + BotAI_BotInitialChat(bs, "game_exit", + EasyClientName(bs->client, name, 32), // 0 + BotRandomOpponentName(bs), // 1 + "[invalid var]", // 2 + "[invalid var]", // 3 + BotMapTitle(), // 4 + NULL); + bs->lastchat_time = FloatTime(); + bs->chatto = CHAT_ALL; + return qtrue; +} + +/* +================== +BotChat_StartLevel +================== +*/ +int BotChat_StartLevel(bot_state_t *bs) { + char name[32]; + float rnd; + + if (bot_nochat.integer) return qfalse; + if (BotIsObserver(bs)) return qfalse; + if (bs->lastchat_time > FloatTime() - TIME_BETWEENCHATTING) return qfalse; + //don't chat in teamplay + if (TeamPlayIsOn()) { + trap_EA_Command(bs->client, "vtaunt"); + return qfalse; + } + // don't chat in tournament mode + if (gametype == GT_TOURNAMENT) return qfalse; + rnd = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_STARTENDLEVEL, 0, 1); + if (!bot_fastchat.integer) { + if (random() > rnd) return qfalse; + } + if (BotNumActivePlayers() <= 1) return qfalse; + BotAI_BotInitialChat(bs, "level_start", + EasyClientName(bs->client, name, 32), // 0 + NULL); + bs->lastchat_time = FloatTime(); + bs->chatto = CHAT_ALL; + return qtrue; +} + +/* +================== +BotChat_EndLevel +================== +*/ +int BotChat_EndLevel(bot_state_t *bs) { + char name[32]; + float rnd; + + if (bot_nochat.integer) return qfalse; + if (BotIsObserver(bs)) return qfalse; + if (bs->lastchat_time > FloatTime() - TIME_BETWEENCHATTING) return qfalse; + // teamplay + if (TeamPlayIsOn()) + { + if (BotIsFirstInRankings(bs)) { + trap_EA_Command(bs->client, "vtaunt"); + } + return qtrue; + } + // don't chat in tournament mode + if (gametype == GT_TOURNAMENT) return qfalse; + rnd = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_STARTENDLEVEL, 0, 1); + if (!bot_fastchat.integer) { + if (random() > rnd) return qfalse; + } + if (BotNumActivePlayers() <= 1) return qfalse; + // + if (BotIsFirstInRankings(bs)) { + BotAI_BotInitialChat(bs, "level_end_victory", + EasyClientName(bs->client, name, 32), // 0 + BotRandomOpponentName(bs), // 1 + "[invalid var]", // 2 + BotLastClientInRankings(), // 3 + BotMapTitle(), // 4 + NULL); + } + else if (BotIsLastInRankings(bs)) { + BotAI_BotInitialChat(bs, "level_end_lose", + EasyClientName(bs->client, name, 32), // 0 + BotRandomOpponentName(bs), // 1 + BotFirstClientInRankings(), // 2 + "[invalid var]", // 3 + BotMapTitle(), // 4 + NULL); + } + else { + BotAI_BotInitialChat(bs, "level_end", + EasyClientName(bs->client, name, 32), // 0 + BotRandomOpponentName(bs), // 1 + BotFirstClientInRankings(), // 2 + BotLastClientInRankings(), // 3 + BotMapTitle(), // 4 + NULL); + } + bs->lastchat_time = FloatTime(); + bs->chatto = CHAT_ALL; + return qtrue; +} + +/* +================== +BotChat_Death +================== +*/ +int BotChat_Death(bot_state_t *bs) { + char name[32]; + float rnd; + + if (bot_nochat.integer) return qfalse; + if (bs->lastchat_time > FloatTime() - TIME_BETWEENCHATTING) return qfalse; + rnd = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_DEATH, 0, 1); + // don't chat in tournament mode + if (gametype == GT_TOURNAMENT) return qfalse; + //if fast chatting is off + if (!bot_fastchat.integer) { + if (random() > rnd) return qfalse; + } + if (BotNumActivePlayers() <= 1) return qfalse; + // + if (bs->lastkilledby >= 0 && bs->lastkilledby < MAX_CLIENTS) + EasyClientName(bs->lastkilledby, name, 32); + else + strcpy(name, "[world]"); + // + if (TeamPlayIsOn() && BotSameTeam(bs, bs->lastkilledby)) { + if (bs->lastkilledby == bs->client) return qfalse; + BotAI_BotInitialChat(bs, "death_teammate", name, NULL); + bs->chatto = CHAT_TEAM; + } + else + { + //teamplay + if (TeamPlayIsOn()) { + trap_EA_Command(bs->client, "vtaunt"); + return qtrue; + } + // + if (bs->botdeathtype == MOD_WATER) + BotAI_BotInitialChat(bs, "death_drown", BotRandomOpponentName(bs), NULL); + else if (bs->botdeathtype == MOD_SLIME) + BotAI_BotInitialChat(bs, "death_slime", BotRandomOpponentName(bs), NULL); + else if (bs->botdeathtype == MOD_LAVA) + BotAI_BotInitialChat(bs, "death_lava", BotRandomOpponentName(bs), NULL); + else if (bs->botdeathtype == MOD_FALLING) + BotAI_BotInitialChat(bs, "death_cratered", BotRandomOpponentName(bs), NULL); + else if (bs->botsuicide || //all other suicides by own weapon + bs->botdeathtype == MOD_CRUSH || + bs->botdeathtype == MOD_SUICIDE || + bs->botdeathtype == MOD_TARGET_LASER || + bs->botdeathtype == MOD_TRIGGER_HURT || + bs->botdeathtype == MOD_UNKNOWN) + BotAI_BotInitialChat(bs, "death_suicide", BotRandomOpponentName(bs), NULL); + else if (bs->botdeathtype == MOD_TELEFRAG) + BotAI_BotInitialChat(bs, "death_telefrag", name, NULL); +#ifdef MISSIONPACK + else if (bs->botdeathtype == MOD_KAMIKAZE && trap_BotNumInitialChats(bs->cs, "death_kamikaze")) + BotAI_BotInitialChat(bs, "death_kamikaze", name, NULL); +#endif + else { + if ((bs->botdeathtype == MOD_GAUNTLET || + bs->botdeathtype == MOD_RAILGUN || + bs->botdeathtype == MOD_BFG || + bs->botdeathtype == MOD_BFG_SPLASH) && random() < 0.5) { + + if (bs->botdeathtype == MOD_GAUNTLET) + BotAI_BotInitialChat(bs, "death_gauntlet", + name, // 0 + BotWeaponNameForMeansOfDeath(bs->botdeathtype), // 1 + NULL); + else if (bs->botdeathtype == MOD_RAILGUN) + BotAI_BotInitialChat(bs, "death_rail", + name, // 0 + BotWeaponNameForMeansOfDeath(bs->botdeathtype), // 1 + NULL); + else + BotAI_BotInitialChat(bs, "death_bfg", + name, // 0 + BotWeaponNameForMeansOfDeath(bs->botdeathtype), // 1 + NULL); + } + //choose between insult and praise + else if (random() < trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_INSULT, 0, 1)) { + BotAI_BotInitialChat(bs, "death_insult", + name, // 0 + BotWeaponNameForMeansOfDeath(bs->botdeathtype), // 1 + NULL); + } + else { + BotAI_BotInitialChat(bs, "death_praise", + name, // 0 + BotWeaponNameForMeansOfDeath(bs->botdeathtype), // 1 + NULL); + } + } + bs->chatto = CHAT_ALL; + } + bs->lastchat_time = FloatTime(); + return qtrue; +} + +/* +================== +BotChat_Kill +================== +*/ +int BotChat_Kill(bot_state_t *bs) { + char name[32]; + float rnd; + + if (bot_nochat.integer) return qfalse; + if (bs->lastchat_time > FloatTime() - TIME_BETWEENCHATTING) return qfalse; + rnd = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_KILL, 0, 1); + // don't chat in tournament mode + if (gametype == GT_TOURNAMENT) return qfalse; + //if fast chat is off + if (!bot_fastchat.integer) { + if (random() > rnd) return qfalse; + } + if (bs->lastkilledplayer == bs->client) return qfalse; + if (BotNumActivePlayers() <= 1) return qfalse; + if (!BotValidChatPosition(bs)) return qfalse; + // + if (BotVisibleEnemies(bs)) return qfalse; + // + EasyClientName(bs->lastkilledplayer, name, 32); + // + bs->chatto = CHAT_ALL; + if (TeamPlayIsOn() && BotSameTeam(bs, bs->lastkilledplayer)) { + BotAI_BotInitialChat(bs, "kill_teammate", name, NULL); + bs->chatto = CHAT_TEAM; + } + else + { + //don't chat in teamplay + if (TeamPlayIsOn()) { + trap_EA_Command(bs->client, "vtaunt"); + return qfalse; // don't wait + } + // + if (bs->enemydeathtype == MOD_GAUNTLET) { + BotAI_BotInitialChat(bs, "kill_gauntlet", name, NULL); + } + else if (bs->enemydeathtype == MOD_RAILGUN) { + BotAI_BotInitialChat(bs, "kill_rail", name, NULL); + } + else if (bs->enemydeathtype == MOD_TELEFRAG) { + BotAI_BotInitialChat(bs, "kill_telefrag", name, NULL); + } +#ifdef MISSIONPACK + else if (bs->botdeathtype == MOD_KAMIKAZE && trap_BotNumInitialChats(bs->cs, "kill_kamikaze")) + BotAI_BotInitialChat(bs, "kill_kamikaze", name, NULL); +#endif + //choose between insult and praise + else if (random() < trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_INSULT, 0, 1)) { + BotAI_BotInitialChat(bs, "kill_insult", name, NULL); + } + else { + BotAI_BotInitialChat(bs, "kill_praise", name, NULL); + } + } + bs->lastchat_time = FloatTime(); + return qtrue; +} + +/* +================== +BotChat_EnemySuicide +================== +*/ +int BotChat_EnemySuicide(bot_state_t *bs) { + char name[32]; + float rnd; + + if (bot_nochat.integer) return qfalse; + if (bs->lastchat_time > FloatTime() - TIME_BETWEENCHATTING) return qfalse; + if (BotNumActivePlayers() <= 1) return qfalse; + // + rnd = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_KILL, 0, 1); + //don't chat in teamplay + if (TeamPlayIsOn()) return qfalse; + // don't chat in tournament mode + if (gametype == GT_TOURNAMENT) return qfalse; + //if fast chat is off + if (!bot_fastchat.integer) { + if (random() > rnd) return qfalse; + } + if (!BotValidChatPosition(bs)) return qfalse; + // + if (BotVisibleEnemies(bs)) return qfalse; + // + if (bs->enemy >= 0) EasyClientName(bs->enemy, name, 32); + else strcpy(name, ""); + BotAI_BotInitialChat(bs, "enemy_suicide", name, NULL); + bs->lastchat_time = FloatTime(); + bs->chatto = CHAT_ALL; + return qtrue; +} + +/* +================== +BotChat_HitTalking +================== +*/ +int BotChat_HitTalking(bot_state_t *bs) { + char name[32], *weap; + int lasthurt_client; + float rnd; + + if (bot_nochat.integer) return qfalse; + if (bs->lastchat_time > FloatTime() - TIME_BETWEENCHATTING) return qfalse; + if (BotNumActivePlayers() <= 1) return qfalse; + lasthurt_client = g_entities[bs->client].client->lasthurt_client; + if (!lasthurt_client) return qfalse; + if (lasthurt_client == bs->client) return qfalse; + // + if (lasthurt_client < 0 || lasthurt_client >= MAX_CLIENTS) return qfalse; + // + rnd = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_HITTALKING, 0, 1); + //don't chat in teamplay + if (TeamPlayIsOn()) return qfalse; + // don't chat in tournament mode + if (gametype == GT_TOURNAMENT) return qfalse; + //if fast chat is off + if (!bot_fastchat.integer) { + if (random() > rnd * 0.5) return qfalse; + } + if (!BotValidChatPosition(bs)) return qfalse; + // + ClientName(g_entities[bs->client].client->lasthurt_client, name, sizeof(name)); + weap = BotWeaponNameForMeansOfDeath(g_entities[bs->client].client->lasthurt_client); + // + BotAI_BotInitialChat(bs, "hit_talking", name, weap, NULL); + bs->lastchat_time = FloatTime(); + bs->chatto = CHAT_ALL; + return qtrue; +} + +/* +================== +BotChat_HitNoDeath +================== +*/ +int BotChat_HitNoDeath(bot_state_t *bs) { + char name[32], *weap; + float rnd; + int lasthurt_client; + aas_entityinfo_t entinfo; + + lasthurt_client = g_entities[bs->client].client->lasthurt_client; + if (!lasthurt_client) return qfalse; + if (lasthurt_client == bs->client) return qfalse; + // + if (lasthurt_client < 0 || lasthurt_client >= MAX_CLIENTS) return qfalse; + // + if (bot_nochat.integer) return qfalse; + if (bs->lastchat_time > FloatTime() - TIME_BETWEENCHATTING) return qfalse; + if (BotNumActivePlayers() <= 1) return qfalse; + rnd = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_HITNODEATH, 0, 1); + //don't chat in teamplay + if (TeamPlayIsOn()) return qfalse; + // don't chat in tournament mode + if (gametype == GT_TOURNAMENT) return qfalse; + //if fast chat is off + if (!bot_fastchat.integer) { + if (random() > rnd * 0.5) return qfalse; + } + if (!BotValidChatPosition(bs)) return qfalse; + // + if (BotVisibleEnemies(bs)) return qfalse; + // + BotEntityInfo(bs->enemy, &entinfo); + if (EntityIsShooting(&entinfo)) return qfalse; + // + ClientName(lasthurt_client, name, sizeof(name)); + weap = BotWeaponNameForMeansOfDeath(g_entities[bs->client].client->lasthurt_mod); + // + BotAI_BotInitialChat(bs, "hit_nodeath", name, weap, NULL); + bs->lastchat_time = FloatTime(); + bs->chatto = CHAT_ALL; + return qtrue; +} + +/* +================== +BotChat_HitNoKill +================== +*/ +int BotChat_HitNoKill(bot_state_t *bs) { + char name[32], *weap; + float rnd; + aas_entityinfo_t entinfo; + + if (bot_nochat.integer) return qfalse; + if (bs->lastchat_time > FloatTime() - TIME_BETWEENCHATTING) return qfalse; + if (BotNumActivePlayers() <= 1) return qfalse; + rnd = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_HITNOKILL, 0, 1); + //don't chat in teamplay + if (TeamPlayIsOn()) return qfalse; + // don't chat in tournament mode + if (gametype == GT_TOURNAMENT) return qfalse; + //if fast chat is off + if (!bot_fastchat.integer) { + if (random() > rnd * 0.5) return qfalse; + } + if (!BotValidChatPosition(bs)) return qfalse; + // + if (BotVisibleEnemies(bs)) return qfalse; + // + BotEntityInfo(bs->enemy, &entinfo); + if (EntityIsShooting(&entinfo)) return qfalse; + // + ClientName(bs->enemy, name, sizeof(name)); + weap = BotWeaponNameForMeansOfDeath(g_entities[bs->enemy].client->lasthurt_mod); + // + BotAI_BotInitialChat(bs, "hit_nokill", name, weap, NULL); + bs->lastchat_time = FloatTime(); + bs->chatto = CHAT_ALL; + return qtrue; +} + +/* +================== +BotChat_Random +================== +*/ +int BotChat_Random(bot_state_t *bs) { + float rnd; + char name[32]; + + if (bot_nochat.integer) return qfalse; + if (BotIsObserver(bs)) return qfalse; + if (bs->lastchat_time > FloatTime() - TIME_BETWEENCHATTING) return qfalse; + // don't chat in tournament mode + if (gametype == GT_TOURNAMENT) return qfalse; + //don't chat when doing something important :) + if (bs->ltgtype == LTG_TEAMHELP || + bs->ltgtype == LTG_TEAMACCOMPANY || + bs->ltgtype == LTG_RUSHBASE) return qfalse; + // + rnd = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_RANDOM, 0, 1); + if (random() > bs->thinktime * 0.1) return qfalse; + if (!bot_fastchat.integer) { + if (random() > rnd) return qfalse; + if (random() > 0.25) return qfalse; + } + if (BotNumActivePlayers() <= 1) return qfalse; + // + if (!BotValidChatPosition(bs)) return qfalse; + // + if (BotVisibleEnemies(bs)) return qfalse; + // + if (bs->lastkilledplayer == bs->client) { + strcpy(name, BotRandomOpponentName(bs)); + } + else { + EasyClientName(bs->lastkilledplayer, name, sizeof(name)); + } + if (TeamPlayIsOn()) { + trap_EA_Command(bs->client, "vtaunt"); + return qfalse; // don't wait + } + // + if (random() < trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_MISC, 0, 1)) { + BotAI_BotInitialChat(bs, "random_misc", + BotRandomOpponentName(bs), // 0 + name, // 1 + "[invalid var]", // 2 + "[invalid var]", // 3 + BotMapTitle(), // 4 + BotRandomWeaponName(), // 5 + NULL); + } + else { + BotAI_BotInitialChat(bs, "random_insult", + BotRandomOpponentName(bs), // 0 + name, // 1 + "[invalid var]", // 2 + "[invalid var]", // 3 + BotMapTitle(), // 4 + BotRandomWeaponName(), // 5 + NULL); + } + bs->lastchat_time = FloatTime(); + bs->chatto = CHAT_ALL; + return qtrue; +} + +/* +================== +BotChatTime +================== +*/ +float BotChatTime(bot_state_t *bs) { + int cpm; + + cpm = trap_Characteristic_BInteger(bs->character, CHARACTERISTIC_CHAT_CPM, 1, 4000); + + return 2.0; //(float) trap_BotChatLength(bs->cs) * 30 / cpm; +} + +/* +================== +BotChatTest +================== +*/ +void BotChatTest(bot_state_t *bs) { + + char name[32]; + char *weap; + int num, i; + + num = trap_BotNumInitialChats(bs->cs, "game_enter"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "game_enter", + EasyClientName(bs->client, name, 32), // 0 + BotRandomOpponentName(bs), // 1 + "[invalid var]", // 2 + "[invalid var]", // 3 + BotMapTitle(), // 4 + NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_ALL); + } + num = trap_BotNumInitialChats(bs->cs, "game_exit"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "game_exit", + EasyClientName(bs->client, name, 32), // 0 + BotRandomOpponentName(bs), // 1 + "[invalid var]", // 2 + "[invalid var]", // 3 + BotMapTitle(), // 4 + NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_ALL); + } + num = trap_BotNumInitialChats(bs->cs, "level_start"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "level_start", + EasyClientName(bs->client, name, 32), // 0 + NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_ALL); + } + num = trap_BotNumInitialChats(bs->cs, "level_end_victory"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "level_end_victory", + EasyClientName(bs->client, name, 32), // 0 + BotRandomOpponentName(bs), // 1 + BotFirstClientInRankings(), // 2 + BotLastClientInRankings(), // 3 + BotMapTitle(), // 4 + NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_ALL); + } + num = trap_BotNumInitialChats(bs->cs, "level_end_lose"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "level_end_lose", + EasyClientName(bs->client, name, 32), // 0 + BotRandomOpponentName(bs), // 1 + BotFirstClientInRankings(), // 2 + BotLastClientInRankings(), // 3 + BotMapTitle(), // 4 + NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_ALL); + } + num = trap_BotNumInitialChats(bs->cs, "level_end"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "level_end", + EasyClientName(bs->client, name, 32), // 0 + BotRandomOpponentName(bs), // 1 + BotFirstClientInRankings(), // 2 + BotLastClientInRankings(), // 3 + BotMapTitle(), // 4 + NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_ALL); + } + EasyClientName(bs->lastkilledby, name, sizeof(name)); + num = trap_BotNumInitialChats(bs->cs, "death_drown"); + for (i = 0; i < num; i++) + { + // + BotAI_BotInitialChat(bs, "death_drown", name, NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_ALL); + } + num = trap_BotNumInitialChats(bs->cs, "death_slime"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "death_slime", name, NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_ALL); + } + num = trap_BotNumInitialChats(bs->cs, "death_lava"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "death_lava", name, NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_ALL); + } + num = trap_BotNumInitialChats(bs->cs, "death_cratered"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "death_cratered", name, NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_ALL); + } + num = trap_BotNumInitialChats(bs->cs, "death_suicide"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "death_suicide", name, NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_ALL); + } + num = trap_BotNumInitialChats(bs->cs, "death_telefrag"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "death_telefrag", name, NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_ALL); + } + num = trap_BotNumInitialChats(bs->cs, "death_gauntlet"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "death_gauntlet", + name, // 0 + BotWeaponNameForMeansOfDeath(bs->botdeathtype), // 1 + NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_ALL); + } + num = trap_BotNumInitialChats(bs->cs, "death_rail"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "death_rail", + name, // 0 + BotWeaponNameForMeansOfDeath(bs->botdeathtype), // 1 + NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_ALL); + } + num = trap_BotNumInitialChats(bs->cs, "death_bfg"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "death_bfg", + name, // 0 + BotWeaponNameForMeansOfDeath(bs->botdeathtype), // 1 + NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_ALL); + } + num = trap_BotNumInitialChats(bs->cs, "death_insult"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "death_insult", + name, // 0 + BotWeaponNameForMeansOfDeath(bs->botdeathtype), // 1 + NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_ALL); + } + num = trap_BotNumInitialChats(bs->cs, "death_praise"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "death_praise", + name, // 0 + BotWeaponNameForMeansOfDeath(bs->botdeathtype), // 1 + NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_ALL); + } + // + EasyClientName(bs->lastkilledplayer, name, 32); + // + num = trap_BotNumInitialChats(bs->cs, "kill_gauntlet"); + for (i = 0; i < num; i++) + { + // + BotAI_BotInitialChat(bs, "kill_gauntlet", name, NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_ALL); + } + num = trap_BotNumInitialChats(bs->cs, "kill_rail"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "kill_rail", name, NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_ALL); + } + num = trap_BotNumInitialChats(bs->cs, "kill_telefrag"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "kill_telefrag", name, NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_ALL); + } + num = trap_BotNumInitialChats(bs->cs, "kill_insult"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "kill_insult", name, NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_ALL); + } + num = trap_BotNumInitialChats(bs->cs, "kill_praise"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "kill_praise", name, NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_ALL); + } + num = trap_BotNumInitialChats(bs->cs, "enemy_suicide"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "enemy_suicide", name, NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_ALL); + } + ClientName(g_entities[bs->client].client->lasthurt_client, name, sizeof(name)); + weap = BotWeaponNameForMeansOfDeath(g_entities[bs->client].client->lasthurt_client); + num = trap_BotNumInitialChats(bs->cs, "hit_talking"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "hit_talking", name, weap, NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_ALL); + } + num = trap_BotNumInitialChats(bs->cs, "hit_nodeath"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "hit_nodeath", name, weap, NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_ALL); + } + num = trap_BotNumInitialChats(bs->cs, "hit_nokill"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "hit_nokill", name, weap, NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_ALL); + } + // + if (bs->lastkilledplayer == bs->client) { + strcpy(name, BotRandomOpponentName(bs)); + } + else { + EasyClientName(bs->lastkilledplayer, name, sizeof(name)); + } + // + num = trap_BotNumInitialChats(bs->cs, "random_misc"); + for (i = 0; i < num; i++) + { + // + BotAI_BotInitialChat(bs, "random_misc", + BotRandomOpponentName(bs), // 0 + name, // 1 + "[invalid var]", // 2 + "[invalid var]", // 3 + BotMapTitle(), // 4 + BotRandomWeaponName(), // 5 + NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_ALL); + } + num = trap_BotNumInitialChats(bs->cs, "random_insult"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "random_insult", + BotRandomOpponentName(bs), // 0 + name, // 1 + "[invalid var]", // 2 + "[invalid var]", // 3 + BotMapTitle(), // 4 + BotRandomWeaponName(), // 5 + NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_ALL); + } +} diff --git a/code/game/ai_chat.h b/code/game/ai_chat.h index 98711c4..3f520e6 100755 --- a/code/game/ai_chat.h +++ b/code/game/ai_chat.h @@ -1,61 +1,61 @@ -/* -=========================================================================== -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 Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// - -/***************************************************************************** - * name: ai_chat.h - * - * desc: Quake3 bot AI - * - * $Archive: /source/code/botai/ai_chat.c $ - * - *****************************************************************************/ - -// -int BotChat_EnterGame(bot_state_t *bs); -// -int BotChat_ExitGame(bot_state_t *bs); -// -int BotChat_StartLevel(bot_state_t *bs); -// -int BotChat_EndLevel(bot_state_t *bs); -// -int BotChat_HitTalking(bot_state_t *bs); -// -int BotChat_HitNoDeath(bot_state_t *bs); -// -int BotChat_HitNoKill(bot_state_t *bs); -// -int BotChat_Death(bot_state_t *bs); -// -int BotChat_Kill(bot_state_t *bs); -// -int BotChat_EnemySuicide(bot_state_t *bs); -// -int BotChat_Random(bot_state_t *bs); -// time the selected chat takes to type in -float BotChatTime(bot_state_t *bs); -// returns true if the bot can chat at the current position -int BotValidChatPosition(bot_state_t *bs); -// test the initial bot chats -void BotChatTest(bot_state_t *bs); - +/* +=========================================================================== +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 Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// + +/***************************************************************************** + * name: ai_chat.h + * + * desc: Quake3 bot AI + * + * $Archive: /source/code/botai/ai_chat.c $ + * + *****************************************************************************/ + +// +int BotChat_EnterGame(bot_state_t *bs); +// +int BotChat_ExitGame(bot_state_t *bs); +// +int BotChat_StartLevel(bot_state_t *bs); +// +int BotChat_EndLevel(bot_state_t *bs); +// +int BotChat_HitTalking(bot_state_t *bs); +// +int BotChat_HitNoDeath(bot_state_t *bs); +// +int BotChat_HitNoKill(bot_state_t *bs); +// +int BotChat_Death(bot_state_t *bs); +// +int BotChat_Kill(bot_state_t *bs); +// +int BotChat_EnemySuicide(bot_state_t *bs); +// +int BotChat_Random(bot_state_t *bs); +// time the selected chat takes to type in +float BotChatTime(bot_state_t *bs); +// returns true if the bot can chat at the current position +int BotValidChatPosition(bot_state_t *bs); +// test the initial bot chats +void BotChatTest(bot_state_t *bs); + diff --git a/code/game/ai_cmd.c b/code/game/ai_cmd.c index 802d7d8..fadf07f 100755 --- a/code/game/ai_cmd.c +++ b/code/game/ai_cmd.c @@ -1,1992 +1,1992 @@ -/* -=========================================================================== -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 Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// - -/***************************************************************************** - * name: ai_cmd.c - * - * desc: Quake3 bot AI - * - * $Archive: /MissionPack/code/game/ai_cmd.c $ - * - *****************************************************************************/ - -#include "g_local.h" -#include "botlib.h" -#include "be_aas.h" -#include "be_ea.h" -#include "be_ai_char.h" -#include "be_ai_chat.h" -#include "be_ai_gen.h" -#include "be_ai_goal.h" -#include "be_ai_move.h" -#include "be_ai_weap.h" -// -#include "ai_main.h" -#include "ai_dmq3.h" -#include "ai_chat.h" -#include "ai_cmd.h" -#include "ai_dmnet.h" -#include "ai_team.h" -// -#include "chars.h" //characteristics -#include "inv.h" //indexes into the inventory -#include "syn.h" //synonyms -#include "match.h" //string matching types and vars - -// for the voice chats -#include "../../ui/menudef.h" - -int notleader[MAX_CLIENTS]; - -#ifdef DEBUG -/* -================== -BotPrintTeamGoal -================== -*/ -void BotPrintTeamGoal(bot_state_t *bs) { - char netname[MAX_NETNAME]; - float t; - - ClientName(bs->client, netname, sizeof(netname)); - t = bs->teamgoal_time - FloatTime(); - switch(bs->ltgtype) { - case LTG_TEAMHELP: - { - BotAI_Print(PRT_MESSAGE, "%s: I'm gonna help a team mate for %1.0f secs\n", netname, t); - break; - } - case LTG_TEAMACCOMPANY: - { - BotAI_Print(PRT_MESSAGE, "%s: I'm gonna accompany a team mate for %1.0f secs\n", netname, t); - break; - } - case LTG_GETFLAG: - { - BotAI_Print(PRT_MESSAGE, "%s: I'm gonna get the flag for %1.0f secs\n", netname, t); - break; - } - case LTG_RUSHBASE: - { - BotAI_Print(PRT_MESSAGE, "%s: I'm gonna rush to the base for %1.0f secs\n", netname, t); - break; - } - case LTG_RETURNFLAG: - { - BotAI_Print(PRT_MESSAGE, "%s: I'm gonna try to return the flag for %1.0f secs\n", netname, t); - break; - } -#ifdef MISSIONPACK - case LTG_ATTACKENEMYBASE: - { - BotAI_Print(PRT_MESSAGE, "%s: I'm gonna attack the enemy base for %1.0f secs\n", netname, t); - break; - } - case LTG_HARVEST: - { - BotAI_Print(PRT_MESSAGE, "%s: I'm gonna harvest for %1.0f secs\n", netname, t); - break; - } -#endif - case LTG_DEFENDKEYAREA: - { - BotAI_Print(PRT_MESSAGE, "%s: I'm gonna defend a key area for %1.0f secs\n", netname, t); - break; - } - case LTG_GETITEM: - { - BotAI_Print(PRT_MESSAGE, "%s: I'm gonna get an item for %1.0f secs\n", netname, t); - break; - } - case LTG_KILL: - { - BotAI_Print(PRT_MESSAGE, "%s: I'm gonna kill someone for %1.0f secs\n", netname, t); - break; - } - case LTG_CAMP: - case LTG_CAMPORDER: - { - BotAI_Print(PRT_MESSAGE, "%s: I'm gonna camp for %1.0f secs\n", netname, t); - break; - } - case LTG_PATROL: - { - BotAI_Print(PRT_MESSAGE, "%s: I'm gonna patrol for %1.0f secs\n", netname, t); - break; - } - default: - { - if (bs->ctfroam_time > FloatTime()) { - t = bs->ctfroam_time - FloatTime(); - BotAI_Print(PRT_MESSAGE, "%s: I'm gonna roam for %1.0f secs\n", netname, t); - } - else { - BotAI_Print(PRT_MESSAGE, "%s: I've got a regular goal\n", netname); - } - } - } -} -#endif //DEBUG - -/* -================== -BotGetItemTeamGoal - -FIXME: add stuff like "upper rocket launcher" -"the rl near the railgun", "lower grenade launcher" etc. -================== -*/ -int BotGetItemTeamGoal(char *goalname, bot_goal_t *goal) { - int i; - - if (!strlen(goalname)) return qfalse; - i = -1; - do { - i = trap_BotGetLevelItemGoal(i, goalname, goal); - if (i > 0) { - //do NOT defend dropped items - if (goal->flags & GFL_DROPPED) - continue; - return qtrue; - } - } while(i > 0); - return qfalse; -} - -/* -================== -BotGetMessageTeamGoal -================== -*/ -int BotGetMessageTeamGoal(bot_state_t *bs, char *goalname, bot_goal_t *goal) { - bot_waypoint_t *cp; - - if (BotGetItemTeamGoal(goalname, goal)) return qtrue; - - cp = BotFindWayPoint(bs->checkpoints, goalname); - if (cp) { - memcpy(goal, &cp->goal, sizeof(bot_goal_t)); - return qtrue; - } - return qfalse; -} - -/* -================== -BotGetTime -================== -*/ -float BotGetTime(bot_match_t *match) { - bot_match_t timematch; - char timestring[MAX_MESSAGE_SIZE]; - float t; - - //if the matched string has a time - if (match->subtype & ST_TIME) { - //get the time string - trap_BotMatchVariable(match, TIME, timestring, MAX_MESSAGE_SIZE); - //match it to find out if the time is in seconds or minutes - if (trap_BotFindMatch(timestring, &timematch, MTCONTEXT_TIME)) { - if (timematch.type == MSG_FOREVER) { - t = 99999999.0f; - } - else if (timematch.type == MSG_FORAWHILE) { - t = 10 * 60; // 10 minutes - } - else if (timematch.type == MSG_FORALONGTIME) { - t = 30 * 60; // 30 minutes - } - else { - trap_BotMatchVariable(&timematch, TIME, timestring, MAX_MESSAGE_SIZE); - if (timematch.type == MSG_MINUTES) t = atof(timestring) * 60; - else if (timematch.type == MSG_SECONDS) t = atof(timestring); - else t = 0; - } - //if there's a valid time - if (t > 0) return FloatTime() + t; - } - } - return 0; -} - -/* -================== -FindClientByName -================== -*/ -int FindClientByName(char *name) { - int i; - char buf[MAX_INFO_STRING]; - static int maxclients; - - if (!maxclients) - maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { - ClientName(i, buf, sizeof(buf)); - if (!Q_stricmp(buf, name)) return i; - } - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { - ClientName(i, buf, sizeof(buf)); - if (stristr(buf, name)) return i; - } - return -1; -} - -/* -================== -FindEnemyByName -================== -*/ -int FindEnemyByName(bot_state_t *bs, char *name) { - int i; - char buf[MAX_INFO_STRING]; - static int maxclients; - - if (!maxclients) - maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { - if (BotSameTeam(bs, i)) continue; - ClientName(i, buf, sizeof(buf)); - if (!Q_stricmp(buf, name)) return i; - } - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { - if (BotSameTeam(bs, i)) continue; - ClientName(i, buf, sizeof(buf)); - if (stristr(buf, name)) return i; - } - return -1; -} - -/* -================== -NumPlayersOnSameTeam -================== -*/ -int NumPlayersOnSameTeam(bot_state_t *bs) { - int i, num; - char buf[MAX_INFO_STRING]; - static int maxclients; - - if (!maxclients) - maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); - - num = 0; - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { - trap_GetConfigstring(CS_PLAYERS+i, buf, MAX_INFO_STRING); - if (strlen(buf)) { - if (BotSameTeam(bs, i+1)) num++; - } - } - return num; -} - -/* -================== -TeamPlayIsOn -================== -*/ -int BotGetPatrolWaypoints(bot_state_t *bs, bot_match_t *match) { - char keyarea[MAX_MESSAGE_SIZE]; - int patrolflags; - bot_waypoint_t *wp, *newwp, *newpatrolpoints; - bot_match_t keyareamatch; - bot_goal_t goal; - - newpatrolpoints = NULL; - patrolflags = 0; - // - trap_BotMatchVariable(match, KEYAREA, keyarea, MAX_MESSAGE_SIZE); - // - while(1) { - if (!trap_BotFindMatch(keyarea, &keyareamatch, MTCONTEXT_PATROLKEYAREA)) { - trap_EA_SayTeam(bs->client, "what do you say?"); - BotFreeWaypoints(newpatrolpoints); - bs->patrolpoints = NULL; - return qfalse; - } - trap_BotMatchVariable(&keyareamatch, KEYAREA, keyarea, MAX_MESSAGE_SIZE); - if (!BotGetMessageTeamGoal(bs, keyarea, &goal)) { - //BotAI_BotInitialChat(bs, "cannotfind", keyarea, NULL); - //trap_BotEnterChat(bs->cs, 0, CHAT_TEAM); - BotFreeWaypoints(newpatrolpoints); - bs->patrolpoints = NULL; - return qfalse; - } - //create a new waypoint - newwp = BotCreateWayPoint(keyarea, goal.origin, goal.areanum); - if (!newwp) - break; - //add the waypoint to the patrol points - newwp->next = NULL; - for (wp = newpatrolpoints; wp && wp->next; wp = wp->next); - if (!wp) { - newpatrolpoints = newwp; - newwp->prev = NULL; - } - else { - wp->next = newwp; - newwp->prev = wp; - } - // - if (keyareamatch.subtype & ST_BACK) { - patrolflags = PATROL_LOOP; - break; - } - else if (keyareamatch.subtype & ST_REVERSE) { - patrolflags = PATROL_REVERSE; - break; - } - else if (keyareamatch.subtype & ST_MORE) { - trap_BotMatchVariable(&keyareamatch, MORE, keyarea, MAX_MESSAGE_SIZE); - } - else { - break; - } - } - // - if (!newpatrolpoints || !newpatrolpoints->next) { - trap_EA_SayTeam(bs->client, "I need more key points to patrol\n"); - BotFreeWaypoints(newpatrolpoints); - newpatrolpoints = NULL; - return qfalse; - } - // - BotFreeWaypoints(bs->patrolpoints); - bs->patrolpoints = newpatrolpoints; - // - bs->curpatrolpoint = bs->patrolpoints; - bs->patrolflags = patrolflags; - // - return qtrue; -} - -/* -================== -BotAddressedToBot -================== -*/ -int BotAddressedToBot(bot_state_t *bs, bot_match_t *match) { - char addressedto[MAX_MESSAGE_SIZE]; - char netname[MAX_MESSAGE_SIZE]; - char name[MAX_MESSAGE_SIZE]; - char botname[128]; - int client; - bot_match_t addresseematch; - - trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); - client = ClientOnSameTeamFromName(bs, netname); - if (client < 0) return qfalse; - //if the message is addressed to someone - if (match->subtype & ST_ADDRESSED) { - trap_BotMatchVariable(match, ADDRESSEE, addressedto, sizeof(addressedto)); - //the name of this bot - ClientName(bs->client, botname, 128); - // - while(trap_BotFindMatch(addressedto, &addresseematch, MTCONTEXT_ADDRESSEE)) { - if (addresseematch.type == MSG_EVERYONE) { - return qtrue; - } - else if (addresseematch.type == MSG_MULTIPLENAMES) { - trap_BotMatchVariable(&addresseematch, TEAMMATE, name, sizeof(name)); - if (strlen(name)) { - if (stristr(botname, name)) return qtrue; - if (stristr(bs->subteam, name)) return qtrue; - } - trap_BotMatchVariable(&addresseematch, MORE, addressedto, MAX_MESSAGE_SIZE); - } - else { - trap_BotMatchVariable(&addresseematch, TEAMMATE, name, MAX_MESSAGE_SIZE); - if (strlen(name)) { - if (stristr(botname, name)) return qtrue; - if (stristr(bs->subteam, name)) return qtrue; - } - break; - } - } - //Com_sprintf(buf, sizeof(buf), "not addressed to me but %s", addressedto); - //trap_EA_Say(bs->client, buf); - return qfalse; - } - else { - bot_match_t tellmatch; - - tellmatch.type = 0; - //if this message wasn't directed solely to this bot - if (!trap_BotFindMatch(match->string, &tellmatch, MTCONTEXT_REPLYCHAT) || - tellmatch.type != MSG_CHATTELL) { - //make sure not everyone reacts to this message - if (random() > (float ) 1.0 / (NumPlayersOnSameTeam(bs)-1)) return qfalse; - } - } - return qtrue; -} - -/* -================== -BotGPSToPosition -================== -*/ -int BotGPSToPosition(char *buf, vec3_t position) { - int i, j = 0; - int num, sign; - - for (i = 0; i < 3; i++) { - num = 0; - while(buf[j] == ' ') j++; - if (buf[j] == '-') { - j++; - sign = -1; - } - else { - sign = 1; - } - while (buf[j]) { - if (buf[j] >= '0' && buf[j] <= '9') { - num = num * 10 + buf[j] - '0'; - j++; - } - else { - j++; - break; - } - } - BotAI_Print(PRT_MESSAGE, "%d\n", sign * num); - position[i] = (float) sign * num; - } - return qtrue; -} - -/* -================== -BotMatch_HelpAccompany -================== -*/ -void BotMatch_HelpAccompany(bot_state_t *bs, bot_match_t *match) { - int client, other, areanum; - char teammate[MAX_MESSAGE_SIZE]; - char netname[MAX_MESSAGE_SIZE]; - char itemname[MAX_MESSAGE_SIZE]; - bot_match_t teammatematch; - aas_entityinfo_t entinfo; - - if (!TeamPlayIsOn()) return; - //if not addressed to this bot - if (!BotAddressedToBot(bs, match)) return; - //get the team mate name - trap_BotMatchVariable(match, TEAMMATE, teammate, sizeof(teammate)); - //get the client to help - if (trap_BotFindMatch(teammate, &teammatematch, MTCONTEXT_TEAMMATE) && - //if someone asks for him or herself - teammatematch.type == MSG_ME) { - //get the netname - trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); - client = ClientFromName(netname); - other = qfalse; - } - else { - //asked for someone else - client = FindClientByName(teammate); - //if this is the bot self - if (client == bs->client) { - other = qfalse; - } - else if (!BotSameTeam(bs, client)) { - //FIXME: say "I don't help the enemy" - return; - } - else { - other = qtrue; - } - } - //if the bot doesn't know who to help (FindClientByName returned -1) - if (client < 0) { - if (other) BotAI_BotInitialChat(bs, "whois", teammate, NULL); - else BotAI_BotInitialChat(bs, "whois", netname, NULL); - client = ClientFromName(netname); - trap_BotEnterChat(bs->cs, client, CHAT_TELL); - return; - } - //don't help or accompany yourself - if (client == bs->client) { - return; - } - // - bs->teamgoal.entitynum = -1; - BotEntityInfo(client, &entinfo); - //if info is valid (in PVS) - if (entinfo.valid) { - areanum = BotPointAreaNum(entinfo.origin); - if (areanum) {// && trap_AAS_AreaReachability(areanum)) { - bs->teamgoal.entitynum = client; - bs->teamgoal.areanum = areanum; - VectorCopy(entinfo.origin, bs->teamgoal.origin); - VectorSet(bs->teamgoal.mins, -8, -8, -8); - VectorSet(bs->teamgoal.maxs, 8, 8, 8); - } - } - //if no teamgoal yet - if (bs->teamgoal.entitynum < 0) { - //if near an item - if (match->subtype & ST_NEARITEM) { - //get the match variable - trap_BotMatchVariable(match, ITEM, itemname, sizeof(itemname)); - // - if (!BotGetMessageTeamGoal(bs, itemname, &bs->teamgoal)) { - //BotAI_BotInitialChat(bs, "cannotfind", itemname, NULL); - //trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); - return; - } - } - } - // - if (bs->teamgoal.entitynum < 0) { - if (other) BotAI_BotInitialChat(bs, "whereis", teammate, NULL); - else BotAI_BotInitialChat(bs, "whereareyou", netname, NULL); - client = ClientFromName(netname); - trap_BotEnterChat(bs->cs, client, CHAT_TEAM); - return; - } - //the team mate - bs->teammate = client; - // - trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); - // - client = ClientFromName(netname); - //the team mate who ordered - bs->decisionmaker = client; - bs->ordered = qtrue; - bs->order_time = FloatTime(); - //last time the team mate was assumed visible - bs->teammatevisible_time = FloatTime(); - //set the time to send a message to the team mates - bs->teammessage_time = FloatTime() + 2 * random(); - //get the team goal time - bs->teamgoal_time = BotGetTime(match); - //set the ltg type - if (match->type == MSG_HELP) { - bs->ltgtype = LTG_TEAMHELP; - if (!bs->teamgoal_time) bs->teamgoal_time = FloatTime() + TEAM_HELP_TIME; - } - else { - bs->ltgtype = LTG_TEAMACCOMPANY; - if (!bs->teamgoal_time) bs->teamgoal_time = FloatTime() + TEAM_ACCOMPANY_TIME; - bs->formation_dist = 3.5 * 32; //3.5 meter - bs->arrive_time = 0; - // - BotSetTeamStatus(bs); - // remember last ordered task - BotRememberLastOrderedTask(bs); - } -#ifdef DEBUG - BotPrintTeamGoal(bs); -#endif //DEBUG -} - -/* -================== -BotMatch_DefendKeyArea -================== -*/ -void BotMatch_DefendKeyArea(bot_state_t *bs, bot_match_t *match) { - char itemname[MAX_MESSAGE_SIZE]; - char netname[MAX_MESSAGE_SIZE]; - int client; - - if (!TeamPlayIsOn()) return; - //if not addressed to this bot - if (!BotAddressedToBot(bs, match)) return; - //get the match variable - trap_BotMatchVariable(match, KEYAREA, itemname, sizeof(itemname)); - // - if (!BotGetMessageTeamGoal(bs, itemname, &bs->teamgoal)) { - //BotAI_BotInitialChat(bs, "cannotfind", itemname, NULL); - //trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); - return; - } - // - trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); - // - client = ClientFromName(netname); - //the team mate who ordered - bs->decisionmaker = client; - bs->ordered = qtrue; - bs->order_time = FloatTime(); - //set the time to send a message to the team mates - bs->teammessage_time = FloatTime() + 2 * random(); - //set the ltg type - bs->ltgtype = LTG_DEFENDKEYAREA; - //get the team goal time - bs->teamgoal_time = BotGetTime(match); - //set the team goal time - if (!bs->teamgoal_time) bs->teamgoal_time = FloatTime() + TEAM_DEFENDKEYAREA_TIME; - //away from defending - bs->defendaway_time = 0; - // - BotSetTeamStatus(bs); - // remember last ordered task - BotRememberLastOrderedTask(bs); -#ifdef DEBUG - BotPrintTeamGoal(bs); -#endif //DEBUG -} - -/* -================== -BotMatch_GetItem -================== -*/ -void BotMatch_GetItem(bot_state_t *bs, bot_match_t *match) { - char itemname[MAX_MESSAGE_SIZE]; - char netname[MAX_MESSAGE_SIZE]; - int client; - - if (!TeamPlayIsOn()) return; - //if not addressed to this bot - if (!BotAddressedToBot(bs, match)) return; - //get the match variable - trap_BotMatchVariable(match, ITEM, itemname, sizeof(itemname)); - // - if (!BotGetMessageTeamGoal(bs, itemname, &bs->teamgoal)) { - //BotAI_BotInitialChat(bs, "cannotfind", itemname, NULL); - //trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); - return; - } - trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); - client = ClientOnSameTeamFromName(bs, netname); - // - bs->decisionmaker = client; - bs->ordered = qtrue; - bs->order_time = FloatTime(); - //set the time to send a message to the team mates - bs->teammessage_time = FloatTime() + 2 * random(); - //set the ltg type - bs->ltgtype = LTG_GETITEM; - //set the team goal time - bs->teamgoal_time = FloatTime() + TEAM_GETITEM_TIME; - // - BotSetTeamStatus(bs); -#ifdef DEBUG - BotPrintTeamGoal(bs); -#endif //DEBUG -} - -/* -================== -BotMatch_Camp -================== -*/ -void BotMatch_Camp(bot_state_t *bs, bot_match_t *match) { - int client, areanum; - char netname[MAX_MESSAGE_SIZE]; - char itemname[MAX_MESSAGE_SIZE]; - aas_entityinfo_t entinfo; - - if (!TeamPlayIsOn()) return; - //if not addressed to this bot - if (!BotAddressedToBot(bs, match)) return; - // - trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); - //asked for someone else - client = FindClientByName(netname); - //if there's no valid client with this name - if (client < 0) { - BotAI_BotInitialChat(bs, "whois", netname, NULL); - trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); - return; - } - //get the match variable - trap_BotMatchVariable(match, KEYAREA, itemname, sizeof(itemname)); - //in CTF it could be the base - if (match->subtype & ST_THERE) { - //camp at the spot the bot is currently standing - bs->teamgoal.entitynum = bs->entitynum; - bs->teamgoal.areanum = bs->areanum; - VectorCopy(bs->origin, bs->teamgoal.origin); - VectorSet(bs->teamgoal.mins, -8, -8, -8); - VectorSet(bs->teamgoal.maxs, 8, 8, 8); - } - else if (match->subtype & ST_HERE) { - //if this is the bot self - if (client == bs->client) return; - // - bs->teamgoal.entitynum = -1; - BotEntityInfo(client, &entinfo); - //if info is valid (in PVS) - if (entinfo.valid) { - areanum = BotPointAreaNum(entinfo.origin); - if (areanum) {// && trap_AAS_AreaReachability(areanum)) { - //NOTE: just assume the bot knows where the person is - //if (BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, client)) { - bs->teamgoal.entitynum = client; - bs->teamgoal.areanum = areanum; - VectorCopy(entinfo.origin, bs->teamgoal.origin); - VectorSet(bs->teamgoal.mins, -8, -8, -8); - VectorSet(bs->teamgoal.maxs, 8, 8, 8); - //} - } - } - //if the other is not visible - if (bs->teamgoal.entitynum < 0) { - BotAI_BotInitialChat(bs, "whereareyou", netname, NULL); - client = ClientFromName(netname); - trap_BotEnterChat(bs->cs, client, CHAT_TELL); - return; - } - } - else if (!BotGetMessageTeamGoal(bs, itemname, &bs->teamgoal)) { - //BotAI_BotInitialChat(bs, "cannotfind", itemname, NULL); - //client = ClientFromName(netname); - //trap_BotEnterChat(bs->cs, client, CHAT_TELL); - return; - } - // - bs->decisionmaker = client; - bs->ordered = qtrue; - bs->order_time = FloatTime(); - //set the time to send a message to the team mates - bs->teammessage_time = FloatTime() + 2 * random(); - //set the ltg type - bs->ltgtype = LTG_CAMPORDER; - //get the team goal time - bs->teamgoal_time = BotGetTime(match); - //set the team goal time - if (!bs->teamgoal_time) bs->teamgoal_time = FloatTime() + TEAM_CAMP_TIME; - //not arrived yet - bs->arrive_time = 0; - // - BotSetTeamStatus(bs); - // remember last ordered task - BotRememberLastOrderedTask(bs); -#ifdef DEBUG - BotPrintTeamGoal(bs); -#endif //DEBUG -} - -/* -================== -BotMatch_Patrol -================== -*/ -void BotMatch_Patrol(bot_state_t *bs, bot_match_t *match) { - char netname[MAX_MESSAGE_SIZE]; - int client; - - if (!TeamPlayIsOn()) return; - //if not addressed to this bot - if (!BotAddressedToBot(bs, match)) return; - //get the patrol waypoints - if (!BotGetPatrolWaypoints(bs, match)) return; - // - trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); - // - client = FindClientByName(netname); - // - bs->decisionmaker = client; - bs->ordered = qtrue; - bs->order_time = FloatTime(); - //set the time to send a message to the team mates - bs->teammessage_time = FloatTime() + 2 * random(); - //set the ltg type - bs->ltgtype = LTG_PATROL; - //get the team goal time - bs->teamgoal_time = BotGetTime(match); - //set the team goal time if not set already - if (!bs->teamgoal_time) bs->teamgoal_time = FloatTime() + TEAM_PATROL_TIME; - // - BotSetTeamStatus(bs); - // remember last ordered task - BotRememberLastOrderedTask(bs); -#ifdef DEBUG - BotPrintTeamGoal(bs); -#endif //DEBUG -} - -/* -================== -BotMatch_GetFlag -================== -*/ -void BotMatch_GetFlag(bot_state_t *bs, bot_match_t *match) { - char netname[MAX_MESSAGE_SIZE]; - int client; - - if (gametype == GT_CTF) { - if (!ctf_redflag.areanum || !ctf_blueflag.areanum) - return; - } -#ifdef MISSIONPACK - else if (gametype == GT_1FCTF) { - if (!ctf_neutralflag.areanum || !ctf_redflag.areanum || !ctf_blueflag.areanum) - return; - } -#endif - else { - return; - } - //if not addressed to this bot - if (!BotAddressedToBot(bs, match)) return; - // - trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); - // - client = FindClientByName(netname); - // - bs->decisionmaker = client; - bs->ordered = qtrue; - bs->order_time = FloatTime(); - //set the time to send a message to the team mates - bs->teammessage_time = FloatTime() + 2 * random(); - //set the ltg type - bs->ltgtype = LTG_GETFLAG; - //set the team goal time - bs->teamgoal_time = FloatTime() + CTF_GETFLAG_TIME; - // get an alternate route in ctf - if (gametype == GT_CTF) { - //get an alternative route goal towards the enemy base - BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs)); - } - // - BotSetTeamStatus(bs); - // remember last ordered task - BotRememberLastOrderedTask(bs); -#ifdef DEBUG - BotPrintTeamGoal(bs); -#endif //DEBUG -} - -/* -================== -BotMatch_AttackEnemyBase -================== -*/ -void BotMatch_AttackEnemyBase(bot_state_t *bs, bot_match_t *match) { - char netname[MAX_MESSAGE_SIZE]; - int client; - - if (gametype == GT_CTF) { - BotMatch_GetFlag(bs, match); - } -#ifdef MISSIONPACK - else if (gametype == GT_1FCTF || gametype == GT_OBELISK || gametype == GT_HARVESTER) { - if (!redobelisk.areanum || !blueobelisk.areanum) - return; - } -#endif - else { - return; - } - //if not addressed to this bot - if (!BotAddressedToBot(bs, match)) return; - // - trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); - // - client = FindClientByName(netname); - // - bs->decisionmaker = client; - bs->ordered = qtrue; - bs->order_time = FloatTime(); - //set the time to send a message to the team mates - bs->teammessage_time = FloatTime() + 2 * random(); - //set the ltg type - bs->ltgtype = LTG_ATTACKENEMYBASE; - //set the team goal time - bs->teamgoal_time = FloatTime() + TEAM_ATTACKENEMYBASE_TIME; - bs->attackaway_time = 0; - // - BotSetTeamStatus(bs); - // remember last ordered task - BotRememberLastOrderedTask(bs); -#ifdef DEBUG - BotPrintTeamGoal(bs); -#endif //DEBUG -} - -#ifdef MISSIONPACK -/* -================== -BotMatch_Harvest -================== -*/ -void BotMatch_Harvest(bot_state_t *bs, bot_match_t *match) { - char netname[MAX_MESSAGE_SIZE]; - int client; - - if (gametype == GT_HARVESTER) { - if (!neutralobelisk.areanum || !redobelisk.areanum || !blueobelisk.areanum) - return; - } - else { - return; - } - //if not addressed to this bot - if (!BotAddressedToBot(bs, match)) return; - // - trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); - // - client = FindClientByName(netname); - // - bs->decisionmaker = client; - bs->ordered = qtrue; - bs->order_time = FloatTime(); - //set the time to send a message to the team mates - bs->teammessage_time = FloatTime() + 2 * random(); - //set the ltg type - bs->ltgtype = LTG_HARVEST; - //set the team goal time - bs->teamgoal_time = FloatTime() + TEAM_HARVEST_TIME; - bs->harvestaway_time = 0; - // - BotSetTeamStatus(bs); - // remember last ordered task - BotRememberLastOrderedTask(bs); -#ifdef DEBUG - BotPrintTeamGoal(bs); -#endif //DEBUG -} -#endif - -/* -================== -BotMatch_RushBase -================== -*/ -void BotMatch_RushBase(bot_state_t *bs, bot_match_t *match) { - char netname[MAX_MESSAGE_SIZE]; - int client; - - if (gametype == GT_CTF) { - if (!ctf_redflag.areanum || !ctf_blueflag.areanum) - return; - } -#ifdef MISSIONPACK - else if (gametype == GT_1FCTF || gametype == GT_HARVESTER) { - if (!redobelisk.areanum || !blueobelisk.areanum) - return; - } -#endif - else { - return; - } - //if not addressed to this bot - if (!BotAddressedToBot(bs, match)) return; - // - trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); - // - client = FindClientByName(netname); - // - bs->decisionmaker = client; - bs->ordered = qtrue; - bs->order_time = FloatTime(); - //set the time to send a message to the team mates - bs->teammessage_time = FloatTime() + 2 * random(); - //set the ltg type - bs->ltgtype = LTG_RUSHBASE; - //set the team goal time - bs->teamgoal_time = FloatTime() + CTF_RUSHBASE_TIME; - bs->rushbaseaway_time = 0; - // - BotSetTeamStatus(bs); -#ifdef DEBUG - BotPrintTeamGoal(bs); -#endif //DEBUG -} - -/* -================== -BotMatch_TaskPreference -================== -*/ -void BotMatch_TaskPreference(bot_state_t *bs, bot_match_t *match) { - char netname[MAX_NETNAME]; - char teammatename[MAX_MESSAGE_SIZE]; - int teammate, preference; - - ClientName(bs->client, netname, sizeof(netname)); - if (Q_stricmp(netname, bs->teamleader) != 0) return; - - trap_BotMatchVariable(match, NETNAME, teammatename, sizeof(teammatename)); - teammate = ClientFromName(teammatename); - if (teammate < 0) return; - - preference = BotGetTeamMateTaskPreference(bs, teammate); - switch(match->subtype) - { - case ST_DEFENDER: - { - preference &= ~TEAMTP_ATTACKER; - preference |= TEAMTP_DEFENDER; - break; - } - case ST_ATTACKER: - { - preference &= ~TEAMTP_DEFENDER; - preference |= TEAMTP_ATTACKER; - break; - } - case ST_ROAMER: - { - preference &= ~(TEAMTP_ATTACKER|TEAMTP_DEFENDER); - break; - } - } - BotSetTeamMateTaskPreference(bs, teammate, preference); - // - EasyClientName(teammate, teammatename, sizeof(teammatename)); - BotAI_BotInitialChat(bs, "keepinmind", teammatename, NULL); - trap_BotEnterChat(bs->cs, teammate, CHAT_TELL); - BotVoiceChatOnly(bs, teammate, VOICECHAT_YES); - trap_EA_Action(bs->client, ACTION_AFFIRMATIVE); -} - -/* -================== -BotMatch_ReturnFlag -================== -*/ -void BotMatch_ReturnFlag(bot_state_t *bs, bot_match_t *match) { - char netname[MAX_MESSAGE_SIZE]; - int client; - - //if not in CTF mode - if ( - gametype != GT_CTF -#ifdef MISSIONPACK - && gametype != GT_1FCTF -#endif - ) - return; - //if not addressed to this bot - if (!BotAddressedToBot(bs, match)) - return; - // - trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); - // - client = FindClientByName(netname); - // - bs->decisionmaker = client; - bs->ordered = qtrue; - bs->order_time = FloatTime(); - //set the time to send a message to the team mates - bs->teammessage_time = FloatTime() + 2 * random(); - //set the ltg type - bs->ltgtype = LTG_RETURNFLAG; - //set the team goal time - bs->teamgoal_time = FloatTime() + CTF_RETURNFLAG_TIME; - bs->rushbaseaway_time = 0; - // - BotSetTeamStatus(bs); -#ifdef DEBUG - BotPrintTeamGoal(bs); -#endif //DEBUG -} - -/* -================== -BotMatch_JoinSubteam -================== -*/ -void BotMatch_JoinSubteam(bot_state_t *bs, bot_match_t *match) { - char teammate[MAX_MESSAGE_SIZE]; - char netname[MAX_MESSAGE_SIZE]; - int client; - - if (!TeamPlayIsOn()) return; - //if not addressed to this bot - if (!BotAddressedToBot(bs, match)) return; - //get the sub team name - trap_BotMatchVariable(match, TEAMNAME, teammate, sizeof(teammate)); - //set the sub team name - strncpy(bs->subteam, teammate, 32); - bs->subteam[31] = '\0'; - // - trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); - BotAI_BotInitialChat(bs, "joinedteam", teammate, NULL); - client = ClientFromName(netname); - trap_BotEnterChat(bs->cs, client, CHAT_TELL); -} - -/* -================== -BotMatch_LeaveSubteam -================== -*/ -void BotMatch_LeaveSubteam(bot_state_t *bs, bot_match_t *match) { - char netname[MAX_MESSAGE_SIZE]; - int client; - - if (!TeamPlayIsOn()) return; - //if not addressed to this bot - if (!BotAddressedToBot(bs, match)) return; - // - if (strlen(bs->subteam)) - { - BotAI_BotInitialChat(bs, "leftteam", bs->subteam, NULL); - trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); - client = ClientFromName(netname); - trap_BotEnterChat(bs->cs, client, CHAT_TELL); - } //end if - strcpy(bs->subteam, ""); -} - -/* -================== -BotMatch_LeaveSubteam -================== -*/ -void BotMatch_WhichTeam(bot_state_t *bs, bot_match_t *match) { - if (!TeamPlayIsOn()) return; - //if not addressed to this bot - if (!BotAddressedToBot(bs, match)) return; - // - if (strlen(bs->subteam)) { - BotAI_BotInitialChat(bs, "inteam", bs->subteam, NULL); - } - else { - BotAI_BotInitialChat(bs, "noteam", NULL); - } - trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); -} - -/* -================== -BotMatch_CheckPoint -================== -*/ -void BotMatch_CheckPoint(bot_state_t *bs, bot_match_t *match) { - int areanum, client; - char buf[MAX_MESSAGE_SIZE]; - char netname[MAX_MESSAGE_SIZE]; - vec3_t position; - bot_waypoint_t *cp; - - if (!TeamPlayIsOn()) return; - // - trap_BotMatchVariable(match, POSITION, buf, MAX_MESSAGE_SIZE); - VectorClear(position); - // - trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); - client = ClientFromName(netname); - //BotGPSToPosition(buf, position); - sscanf(buf, "%f %f %f", &position[0], &position[1], &position[2]); - position[2] += 0.5; - areanum = BotPointAreaNum(position); - if (!areanum) { - if (BotAddressedToBot(bs, match)) { - BotAI_BotInitialChat(bs, "checkpoint_invalid", NULL); - trap_BotEnterChat(bs->cs, client, CHAT_TELL); - } - return; - } - // - trap_BotMatchVariable(match, NAME, buf, MAX_MESSAGE_SIZE); - //check if there already exists a checkpoint with this name - cp = BotFindWayPoint(bs->checkpoints, buf); - if (cp) { - if (cp->next) cp->next->prev = cp->prev; - if (cp->prev) cp->prev->next = cp->next; - else bs->checkpoints = cp->next; - cp->inuse = qfalse; - } - //create a new check point - cp = BotCreateWayPoint(buf, position, areanum); - //add the check point to the bot's known chech points - cp->next = bs->checkpoints; - if (bs->checkpoints) bs->checkpoints->prev = cp; - bs->checkpoints = cp; - // - if (BotAddressedToBot(bs, match)) { - Com_sprintf(buf, sizeof(buf), "%1.0f %1.0f %1.0f", cp->goal.origin[0], - cp->goal.origin[1], - cp->goal.origin[2]); - - BotAI_BotInitialChat(bs, "checkpoint_confirm", cp->name, buf, NULL); - trap_BotEnterChat(bs->cs, client, CHAT_TELL); - } -} - -/* -================== -BotMatch_FormationSpace -================== -*/ -void BotMatch_FormationSpace(bot_state_t *bs, bot_match_t *match) { - char buf[MAX_MESSAGE_SIZE]; - float space; - - if (!TeamPlayIsOn()) return; - //if not addressed to this bot - if (!BotAddressedToBot(bs, match)) return; - // - trap_BotMatchVariable(match, NUMBER, buf, MAX_MESSAGE_SIZE); - //if it's the distance in feet - if (match->subtype & ST_FEET) space = 0.3048 * 32 * atof(buf); - //else it's in meters - else space = 32 * atof(buf); - //check if the formation intervening space is valid - if (space < 48 || space > 500) space = 100; - bs->formation_dist = space; -} - -/* -================== -BotMatch_Dismiss -================== -*/ -void BotMatch_Dismiss(bot_state_t *bs, bot_match_t *match) { - char netname[MAX_MESSAGE_SIZE]; - int client; - - if (!TeamPlayIsOn()) return; - //if not addressed to this bot - if (!BotAddressedToBot(bs, match)) return; - trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); - client = ClientFromName(netname); - // - bs->decisionmaker = client; - // - bs->ltgtype = 0; - bs->lead_time = 0; - bs->lastgoal_ltgtype = 0; - // - BotAI_BotInitialChat(bs, "dismissed", NULL); - trap_BotEnterChat(bs->cs, client, CHAT_TELL); -} - -/* -================== -BotMatch_Suicide -================== -*/ -void BotMatch_Suicide(bot_state_t *bs, bot_match_t *match) { - char netname[MAX_MESSAGE_SIZE]; - int client; - - if (!TeamPlayIsOn()) return; - //if not addressed to this bot - if (!BotAddressedToBot(bs, match)) return; - // - trap_EA_Command(bs->client, "kill"); - // - trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); - client = ClientFromName(netname); - // - BotVoiceChat(bs, client, VOICECHAT_TAUNT); - trap_EA_Action(bs->client, ACTION_AFFIRMATIVE); -} - -/* -================== -BotMatch_StartTeamLeaderShip -================== -*/ -void BotMatch_StartTeamLeaderShip(bot_state_t *bs, bot_match_t *match) { - int client; - char teammate[MAX_MESSAGE_SIZE]; - - if (!TeamPlayIsOn()) return; - //if chats for him or herself - if (match->subtype & ST_I) { - //get the team mate that will be the team leader - trap_BotMatchVariable(match, NETNAME, teammate, sizeof(teammate)); - strncpy(bs->teamleader, teammate, sizeof(bs->teamleader)); - bs->teamleader[sizeof(bs->teamleader)] = '\0'; - } - //chats for someone else - else { - //get the team mate that will be the team leader - trap_BotMatchVariable(match, TEAMMATE, teammate, sizeof(teammate)); - client = FindClientByName(teammate); - if (client >= 0) ClientName(client, bs->teamleader, sizeof(bs->teamleader)); - } -} - -/* -================== -BotMatch_StopTeamLeaderShip -================== -*/ -void BotMatch_StopTeamLeaderShip(bot_state_t *bs, bot_match_t *match) { - int client; - char teammate[MAX_MESSAGE_SIZE]; - char netname[MAX_MESSAGE_SIZE]; - - if (!TeamPlayIsOn()) return; - //get the team mate that stops being the team leader - trap_BotMatchVariable(match, TEAMMATE, teammate, sizeof(teammate)); - //if chats for him or herself - if (match->subtype & ST_I) { - trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); - client = FindClientByName(netname); - } - //chats for someone else - else { - client = FindClientByName(teammate); - } //end else - if (client >= 0) { - if (!Q_stricmp(bs->teamleader, ClientName(client, netname, sizeof(netname)))) { - bs->teamleader[0] = '\0'; - notleader[client] = qtrue; - } - } -} - -/* -================== -BotMatch_WhoIsTeamLeader -================== -*/ -void BotMatch_WhoIsTeamLeader(bot_state_t *bs, bot_match_t *match) { - char netname[MAX_MESSAGE_SIZE]; - - if (!TeamPlayIsOn()) return; - - ClientName(bs->client, netname, sizeof(netname)); - //if this bot IS the team leader - if (!Q_stricmp(netname, bs->teamleader)) { - trap_EA_SayTeam(bs->client, "I'm the team leader\n"); - } -} - -/* -================== -BotMatch_WhatAreYouDoing -================== -*/ -void BotMatch_WhatAreYouDoing(bot_state_t *bs, bot_match_t *match) { - char netname[MAX_MESSAGE_SIZE]; - char goalname[MAX_MESSAGE_SIZE]; - int client; - - //if not addressed to this bot - if (!BotAddressedToBot(bs, match)) return; - // - switch(bs->ltgtype) { - case LTG_TEAMHELP: - { - EasyClientName(bs->teammate, netname, sizeof(netname)); - BotAI_BotInitialChat(bs, "helping", netname, NULL); - break; - } - case LTG_TEAMACCOMPANY: - { - EasyClientName(bs->teammate, netname, sizeof(netname)); - BotAI_BotInitialChat(bs, "accompanying", netname, NULL); - break; - } - case LTG_DEFENDKEYAREA: - { - trap_BotGoalName(bs->teamgoal.number, goalname, sizeof(goalname)); - BotAI_BotInitialChat(bs, "defending", goalname, NULL); - break; - } - case LTG_GETITEM: - { - trap_BotGoalName(bs->teamgoal.number, goalname, sizeof(goalname)); - BotAI_BotInitialChat(bs, "gettingitem", goalname, NULL); - break; - } - case LTG_KILL: - { - ClientName(bs->teamgoal.entitynum, netname, sizeof(netname)); - BotAI_BotInitialChat(bs, "killing", netname, NULL); - break; - } - case LTG_CAMP: - case LTG_CAMPORDER: - { - BotAI_BotInitialChat(bs, "camping", NULL); - break; - } - case LTG_PATROL: - { - BotAI_BotInitialChat(bs, "patrolling", NULL); - break; - } - case LTG_GETFLAG: - { - BotAI_BotInitialChat(bs, "capturingflag", NULL); - break; - } - case LTG_RUSHBASE: - { - BotAI_BotInitialChat(bs, "rushingbase", NULL); - break; - } - case LTG_RETURNFLAG: - { - BotAI_BotInitialChat(bs, "returningflag", NULL); - break; - } -#ifdef MISSIONPACK - case LTG_ATTACKENEMYBASE: - { - BotAI_BotInitialChat(bs, "attackingenemybase", NULL); - break; - } - case LTG_HARVEST: - { - BotAI_BotInitialChat(bs, "harvesting", NULL); - break; - } -#endif - default: - { - BotAI_BotInitialChat(bs, "roaming", NULL); - break; - } - } - //chat what the bot is doing - trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); - client = ClientFromName(netname); - trap_BotEnterChat(bs->cs, client, CHAT_TELL); -} - -/* -================== -BotMatch_WhatIsMyCommand -================== -*/ -void BotMatch_WhatIsMyCommand(bot_state_t *bs, bot_match_t *match) { - char netname[MAX_NETNAME]; - - ClientName(bs->client, netname, sizeof(netname)); - if (Q_stricmp(netname, bs->teamleader) != 0) return; - bs->forceorders = qtrue; -} - -/* -================== -BotNearestVisibleItem -================== -*/ -float BotNearestVisibleItem(bot_state_t *bs, char *itemname, bot_goal_t *goal) { - int i; - char name[64]; - bot_goal_t tmpgoal; - float dist, bestdist; - vec3_t dir; - bsp_trace_t trace; - - bestdist = 999999; - i = -1; - do { - i = trap_BotGetLevelItemGoal(i, itemname, &tmpgoal); - trap_BotGoalName(tmpgoal.number, name, sizeof(name)); - if (Q_stricmp(itemname, name) != 0) - continue; - VectorSubtract(tmpgoal.origin, bs->origin, dir); - dist = VectorLength(dir); - if (dist < bestdist) { - //trace from start to end - BotAI_Trace(&trace, bs->eye, NULL, NULL, tmpgoal.origin, bs->client, CONTENTS_SOLID|CONTENTS_PLAYERCLIP); - if (trace.fraction >= 1.0) { - bestdist = dist; - memcpy(goal, &tmpgoal, sizeof(bot_goal_t)); - } - } - } while(i > 0); - return bestdist; -} - -/* -================== -BotMatch_WhereAreYou -================== -*/ -void BotMatch_WhereAreYou(bot_state_t *bs, bot_match_t *match) { - float dist, bestdist; - int i, bestitem, redtt, bluett, client; - bot_goal_t goal; - char netname[MAX_MESSAGE_SIZE]; - char *nearbyitems[] = { - "Shotgun", - "Grenade Launcher", - "Rocket Launcher", - "Plasmagun", - "Railgun", - "Lightning Gun", - "BFG10K", - "Quad Damage", - "Regeneration", - "Battle Suit", - "Speed", - "Invisibility", - "Flight", - "Armor", - "Heavy Armor", - "Red Flag", - "Blue Flag", -#ifdef MISSIONPACK - "Nailgun", - "Prox Launcher", - "Chaingun", - "Scout", - "Guard", - "Doubler", - "Ammo Regen", - "Neutral Flag", - "Red Obelisk", - "Blue Obelisk", - "Neutral Obelisk", -#endif - NULL - }; - // - if (!TeamPlayIsOn()) - return; - //if not addressed to this bot - if (!BotAddressedToBot(bs, match)) - return; - - bestitem = -1; - bestdist = 999999; - for (i = 0; nearbyitems[i]; i++) { - dist = BotNearestVisibleItem(bs, nearbyitems[i], &goal); - if (dist < bestdist) { - bestdist = dist; - bestitem = i; - } - } - if (bestitem != -1) { - if (gametype == GT_CTF -#ifdef MISSIONPACK - || gametype == GT_1FCTF -#endif - ) { - redtt = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, ctf_redflag.areanum, TFL_DEFAULT); - bluett = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, ctf_blueflag.areanum, TFL_DEFAULT); - if (redtt < (redtt + bluett) * 0.4) { - BotAI_BotInitialChat(bs, "teamlocation", nearbyitems[bestitem], "red", NULL); - } - else if (bluett < (redtt + bluett) * 0.4) { - BotAI_BotInitialChat(bs, "teamlocation", nearbyitems[bestitem], "blue", NULL); - } - else { - BotAI_BotInitialChat(bs, "location", nearbyitems[bestitem], NULL); - } - } -#ifdef MISSIONPACK - else if (gametype == GT_OBELISK || gametype == GT_HARVESTER) { - redtt = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, redobelisk.areanum, TFL_DEFAULT); - bluett = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, blueobelisk.areanum, TFL_DEFAULT); - if (redtt < (redtt + bluett) * 0.4) { - BotAI_BotInitialChat(bs, "teamlocation", nearbyitems[bestitem], "red", NULL); - } - else if (bluett < (redtt + bluett) * 0.4) { - BotAI_BotInitialChat(bs, "teamlocation", nearbyitems[bestitem], "blue", NULL); - } - else { - BotAI_BotInitialChat(bs, "location", nearbyitems[bestitem], NULL); - } - } -#endif - else { - BotAI_BotInitialChat(bs, "location", nearbyitems[bestitem], NULL); - } - trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); - client = ClientFromName(netname); - trap_BotEnterChat(bs->cs, client, CHAT_TELL); - } -} - -/* -================== -BotMatch_LeadTheWay -================== -*/ -void BotMatch_LeadTheWay(bot_state_t *bs, bot_match_t *match) { - aas_entityinfo_t entinfo; - char netname[MAX_MESSAGE_SIZE], teammate[MAX_MESSAGE_SIZE]; - int client, areanum, other; - - if (!TeamPlayIsOn()) return; - //if not addressed to this bot - if (!BotAddressedToBot(bs, match)) return; - //if someone asks for someone else - if (match->subtype & ST_SOMEONE) { - //get the team mate name - trap_BotMatchVariable(match, TEAMMATE, teammate, sizeof(teammate)); - client = FindClientByName(teammate); - //if this is the bot self - if (client == bs->client) { - other = qfalse; - } - else if (!BotSameTeam(bs, client)) { - //FIXME: say "I don't help the enemy" - return; - } - else { - other = qtrue; - } - } - else { - //get the netname - trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); - client = ClientFromName(netname); - other = qfalse; - } - //if the bot doesn't know who to help (FindClientByName returned -1) - if (client < 0) { - BotAI_BotInitialChat(bs, "whois", netname, NULL); - trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); - return; - } - // - bs->lead_teamgoal.entitynum = -1; - BotEntityInfo(client, &entinfo); - //if info is valid (in PVS) - if (entinfo.valid) { - areanum = BotPointAreaNum(entinfo.origin); - if (areanum) { // && trap_AAS_AreaReachability(areanum)) { - bs->lead_teamgoal.entitynum = client; - bs->lead_teamgoal.areanum = areanum; - VectorCopy(entinfo.origin, bs->lead_teamgoal.origin); - VectorSet(bs->lead_teamgoal.mins, -8, -8, -8); - VectorSet(bs->lead_teamgoal.maxs, 8, 8, 8); - } - } - - if (bs->teamgoal.entitynum < 0) { - if (other) BotAI_BotInitialChat(bs, "whereis", teammate, NULL); - else BotAI_BotInitialChat(bs, "whereareyou", netname, NULL); - trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); - return; - } - bs->lead_teammate = client; - bs->lead_time = FloatTime() + TEAM_LEAD_TIME; - bs->leadvisible_time = 0; - bs->leadmessage_time = -(FloatTime() + 2 * random()); -} - -/* -================== -BotMatch_Kill -================== -*/ -void BotMatch_Kill(bot_state_t *bs, bot_match_t *match) { - char enemy[MAX_MESSAGE_SIZE]; - char netname[MAX_MESSAGE_SIZE]; - int client; - - if (!TeamPlayIsOn()) return; - //if not addressed to this bot - if (!BotAddressedToBot(bs, match)) return; - - trap_BotMatchVariable(match, ENEMY, enemy, sizeof(enemy)); - // - client = FindEnemyByName(bs, enemy); - if (client < 0) { - BotAI_BotInitialChat(bs, "whois", enemy, NULL); - trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); - client = ClientFromName(netname); - trap_BotEnterChat(bs->cs, client, CHAT_TELL); - return; - } - bs->teamgoal.entitynum = client; - //set the time to send a message to the team mates - bs->teammessage_time = FloatTime() + 2 * random(); - //set the ltg type - bs->ltgtype = LTG_KILL; - //set the team goal time - bs->teamgoal_time = FloatTime() + TEAM_KILL_SOMEONE; - // - BotSetTeamStatus(bs); -#ifdef DEBUG - BotPrintTeamGoal(bs); -#endif //DEBUG -} - -/* -================== -BotMatch_CTF -================== -*/ -void BotMatch_CTF(bot_state_t *bs, bot_match_t *match) { - - char flag[128], netname[MAX_NETNAME]; - - if (gametype == GT_CTF) { - trap_BotMatchVariable(match, FLAG, flag, sizeof(flag)); - if (match->subtype & ST_GOTFLAG) { - if (!Q_stricmp(flag, "red")) { - bs->redflagstatus = 1; - if (BotTeam(bs) == TEAM_BLUE) { - trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); - bs->flagcarrier = ClientFromName(netname); - } - } - else { - bs->blueflagstatus = 1; - if (BotTeam(bs) == TEAM_RED) { - trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); - bs->flagcarrier = ClientFromName(netname); - } - } - bs->flagstatuschanged = 1; - bs->lastflagcapture_time = FloatTime(); - } - else if (match->subtype & ST_CAPTUREDFLAG) { - bs->redflagstatus = 0; - bs->blueflagstatus = 0; - bs->flagcarrier = 0; - bs->flagstatuschanged = 1; - } - else if (match->subtype & ST_RETURNEDFLAG) { - if (!Q_stricmp(flag, "red")) bs->redflagstatus = 0; - else bs->blueflagstatus = 0; - bs->flagstatuschanged = 1; - } - } -#ifdef MISSIONPACK - else if (gametype == GT_1FCTF) { - if (match->subtype & ST_1FCTFGOTFLAG) { - trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); - bs->flagcarrier = ClientFromName(netname); - } - } -#endif -} - -void BotMatch_EnterGame(bot_state_t *bs, bot_match_t *match) { - int client; - char netname[MAX_NETNAME]; - - trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); - client = FindClientByName(netname); - if (client >= 0) { - notleader[client] = qfalse; - } - //NOTE: eliza chats will catch this - //Com_sprintf(buf, sizeof(buf), "heya %s", netname); - //EA_Say(bs->client, buf); -} - -void BotMatch_NewLeader(bot_state_t *bs, bot_match_t *match) { - int client; - char netname[MAX_NETNAME]; - - trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); - client = FindClientByName(netname); - if (!BotSameTeam(bs, client)) - return; - Q_strncpyz(bs->teamleader, netname, sizeof(bs->teamleader)); -} - -/* -================== -BotMatchMessage -================== -*/ -int BotMatchMessage(bot_state_t *bs, char *message) { - bot_match_t match; - - match.type = 0; - //if it is an unknown message - if (!trap_BotFindMatch(message, &match, MTCONTEXT_MISC - |MTCONTEXT_INITIALTEAMCHAT - |MTCONTEXT_CTF)) { - return qfalse; - } - //react to the found message - switch(match.type) - { - case MSG_HELP: //someone calling for help - case MSG_ACCOMPANY: //someone calling for company - { - BotMatch_HelpAccompany(bs, &match); - break; - } - case MSG_DEFENDKEYAREA: //teamplay defend a key area - { - BotMatch_DefendKeyArea(bs, &match); - break; - } - case MSG_CAMP: //camp somewhere - { - BotMatch_Camp(bs, &match); - break; - } - case MSG_PATROL: //patrol between several key areas - { - BotMatch_Patrol(bs, &match); - break; - } - //CTF & 1FCTF - case MSG_GETFLAG: //ctf get the enemy flag - { - BotMatch_GetFlag(bs, &match); - break; - } -#ifdef MISSIONPACK - //CTF & 1FCTF & Obelisk & Harvester - case MSG_ATTACKENEMYBASE: - { - BotMatch_AttackEnemyBase(bs, &match); - break; - } - //Harvester - case MSG_HARVEST: - { - BotMatch_Harvest(bs, &match); - break; - } -#endif - //CTF & 1FCTF & Harvester - case MSG_RUSHBASE: //ctf rush to the base - { - BotMatch_RushBase(bs, &match); - break; - } - //CTF & 1FCTF - case MSG_RETURNFLAG: - { - BotMatch_ReturnFlag(bs, &match); - break; - } - //CTF & 1FCTF & Obelisk & Harvester - case MSG_TASKPREFERENCE: - { - BotMatch_TaskPreference(bs, &match); - break; - } - //CTF & 1FCTF - case MSG_CTF: - { - BotMatch_CTF(bs, &match); - break; - } - case MSG_GETITEM: - { - BotMatch_GetItem(bs, &match); - break; - } - case MSG_JOINSUBTEAM: //join a sub team - { - BotMatch_JoinSubteam(bs, &match); - break; - } - case MSG_LEAVESUBTEAM: //leave a sub team - { - BotMatch_LeaveSubteam(bs, &match); - break; - } - case MSG_WHICHTEAM: - { - BotMatch_WhichTeam(bs, &match); - break; - } - case MSG_CHECKPOINT: //remember a check point - { - BotMatch_CheckPoint(bs, &match); - break; - } - case MSG_CREATENEWFORMATION: //start the creation of a new formation - { - trap_EA_SayTeam(bs->client, "the part of my brain to create formations has been damaged"); - break; - } - case MSG_FORMATIONPOSITION: //tell someone his/her position in the formation - { - trap_EA_SayTeam(bs->client, "the part of my brain to create formations has been damaged"); - break; - } - case MSG_FORMATIONSPACE: //set the formation space - { - BotMatch_FormationSpace(bs, &match); - break; - } - case MSG_DOFORMATION: //form a certain formation - { - break; - } - case MSG_DISMISS: //dismiss someone - { - BotMatch_Dismiss(bs, &match); - break; - } - case MSG_STARTTEAMLEADERSHIP: //someone will become the team leader - { - BotMatch_StartTeamLeaderShip(bs, &match); - break; - } - case MSG_STOPTEAMLEADERSHIP: //someone will stop being the team leader - { - BotMatch_StopTeamLeaderShip(bs, &match); - break; - } - case MSG_WHOISTEAMLAEDER: - { - BotMatch_WhoIsTeamLeader(bs, &match); - break; - } - case MSG_WHATAREYOUDOING: //ask a bot what he/she is doing - { - BotMatch_WhatAreYouDoing(bs, &match); - break; - } - case MSG_WHATISMYCOMMAND: - { - BotMatch_WhatIsMyCommand(bs, &match); - break; - } - case MSG_WHEREAREYOU: - { - BotMatch_WhereAreYou(bs, &match); - break; - } - case MSG_LEADTHEWAY: - { - BotMatch_LeadTheWay(bs, &match); - break; - } - case MSG_KILL: - { - BotMatch_Kill(bs, &match); - break; - } - case MSG_ENTERGAME: //someone entered the game - { - BotMatch_EnterGame(bs, &match); - break; - } - case MSG_NEWLEADER: - { - BotMatch_NewLeader(bs, &match); - break; - } - case MSG_WAIT: - { - break; - } - case MSG_SUICIDE: - { - BotMatch_Suicide(bs, &match); - break; - } - default: - { - BotAI_Print(PRT_MESSAGE, "unknown match type\n"); - break; - } - } - return qtrue; -} +/* +=========================================================================== +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 Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// + +/***************************************************************************** + * name: ai_cmd.c + * + * desc: Quake3 bot AI + * + * $Archive: /MissionPack/code/game/ai_cmd.c $ + * + *****************************************************************************/ + +#include "g_local.h" +#include "botlib.h" +#include "be_aas.h" +#include "be_ea.h" +#include "be_ai_char.h" +#include "be_ai_chat.h" +#include "be_ai_gen.h" +#include "be_ai_goal.h" +#include "be_ai_move.h" +#include "be_ai_weap.h" +// +#include "ai_main.h" +#include "ai_dmq3.h" +#include "ai_chat.h" +#include "ai_cmd.h" +#include "ai_dmnet.h" +#include "ai_team.h" +// +#include "chars.h" //characteristics +#include "inv.h" //indexes into the inventory +#include "syn.h" //synonyms +#include "match.h" //string matching types and vars + +// for the voice chats +#include "../../ui/menudef.h" + +int notleader[MAX_CLIENTS]; + +#ifdef DEBUG +/* +================== +BotPrintTeamGoal +================== +*/ +void BotPrintTeamGoal(bot_state_t *bs) { + char netname[MAX_NETNAME]; + float t; + + ClientName(bs->client, netname, sizeof(netname)); + t = bs->teamgoal_time - FloatTime(); + switch(bs->ltgtype) { + case LTG_TEAMHELP: + { + BotAI_Print(PRT_MESSAGE, "%s: I'm gonna help a team mate for %1.0f secs\n", netname, t); + break; + } + case LTG_TEAMACCOMPANY: + { + BotAI_Print(PRT_MESSAGE, "%s: I'm gonna accompany a team mate for %1.0f secs\n", netname, t); + break; + } + case LTG_GETFLAG: + { + BotAI_Print(PRT_MESSAGE, "%s: I'm gonna get the flag for %1.0f secs\n", netname, t); + break; + } + case LTG_RUSHBASE: + { + BotAI_Print(PRT_MESSAGE, "%s: I'm gonna rush to the base for %1.0f secs\n", netname, t); + break; + } + case LTG_RETURNFLAG: + { + BotAI_Print(PRT_MESSAGE, "%s: I'm gonna try to return the flag for %1.0f secs\n", netname, t); + break; + } +#ifdef MISSIONPACK + case LTG_ATTACKENEMYBASE: + { + BotAI_Print(PRT_MESSAGE, "%s: I'm gonna attack the enemy base for %1.0f secs\n", netname, t); + break; + } + case LTG_HARVEST: + { + BotAI_Print(PRT_MESSAGE, "%s: I'm gonna harvest for %1.0f secs\n", netname, t); + break; + } +#endif + case LTG_DEFENDKEYAREA: + { + BotAI_Print(PRT_MESSAGE, "%s: I'm gonna defend a key area for %1.0f secs\n", netname, t); + break; + } + case LTG_GETITEM: + { + BotAI_Print(PRT_MESSAGE, "%s: I'm gonna get an item for %1.0f secs\n", netname, t); + break; + } + case LTG_KILL: + { + BotAI_Print(PRT_MESSAGE, "%s: I'm gonna kill someone for %1.0f secs\n", netname, t); + break; + } + case LTG_CAMP: + case LTG_CAMPORDER: + { + BotAI_Print(PRT_MESSAGE, "%s: I'm gonna camp for %1.0f secs\n", netname, t); + break; + } + case LTG_PATROL: + { + BotAI_Print(PRT_MESSAGE, "%s: I'm gonna patrol for %1.0f secs\n", netname, t); + break; + } + default: + { + if (bs->ctfroam_time > FloatTime()) { + t = bs->ctfroam_time - FloatTime(); + BotAI_Print(PRT_MESSAGE, "%s: I'm gonna roam for %1.0f secs\n", netname, t); + } + else { + BotAI_Print(PRT_MESSAGE, "%s: I've got a regular goal\n", netname); + } + } + } +} +#endif //DEBUG + +/* +================== +BotGetItemTeamGoal + +FIXME: add stuff like "upper rocket launcher" +"the rl near the railgun", "lower grenade launcher" etc. +================== +*/ +int BotGetItemTeamGoal(char *goalname, bot_goal_t *goal) { + int i; + + if (!strlen(goalname)) return qfalse; + i = -1; + do { + i = trap_BotGetLevelItemGoal(i, goalname, goal); + if (i > 0) { + //do NOT defend dropped items + if (goal->flags & GFL_DROPPED) + continue; + return qtrue; + } + } while(i > 0); + return qfalse; +} + +/* +================== +BotGetMessageTeamGoal +================== +*/ +int BotGetMessageTeamGoal(bot_state_t *bs, char *goalname, bot_goal_t *goal) { + bot_waypoint_t *cp; + + if (BotGetItemTeamGoal(goalname, goal)) return qtrue; + + cp = BotFindWayPoint(bs->checkpoints, goalname); + if (cp) { + memcpy(goal, &cp->goal, sizeof(bot_goal_t)); + return qtrue; + } + return qfalse; +} + +/* +================== +BotGetTime +================== +*/ +float BotGetTime(bot_match_t *match) { + bot_match_t timematch; + char timestring[MAX_MESSAGE_SIZE]; + float t; + + //if the matched string has a time + if (match->subtype & ST_TIME) { + //get the time string + trap_BotMatchVariable(match, TIME, timestring, MAX_MESSAGE_SIZE); + //match it to find out if the time is in seconds or minutes + if (trap_BotFindMatch(timestring, &timematch, MTCONTEXT_TIME)) { + if (timematch.type == MSG_FOREVER) { + t = 99999999.0f; + } + else if (timematch.type == MSG_FORAWHILE) { + t = 10 * 60; // 10 minutes + } + else if (timematch.type == MSG_FORALONGTIME) { + t = 30 * 60; // 30 minutes + } + else { + trap_BotMatchVariable(&timematch, TIME, timestring, MAX_MESSAGE_SIZE); + if (timematch.type == MSG_MINUTES) t = atof(timestring) * 60; + else if (timematch.type == MSG_SECONDS) t = atof(timestring); + else t = 0; + } + //if there's a valid time + if (t > 0) return FloatTime() + t; + } + } + return 0; +} + +/* +================== +FindClientByName +================== +*/ +int FindClientByName(char *name) { + int i; + char buf[MAX_INFO_STRING]; + static int maxclients; + + if (!maxclients) + maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + ClientName(i, buf, sizeof(buf)); + if (!Q_stricmp(buf, name)) return i; + } + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + ClientName(i, buf, sizeof(buf)); + if (stristr(buf, name)) return i; + } + return -1; +} + +/* +================== +FindEnemyByName +================== +*/ +int FindEnemyByName(bot_state_t *bs, char *name) { + int i; + char buf[MAX_INFO_STRING]; + static int maxclients; + + if (!maxclients) + maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + if (BotSameTeam(bs, i)) continue; + ClientName(i, buf, sizeof(buf)); + if (!Q_stricmp(buf, name)) return i; + } + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + if (BotSameTeam(bs, i)) continue; + ClientName(i, buf, sizeof(buf)); + if (stristr(buf, name)) return i; + } + return -1; +} + +/* +================== +NumPlayersOnSameTeam +================== +*/ +int NumPlayersOnSameTeam(bot_state_t *bs) { + int i, num; + char buf[MAX_INFO_STRING]; + static int maxclients; + + if (!maxclients) + maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); + + num = 0; + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + trap_GetConfigstring(CS_PLAYERS+i, buf, MAX_INFO_STRING); + if (strlen(buf)) { + if (BotSameTeam(bs, i+1)) num++; + } + } + return num; +} + +/* +================== +TeamPlayIsOn +================== +*/ +int BotGetPatrolWaypoints(bot_state_t *bs, bot_match_t *match) { + char keyarea[MAX_MESSAGE_SIZE]; + int patrolflags; + bot_waypoint_t *wp, *newwp, *newpatrolpoints; + bot_match_t keyareamatch; + bot_goal_t goal; + + newpatrolpoints = NULL; + patrolflags = 0; + // + trap_BotMatchVariable(match, KEYAREA, keyarea, MAX_MESSAGE_SIZE); + // + while(1) { + if (!trap_BotFindMatch(keyarea, &keyareamatch, MTCONTEXT_PATROLKEYAREA)) { + trap_EA_SayTeam(bs->client, "what do you say?"); + BotFreeWaypoints(newpatrolpoints); + bs->patrolpoints = NULL; + return qfalse; + } + trap_BotMatchVariable(&keyareamatch, KEYAREA, keyarea, MAX_MESSAGE_SIZE); + if (!BotGetMessageTeamGoal(bs, keyarea, &goal)) { + //BotAI_BotInitialChat(bs, "cannotfind", keyarea, NULL); + //trap_BotEnterChat(bs->cs, 0, CHAT_TEAM); + BotFreeWaypoints(newpatrolpoints); + bs->patrolpoints = NULL; + return qfalse; + } + //create a new waypoint + newwp = BotCreateWayPoint(keyarea, goal.origin, goal.areanum); + if (!newwp) + break; + //add the waypoint to the patrol points + newwp->next = NULL; + for (wp = newpatrolpoints; wp && wp->next; wp = wp->next); + if (!wp) { + newpatrolpoints = newwp; + newwp->prev = NULL; + } + else { + wp->next = newwp; + newwp->prev = wp; + } + // + if (keyareamatch.subtype & ST_BACK) { + patrolflags = PATROL_LOOP; + break; + } + else if (keyareamatch.subtype & ST_REVERSE) { + patrolflags = PATROL_REVERSE; + break; + } + else if (keyareamatch.subtype & ST_MORE) { + trap_BotMatchVariable(&keyareamatch, MORE, keyarea, MAX_MESSAGE_SIZE); + } + else { + break; + } + } + // + if (!newpatrolpoints || !newpatrolpoints->next) { + trap_EA_SayTeam(bs->client, "I need more key points to patrol\n"); + BotFreeWaypoints(newpatrolpoints); + newpatrolpoints = NULL; + return qfalse; + } + // + BotFreeWaypoints(bs->patrolpoints); + bs->patrolpoints = newpatrolpoints; + // + bs->curpatrolpoint = bs->patrolpoints; + bs->patrolflags = patrolflags; + // + return qtrue; +} + +/* +================== +BotAddressedToBot +================== +*/ +int BotAddressedToBot(bot_state_t *bs, bot_match_t *match) { + char addressedto[MAX_MESSAGE_SIZE]; + char netname[MAX_MESSAGE_SIZE]; + char name[MAX_MESSAGE_SIZE]; + char botname[128]; + int client; + bot_match_t addresseematch; + + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + client = ClientOnSameTeamFromName(bs, netname); + if (client < 0) return qfalse; + //if the message is addressed to someone + if (match->subtype & ST_ADDRESSED) { + trap_BotMatchVariable(match, ADDRESSEE, addressedto, sizeof(addressedto)); + //the name of this bot + ClientName(bs->client, botname, 128); + // + while(trap_BotFindMatch(addressedto, &addresseematch, MTCONTEXT_ADDRESSEE)) { + if (addresseematch.type == MSG_EVERYONE) { + return qtrue; + } + else if (addresseematch.type == MSG_MULTIPLENAMES) { + trap_BotMatchVariable(&addresseematch, TEAMMATE, name, sizeof(name)); + if (strlen(name)) { + if (stristr(botname, name)) return qtrue; + if (stristr(bs->subteam, name)) return qtrue; + } + trap_BotMatchVariable(&addresseematch, MORE, addressedto, MAX_MESSAGE_SIZE); + } + else { + trap_BotMatchVariable(&addresseematch, TEAMMATE, name, MAX_MESSAGE_SIZE); + if (strlen(name)) { + if (stristr(botname, name)) return qtrue; + if (stristr(bs->subteam, name)) return qtrue; + } + break; + } + } + //Com_sprintf(buf, sizeof(buf), "not addressed to me but %s", addressedto); + //trap_EA_Say(bs->client, buf); + return qfalse; + } + else { + bot_match_t tellmatch; + + tellmatch.type = 0; + //if this message wasn't directed solely to this bot + if (!trap_BotFindMatch(match->string, &tellmatch, MTCONTEXT_REPLYCHAT) || + tellmatch.type != MSG_CHATTELL) { + //make sure not everyone reacts to this message + if (random() > (float ) 1.0 / (NumPlayersOnSameTeam(bs)-1)) return qfalse; + } + } + return qtrue; +} + +/* +================== +BotGPSToPosition +================== +*/ +int BotGPSToPosition(char *buf, vec3_t position) { + int i, j = 0; + int num, sign; + + for (i = 0; i < 3; i++) { + num = 0; + while(buf[j] == ' ') j++; + if (buf[j] == '-') { + j++; + sign = -1; + } + else { + sign = 1; + } + while (buf[j]) { + if (buf[j] >= '0' && buf[j] <= '9') { + num = num * 10 + buf[j] - '0'; + j++; + } + else { + j++; + break; + } + } + BotAI_Print(PRT_MESSAGE, "%d\n", sign * num); + position[i] = (float) sign * num; + } + return qtrue; +} + +/* +================== +BotMatch_HelpAccompany +================== +*/ +void BotMatch_HelpAccompany(bot_state_t *bs, bot_match_t *match) { + int client, other, areanum; + char teammate[MAX_MESSAGE_SIZE]; + char netname[MAX_MESSAGE_SIZE]; + char itemname[MAX_MESSAGE_SIZE]; + bot_match_t teammatematch; + aas_entityinfo_t entinfo; + + if (!TeamPlayIsOn()) return; + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) return; + //get the team mate name + trap_BotMatchVariable(match, TEAMMATE, teammate, sizeof(teammate)); + //get the client to help + if (trap_BotFindMatch(teammate, &teammatematch, MTCONTEXT_TEAMMATE) && + //if someone asks for him or herself + teammatematch.type == MSG_ME) { + //get the netname + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + client = ClientFromName(netname); + other = qfalse; + } + else { + //asked for someone else + client = FindClientByName(teammate); + //if this is the bot self + if (client == bs->client) { + other = qfalse; + } + else if (!BotSameTeam(bs, client)) { + //FIXME: say "I don't help the enemy" + return; + } + else { + other = qtrue; + } + } + //if the bot doesn't know who to help (FindClientByName returned -1) + if (client < 0) { + if (other) BotAI_BotInitialChat(bs, "whois", teammate, NULL); + else BotAI_BotInitialChat(bs, "whois", netname, NULL); + client = ClientFromName(netname); + trap_BotEnterChat(bs->cs, client, CHAT_TELL); + return; + } + //don't help or accompany yourself + if (client == bs->client) { + return; + } + // + bs->teamgoal.entitynum = -1; + BotEntityInfo(client, &entinfo); + //if info is valid (in PVS) + if (entinfo.valid) { + areanum = BotPointAreaNum(entinfo.origin); + if (areanum) {// && trap_AAS_AreaReachability(areanum)) { + bs->teamgoal.entitynum = client; + bs->teamgoal.areanum = areanum; + VectorCopy(entinfo.origin, bs->teamgoal.origin); + VectorSet(bs->teamgoal.mins, -8, -8, -8); + VectorSet(bs->teamgoal.maxs, 8, 8, 8); + } + } + //if no teamgoal yet + if (bs->teamgoal.entitynum < 0) { + //if near an item + if (match->subtype & ST_NEARITEM) { + //get the match variable + trap_BotMatchVariable(match, ITEM, itemname, sizeof(itemname)); + // + if (!BotGetMessageTeamGoal(bs, itemname, &bs->teamgoal)) { + //BotAI_BotInitialChat(bs, "cannotfind", itemname, NULL); + //trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); + return; + } + } + } + // + if (bs->teamgoal.entitynum < 0) { + if (other) BotAI_BotInitialChat(bs, "whereis", teammate, NULL); + else BotAI_BotInitialChat(bs, "whereareyou", netname, NULL); + client = ClientFromName(netname); + trap_BotEnterChat(bs->cs, client, CHAT_TEAM); + return; + } + //the team mate + bs->teammate = client; + // + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + // + client = ClientFromName(netname); + //the team mate who ordered + bs->decisionmaker = client; + bs->ordered = qtrue; + bs->order_time = FloatTime(); + //last time the team mate was assumed visible + bs->teammatevisible_time = FloatTime(); + //set the time to send a message to the team mates + bs->teammessage_time = FloatTime() + 2 * random(); + //get the team goal time + bs->teamgoal_time = BotGetTime(match); + //set the ltg type + if (match->type == MSG_HELP) { + bs->ltgtype = LTG_TEAMHELP; + if (!bs->teamgoal_time) bs->teamgoal_time = FloatTime() + TEAM_HELP_TIME; + } + else { + bs->ltgtype = LTG_TEAMACCOMPANY; + if (!bs->teamgoal_time) bs->teamgoal_time = FloatTime() + TEAM_ACCOMPANY_TIME; + bs->formation_dist = 3.5 * 32; //3.5 meter + bs->arrive_time = 0; + // + BotSetTeamStatus(bs); + // remember last ordered task + BotRememberLastOrderedTask(bs); + } +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} + +/* +================== +BotMatch_DefendKeyArea +================== +*/ +void BotMatch_DefendKeyArea(bot_state_t *bs, bot_match_t *match) { + char itemname[MAX_MESSAGE_SIZE]; + char netname[MAX_MESSAGE_SIZE]; + int client; + + if (!TeamPlayIsOn()) return; + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) return; + //get the match variable + trap_BotMatchVariable(match, KEYAREA, itemname, sizeof(itemname)); + // + if (!BotGetMessageTeamGoal(bs, itemname, &bs->teamgoal)) { + //BotAI_BotInitialChat(bs, "cannotfind", itemname, NULL); + //trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); + return; + } + // + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + // + client = ClientFromName(netname); + //the team mate who ordered + bs->decisionmaker = client; + bs->ordered = qtrue; + bs->order_time = FloatTime(); + //set the time to send a message to the team mates + bs->teammessage_time = FloatTime() + 2 * random(); + //set the ltg type + bs->ltgtype = LTG_DEFENDKEYAREA; + //get the team goal time + bs->teamgoal_time = BotGetTime(match); + //set the team goal time + if (!bs->teamgoal_time) bs->teamgoal_time = FloatTime() + TEAM_DEFENDKEYAREA_TIME; + //away from defending + bs->defendaway_time = 0; + // + BotSetTeamStatus(bs); + // remember last ordered task + BotRememberLastOrderedTask(bs); +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} + +/* +================== +BotMatch_GetItem +================== +*/ +void BotMatch_GetItem(bot_state_t *bs, bot_match_t *match) { + char itemname[MAX_MESSAGE_SIZE]; + char netname[MAX_MESSAGE_SIZE]; + int client; + + if (!TeamPlayIsOn()) return; + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) return; + //get the match variable + trap_BotMatchVariable(match, ITEM, itemname, sizeof(itemname)); + // + if (!BotGetMessageTeamGoal(bs, itemname, &bs->teamgoal)) { + //BotAI_BotInitialChat(bs, "cannotfind", itemname, NULL); + //trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); + return; + } + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + client = ClientOnSameTeamFromName(bs, netname); + // + bs->decisionmaker = client; + bs->ordered = qtrue; + bs->order_time = FloatTime(); + //set the time to send a message to the team mates + bs->teammessage_time = FloatTime() + 2 * random(); + //set the ltg type + bs->ltgtype = LTG_GETITEM; + //set the team goal time + bs->teamgoal_time = FloatTime() + TEAM_GETITEM_TIME; + // + BotSetTeamStatus(bs); +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} + +/* +================== +BotMatch_Camp +================== +*/ +void BotMatch_Camp(bot_state_t *bs, bot_match_t *match) { + int client, areanum; + char netname[MAX_MESSAGE_SIZE]; + char itemname[MAX_MESSAGE_SIZE]; + aas_entityinfo_t entinfo; + + if (!TeamPlayIsOn()) return; + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) return; + // + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + //asked for someone else + client = FindClientByName(netname); + //if there's no valid client with this name + if (client < 0) { + BotAI_BotInitialChat(bs, "whois", netname, NULL); + trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); + return; + } + //get the match variable + trap_BotMatchVariable(match, KEYAREA, itemname, sizeof(itemname)); + //in CTF it could be the base + if (match->subtype & ST_THERE) { + //camp at the spot the bot is currently standing + bs->teamgoal.entitynum = bs->entitynum; + bs->teamgoal.areanum = bs->areanum; + VectorCopy(bs->origin, bs->teamgoal.origin); + VectorSet(bs->teamgoal.mins, -8, -8, -8); + VectorSet(bs->teamgoal.maxs, 8, 8, 8); + } + else if (match->subtype & ST_HERE) { + //if this is the bot self + if (client == bs->client) return; + // + bs->teamgoal.entitynum = -1; + BotEntityInfo(client, &entinfo); + //if info is valid (in PVS) + if (entinfo.valid) { + areanum = BotPointAreaNum(entinfo.origin); + if (areanum) {// && trap_AAS_AreaReachability(areanum)) { + //NOTE: just assume the bot knows where the person is + //if (BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, client)) { + bs->teamgoal.entitynum = client; + bs->teamgoal.areanum = areanum; + VectorCopy(entinfo.origin, bs->teamgoal.origin); + VectorSet(bs->teamgoal.mins, -8, -8, -8); + VectorSet(bs->teamgoal.maxs, 8, 8, 8); + //} + } + } + //if the other is not visible + if (bs->teamgoal.entitynum < 0) { + BotAI_BotInitialChat(bs, "whereareyou", netname, NULL); + client = ClientFromName(netname); + trap_BotEnterChat(bs->cs, client, CHAT_TELL); + return; + } + } + else if (!BotGetMessageTeamGoal(bs, itemname, &bs->teamgoal)) { + //BotAI_BotInitialChat(bs, "cannotfind", itemname, NULL); + //client = ClientFromName(netname); + //trap_BotEnterChat(bs->cs, client, CHAT_TELL); + return; + } + // + bs->decisionmaker = client; + bs->ordered = qtrue; + bs->order_time = FloatTime(); + //set the time to send a message to the team mates + bs->teammessage_time = FloatTime() + 2 * random(); + //set the ltg type + bs->ltgtype = LTG_CAMPORDER; + //get the team goal time + bs->teamgoal_time = BotGetTime(match); + //set the team goal time + if (!bs->teamgoal_time) bs->teamgoal_time = FloatTime() + TEAM_CAMP_TIME; + //not arrived yet + bs->arrive_time = 0; + // + BotSetTeamStatus(bs); + // remember last ordered task + BotRememberLastOrderedTask(bs); +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} + +/* +================== +BotMatch_Patrol +================== +*/ +void BotMatch_Patrol(bot_state_t *bs, bot_match_t *match) { + char netname[MAX_MESSAGE_SIZE]; + int client; + + if (!TeamPlayIsOn()) return; + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) return; + //get the patrol waypoints + if (!BotGetPatrolWaypoints(bs, match)) return; + // + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + // + client = FindClientByName(netname); + // + bs->decisionmaker = client; + bs->ordered = qtrue; + bs->order_time = FloatTime(); + //set the time to send a message to the team mates + bs->teammessage_time = FloatTime() + 2 * random(); + //set the ltg type + bs->ltgtype = LTG_PATROL; + //get the team goal time + bs->teamgoal_time = BotGetTime(match); + //set the team goal time if not set already + if (!bs->teamgoal_time) bs->teamgoal_time = FloatTime() + TEAM_PATROL_TIME; + // + BotSetTeamStatus(bs); + // remember last ordered task + BotRememberLastOrderedTask(bs); +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} + +/* +================== +BotMatch_GetFlag +================== +*/ +void BotMatch_GetFlag(bot_state_t *bs, bot_match_t *match) { + char netname[MAX_MESSAGE_SIZE]; + int client; + + if (gametype == GT_CTF) { + if (!ctf_redflag.areanum || !ctf_blueflag.areanum) + return; + } +#ifdef MISSIONPACK + else if (gametype == GT_1FCTF) { + if (!ctf_neutralflag.areanum || !ctf_redflag.areanum || !ctf_blueflag.areanum) + return; + } +#endif + else { + return; + } + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) return; + // + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + // + client = FindClientByName(netname); + // + bs->decisionmaker = client; + bs->ordered = qtrue; + bs->order_time = FloatTime(); + //set the time to send a message to the team mates + bs->teammessage_time = FloatTime() + 2 * random(); + //set the ltg type + bs->ltgtype = LTG_GETFLAG; + //set the team goal time + bs->teamgoal_time = FloatTime() + CTF_GETFLAG_TIME; + // get an alternate route in ctf + if (gametype == GT_CTF) { + //get an alternative route goal towards the enemy base + BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs)); + } + // + BotSetTeamStatus(bs); + // remember last ordered task + BotRememberLastOrderedTask(bs); +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} + +/* +================== +BotMatch_AttackEnemyBase +================== +*/ +void BotMatch_AttackEnemyBase(bot_state_t *bs, bot_match_t *match) { + char netname[MAX_MESSAGE_SIZE]; + int client; + + if (gametype == GT_CTF) { + BotMatch_GetFlag(bs, match); + } +#ifdef MISSIONPACK + else if (gametype == GT_1FCTF || gametype == GT_OBELISK || gametype == GT_HARVESTER) { + if (!redobelisk.areanum || !blueobelisk.areanum) + return; + } +#endif + else { + return; + } + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) return; + // + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + // + client = FindClientByName(netname); + // + bs->decisionmaker = client; + bs->ordered = qtrue; + bs->order_time = FloatTime(); + //set the time to send a message to the team mates + bs->teammessage_time = FloatTime() + 2 * random(); + //set the ltg type + bs->ltgtype = LTG_ATTACKENEMYBASE; + //set the team goal time + bs->teamgoal_time = FloatTime() + TEAM_ATTACKENEMYBASE_TIME; + bs->attackaway_time = 0; + // + BotSetTeamStatus(bs); + // remember last ordered task + BotRememberLastOrderedTask(bs); +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} + +#ifdef MISSIONPACK +/* +================== +BotMatch_Harvest +================== +*/ +void BotMatch_Harvest(bot_state_t *bs, bot_match_t *match) { + char netname[MAX_MESSAGE_SIZE]; + int client; + + if (gametype == GT_HARVESTER) { + if (!neutralobelisk.areanum || !redobelisk.areanum || !blueobelisk.areanum) + return; + } + else { + return; + } + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) return; + // + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + // + client = FindClientByName(netname); + // + bs->decisionmaker = client; + bs->ordered = qtrue; + bs->order_time = FloatTime(); + //set the time to send a message to the team mates + bs->teammessage_time = FloatTime() + 2 * random(); + //set the ltg type + bs->ltgtype = LTG_HARVEST; + //set the team goal time + bs->teamgoal_time = FloatTime() + TEAM_HARVEST_TIME; + bs->harvestaway_time = 0; + // + BotSetTeamStatus(bs); + // remember last ordered task + BotRememberLastOrderedTask(bs); +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} +#endif + +/* +================== +BotMatch_RushBase +================== +*/ +void BotMatch_RushBase(bot_state_t *bs, bot_match_t *match) { + char netname[MAX_MESSAGE_SIZE]; + int client; + + if (gametype == GT_CTF) { + if (!ctf_redflag.areanum || !ctf_blueflag.areanum) + return; + } +#ifdef MISSIONPACK + else if (gametype == GT_1FCTF || gametype == GT_HARVESTER) { + if (!redobelisk.areanum || !blueobelisk.areanum) + return; + } +#endif + else { + return; + } + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) return; + // + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + // + client = FindClientByName(netname); + // + bs->decisionmaker = client; + bs->ordered = qtrue; + bs->order_time = FloatTime(); + //set the time to send a message to the team mates + bs->teammessage_time = FloatTime() + 2 * random(); + //set the ltg type + bs->ltgtype = LTG_RUSHBASE; + //set the team goal time + bs->teamgoal_time = FloatTime() + CTF_RUSHBASE_TIME; + bs->rushbaseaway_time = 0; + // + BotSetTeamStatus(bs); +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} + +/* +================== +BotMatch_TaskPreference +================== +*/ +void BotMatch_TaskPreference(bot_state_t *bs, bot_match_t *match) { + char netname[MAX_NETNAME]; + char teammatename[MAX_MESSAGE_SIZE]; + int teammate, preference; + + ClientName(bs->client, netname, sizeof(netname)); + if (Q_stricmp(netname, bs->teamleader) != 0) return; + + trap_BotMatchVariable(match, NETNAME, teammatename, sizeof(teammatename)); + teammate = ClientFromName(teammatename); + if (teammate < 0) return; + + preference = BotGetTeamMateTaskPreference(bs, teammate); + switch(match->subtype) + { + case ST_DEFENDER: + { + preference &= ~TEAMTP_ATTACKER; + preference |= TEAMTP_DEFENDER; + break; + } + case ST_ATTACKER: + { + preference &= ~TEAMTP_DEFENDER; + preference |= TEAMTP_ATTACKER; + break; + } + case ST_ROAMER: + { + preference &= ~(TEAMTP_ATTACKER|TEAMTP_DEFENDER); + break; + } + } + BotSetTeamMateTaskPreference(bs, teammate, preference); + // + EasyClientName(teammate, teammatename, sizeof(teammatename)); + BotAI_BotInitialChat(bs, "keepinmind", teammatename, NULL); + trap_BotEnterChat(bs->cs, teammate, CHAT_TELL); + BotVoiceChatOnly(bs, teammate, VOICECHAT_YES); + trap_EA_Action(bs->client, ACTION_AFFIRMATIVE); +} + +/* +================== +BotMatch_ReturnFlag +================== +*/ +void BotMatch_ReturnFlag(bot_state_t *bs, bot_match_t *match) { + char netname[MAX_MESSAGE_SIZE]; + int client; + + //if not in CTF mode + if ( + gametype != GT_CTF +#ifdef MISSIONPACK + && gametype != GT_1FCTF +#endif + ) + return; + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) + return; + // + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + // + client = FindClientByName(netname); + // + bs->decisionmaker = client; + bs->ordered = qtrue; + bs->order_time = FloatTime(); + //set the time to send a message to the team mates + bs->teammessage_time = FloatTime() + 2 * random(); + //set the ltg type + bs->ltgtype = LTG_RETURNFLAG; + //set the team goal time + bs->teamgoal_time = FloatTime() + CTF_RETURNFLAG_TIME; + bs->rushbaseaway_time = 0; + // + BotSetTeamStatus(bs); +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} + +/* +================== +BotMatch_JoinSubteam +================== +*/ +void BotMatch_JoinSubteam(bot_state_t *bs, bot_match_t *match) { + char teammate[MAX_MESSAGE_SIZE]; + char netname[MAX_MESSAGE_SIZE]; + int client; + + if (!TeamPlayIsOn()) return; + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) return; + //get the sub team name + trap_BotMatchVariable(match, TEAMNAME, teammate, sizeof(teammate)); + //set the sub team name + strncpy(bs->subteam, teammate, 32); + bs->subteam[31] = '\0'; + // + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + BotAI_BotInitialChat(bs, "joinedteam", teammate, NULL); + client = ClientFromName(netname); + trap_BotEnterChat(bs->cs, client, CHAT_TELL); +} + +/* +================== +BotMatch_LeaveSubteam +================== +*/ +void BotMatch_LeaveSubteam(bot_state_t *bs, bot_match_t *match) { + char netname[MAX_MESSAGE_SIZE]; + int client; + + if (!TeamPlayIsOn()) return; + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) return; + // + if (strlen(bs->subteam)) + { + BotAI_BotInitialChat(bs, "leftteam", bs->subteam, NULL); + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + client = ClientFromName(netname); + trap_BotEnterChat(bs->cs, client, CHAT_TELL); + } //end if + strcpy(bs->subteam, ""); +} + +/* +================== +BotMatch_LeaveSubteam +================== +*/ +void BotMatch_WhichTeam(bot_state_t *bs, bot_match_t *match) { + if (!TeamPlayIsOn()) return; + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) return; + // + if (strlen(bs->subteam)) { + BotAI_BotInitialChat(bs, "inteam", bs->subteam, NULL); + } + else { + BotAI_BotInitialChat(bs, "noteam", NULL); + } + trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); +} + +/* +================== +BotMatch_CheckPoint +================== +*/ +void BotMatch_CheckPoint(bot_state_t *bs, bot_match_t *match) { + int areanum, client; + char buf[MAX_MESSAGE_SIZE]; + char netname[MAX_MESSAGE_SIZE]; + vec3_t position; + bot_waypoint_t *cp; + + if (!TeamPlayIsOn()) return; + // + trap_BotMatchVariable(match, POSITION, buf, MAX_MESSAGE_SIZE); + VectorClear(position); + // + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + client = ClientFromName(netname); + //BotGPSToPosition(buf, position); + sscanf(buf, "%f %f %f", &position[0], &position[1], &position[2]); + position[2] += 0.5; + areanum = BotPointAreaNum(position); + if (!areanum) { + if (BotAddressedToBot(bs, match)) { + BotAI_BotInitialChat(bs, "checkpoint_invalid", NULL); + trap_BotEnterChat(bs->cs, client, CHAT_TELL); + } + return; + } + // + trap_BotMatchVariable(match, NAME, buf, MAX_MESSAGE_SIZE); + //check if there already exists a checkpoint with this name + cp = BotFindWayPoint(bs->checkpoints, buf); + if (cp) { + if (cp->next) cp->next->prev = cp->prev; + if (cp->prev) cp->prev->next = cp->next; + else bs->checkpoints = cp->next; + cp->inuse = qfalse; + } + //create a new check point + cp = BotCreateWayPoint(buf, position, areanum); + //add the check point to the bot's known chech points + cp->next = bs->checkpoints; + if (bs->checkpoints) bs->checkpoints->prev = cp; + bs->checkpoints = cp; + // + if (BotAddressedToBot(bs, match)) { + Com_sprintf(buf, sizeof(buf), "%1.0f %1.0f %1.0f", cp->goal.origin[0], + cp->goal.origin[1], + cp->goal.origin[2]); + + BotAI_BotInitialChat(bs, "checkpoint_confirm", cp->name, buf, NULL); + trap_BotEnterChat(bs->cs, client, CHAT_TELL); + } +} + +/* +================== +BotMatch_FormationSpace +================== +*/ +void BotMatch_FormationSpace(bot_state_t *bs, bot_match_t *match) { + char buf[MAX_MESSAGE_SIZE]; + float space; + + if (!TeamPlayIsOn()) return; + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) return; + // + trap_BotMatchVariable(match, NUMBER, buf, MAX_MESSAGE_SIZE); + //if it's the distance in feet + if (match->subtype & ST_FEET) space = 0.3048 * 32 * atof(buf); + //else it's in meters + else space = 32 * atof(buf); + //check if the formation intervening space is valid + if (space < 48 || space > 500) space = 100; + bs->formation_dist = space; +} + +/* +================== +BotMatch_Dismiss +================== +*/ +void BotMatch_Dismiss(bot_state_t *bs, bot_match_t *match) { + char netname[MAX_MESSAGE_SIZE]; + int client; + + if (!TeamPlayIsOn()) return; + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) return; + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + client = ClientFromName(netname); + // + bs->decisionmaker = client; + // + bs->ltgtype = 0; + bs->lead_time = 0; + bs->lastgoal_ltgtype = 0; + // + BotAI_BotInitialChat(bs, "dismissed", NULL); + trap_BotEnterChat(bs->cs, client, CHAT_TELL); +} + +/* +================== +BotMatch_Suicide +================== +*/ +void BotMatch_Suicide(bot_state_t *bs, bot_match_t *match) { + char netname[MAX_MESSAGE_SIZE]; + int client; + + if (!TeamPlayIsOn()) return; + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) return; + // + trap_EA_Command(bs->client, "kill"); + // + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + client = ClientFromName(netname); + // + BotVoiceChat(bs, client, VOICECHAT_TAUNT); + trap_EA_Action(bs->client, ACTION_AFFIRMATIVE); +} + +/* +================== +BotMatch_StartTeamLeaderShip +================== +*/ +void BotMatch_StartTeamLeaderShip(bot_state_t *bs, bot_match_t *match) { + int client; + char teammate[MAX_MESSAGE_SIZE]; + + if (!TeamPlayIsOn()) return; + //if chats for him or herself + if (match->subtype & ST_I) { + //get the team mate that will be the team leader + trap_BotMatchVariable(match, NETNAME, teammate, sizeof(teammate)); + strncpy(bs->teamleader, teammate, sizeof(bs->teamleader)); + bs->teamleader[sizeof(bs->teamleader)] = '\0'; + } + //chats for someone else + else { + //get the team mate that will be the team leader + trap_BotMatchVariable(match, TEAMMATE, teammate, sizeof(teammate)); + client = FindClientByName(teammate); + if (client >= 0) ClientName(client, bs->teamleader, sizeof(bs->teamleader)); + } +} + +/* +================== +BotMatch_StopTeamLeaderShip +================== +*/ +void BotMatch_StopTeamLeaderShip(bot_state_t *bs, bot_match_t *match) { + int client; + char teammate[MAX_MESSAGE_SIZE]; + char netname[MAX_MESSAGE_SIZE]; + + if (!TeamPlayIsOn()) return; + //get the team mate that stops being the team leader + trap_BotMatchVariable(match, TEAMMATE, teammate, sizeof(teammate)); + //if chats for him or herself + if (match->subtype & ST_I) { + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + client = FindClientByName(netname); + } + //chats for someone else + else { + client = FindClientByName(teammate); + } //end else + if (client >= 0) { + if (!Q_stricmp(bs->teamleader, ClientName(client, netname, sizeof(netname)))) { + bs->teamleader[0] = '\0'; + notleader[client] = qtrue; + } + } +} + +/* +================== +BotMatch_WhoIsTeamLeader +================== +*/ +void BotMatch_WhoIsTeamLeader(bot_state_t *bs, bot_match_t *match) { + char netname[MAX_MESSAGE_SIZE]; + + if (!TeamPlayIsOn()) return; + + ClientName(bs->client, netname, sizeof(netname)); + //if this bot IS the team leader + if (!Q_stricmp(netname, bs->teamleader)) { + trap_EA_SayTeam(bs->client, "I'm the team leader\n"); + } +} + +/* +================== +BotMatch_WhatAreYouDoing +================== +*/ +void BotMatch_WhatAreYouDoing(bot_state_t *bs, bot_match_t *match) { + char netname[MAX_MESSAGE_SIZE]; + char goalname[MAX_MESSAGE_SIZE]; + int client; + + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) return; + // + switch(bs->ltgtype) { + case LTG_TEAMHELP: + { + EasyClientName(bs->teammate, netname, sizeof(netname)); + BotAI_BotInitialChat(bs, "helping", netname, NULL); + break; + } + case LTG_TEAMACCOMPANY: + { + EasyClientName(bs->teammate, netname, sizeof(netname)); + BotAI_BotInitialChat(bs, "accompanying", netname, NULL); + break; + } + case LTG_DEFENDKEYAREA: + { + trap_BotGoalName(bs->teamgoal.number, goalname, sizeof(goalname)); + BotAI_BotInitialChat(bs, "defending", goalname, NULL); + break; + } + case LTG_GETITEM: + { + trap_BotGoalName(bs->teamgoal.number, goalname, sizeof(goalname)); + BotAI_BotInitialChat(bs, "gettingitem", goalname, NULL); + break; + } + case LTG_KILL: + { + ClientName(bs->teamgoal.entitynum, netname, sizeof(netname)); + BotAI_BotInitialChat(bs, "killing", netname, NULL); + break; + } + case LTG_CAMP: + case LTG_CAMPORDER: + { + BotAI_BotInitialChat(bs, "camping", NULL); + break; + } + case LTG_PATROL: + { + BotAI_BotInitialChat(bs, "patrolling", NULL); + break; + } + case LTG_GETFLAG: + { + BotAI_BotInitialChat(bs, "capturingflag", NULL); + break; + } + case LTG_RUSHBASE: + { + BotAI_BotInitialChat(bs, "rushingbase", NULL); + break; + } + case LTG_RETURNFLAG: + { + BotAI_BotInitialChat(bs, "returningflag", NULL); + break; + } +#ifdef MISSIONPACK + case LTG_ATTACKENEMYBASE: + { + BotAI_BotInitialChat(bs, "attackingenemybase", NULL); + break; + } + case LTG_HARVEST: + { + BotAI_BotInitialChat(bs, "harvesting", NULL); + break; + } +#endif + default: + { + BotAI_BotInitialChat(bs, "roaming", NULL); + break; + } + } + //chat what the bot is doing + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + client = ClientFromName(netname); + trap_BotEnterChat(bs->cs, client, CHAT_TELL); +} + +/* +================== +BotMatch_WhatIsMyCommand +================== +*/ +void BotMatch_WhatIsMyCommand(bot_state_t *bs, bot_match_t *match) { + char netname[MAX_NETNAME]; + + ClientName(bs->client, netname, sizeof(netname)); + if (Q_stricmp(netname, bs->teamleader) != 0) return; + bs->forceorders = qtrue; +} + +/* +================== +BotNearestVisibleItem +================== +*/ +float BotNearestVisibleItem(bot_state_t *bs, char *itemname, bot_goal_t *goal) { + int i; + char name[64]; + bot_goal_t tmpgoal; + float dist, bestdist; + vec3_t dir; + bsp_trace_t trace; + + bestdist = 999999; + i = -1; + do { + i = trap_BotGetLevelItemGoal(i, itemname, &tmpgoal); + trap_BotGoalName(tmpgoal.number, name, sizeof(name)); + if (Q_stricmp(itemname, name) != 0) + continue; + VectorSubtract(tmpgoal.origin, bs->origin, dir); + dist = VectorLength(dir); + if (dist < bestdist) { + //trace from start to end + BotAI_Trace(&trace, bs->eye, NULL, NULL, tmpgoal.origin, bs->client, CONTENTS_SOLID|CONTENTS_PLAYERCLIP); + if (trace.fraction >= 1.0) { + bestdist = dist; + memcpy(goal, &tmpgoal, sizeof(bot_goal_t)); + } + } + } while(i > 0); + return bestdist; +} + +/* +================== +BotMatch_WhereAreYou +================== +*/ +void BotMatch_WhereAreYou(bot_state_t *bs, bot_match_t *match) { + float dist, bestdist; + int i, bestitem, redtt, bluett, client; + bot_goal_t goal; + char netname[MAX_MESSAGE_SIZE]; + char *nearbyitems[] = { + "Shotgun", + "Grenade Launcher", + "Rocket Launcher", + "Plasmagun", + "Railgun", + "Lightning Gun", + "BFG10K", + "Quad Damage", + "Regeneration", + "Battle Suit", + "Speed", + "Invisibility", + "Flight", + "Armor", + "Heavy Armor", + "Red Flag", + "Blue Flag", +#ifdef MISSIONPACK + "Nailgun", + "Prox Launcher", + "Chaingun", + "Scout", + "Guard", + "Doubler", + "Ammo Regen", + "Neutral Flag", + "Red Obelisk", + "Blue Obelisk", + "Neutral Obelisk", +#endif + NULL + }; + // + if (!TeamPlayIsOn()) + return; + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) + return; + + bestitem = -1; + bestdist = 999999; + for (i = 0; nearbyitems[i]; i++) { + dist = BotNearestVisibleItem(bs, nearbyitems[i], &goal); + if (dist < bestdist) { + bestdist = dist; + bestitem = i; + } + } + if (bestitem != -1) { + if (gametype == GT_CTF +#ifdef MISSIONPACK + || gametype == GT_1FCTF +#endif + ) { + redtt = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, ctf_redflag.areanum, TFL_DEFAULT); + bluett = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, ctf_blueflag.areanum, TFL_DEFAULT); + if (redtt < (redtt + bluett) * 0.4) { + BotAI_BotInitialChat(bs, "teamlocation", nearbyitems[bestitem], "red", NULL); + } + else if (bluett < (redtt + bluett) * 0.4) { + BotAI_BotInitialChat(bs, "teamlocation", nearbyitems[bestitem], "blue", NULL); + } + else { + BotAI_BotInitialChat(bs, "location", nearbyitems[bestitem], NULL); + } + } +#ifdef MISSIONPACK + else if (gametype == GT_OBELISK || gametype == GT_HARVESTER) { + redtt = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, redobelisk.areanum, TFL_DEFAULT); + bluett = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, blueobelisk.areanum, TFL_DEFAULT); + if (redtt < (redtt + bluett) * 0.4) { + BotAI_BotInitialChat(bs, "teamlocation", nearbyitems[bestitem], "red", NULL); + } + else if (bluett < (redtt + bluett) * 0.4) { + BotAI_BotInitialChat(bs, "teamlocation", nearbyitems[bestitem], "blue", NULL); + } + else { + BotAI_BotInitialChat(bs, "location", nearbyitems[bestitem], NULL); + } + } +#endif + else { + BotAI_BotInitialChat(bs, "location", nearbyitems[bestitem], NULL); + } + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + client = ClientFromName(netname); + trap_BotEnterChat(bs->cs, client, CHAT_TELL); + } +} + +/* +================== +BotMatch_LeadTheWay +================== +*/ +void BotMatch_LeadTheWay(bot_state_t *bs, bot_match_t *match) { + aas_entityinfo_t entinfo; + char netname[MAX_MESSAGE_SIZE], teammate[MAX_MESSAGE_SIZE]; + int client, areanum, other; + + if (!TeamPlayIsOn()) return; + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) return; + //if someone asks for someone else + if (match->subtype & ST_SOMEONE) { + //get the team mate name + trap_BotMatchVariable(match, TEAMMATE, teammate, sizeof(teammate)); + client = FindClientByName(teammate); + //if this is the bot self + if (client == bs->client) { + other = qfalse; + } + else if (!BotSameTeam(bs, client)) { + //FIXME: say "I don't help the enemy" + return; + } + else { + other = qtrue; + } + } + else { + //get the netname + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + client = ClientFromName(netname); + other = qfalse; + } + //if the bot doesn't know who to help (FindClientByName returned -1) + if (client < 0) { + BotAI_BotInitialChat(bs, "whois", netname, NULL); + trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); + return; + } + // + bs->lead_teamgoal.entitynum = -1; + BotEntityInfo(client, &entinfo); + //if info is valid (in PVS) + if (entinfo.valid) { + areanum = BotPointAreaNum(entinfo.origin); + if (areanum) { // && trap_AAS_AreaReachability(areanum)) { + bs->lead_teamgoal.entitynum = client; + bs->lead_teamgoal.areanum = areanum; + VectorCopy(entinfo.origin, bs->lead_teamgoal.origin); + VectorSet(bs->lead_teamgoal.mins, -8, -8, -8); + VectorSet(bs->lead_teamgoal.maxs, 8, 8, 8); + } + } + + if (bs->teamgoal.entitynum < 0) { + if (other) BotAI_BotInitialChat(bs, "whereis", teammate, NULL); + else BotAI_BotInitialChat(bs, "whereareyou", netname, NULL); + trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); + return; + } + bs->lead_teammate = client; + bs->lead_time = FloatTime() + TEAM_LEAD_TIME; + bs->leadvisible_time = 0; + bs->leadmessage_time = -(FloatTime() + 2 * random()); +} + +/* +================== +BotMatch_Kill +================== +*/ +void BotMatch_Kill(bot_state_t *bs, bot_match_t *match) { + char enemy[MAX_MESSAGE_SIZE]; + char netname[MAX_MESSAGE_SIZE]; + int client; + + if (!TeamPlayIsOn()) return; + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) return; + + trap_BotMatchVariable(match, ENEMY, enemy, sizeof(enemy)); + // + client = FindEnemyByName(bs, enemy); + if (client < 0) { + BotAI_BotInitialChat(bs, "whois", enemy, NULL); + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + client = ClientFromName(netname); + trap_BotEnterChat(bs->cs, client, CHAT_TELL); + return; + } + bs->teamgoal.entitynum = client; + //set the time to send a message to the team mates + bs->teammessage_time = FloatTime() + 2 * random(); + //set the ltg type + bs->ltgtype = LTG_KILL; + //set the team goal time + bs->teamgoal_time = FloatTime() + TEAM_KILL_SOMEONE; + // + BotSetTeamStatus(bs); +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} + +/* +================== +BotMatch_CTF +================== +*/ +void BotMatch_CTF(bot_state_t *bs, bot_match_t *match) { + + char flag[128], netname[MAX_NETNAME]; + + if (gametype == GT_CTF) { + trap_BotMatchVariable(match, FLAG, flag, sizeof(flag)); + if (match->subtype & ST_GOTFLAG) { + if (!Q_stricmp(flag, "red")) { + bs->redflagstatus = 1; + if (BotTeam(bs) == TEAM_BLUE) { + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + bs->flagcarrier = ClientFromName(netname); + } + } + else { + bs->blueflagstatus = 1; + if (BotTeam(bs) == TEAM_RED) { + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + bs->flagcarrier = ClientFromName(netname); + } + } + bs->flagstatuschanged = 1; + bs->lastflagcapture_time = FloatTime(); + } + else if (match->subtype & ST_CAPTUREDFLAG) { + bs->redflagstatus = 0; + bs->blueflagstatus = 0; + bs->flagcarrier = 0; + bs->flagstatuschanged = 1; + } + else if (match->subtype & ST_RETURNEDFLAG) { + if (!Q_stricmp(flag, "red")) bs->redflagstatus = 0; + else bs->blueflagstatus = 0; + bs->flagstatuschanged = 1; + } + } +#ifdef MISSIONPACK + else if (gametype == GT_1FCTF) { + if (match->subtype & ST_1FCTFGOTFLAG) { + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + bs->flagcarrier = ClientFromName(netname); + } + } +#endif +} + +void BotMatch_EnterGame(bot_state_t *bs, bot_match_t *match) { + int client; + char netname[MAX_NETNAME]; + + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + client = FindClientByName(netname); + if (client >= 0) { + notleader[client] = qfalse; + } + //NOTE: eliza chats will catch this + //Com_sprintf(buf, sizeof(buf), "heya %s", netname); + //EA_Say(bs->client, buf); +} + +void BotMatch_NewLeader(bot_state_t *bs, bot_match_t *match) { + int client; + char netname[MAX_NETNAME]; + + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + client = FindClientByName(netname); + if (!BotSameTeam(bs, client)) + return; + Q_strncpyz(bs->teamleader, netname, sizeof(bs->teamleader)); +} + +/* +================== +BotMatchMessage +================== +*/ +int BotMatchMessage(bot_state_t *bs, char *message) { + bot_match_t match; + + match.type = 0; + //if it is an unknown message + if (!trap_BotFindMatch(message, &match, MTCONTEXT_MISC + |MTCONTEXT_INITIALTEAMCHAT + |MTCONTEXT_CTF)) { + return qfalse; + } + //react to the found message + switch(match.type) + { + case MSG_HELP: //someone calling for help + case MSG_ACCOMPANY: //someone calling for company + { + BotMatch_HelpAccompany(bs, &match); + break; + } + case MSG_DEFENDKEYAREA: //teamplay defend a key area + { + BotMatch_DefendKeyArea(bs, &match); + break; + } + case MSG_CAMP: //camp somewhere + { + BotMatch_Camp(bs, &match); + break; + } + case MSG_PATROL: //patrol between several key areas + { + BotMatch_Patrol(bs, &match); + break; + } + //CTF & 1FCTF + case MSG_GETFLAG: //ctf get the enemy flag + { + BotMatch_GetFlag(bs, &match); + break; + } +#ifdef MISSIONPACK + //CTF & 1FCTF & Obelisk & Harvester + case MSG_ATTACKENEMYBASE: + { + BotMatch_AttackEnemyBase(bs, &match); + break; + } + //Harvester + case MSG_HARVEST: + { + BotMatch_Harvest(bs, &match); + break; + } +#endif + //CTF & 1FCTF & Harvester + case MSG_RUSHBASE: //ctf rush to the base + { + BotMatch_RushBase(bs, &match); + break; + } + //CTF & 1FCTF + case MSG_RETURNFLAG: + { + BotMatch_ReturnFlag(bs, &match); + break; + } + //CTF & 1FCTF & Obelisk & Harvester + case MSG_TASKPREFERENCE: + { + BotMatch_TaskPreference(bs, &match); + break; + } + //CTF & 1FCTF + case MSG_CTF: + { + BotMatch_CTF(bs, &match); + break; + } + case MSG_GETITEM: + { + BotMatch_GetItem(bs, &match); + break; + } + case MSG_JOINSUBTEAM: //join a sub team + { + BotMatch_JoinSubteam(bs, &match); + break; + } + case MSG_LEAVESUBTEAM: //leave a sub team + { + BotMatch_LeaveSubteam(bs, &match); + break; + } + case MSG_WHICHTEAM: + { + BotMatch_WhichTeam(bs, &match); + break; + } + case MSG_CHECKPOINT: //remember a check point + { + BotMatch_CheckPoint(bs, &match); + break; + } + case MSG_CREATENEWFORMATION: //start the creation of a new formation + { + trap_EA_SayTeam(bs->client, "the part of my brain to create formations has been damaged"); + break; + } + case MSG_FORMATIONPOSITION: //tell someone his/her position in the formation + { + trap_EA_SayTeam(bs->client, "the part of my brain to create formations has been damaged"); + break; + } + case MSG_FORMATIONSPACE: //set the formation space + { + BotMatch_FormationSpace(bs, &match); + break; + } + case MSG_DOFORMATION: //form a certain formation + { + break; + } + case MSG_DISMISS: //dismiss someone + { + BotMatch_Dismiss(bs, &match); + break; + } + case MSG_STARTTEAMLEADERSHIP: //someone will become the team leader + { + BotMatch_StartTeamLeaderShip(bs, &match); + break; + } + case MSG_STOPTEAMLEADERSHIP: //someone will stop being the team leader + { + BotMatch_StopTeamLeaderShip(bs, &match); + break; + } + case MSG_WHOISTEAMLAEDER: + { + BotMatch_WhoIsTeamLeader(bs, &match); + break; + } + case MSG_WHATAREYOUDOING: //ask a bot what he/she is doing + { + BotMatch_WhatAreYouDoing(bs, &match); + break; + } + case MSG_WHATISMYCOMMAND: + { + BotMatch_WhatIsMyCommand(bs, &match); + break; + } + case MSG_WHEREAREYOU: + { + BotMatch_WhereAreYou(bs, &match); + break; + } + case MSG_LEADTHEWAY: + { + BotMatch_LeadTheWay(bs, &match); + break; + } + case MSG_KILL: + { + BotMatch_Kill(bs, &match); + break; + } + case MSG_ENTERGAME: //someone entered the game + { + BotMatch_EnterGame(bs, &match); + break; + } + case MSG_NEWLEADER: + { + BotMatch_NewLeader(bs, &match); + break; + } + case MSG_WAIT: + { + break; + } + case MSG_SUICIDE: + { + BotMatch_Suicide(bs, &match); + break; + } + default: + { + BotAI_Print(PRT_MESSAGE, "unknown match type\n"); + break; + } + } + return qtrue; +} diff --git a/code/game/ai_cmd.h b/code/game/ai_cmd.h index 511c57a..f5447f7 100755 --- a/code/game/ai_cmd.h +++ b/code/game/ai_cmd.h @@ -1,37 +1,37 @@ -/* -=========================================================================== -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 Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// - -/***************************************************************************** - * name: ai_cmd.h - * - * desc: Quake3 bot AI - * - * $Archive: /source/code/botai/ai_chat.c $ - * - *****************************************************************************/ - -extern int notleader[MAX_CLIENTS]; - -int BotMatchMessage(bot_state_t *bs, char *message); -void BotPrintTeamGoal(bot_state_t *bs); - +/* +=========================================================================== +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 Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// + +/***************************************************************************** + * name: ai_cmd.h + * + * desc: Quake3 bot AI + * + * $Archive: /source/code/botai/ai_chat.c $ + * + *****************************************************************************/ + +extern int notleader[MAX_CLIENTS]; + +int BotMatchMessage(bot_state_t *bs, char *message); +void BotPrintTeamGoal(bot_state_t *bs); + diff --git a/code/game/ai_dmnet.c b/code/game/ai_dmnet.c index 3bf3a8a..a791edd 100755 --- a/code/game/ai_dmnet.c +++ b/code/game/ai_dmnet.c @@ -1,2610 +1,2610 @@ -/* -=========================================================================== -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 Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// - -/***************************************************************************** - * name: ai_dmnet.c - * - * desc: Quake3 bot AI - * - * $Archive: /MissionPack/code/game/ai_dmnet.c $ - * - *****************************************************************************/ - -#include "g_local.h" -#include "botlib.h" -#include "be_aas.h" -#include "be_ea.h" -#include "be_ai_char.h" -#include "be_ai_chat.h" -#include "be_ai_gen.h" -#include "be_ai_goal.h" -#include "be_ai_move.h" -#include "be_ai_weap.h" -// -#include "ai_main.h" -#include "ai_dmq3.h" -#include "ai_chat.h" -#include "ai_cmd.h" -#include "ai_dmnet.h" -#include "ai_team.h" -//data file headers -#include "chars.h" //characteristics -#include "inv.h" //indexes into the inventory -#include "syn.h" //synonyms -#include "match.h" //string matching types and vars - -// for the voice chats -#include "../../ui/menudef.h" - -//goal flag, see be_ai_goal.h for the other GFL_* -#define GFL_AIR 128 - -int numnodeswitches; -char nodeswitch[MAX_NODESWITCHES+1][144]; - -#define LOOKAHEAD_DISTANCE 300 - -/* -================== -BotResetNodeSwitches -================== -*/ -void BotResetNodeSwitches(void) { - numnodeswitches = 0; -} - -/* -================== -BotDumpNodeSwitches -================== -*/ -void BotDumpNodeSwitches(bot_state_t *bs) { - int i; - char netname[MAX_NETNAME]; - - ClientName(bs->client, netname, sizeof(netname)); - BotAI_Print(PRT_MESSAGE, "%s at %1.1f switched more than %d AI nodes\n", netname, FloatTime(), MAX_NODESWITCHES); - for (i = 0; i < numnodeswitches; i++) { - BotAI_Print(PRT_MESSAGE, nodeswitch[i]); - } - BotAI_Print(PRT_FATAL, ""); -} - -/* -================== -BotRecordNodeSwitch -================== -*/ -void BotRecordNodeSwitch(bot_state_t *bs, char *node, char *str, char *s) { - char netname[MAX_NETNAME]; - - ClientName(bs->client, netname, sizeof(netname)); - Com_sprintf(nodeswitch[numnodeswitches], 144, "%s at %2.1f entered %s: %s from %s\n", netname, FloatTime(), node, str, s); -#ifdef DEBUG - if (0) { - BotAI_Print(PRT_MESSAGE, nodeswitch[numnodeswitches]); - } -#endif //DEBUG - numnodeswitches++; -} - -/* -================== -BotGetAirGoal -================== -*/ -int BotGetAirGoal(bot_state_t *bs, bot_goal_t *goal) { - bsp_trace_t bsptrace; - vec3_t end, mins = {-15, -15, -2}, maxs = {15, 15, 2}; - int areanum; - - //trace up until we hit solid - VectorCopy(bs->origin, end); - end[2] += 1000; - BotAI_Trace(&bsptrace, bs->origin, mins, maxs, end, bs->entitynum, CONTENTS_SOLID|CONTENTS_PLAYERCLIP); - //trace down until we hit water - VectorCopy(bsptrace.endpos, end); - BotAI_Trace(&bsptrace, end, mins, maxs, bs->origin, bs->entitynum, CONTENTS_WATER|CONTENTS_SLIME|CONTENTS_LAVA); - //if we found the water surface - if (bsptrace.fraction > 0) { - areanum = BotPointAreaNum(bsptrace.endpos); - if (areanum) { - VectorCopy(bsptrace.endpos, goal->origin); - goal->origin[2] -= 2; - goal->areanum = areanum; - goal->mins[0] = -15; - goal->mins[1] = -15; - goal->mins[2] = -1; - goal->maxs[0] = 15; - goal->maxs[1] = 15; - goal->maxs[2] = 1; - goal->flags = GFL_AIR; - goal->number = 0; - goal->iteminfo = 0; - goal->entitynum = 0; - return qtrue; - } - } - return qfalse; -} - -/* -================== -BotGoForAir -================== -*/ -int BotGoForAir(bot_state_t *bs, int tfl, bot_goal_t *ltg, float range) { - bot_goal_t goal; - - //if the bot needs air - if (bs->lastair_time < FloatTime() - 6) { - // -#ifdef DEBUG - //BotAI_Print(PRT_MESSAGE, "going for air\n"); -#endif //DEBUG - //if we can find an air goal - if (BotGetAirGoal(bs, &goal)) { - trap_BotPushGoal(bs->gs, &goal); - return qtrue; - } - else { - //get a nearby goal outside the water - while(trap_BotChooseNBGItem(bs->gs, bs->origin, bs->inventory, tfl, ltg, range)) { - trap_BotGetTopGoal(bs->gs, &goal); - //if the goal is not in water - if (!(trap_AAS_PointContents(goal.origin) & (CONTENTS_WATER|CONTENTS_SLIME|CONTENTS_LAVA))) { - return qtrue; - } - trap_BotPopGoal(bs->gs); - } - trap_BotResetAvoidGoals(bs->gs); - } - } - return qfalse; -} - -/* -================== -BotNearbyGoal -================== -*/ -int BotNearbyGoal(bot_state_t *bs, int tfl, bot_goal_t *ltg, float range) { - int ret; - - //check if the bot should go for air - if (BotGoForAir(bs, tfl, ltg, range)) return qtrue; - //if the bot is carrying the enemy flag - if (BotCTFCarryingFlag(bs)) { - //if the bot is just a few secs away from the base - if (trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, - bs->teamgoal.areanum, TFL_DEFAULT) < 300) { - //make the range really small - range = 50; - } - } - // - ret = trap_BotChooseNBGItem(bs->gs, bs->origin, bs->inventory, tfl, ltg, range); - /* - if (ret) - { - char buf[128]; - //get the goal at the top of the stack - trap_BotGetTopGoal(bs->gs, &goal); - trap_BotGoalName(goal.number, buf, sizeof(buf)); - BotAI_Print(PRT_MESSAGE, "%1.1f: new nearby goal %s\n", FloatTime(), buf); - } - */ - return ret; -} - -/* -================== -BotReachedGoal -================== -*/ -int BotReachedGoal(bot_state_t *bs, bot_goal_t *goal) { - if (goal->flags & GFL_ITEM) { - //if touching the goal - if (trap_BotTouchingGoal(bs->origin, goal)) { - if (!(goal->flags & GFL_DROPPED)) { - trap_BotSetAvoidGoalTime(bs->gs, goal->number, -1); - } - return qtrue; - } - //if the goal isn't there - if (trap_BotItemGoalInVisButNotVisible(bs->entitynum, bs->eye, bs->viewangles, goal)) { - /* - float avoidtime; - int t; - - avoidtime = trap_BotAvoidGoalTime(bs->gs, goal->number); - if (avoidtime > 0) { - t = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, goal->areanum, bs->tfl); - if ((float) t * 0.009 < avoidtime) - return qtrue; - } - */ - return qtrue; - } - //if in the goal area and below or above the goal and not swimming - if (bs->areanum == goal->areanum) { - if (bs->origin[0] > goal->origin[0] + goal->mins[0] && bs->origin[0] < goal->origin[0] + goal->maxs[0]) { - if (bs->origin[1] > goal->origin[1] + goal->mins[1] && bs->origin[1] < goal->origin[1] + goal->maxs[1]) { - if (!trap_AAS_Swimming(bs->origin)) { - return qtrue; - } - } - } - } - } - else if (goal->flags & GFL_AIR) { - //if touching the goal - if (trap_BotTouchingGoal(bs->origin, goal)) return qtrue; - //if the bot got air - if (bs->lastair_time > FloatTime() - 1) return qtrue; - } - else { - //if touching the goal - if (trap_BotTouchingGoal(bs->origin, goal)) return qtrue; - } - return qfalse; -} - -/* -================== -BotGetItemLongTermGoal -================== -*/ -int BotGetItemLongTermGoal(bot_state_t *bs, int tfl, bot_goal_t *goal) { - //if the bot has no goal - if (!trap_BotGetTopGoal(bs->gs, goal)) { - //BotAI_Print(PRT_MESSAGE, "no ltg on stack\n"); - bs->ltg_time = 0; - } - //if the bot touches the current goal - else if (BotReachedGoal(bs, goal)) { - BotChooseWeapon(bs); - bs->ltg_time = 0; - } - //if it is time to find a new long term goal - if (bs->ltg_time < FloatTime()) { - //pop the current goal from the stack - trap_BotPopGoal(bs->gs); - //BotAI_Print(PRT_MESSAGE, "%s: choosing new ltg\n", ClientName(bs->client, netname, sizeof(netname))); - //choose a new goal - //BotAI_Print(PRT_MESSAGE, "%6.1f client %d: BotChooseLTGItem\n", FloatTime(), bs->client); - if (trap_BotChooseLTGItem(bs->gs, bs->origin, bs->inventory, tfl)) { - /* - char buf[128]; - //get the goal at the top of the stack - trap_BotGetTopGoal(bs->gs, goal); - trap_BotGoalName(goal->number, buf, sizeof(buf)); - BotAI_Print(PRT_MESSAGE, "%1.1f: new long term goal %s\n", FloatTime(), buf); - */ - bs->ltg_time = FloatTime() + 20; - } - else {//the bot gets sorta stuck with all the avoid timings, shouldn't happen though - // -#ifdef DEBUG - char netname[128]; - - BotAI_Print(PRT_MESSAGE, "%s: no valid ltg (probably stuck)\n", ClientName(bs->client, netname, sizeof(netname))); -#endif - //trap_BotDumpAvoidGoals(bs->gs); - //reset the avoid goals and the avoid reach - trap_BotResetAvoidGoals(bs->gs); - trap_BotResetAvoidReach(bs->ms); - } - //get the goal at the top of the stack - return trap_BotGetTopGoal(bs->gs, goal); - } - return qtrue; -} - -/* -================== -BotGetLongTermGoal - -we could also create a seperate AI node for every long term goal type -however this saves us a lot of code -================== -*/ -int BotGetLongTermGoal(bot_state_t *bs, int tfl, int retreat, bot_goal_t *goal) { - vec3_t target, dir, dir2; - char netname[MAX_NETNAME]; - char buf[MAX_MESSAGE_SIZE]; - int areanum; - float croucher; - aas_entityinfo_t entinfo, botinfo; - bot_waypoint_t *wp; - - if (bs->ltgtype == LTG_TEAMHELP && !retreat) { - //check for bot typing status message - if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { - BotAI_BotInitialChat(bs, "help_start", EasyClientName(bs->teammate, netname, sizeof(netname)), NULL); - trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); - BotVoiceChatOnly(bs, bs->decisionmaker, VOICECHAT_YES); - trap_EA_Action(bs->client, ACTION_AFFIRMATIVE); - bs->teammessage_time = 0; - } - //if trying to help the team mate for more than a minute - if (bs->teamgoal_time < FloatTime()) - bs->ltgtype = 0; - //if the team mate IS visible for quite some time - if (bs->teammatevisible_time < FloatTime() - 10) bs->ltgtype = 0; - //get entity information of the companion - BotEntityInfo(bs->teammate, &entinfo); - //if the team mate is visible - if (BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->teammate)) { - //if close just stand still there - VectorSubtract(entinfo.origin, bs->origin, dir); - if (VectorLengthSquared(dir) < Square(100)) { - trap_BotResetAvoidReach(bs->ms); - return qfalse; - } - } - else { - //last time the bot was NOT visible - bs->teammatevisible_time = FloatTime(); - } - //if the entity information is valid (entity in PVS) - if (entinfo.valid) { - areanum = BotPointAreaNum(entinfo.origin); - if (areanum && trap_AAS_AreaReachability(areanum)) { - //update team goal - bs->teamgoal.entitynum = bs->teammate; - bs->teamgoal.areanum = areanum; - VectorCopy(entinfo.origin, bs->teamgoal.origin); - VectorSet(bs->teamgoal.mins, -8, -8, -8); - VectorSet(bs->teamgoal.maxs, 8, 8, 8); - } - } - memcpy(goal, &bs->teamgoal, sizeof(bot_goal_t)); - return qtrue; - } - //if the bot accompanies someone - if (bs->ltgtype == LTG_TEAMACCOMPANY && !retreat) { - //check for bot typing status message - if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { - BotAI_BotInitialChat(bs, "accompany_start", EasyClientName(bs->teammate, netname, sizeof(netname)), NULL); - trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); - BotVoiceChatOnly(bs, bs->decisionmaker, VOICECHAT_YES); - trap_EA_Action(bs->client, ACTION_AFFIRMATIVE); - bs->teammessage_time = 0; - } - //if accompanying the companion for 3 minutes - if (bs->teamgoal_time < FloatTime()) { - BotAI_BotInitialChat(bs, "accompany_stop", EasyClientName(bs->teammate, netname, sizeof(netname)), NULL); - trap_BotEnterChat(bs->cs, bs->teammate, CHAT_TELL); - bs->ltgtype = 0; - } - //get entity information of the companion - BotEntityInfo(bs->teammate, &entinfo); - //if the companion is visible - if (BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->teammate)) { - //update visible time - bs->teammatevisible_time = FloatTime(); - VectorSubtract(entinfo.origin, bs->origin, dir); - if (VectorLengthSquared(dir) < Square(bs->formation_dist)) { - // - // if the client being followed bumps into this bot then - // the bot should back up - BotEntityInfo(bs->entitynum, &botinfo); - // if the followed client is not standing ontop of the bot - if (botinfo.origin[2] + botinfo.maxs[2] > entinfo.origin[2] + entinfo.mins[2]) { - // if the bounding boxes touch each other - if (botinfo.origin[0] + botinfo.maxs[0] > entinfo.origin[0] + entinfo.mins[0] - 4&& - botinfo.origin[0] + botinfo.mins[0] < entinfo.origin[0] + entinfo.maxs[0] + 4) { - if (botinfo.origin[1] + botinfo.maxs[1] > entinfo.origin[1] + entinfo.mins[1] - 4 && - botinfo.origin[1] + botinfo.mins[1] < entinfo.origin[1] + entinfo.maxs[1] + 4) { - if (botinfo.origin[2] + botinfo.maxs[2] > entinfo.origin[2] + entinfo.mins[2] - 4 && - botinfo.origin[2] + botinfo.mins[2] < entinfo.origin[2] + entinfo.maxs[2] + 4) { - // if the followed client looks in the direction of this bot - AngleVectors(entinfo.angles, dir, NULL, NULL); - dir[2] = 0; - VectorNormalize(dir); - //VectorSubtract(entinfo.origin, entinfo.lastvisorigin, dir); - VectorSubtract(bs->origin, entinfo.origin, dir2); - VectorNormalize(dir2); - if (DotProduct(dir, dir2) > 0.7) { - // back up - BotSetupForMovement(bs); - trap_BotMoveInDirection(bs->ms, dir2, 400, MOVE_WALK); - } - } - } - } - } - //check if the bot wants to crouch - //don't crouch if crouched less than 5 seconds ago - if (bs->attackcrouch_time < FloatTime() - 5) { - croucher = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CROUCHER, 0, 1); - if (random() < bs->thinktime * croucher) { - bs->attackcrouch_time = FloatTime() + 5 + croucher * 15; - } - } - //don't crouch when swimming - if (trap_AAS_Swimming(bs->origin)) bs->attackcrouch_time = FloatTime() - 1; - //if not arrived yet or arived some time ago - if (bs->arrive_time < FloatTime() - 2) { - //if not arrived yet - if (!bs->arrive_time) { - trap_EA_Gesture(bs->client); - BotAI_BotInitialChat(bs, "accompany_arrive", EasyClientName(bs->teammate, netname, sizeof(netname)), NULL); - trap_BotEnterChat(bs->cs, bs->teammate, CHAT_TELL); - bs->arrive_time = FloatTime(); - } - //if the bot wants to crouch - else if (bs->attackcrouch_time > FloatTime()) { - trap_EA_Crouch(bs->client); - } - //else do some model taunts - else if (random() < bs->thinktime * 0.05) { - //do a gesture :) - trap_EA_Gesture(bs->client); - } - } - //if just arrived look at the companion - if (bs->arrive_time > FloatTime() - 2) { - VectorSubtract(entinfo.origin, bs->origin, dir); - vectoangles(dir, bs->ideal_viewangles); - bs->ideal_viewangles[2] *= 0.5; - } - //else look strategically around for enemies - else if (random() < bs->thinktime * 0.8) { - BotRoamGoal(bs, target); - VectorSubtract(target, bs->origin, dir); - vectoangles(dir, bs->ideal_viewangles); - bs->ideal_viewangles[2] *= 0.5; - } - //check if the bot wants to go for air - if (BotGoForAir(bs, bs->tfl, &bs->teamgoal, 400)) { - trap_BotResetLastAvoidReach(bs->ms); - //get the goal at the top of the stack - //trap_BotGetTopGoal(bs->gs, &tmpgoal); - //trap_BotGoalName(tmpgoal.number, buf, 144); - //BotAI_Print(PRT_MESSAGE, "new nearby goal %s\n", buf); - //time the bot gets to pick up the nearby goal item - bs->nbg_time = FloatTime() + 8; - AIEnter_Seek_NBG(bs, "BotLongTermGoal: go for air"); - return qfalse; - } - // - trap_BotResetAvoidReach(bs->ms); - return qfalse; - } - } - //if the entity information is valid (entity in PVS) - if (entinfo.valid) { - areanum = BotPointAreaNum(entinfo.origin); - if (areanum && trap_AAS_AreaReachability(areanum)) { - //update team goal - bs->teamgoal.entitynum = bs->teammate; - bs->teamgoal.areanum = areanum; - VectorCopy(entinfo.origin, bs->teamgoal.origin); - VectorSet(bs->teamgoal.mins, -8, -8, -8); - VectorSet(bs->teamgoal.maxs, 8, 8, 8); - } - } - //the goal the bot should go for - memcpy(goal, &bs->teamgoal, sizeof(bot_goal_t)); - //if the companion is NOT visible for too long - if (bs->teammatevisible_time < FloatTime() - 60) { - BotAI_BotInitialChat(bs, "accompany_cannotfind", EasyClientName(bs->teammate, netname, sizeof(netname)), NULL); - trap_BotEnterChat(bs->cs, bs->teammate, CHAT_TELL); - bs->ltgtype = 0; - // just to make sure the bot won't spam this message - bs->teammatevisible_time = FloatTime(); - } - return qtrue; - } - // - if (bs->ltgtype == LTG_DEFENDKEYAREA) { - if (trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, - bs->teamgoal.areanum, TFL_DEFAULT) > bs->defendaway_range) { - bs->defendaway_time = 0; - } - } - //if defending a key area - if (bs->ltgtype == LTG_DEFENDKEYAREA && !retreat && - bs->defendaway_time < FloatTime()) { - //check for bot typing status message - if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { - trap_BotGoalName(bs->teamgoal.number, buf, sizeof(buf)); - BotAI_BotInitialChat(bs, "defend_start", buf, NULL); - trap_BotEnterChat(bs->cs, 0, CHAT_TEAM); - BotVoiceChatOnly(bs, -1, VOICECHAT_ONDEFENSE); - bs->teammessage_time = 0; - } - //set the bot goal - memcpy(goal, &bs->teamgoal, sizeof(bot_goal_t)); - //stop after 2 minutes - if (bs->teamgoal_time < FloatTime()) { - trap_BotGoalName(bs->teamgoal.number, buf, sizeof(buf)); - BotAI_BotInitialChat(bs, "defend_stop", buf, NULL); - trap_BotEnterChat(bs->cs, 0, CHAT_TEAM); - bs->ltgtype = 0; - } - //if very close... go away for some time - VectorSubtract(goal->origin, bs->origin, dir); - if (VectorLengthSquared(dir) < Square(70)) { - trap_BotResetAvoidReach(bs->ms); - bs->defendaway_time = FloatTime() + 3 + 3 * random(); - if (BotHasPersistantPowerupAndWeapon(bs)) { - bs->defendaway_range = 100; - } - else { - bs->defendaway_range = 350; - } - } - return qtrue; - } - //going to kill someone - if (bs->ltgtype == LTG_KILL && !retreat) { - //check for bot typing status message - if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { - EasyClientName(bs->teamgoal.entitynum, buf, sizeof(buf)); - BotAI_BotInitialChat(bs, "kill_start", buf, NULL); - trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); - bs->teammessage_time = 0; - } - // - if (bs->lastkilledplayer == bs->teamgoal.entitynum) { - EasyClientName(bs->teamgoal.entitynum, buf, sizeof(buf)); - BotAI_BotInitialChat(bs, "kill_done", buf, NULL); - trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); - bs->lastkilledplayer = -1; - bs->ltgtype = 0; - } - // - if (bs->teamgoal_time < FloatTime()) { - bs->ltgtype = 0; - } - //just roam around - return BotGetItemLongTermGoal(bs, tfl, goal); - } - //get an item - if (bs->ltgtype == LTG_GETITEM && !retreat) { - //check for bot typing status message - if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { - trap_BotGoalName(bs->teamgoal.number, buf, sizeof(buf)); - BotAI_BotInitialChat(bs, "getitem_start", buf, NULL); - trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); - BotVoiceChatOnly(bs, bs->decisionmaker, VOICECHAT_YES); - trap_EA_Action(bs->client, ACTION_AFFIRMATIVE); - bs->teammessage_time = 0; - } - //set the bot goal - memcpy(goal, &bs->teamgoal, sizeof(bot_goal_t)); - //stop after some time - if (bs->teamgoal_time < FloatTime()) { - bs->ltgtype = 0; - } - // - if (trap_BotItemGoalInVisButNotVisible(bs->entitynum, bs->eye, bs->viewangles, goal)) { - trap_BotGoalName(bs->teamgoal.number, buf, sizeof(buf)); - BotAI_BotInitialChat(bs, "getitem_notthere", buf, NULL); - trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); - bs->ltgtype = 0; - } - else if (BotReachedGoal(bs, goal)) { - trap_BotGoalName(bs->teamgoal.number, buf, sizeof(buf)); - BotAI_BotInitialChat(bs, "getitem_gotit", buf, NULL); - trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); - bs->ltgtype = 0; - } - return qtrue; - } - //if camping somewhere - if ((bs->ltgtype == LTG_CAMP || bs->ltgtype == LTG_CAMPORDER) && !retreat) { - //check for bot typing status message - if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { - if (bs->ltgtype == LTG_CAMPORDER) { - BotAI_BotInitialChat(bs, "camp_start", EasyClientName(bs->teammate, netname, sizeof(netname)), NULL); - trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); - BotVoiceChatOnly(bs, bs->decisionmaker, VOICECHAT_YES); - trap_EA_Action(bs->client, ACTION_AFFIRMATIVE); - } - bs->teammessage_time = 0; - } - //set the bot goal - memcpy(goal, &bs->teamgoal, sizeof(bot_goal_t)); - // - if (bs->teamgoal_time < FloatTime()) { - if (bs->ltgtype == LTG_CAMPORDER) { - BotAI_BotInitialChat(bs, "camp_stop", NULL); - trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); - } - bs->ltgtype = 0; - } - //if really near the camp spot - VectorSubtract(goal->origin, bs->origin, dir); - if (VectorLengthSquared(dir) < Square(60)) - { - //if not arrived yet - if (!bs->arrive_time) { - if (bs->ltgtype == LTG_CAMPORDER) { - BotAI_BotInitialChat(bs, "camp_arrive", EasyClientName(bs->teammate, netname, sizeof(netname)), NULL); - trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); - BotVoiceChatOnly(bs, bs->decisionmaker, VOICECHAT_INPOSITION); - } - bs->arrive_time = FloatTime(); - } - //look strategically around for enemies - if (random() < bs->thinktime * 0.8) { - BotRoamGoal(bs, target); - VectorSubtract(target, bs->origin, dir); - vectoangles(dir, bs->ideal_viewangles); - bs->ideal_viewangles[2] *= 0.5; - } - //check if the bot wants to crouch - //don't crouch if crouched less than 5 seconds ago - if (bs->attackcrouch_time < FloatTime() - 5) { - croucher = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CROUCHER, 0, 1); - if (random() < bs->thinktime * croucher) { - bs->attackcrouch_time = FloatTime() + 5 + croucher * 15; - } - } - //if the bot wants to crouch - if (bs->attackcrouch_time > FloatTime()) { - trap_EA_Crouch(bs->client); - } - //don't crouch when swimming - if (trap_AAS_Swimming(bs->origin)) bs->attackcrouch_time = FloatTime() - 1; - //make sure the bot is not gonna drown - if (trap_PointContents(bs->eye,bs->entitynum) & (CONTENTS_WATER|CONTENTS_SLIME|CONTENTS_LAVA)) { - if (bs->ltgtype == LTG_CAMPORDER) { - BotAI_BotInitialChat(bs, "camp_stop", NULL); - trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); - // - if (bs->lastgoal_ltgtype == LTG_CAMPORDER) { - bs->lastgoal_ltgtype = 0; - } - } - bs->ltgtype = 0; - } - // - if (bs->camp_range > 0) { - //FIXME: move around a bit - } - // - trap_BotResetAvoidReach(bs->ms); - return qfalse; - } - return qtrue; - } - //patrolling along several waypoints - if (bs->ltgtype == LTG_PATROL && !retreat) { - //check for bot typing status message - if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { - strcpy(buf, ""); - for (wp = bs->patrolpoints; wp; wp = wp->next) { - strcat(buf, wp->name); - if (wp->next) strcat(buf, " to "); - } - BotAI_BotInitialChat(bs, "patrol_start", buf, NULL); - trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); - BotVoiceChatOnly(bs, bs->decisionmaker, VOICECHAT_YES); - trap_EA_Action(bs->client, ACTION_AFFIRMATIVE); - bs->teammessage_time = 0; - } - // - if (!bs->curpatrolpoint) { - bs->ltgtype = 0; - return qfalse; - } - //if the bot touches the current goal - if (trap_BotTouchingGoal(bs->origin, &bs->curpatrolpoint->goal)) { - if (bs->patrolflags & PATROL_BACK) { - if (bs->curpatrolpoint->prev) { - bs->curpatrolpoint = bs->curpatrolpoint->prev; - } - else { - bs->curpatrolpoint = bs->curpatrolpoint->next; - bs->patrolflags &= ~PATROL_BACK; - } - } - else { - if (bs->curpatrolpoint->next) { - bs->curpatrolpoint = bs->curpatrolpoint->next; - } - else { - bs->curpatrolpoint = bs->curpatrolpoint->prev; - bs->patrolflags |= PATROL_BACK; - } - } - } - //stop after 5 minutes - if (bs->teamgoal_time < FloatTime()) { - BotAI_BotInitialChat(bs, "patrol_stop", NULL); - trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); - bs->ltgtype = 0; - } - if (!bs->curpatrolpoint) { - bs->ltgtype = 0; - return qfalse; - } - memcpy(goal, &bs->curpatrolpoint->goal, sizeof(bot_goal_t)); - return qtrue; - } -#ifdef CTF - if (gametype == GT_CTF) { - //if going for enemy flag - if (bs->ltgtype == LTG_GETFLAG) { - //check for bot typing status message - if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { - BotAI_BotInitialChat(bs, "captureflag_start", NULL); - trap_BotEnterChat(bs->cs, 0, CHAT_TEAM); - BotVoiceChatOnly(bs, -1, VOICECHAT_ONGETFLAG); - bs->teammessage_time = 0; - } - // - switch(BotTeam(bs)) { - case TEAM_RED: memcpy(goal, &ctf_blueflag, sizeof(bot_goal_t)); break; - case TEAM_BLUE: memcpy(goal, &ctf_redflag, sizeof(bot_goal_t)); break; - default: bs->ltgtype = 0; return qfalse; - } - //if touching the flag - if (trap_BotTouchingGoal(bs->origin, goal)) { - // make sure the bot knows the flag isn't there anymore - switch(BotTeam(bs)) { - case TEAM_RED: bs->blueflagstatus = 1; break; - case TEAM_BLUE: bs->redflagstatus = 1; break; - } - bs->ltgtype = 0; - } - //stop after 3 minutes - if (bs->teamgoal_time < FloatTime()) { - bs->ltgtype = 0; - } - BotAlternateRoute(bs, goal); - return qtrue; - } - //if rushing to the base - if (bs->ltgtype == LTG_RUSHBASE && bs->rushbaseaway_time < FloatTime()) { - switch(BotTeam(bs)) { - case TEAM_RED: memcpy(goal, &ctf_redflag, sizeof(bot_goal_t)); break; - case TEAM_BLUE: memcpy(goal, &ctf_blueflag, sizeof(bot_goal_t)); break; - default: bs->ltgtype = 0; return qfalse; - } - //if not carrying the flag anymore - if (!BotCTFCarryingFlag(bs)) bs->ltgtype = 0; - //quit rushing after 2 minutes - if (bs->teamgoal_time < FloatTime()) bs->ltgtype = 0; - //if touching the base flag the bot should loose the enemy flag - if (trap_BotTouchingGoal(bs->origin, goal)) { - //if the bot is still carrying the enemy flag then the - //base flag is gone, now just walk near the base a bit - if (BotCTFCarryingFlag(bs)) { - trap_BotResetAvoidReach(bs->ms); - bs->rushbaseaway_time = FloatTime() + 5 + 10 * random(); - //FIXME: add chat to tell the others to get back the flag - } - else { - bs->ltgtype = 0; - } - } - BotAlternateRoute(bs, goal); - return qtrue; - } - //returning flag - if (bs->ltgtype == LTG_RETURNFLAG) { - //check for bot typing status message - if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { - BotAI_BotInitialChat(bs, "returnflag_start", NULL); - trap_BotEnterChat(bs->cs, 0, CHAT_TEAM); - BotVoiceChatOnly(bs, -1, VOICECHAT_ONRETURNFLAG); - bs->teammessage_time = 0; - } - // - switch(BotTeam(bs)) { - case TEAM_RED: memcpy(goal, &ctf_blueflag, sizeof(bot_goal_t)); break; - case TEAM_BLUE: memcpy(goal, &ctf_redflag, sizeof(bot_goal_t)); break; - default: bs->ltgtype = 0; return qfalse; - } - //if touching the flag - if (trap_BotTouchingGoal(bs->origin, goal)) bs->ltgtype = 0; - //stop after 3 minutes - if (bs->teamgoal_time < FloatTime()) { - bs->ltgtype = 0; - } - BotAlternateRoute(bs, goal); - return qtrue; - } - } -#endif //CTF -#ifdef MISSIONPACK - else if (gametype == GT_1FCTF) { - if (bs->ltgtype == LTG_GETFLAG) { - //check for bot typing status message - if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { - BotAI_BotInitialChat(bs, "captureflag_start", NULL); - trap_BotEnterChat(bs->cs, 0, CHAT_TEAM); - BotVoiceChatOnly(bs, -1, VOICECHAT_ONGETFLAG); - bs->teammessage_time = 0; - } - memcpy(goal, &ctf_neutralflag, sizeof(bot_goal_t)); - //if touching the flag - if (trap_BotTouchingGoal(bs->origin, goal)) { - bs->ltgtype = 0; - } - //stop after 3 minutes - if (bs->teamgoal_time < FloatTime()) { - bs->ltgtype = 0; - } - return qtrue; - } - //if rushing to the base - if (bs->ltgtype == LTG_RUSHBASE) { - switch(BotTeam(bs)) { - case TEAM_RED: memcpy(goal, &ctf_blueflag, sizeof(bot_goal_t)); break; - case TEAM_BLUE: memcpy(goal, &ctf_redflag, sizeof(bot_goal_t)); break; - default: bs->ltgtype = 0; return qfalse; - } - //if not carrying the flag anymore - if (!Bot1FCTFCarryingFlag(bs)) { - bs->ltgtype = 0; - } - //quit rushing after 2 minutes - if (bs->teamgoal_time < FloatTime()) { - bs->ltgtype = 0; - } - //if touching the base flag the bot should loose the enemy flag - if (trap_BotTouchingGoal(bs->origin, goal)) { - bs->ltgtype = 0; - } - BotAlternateRoute(bs, goal); - return qtrue; - } - //attack the enemy base - if (bs->ltgtype == LTG_ATTACKENEMYBASE && - bs->attackaway_time < FloatTime()) { - //check for bot typing status message - if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { - BotAI_BotInitialChat(bs, "attackenemybase_start", NULL); - trap_BotEnterChat(bs->cs, 0, CHAT_TEAM); - BotVoiceChatOnly(bs, -1, VOICECHAT_ONOFFENSE); - bs->teammessage_time = 0; - } - switch(BotTeam(bs)) { - case TEAM_RED: memcpy(goal, &ctf_blueflag, sizeof(bot_goal_t)); break; - case TEAM_BLUE: memcpy(goal, &ctf_redflag, sizeof(bot_goal_t)); break; - default: bs->ltgtype = 0; return qfalse; - } - //quit rushing after 2 minutes - if (bs->teamgoal_time < FloatTime()) { - bs->ltgtype = 0; - } - //if touching the base flag the bot should loose the enemy flag - if (trap_BotTouchingGoal(bs->origin, goal)) { - bs->attackaway_time = FloatTime() + 2 + 5 * random(); - } - return qtrue; - } - //returning flag - if (bs->ltgtype == LTG_RETURNFLAG) { - //check for bot typing status message - if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { - BotAI_BotInitialChat(bs, "returnflag_start", NULL); - trap_BotEnterChat(bs->cs, 0, CHAT_TEAM); - BotVoiceChatOnly(bs, -1, VOICECHAT_ONRETURNFLAG); - bs->teammessage_time = 0; - } - // - if (bs->teamgoal_time < FloatTime()) { - bs->ltgtype = 0; - } - //just roam around - return BotGetItemLongTermGoal(bs, tfl, goal); - } - } - else if (gametype == GT_OBELISK) { - if (bs->ltgtype == LTG_ATTACKENEMYBASE && - bs->attackaway_time < FloatTime()) { - - //check for bot typing status message - if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { - BotAI_BotInitialChat(bs, "attackenemybase_start", NULL); - trap_BotEnterChat(bs->cs, 0, CHAT_TEAM); - BotVoiceChatOnly(bs, -1, VOICECHAT_ONOFFENSE); - bs->teammessage_time = 0; - } - switch(BotTeam(bs)) { - case TEAM_RED: memcpy(goal, &blueobelisk, sizeof(bot_goal_t)); break; - case TEAM_BLUE: memcpy(goal, &redobelisk, sizeof(bot_goal_t)); break; - default: bs->ltgtype = 0; return qfalse; - } - //if the bot no longer wants to attack the obelisk - if (BotFeelingBad(bs) > 50) { - return BotGetItemLongTermGoal(bs, tfl, goal); - } - //if touching the obelisk - if (trap_BotTouchingGoal(bs->origin, goal)) { - bs->attackaway_time = FloatTime() + 3 + 5 * random(); - } - // or very close to the obelisk - VectorSubtract(bs->origin, goal->origin, dir); - if (VectorLengthSquared(dir) < Square(60)) { - bs->attackaway_time = FloatTime() + 3 + 5 * random(); - } - //quit rushing after 2 minutes - if (bs->teamgoal_time < FloatTime()) { - bs->ltgtype = 0; - } - BotAlternateRoute(bs, goal); - //just move towards the obelisk - return qtrue; - } - } - else if (gametype == GT_HARVESTER) { - //if rushing to the base - if (bs->ltgtype == LTG_RUSHBASE) { - switch(BotTeam(bs)) { - case TEAM_RED: memcpy(goal, &blueobelisk, sizeof(bot_goal_t)); break; - case TEAM_BLUE: memcpy(goal, &redobelisk, sizeof(bot_goal_t)); break; - default: BotGoHarvest(bs); return qfalse; - } - //if not carrying any cubes - if (!BotHarvesterCarryingCubes(bs)) { - BotGoHarvest(bs); - return qfalse; - } - //quit rushing after 2 minutes - if (bs->teamgoal_time < FloatTime()) { - BotGoHarvest(bs); - return qfalse; - } - //if touching the base flag the bot should loose the enemy flag - if (trap_BotTouchingGoal(bs->origin, goal)) { - BotGoHarvest(bs); - return qfalse; - } - BotAlternateRoute(bs, goal); - return qtrue; - } - //attack the enemy base - if (bs->ltgtype == LTG_ATTACKENEMYBASE && - bs->attackaway_time < FloatTime()) { - //check for bot typing status message - if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { - BotAI_BotInitialChat(bs, "attackenemybase_start", NULL); - trap_BotEnterChat(bs->cs, 0, CHAT_TEAM); - BotVoiceChatOnly(bs, -1, VOICECHAT_ONOFFENSE); - bs->teammessage_time = 0; - } - switch(BotTeam(bs)) { - case TEAM_RED: memcpy(goal, &blueobelisk, sizeof(bot_goal_t)); break; - case TEAM_BLUE: memcpy(goal, &redobelisk, sizeof(bot_goal_t)); break; - default: bs->ltgtype = 0; return qfalse; - } - //quit rushing after 2 minutes - if (bs->teamgoal_time < FloatTime()) { - bs->ltgtype = 0; - } - //if touching the base flag the bot should loose the enemy flag - if (trap_BotTouchingGoal(bs->origin, goal)) { - bs->attackaway_time = FloatTime() + 2 + 5 * random(); - } - return qtrue; - } - //harvest cubes - if (bs->ltgtype == LTG_HARVEST && - bs->harvestaway_time < FloatTime()) { - //check for bot typing status message - if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { - BotAI_BotInitialChat(bs, "harvest_start", NULL); - trap_BotEnterChat(bs->cs, 0, CHAT_TEAM); - BotVoiceChatOnly(bs, -1, VOICECHAT_ONOFFENSE); - bs->teammessage_time = 0; - } - memcpy(goal, &neutralobelisk, sizeof(bot_goal_t)); - // - if (bs->teamgoal_time < FloatTime()) { - bs->ltgtype = 0; - } - // - if (trap_BotTouchingGoal(bs->origin, goal)) { - bs->harvestaway_time = FloatTime() + 4 + 3 * random(); - } - return qtrue; - } - } -#endif - //normal goal stuff - return BotGetItemLongTermGoal(bs, tfl, goal); -} - -/* -================== -BotLongTermGoal -================== -*/ -int BotLongTermGoal(bot_state_t *bs, int tfl, int retreat, bot_goal_t *goal) { - aas_entityinfo_t entinfo; - char teammate[MAX_MESSAGE_SIZE]; - float squaredist; - int areanum; - vec3_t dir; - - //FIXME: also have air long term goals? - // - //if the bot is leading someone and not retreating - if (bs->lead_time > 0 && !retreat) { - if (bs->lead_time < FloatTime()) { - BotAI_BotInitialChat(bs, "lead_stop", EasyClientName(bs->lead_teammate, teammate, sizeof(teammate)), NULL); - trap_BotEnterChat(bs->cs, bs->teammate, CHAT_TELL); - bs->lead_time = 0; - return BotGetLongTermGoal(bs, tfl, retreat, goal); - } - // - if (bs->leadmessage_time < 0 && -bs->leadmessage_time < FloatTime()) { - BotAI_BotInitialChat(bs, "followme", EasyClientName(bs->lead_teammate, teammate, sizeof(teammate)), NULL); - trap_BotEnterChat(bs->cs, bs->teammate, CHAT_TELL); - bs->leadmessage_time = FloatTime(); - } - //get entity information of the companion - BotEntityInfo(bs->lead_teammate, &entinfo); - // - if (entinfo.valid) { - areanum = BotPointAreaNum(entinfo.origin); - if (areanum && trap_AAS_AreaReachability(areanum)) { - //update team goal - bs->lead_teamgoal.entitynum = bs->lead_teammate; - bs->lead_teamgoal.areanum = areanum; - VectorCopy(entinfo.origin, bs->lead_teamgoal.origin); - VectorSet(bs->lead_teamgoal.mins, -8, -8, -8); - VectorSet(bs->lead_teamgoal.maxs, 8, 8, 8); - } - } - //if the team mate is visible - if (BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->lead_teammate)) { - bs->leadvisible_time = FloatTime(); - } - //if the team mate is not visible for 1 seconds - if (bs->leadvisible_time < FloatTime() - 1) { - bs->leadbackup_time = FloatTime() + 2; - } - //distance towards the team mate - VectorSubtract(bs->origin, bs->lead_teamgoal.origin, dir); - squaredist = VectorLengthSquared(dir); - //if backing up towards the team mate - if (bs->leadbackup_time > FloatTime()) { - if (bs->leadmessage_time < FloatTime() - 20) { - BotAI_BotInitialChat(bs, "followme", EasyClientName(bs->lead_teammate, teammate, sizeof(teammate)), NULL); - trap_BotEnterChat(bs->cs, bs->teammate, CHAT_TELL); - bs->leadmessage_time = FloatTime(); - } - //if very close to the team mate - if (squaredist < Square(100)) { - bs->leadbackup_time = 0; - } - //the bot should go back to the team mate - memcpy(goal, &bs->lead_teamgoal, sizeof(bot_goal_t)); - return qtrue; - } - else { - //if quite distant from the team mate - if (squaredist > Square(500)) { - if (bs->leadmessage_time < FloatTime() - 20) { - BotAI_BotInitialChat(bs, "followme", EasyClientName(bs->lead_teammate, teammate, sizeof(teammate)), NULL); - trap_BotEnterChat(bs->cs, bs->teammate, CHAT_TELL); - bs->leadmessage_time = FloatTime(); - } - //look at the team mate - VectorSubtract(entinfo.origin, bs->origin, dir); - vectoangles(dir, bs->ideal_viewangles); - bs->ideal_viewangles[2] *= 0.5; - //just wait for the team mate - return qfalse; - } - } - } - return BotGetLongTermGoal(bs, tfl, retreat, goal); -} - -/* -================== -AIEnter_Intermission -================== -*/ -void AIEnter_Intermission(bot_state_t *bs, char *s) { - BotRecordNodeSwitch(bs, "intermission", "", s); - //reset the bot state - BotResetState(bs); - //check for end level chat - if (BotChat_EndLevel(bs)) { - trap_BotEnterChat(bs->cs, 0, bs->chatto); - } - bs->ainode = AINode_Intermission; -} - -/* -================== -AINode_Intermission -================== -*/ -int AINode_Intermission(bot_state_t *bs) { - //if the intermission ended - if (!BotIntermission(bs)) { - if (BotChat_StartLevel(bs)) { - bs->stand_time = FloatTime() + BotChatTime(bs); - } - else { - bs->stand_time = FloatTime() + 2; - } - AIEnter_Stand(bs, "intermission: chat"); - } - return qtrue; -} - -/* -================== -AIEnter_Observer -================== -*/ -void AIEnter_Observer(bot_state_t *bs, char *s) { - BotRecordNodeSwitch(bs, "observer", "", s); - //reset the bot state - BotResetState(bs); - bs->ainode = AINode_Observer; -} - -/* -================== -AINode_Observer -================== -*/ -int AINode_Observer(bot_state_t *bs) { - //if the bot left observer mode - if (!BotIsObserver(bs)) { - AIEnter_Stand(bs, "observer: left observer"); - } - return qtrue; -} - -/* -================== -AIEnter_Stand -================== -*/ -void AIEnter_Stand(bot_state_t *bs, char *s) { - BotRecordNodeSwitch(bs, "stand", "", s); - bs->standfindenemy_time = FloatTime() + 1; - bs->ainode = AINode_Stand; -} - -/* -================== -AINode_Stand -================== -*/ -int AINode_Stand(bot_state_t *bs) { - - //if the bot's health decreased - if (bs->lastframe_health > bs->inventory[INVENTORY_HEALTH]) { - if (BotChat_HitTalking(bs)) { - bs->standfindenemy_time = FloatTime() + BotChatTime(bs) + 0.1; - bs->stand_time = FloatTime() + BotChatTime(bs) + 0.1; - } - } - if (bs->standfindenemy_time < FloatTime()) { - if (BotFindEnemy(bs, -1)) { - AIEnter_Battle_Fight(bs, "stand: found enemy"); - return qfalse; - } - bs->standfindenemy_time = FloatTime() + 1; - } - // put up chat icon - trap_EA_Talk(bs->client); - // when done standing - if (bs->stand_time < FloatTime()) { - trap_BotEnterChat(bs->cs, 0, bs->chatto); - AIEnter_Seek_LTG(bs, "stand: time out"); - return qfalse; - } - // - return qtrue; -} - -/* -================== -AIEnter_Respawn -================== -*/ -void AIEnter_Respawn(bot_state_t *bs, char *s) { - BotRecordNodeSwitch(bs, "respawn", "", s); - //reset some states - trap_BotResetMoveState(bs->ms); - trap_BotResetGoalState(bs->gs); - trap_BotResetAvoidGoals(bs->gs); - trap_BotResetAvoidReach(bs->ms); - //if the bot wants to chat - if (BotChat_Death(bs)) { - bs->respawn_time = FloatTime() + BotChatTime(bs); - bs->respawnchat_time = FloatTime(); - } - else { - bs->respawn_time = FloatTime() + 1 + random(); - bs->respawnchat_time = 0; - } - //set respawn state - bs->respawn_wait = qfalse; - bs->ainode = AINode_Respawn; -} - -/* -================== -AINode_Respawn -================== -*/ -int AINode_Respawn(bot_state_t *bs) { - // if waiting for the actual respawn - if (bs->respawn_wait) { - if (!BotIsDead(bs)) { - AIEnter_Seek_LTG(bs, "respawn: respawned"); - } - else { - trap_EA_Respawn(bs->client); - } - } - else if (bs->respawn_time < FloatTime()) { - // wait until respawned - bs->respawn_wait = qtrue; - // elementary action respawn - trap_EA_Respawn(bs->client); - // - if (bs->respawnchat_time) { - trap_BotEnterChat(bs->cs, 0, bs->chatto); - bs->enemy = -1; - } - } - if (bs->respawnchat_time && bs->respawnchat_time < FloatTime() - 0.5) { - trap_EA_Talk(bs->client); - } - // - return qtrue; -} - -/* -================== -BotSelectActivateWeapon -================== -*/ -int BotSelectActivateWeapon(bot_state_t *bs) { - // - if (bs->inventory[INVENTORY_MACHINEGUN] > 0 && bs->inventory[INVENTORY_BULLETS] > 0) - return WEAPONINDEX_MACHINEGUN; - else if (bs->inventory[INVENTORY_SHOTGUN] > 0 && bs->inventory[INVENTORY_SHELLS] > 0) - return WEAPONINDEX_SHOTGUN; - else if (bs->inventory[INVENTORY_PLASMAGUN] > 0 && bs->inventory[INVENTORY_CELLS] > 0) - return WEAPONINDEX_PLASMAGUN; - else if (bs->inventory[INVENTORY_LIGHTNING] > 0 && bs->inventory[INVENTORY_LIGHTNINGAMMO] > 0) - return WEAPONINDEX_LIGHTNING; -#ifdef MISSIONPACK - else if (bs->inventory[INVENTORY_CHAINGUN] > 0 && bs->inventory[INVENTORY_BELT] > 0) - return WEAPONINDEX_CHAINGUN; - else if (bs->inventory[INVENTORY_NAILGUN] > 0 && bs->inventory[INVENTORY_NAILS] > 0) - return WEAPONINDEX_NAILGUN; -#endif - else if (bs->inventory[INVENTORY_RAILGUN] > 0 && bs->inventory[INVENTORY_SLUGS] > 0) - return WEAPONINDEX_RAILGUN; - else if (bs->inventory[INVENTORY_ROCKETLAUNCHER] > 0 && bs->inventory[INVENTORY_ROCKETS] > 0) - return WEAPONINDEX_ROCKET_LAUNCHER; - else if (bs->inventory[INVENTORY_BFG10K] > 0 && bs->inventory[INVENTORY_BFGAMMO] > 0) - return WEAPONINDEX_BFG; - else { - return -1; - } -} - -/* -================== -BotClearPath - - try to deactivate obstacles like proximity mines on the bot's path -================== -*/ -void BotClearPath(bot_state_t *bs, bot_moveresult_t *moveresult) { - int i, bestmine; - float dist, bestdist; - vec3_t target, dir; - bsp_trace_t bsptrace; - entityState_t state; - - // if there is a dead body wearing kamikze nearby - if (bs->kamikazebody) { - // if the bot's view angles and weapon are not used for movement - if ( !(moveresult->flags & (MOVERESULT_MOVEMENTVIEW | MOVERESULT_MOVEMENTWEAPON)) ) { - // - BotAI_GetEntityState(bs->kamikazebody, &state); - VectorCopy(state.pos.trBase, target); - target[2] += 8; - VectorSubtract(target, bs->eye, dir); - vectoangles(dir, moveresult->ideal_viewangles); - // - moveresult->weapon = BotSelectActivateWeapon(bs); - if (moveresult->weapon == -1) { - // FIXME: run away! - moveresult->weapon = 0; - } - if (moveresult->weapon) { - // - moveresult->flags |= MOVERESULT_MOVEMENTWEAPON | MOVERESULT_MOVEMENTVIEW; - // if holding the right weapon - if (bs->cur_ps.weapon == moveresult->weapon) { - // if the bot is pretty close with it's aim - if (InFieldOfVision(bs->viewangles, 20, moveresult->ideal_viewangles)) { - // - BotAI_Trace(&bsptrace, bs->eye, NULL, NULL, target, bs->entitynum, MASK_SHOT); - // if the mine is visible from the current position - if (bsptrace.fraction >= 1.0 || bsptrace.ent == state.number) { - // shoot at the mine - trap_EA_Attack(bs->client); - } - } - } - } - } - } - if (moveresult->flags & MOVERESULT_BLOCKEDBYAVOIDSPOT) { - bs->blockedbyavoidspot_time = FloatTime() + 5; - } - // if blocked by an avoid spot and the view angles and weapon are used for movement - if (bs->blockedbyavoidspot_time > FloatTime() && - !(moveresult->flags & (MOVERESULT_MOVEMENTVIEW | MOVERESULT_MOVEMENTWEAPON)) ) { - bestdist = 300; - bestmine = -1; - for (i = 0; i < bs->numproxmines; i++) { - BotAI_GetEntityState(bs->proxmines[i], &state); - VectorSubtract(state.pos.trBase, bs->origin, dir); - dist = VectorLength(dir); - if (dist < bestdist) { - bestdist = dist; - bestmine = i; - } - } - if (bestmine != -1) { - // - // state->generic1 == TEAM_RED || state->generic1 == TEAM_BLUE - // - // deactivate prox mines in the bot's path by shooting - // rockets or plasma cells etc. at them - BotAI_GetEntityState(bs->proxmines[bestmine], &state); - VectorCopy(state.pos.trBase, target); - target[2] += 2; - VectorSubtract(target, bs->eye, dir); - vectoangles(dir, moveresult->ideal_viewangles); - // if the bot has a weapon that does splash damage - if (bs->inventory[INVENTORY_PLASMAGUN] > 0 && bs->inventory[INVENTORY_CELLS] > 0) - moveresult->weapon = WEAPONINDEX_PLASMAGUN; - else if (bs->inventory[INVENTORY_ROCKETLAUNCHER] > 0 && bs->inventory[INVENTORY_ROCKETS] > 0) - moveresult->weapon = WEAPONINDEX_ROCKET_LAUNCHER; - else if (bs->inventory[INVENTORY_BFG10K] > 0 && bs->inventory[INVENTORY_BFGAMMO] > 0) - moveresult->weapon = WEAPONINDEX_BFG; - else { - moveresult->weapon = 0; - } - if (moveresult->weapon) { - // - moveresult->flags |= MOVERESULT_MOVEMENTWEAPON | MOVERESULT_MOVEMENTVIEW; - // if holding the right weapon - if (bs->cur_ps.weapon == moveresult->weapon) { - // if the bot is pretty close with it's aim - if (InFieldOfVision(bs->viewangles, 20, moveresult->ideal_viewangles)) { - // - BotAI_Trace(&bsptrace, bs->eye, NULL, NULL, target, bs->entitynum, MASK_SHOT); - // if the mine is visible from the current position - if (bsptrace.fraction >= 1.0 || bsptrace.ent == state.number) { - // shoot at the mine - trap_EA_Attack(bs->client); - } - } - } - } - } - } -} - -/* -================== -AIEnter_Seek_ActivateEntity -================== -*/ -void AIEnter_Seek_ActivateEntity(bot_state_t *bs, char *s) { - BotRecordNodeSwitch(bs, "activate entity", "", s); - bs->ainode = AINode_Seek_ActivateEntity; -} - -/* -================== -AINode_Seek_Activate_Entity -================== -*/ -int AINode_Seek_ActivateEntity(bot_state_t *bs) { - bot_goal_t *goal; - vec3_t target, dir, ideal_viewangles; - bot_moveresult_t moveresult; - int targetvisible; - bsp_trace_t bsptrace; - aas_entityinfo_t entinfo; - - if (BotIsObserver(bs)) { - BotClearActivateGoalStack(bs); - AIEnter_Observer(bs, "active entity: observer"); - return qfalse; - } - //if in the intermission - if (BotIntermission(bs)) { - BotClearActivateGoalStack(bs); - AIEnter_Intermission(bs, "activate entity: intermission"); - return qfalse; - } - //respawn if dead - if (BotIsDead(bs)) { - BotClearActivateGoalStack(bs); - AIEnter_Respawn(bs, "activate entity: bot dead"); - return qfalse; - } - // - bs->tfl = TFL_DEFAULT; - if (bot_grapple.integer) bs->tfl |= TFL_GRAPPLEHOOK; - // if in lava or slime the bot should be able to get out - if (BotInLavaOrSlime(bs)) bs->tfl |= TFL_LAVA|TFL_SLIME; - // map specific code - BotMapScripts(bs); - // no enemy - bs->enemy = -1; - // if the bot has no activate goal - if (!bs->activatestack) { - BotClearActivateGoalStack(bs); - AIEnter_Seek_NBG(bs, "activate entity: no goal"); - return qfalse; - } - // - goal = &bs->activatestack->goal; - // initialize target being visible to false - targetvisible = qfalse; - // if the bot has to shoot at a target to activate something - if (bs->activatestack->shoot) { - // - BotAI_Trace(&bsptrace, bs->eye, NULL, NULL, bs->activatestack->target, bs->entitynum, MASK_SHOT); - // if the shootable entity is visible from the current position - if (bsptrace.fraction >= 1.0 || bsptrace.ent == goal->entitynum) { - targetvisible = qtrue; - // if holding the right weapon - if (bs->cur_ps.weapon == bs->activatestack->weapon) { - VectorSubtract(bs->activatestack->target, bs->eye, dir); - vectoangles(dir, ideal_viewangles); - // if the bot is pretty close with it's aim - if (InFieldOfVision(bs->viewangles, 20, ideal_viewangles)) { - trap_EA_Attack(bs->client); - } - } - } - } - // if the shoot target is visible - if (targetvisible) { - // get the entity info of the entity the bot is shooting at - BotEntityInfo(goal->entitynum, &entinfo); - // if the entity the bot shoots at moved - if (!VectorCompare(bs->activatestack->origin, entinfo.origin)) { -#ifdef DEBUG - BotAI_Print(PRT_MESSAGE, "hit shootable button or trigger\n"); -#endif //DEBUG - bs->activatestack->time = 0; - } - // if the activate goal has been activated or the bot takes too long - if (bs->activatestack->time < FloatTime()) { - BotPopFromActivateGoalStack(bs); - // if there are more activate goals on the stack - if (bs->activatestack) { - bs->activatestack->time = FloatTime() + 10; - return qfalse; - } - AIEnter_Seek_NBG(bs, "activate entity: time out"); - return qfalse; - } - memset(&moveresult, 0, sizeof(bot_moveresult_t)); - } - else { - // if the bot has no goal - if (!goal) { - bs->activatestack->time = 0; - } - // if the bot does not have a shoot goal - else if (!bs->activatestack->shoot) { - //if the bot touches the current goal - if (trap_BotTouchingGoal(bs->origin, goal)) { -#ifdef DEBUG - BotAI_Print(PRT_MESSAGE, "touched button or trigger\n"); -#endif //DEBUG - bs->activatestack->time = 0; - } - } - // if the activate goal has been activated or the bot takes too long - if (bs->activatestack->time < FloatTime()) { - BotPopFromActivateGoalStack(bs); - // if there are more activate goals on the stack - if (bs->activatestack) { - bs->activatestack->time = FloatTime() + 10; - return qfalse; - } - AIEnter_Seek_NBG(bs, "activate entity: activated"); - return qfalse; - } - //predict obstacles - if (BotAIPredictObstacles(bs, goal)) - return qfalse; - //initialize the movement state - BotSetupForMovement(bs); - //move towards the goal - trap_BotMoveToGoal(&moveresult, bs->ms, goal, bs->tfl); - //if the movement failed - if (moveresult.failure) { - //reset the avoid reach, otherwise bot is stuck in current area - trap_BotResetAvoidReach(bs->ms); - // - bs->activatestack->time = 0; - } - //check if the bot is blocked - BotAIBlocked(bs, &moveresult, qtrue); - } - // - BotClearPath(bs, &moveresult); - // if the bot has to shoot to activate - if (bs->activatestack->shoot) { - // if the view angles aren't yet used for the movement - if (!(moveresult.flags & MOVERESULT_MOVEMENTVIEW)) { - VectorSubtract(bs->activatestack->target, bs->eye, dir); - vectoangles(dir, moveresult.ideal_viewangles); - moveresult.flags |= MOVERESULT_MOVEMENTVIEW; - } - // if there's no weapon yet used for the movement - if (!(moveresult.flags & MOVERESULT_MOVEMENTWEAPON)) { - moveresult.flags |= MOVERESULT_MOVEMENTWEAPON; - // - bs->activatestack->weapon = BotSelectActivateWeapon(bs); - if (bs->activatestack->weapon == -1) { - //FIXME: find a decent weapon first - bs->activatestack->weapon = 0; - } - moveresult.weapon = bs->activatestack->weapon; - } - } - // if the ideal view angles are set for movement - if (moveresult.flags & (MOVERESULT_MOVEMENTVIEWSET|MOVERESULT_MOVEMENTVIEW|MOVERESULT_SWIMVIEW)) { - VectorCopy(moveresult.ideal_viewangles, bs->ideal_viewangles); - } - // if waiting for something - else if (moveresult.flags & MOVERESULT_WAITING) { - if (random() < bs->thinktime * 0.8) { - BotRoamGoal(bs, target); - VectorSubtract(target, bs->origin, dir); - vectoangles(dir, bs->ideal_viewangles); - bs->ideal_viewangles[2] *= 0.5; - } - } - else if (!(bs->flags & BFL_IDEALVIEWSET)) { - if (trap_BotMovementViewTarget(bs->ms, goal, bs->tfl, 300, target)) { - VectorSubtract(target, bs->origin, dir); - vectoangles(dir, bs->ideal_viewangles); - } - else { - vectoangles(moveresult.movedir, bs->ideal_viewangles); - } - bs->ideal_viewangles[2] *= 0.5; - } - // if the weapon is used for the bot movement - if (moveresult.flags & MOVERESULT_MOVEMENTWEAPON) - bs->weaponnum = moveresult.weapon; - // if there is an enemy - if (BotFindEnemy(bs, -1)) { - if (BotWantsToRetreat(bs)) { - //keep the current long term goal and retreat - AIEnter_Battle_NBG(bs, "activate entity: found enemy"); - } - else { - trap_BotResetLastAvoidReach(bs->ms); - //empty the goal stack - trap_BotEmptyGoalStack(bs->gs); - //go fight - AIEnter_Battle_Fight(bs, "activate entity: found enemy"); - } - BotClearActivateGoalStack(bs); - } - return qtrue; -} - -/* -================== -AIEnter_Seek_NBG -================== -*/ -void AIEnter_Seek_NBG(bot_state_t *bs, char *s) { - bot_goal_t goal; - char buf[144]; - - if (trap_BotGetTopGoal(bs->gs, &goal)) { - trap_BotGoalName(goal.number, buf, 144); - BotRecordNodeSwitch(bs, "seek NBG", buf, s); - } - else { - BotRecordNodeSwitch(bs, "seek NBG", "no goal", s); - } - bs->ainode = AINode_Seek_NBG; -} - -/* -================== -AINode_Seek_NBG -================== -*/ -int AINode_Seek_NBG(bot_state_t *bs) { - bot_goal_t goal; - vec3_t target, dir; - bot_moveresult_t moveresult; - - if (BotIsObserver(bs)) { - AIEnter_Observer(bs, "seek nbg: observer"); - return qfalse; - } - //if in the intermission - if (BotIntermission(bs)) { - AIEnter_Intermission(bs, "seek nbg: intermision"); - return qfalse; - } - //respawn if dead - if (BotIsDead(bs)) { - AIEnter_Respawn(bs, "seek nbg: bot dead"); - return qfalse; - } - // - bs->tfl = TFL_DEFAULT; - if (bot_grapple.integer) bs->tfl |= TFL_GRAPPLEHOOK; - //if in lava or slime the bot should be able to get out - if (BotInLavaOrSlime(bs)) bs->tfl |= TFL_LAVA|TFL_SLIME; - // - if (BotCanAndWantsToRocketJump(bs)) { - bs->tfl |= TFL_ROCKETJUMP; - } - //map specific code - BotMapScripts(bs); - //no enemy - bs->enemy = -1; - //if the bot has no goal - if (!trap_BotGetTopGoal(bs->gs, &goal)) bs->nbg_time = 0; - //if the bot touches the current goal - else if (BotReachedGoal(bs, &goal)) { - BotChooseWeapon(bs); - bs->nbg_time = 0; - } - // - if (bs->nbg_time < FloatTime()) { - //pop the current goal from the stack - trap_BotPopGoal(bs->gs); - //check for new nearby items right away - //NOTE: we canNOT reset the check_time to zero because it would create an endless loop of node switches - bs->check_time = FloatTime() + 0.05; - //go back to seek ltg - AIEnter_Seek_LTG(bs, "seek nbg: time out"); - return qfalse; - } - //predict obstacles - if (BotAIPredictObstacles(bs, &goal)) - return qfalse; - //initialize the movement state - BotSetupForMovement(bs); - //move towards the goal - trap_BotMoveToGoal(&moveresult, bs->ms, &goal, bs->tfl); - //if the movement failed - if (moveresult.failure) { - //reset the avoid reach, otherwise bot is stuck in current area - trap_BotResetAvoidReach(bs->ms); - bs->nbg_time = 0; - } - //check if the bot is blocked - BotAIBlocked(bs, &moveresult, qtrue); - // - BotClearPath(bs, &moveresult); - //if the viewangles are used for the movement - if (moveresult.flags & (MOVERESULT_MOVEMENTVIEWSET|MOVERESULT_MOVEMENTVIEW|MOVERESULT_SWIMVIEW)) { - VectorCopy(moveresult.ideal_viewangles, bs->ideal_viewangles); - } - //if waiting for something - else if (moveresult.flags & MOVERESULT_WAITING) { - if (random() < bs->thinktime * 0.8) { - BotRoamGoal(bs, target); - VectorSubtract(target, bs->origin, dir); - vectoangles(dir, bs->ideal_viewangles); - bs->ideal_viewangles[2] *= 0.5; - } - } - else if (!(bs->flags & BFL_IDEALVIEWSET)) { - if (!trap_BotGetSecondGoal(bs->gs, &goal)) trap_BotGetTopGoal(bs->gs, &goal); - if (trap_BotMovementViewTarget(bs->ms, &goal, bs->tfl, 300, target)) { - VectorSubtract(target, bs->origin, dir); - vectoangles(dir, bs->ideal_viewangles); - } - //FIXME: look at cluster portals? - else vectoangles(moveresult.movedir, bs->ideal_viewangles); - bs->ideal_viewangles[2] *= 0.5; - } - //if the weapon is used for the bot movement - if (moveresult.flags & MOVERESULT_MOVEMENTWEAPON) bs->weaponnum = moveresult.weapon; - //if there is an enemy - if (BotFindEnemy(bs, -1)) { - if (BotWantsToRetreat(bs)) { - //keep the current long term goal and retreat - AIEnter_Battle_NBG(bs, "seek nbg: found enemy"); - } - else { - trap_BotResetLastAvoidReach(bs->ms); - //empty the goal stack - trap_BotEmptyGoalStack(bs->gs); - //go fight - AIEnter_Battle_Fight(bs, "seek nbg: found enemy"); - } - } - return qtrue; -} - -/* -================== -AIEnter_Seek_LTG -================== -*/ -void AIEnter_Seek_LTG(bot_state_t *bs, char *s) { - bot_goal_t goal; - char buf[144]; - - if (trap_BotGetTopGoal(bs->gs, &goal)) { - trap_BotGoalName(goal.number, buf, 144); - BotRecordNodeSwitch(bs, "seek LTG", buf, s); - } - else { - BotRecordNodeSwitch(bs, "seek LTG", "no goal", s); - } - bs->ainode = AINode_Seek_LTG; -} - -/* -================== -AINode_Seek_LTG -================== -*/ -int AINode_Seek_LTG(bot_state_t *bs) -{ - bot_goal_t goal; - vec3_t target, dir; - bot_moveresult_t moveresult; - int range; - //char buf[128]; - //bot_goal_t tmpgoal; - - if (BotIsObserver(bs)) { - AIEnter_Observer(bs, "seek ltg: observer"); - return qfalse; - } - //if in the intermission - if (BotIntermission(bs)) { - AIEnter_Intermission(bs, "seek ltg: intermission"); - return qfalse; - } - //respawn if dead - if (BotIsDead(bs)) { - AIEnter_Respawn(bs, "seek ltg: bot dead"); - return qfalse; - } - // - if (BotChat_Random(bs)) { - bs->stand_time = FloatTime() + BotChatTime(bs); - AIEnter_Stand(bs, "seek ltg: random chat"); - return qfalse; - } - // - bs->tfl = TFL_DEFAULT; - if (bot_grapple.integer) bs->tfl |= TFL_GRAPPLEHOOK; - //if in lava or slime the bot should be able to get out - if (BotInLavaOrSlime(bs)) bs->tfl |= TFL_LAVA|TFL_SLIME; - // - if (BotCanAndWantsToRocketJump(bs)) { - bs->tfl |= TFL_ROCKETJUMP; - } - //map specific code - BotMapScripts(bs); - //no enemy - bs->enemy = -1; - // - if (bs->killedenemy_time > FloatTime() - 2) { - if (random() < bs->thinktime * 1) { - trap_EA_Gesture(bs->client); - } - } - //if there is an enemy - if (BotFindEnemy(bs, -1)) { - if (BotWantsToRetreat(bs)) { - //keep the current long term goal and retreat - AIEnter_Battle_Retreat(bs, "seek ltg: found enemy"); - return qfalse; - } - else { - trap_BotResetLastAvoidReach(bs->ms); - //empty the goal stack - trap_BotEmptyGoalStack(bs->gs); - //go fight - AIEnter_Battle_Fight(bs, "seek ltg: found enemy"); - return qfalse; - } - } - // - BotTeamGoals(bs, qfalse); - //get the current long term goal - if (!BotLongTermGoal(bs, bs->tfl, qfalse, &goal)) { - return qtrue; - } - //check for nearby goals periodicly - if (bs->check_time < FloatTime()) { - bs->check_time = FloatTime() + 0.5; - //check if the bot wants to camp - BotWantsToCamp(bs); - // - if (bs->ltgtype == LTG_DEFENDKEYAREA) range = 400; - else range = 150; - // -#ifdef CTF - if (gametype == GT_CTF) { - //if carrying a flag the bot shouldn't be distracted too much - if (BotCTFCarryingFlag(bs)) - range = 50; - } -#endif //CTF -#ifdef MISSIONPACK - else if (gametype == GT_1FCTF) { - if (Bot1FCTFCarryingFlag(bs)) - range = 50; - } - else if (gametype == GT_HARVESTER) { - if (BotHarvesterCarryingCubes(bs)) - range = 80; - } -#endif - // - if (BotNearbyGoal(bs, bs->tfl, &goal, range)) { - trap_BotResetLastAvoidReach(bs->ms); - //get the goal at the top of the stack - //trap_BotGetTopGoal(bs->gs, &tmpgoal); - //trap_BotGoalName(tmpgoal.number, buf, 144); - //BotAI_Print(PRT_MESSAGE, "new nearby goal %s\n", buf); - //time the bot gets to pick up the nearby goal item - bs->nbg_time = FloatTime() + 4 + range * 0.01; - AIEnter_Seek_NBG(bs, "ltg seek: nbg"); - return qfalse; - } - } - //predict obstacles - if (BotAIPredictObstacles(bs, &goal)) - return qfalse; - //initialize the movement state - BotSetupForMovement(bs); - //move towards the goal - trap_BotMoveToGoal(&moveresult, bs->ms, &goal, bs->tfl); - //if the movement failed - if (moveresult.failure) { - //reset the avoid reach, otherwise bot is stuck in current area - trap_BotResetAvoidReach(bs->ms); - //BotAI_Print(PRT_MESSAGE, "movement failure %d\n", moveresult.traveltype); - bs->ltg_time = 0; - } - // - BotAIBlocked(bs, &moveresult, qtrue); - // - BotClearPath(bs, &moveresult); - //if the viewangles are used for the movement - if (moveresult.flags & (MOVERESULT_MOVEMENTVIEWSET|MOVERESULT_MOVEMENTVIEW|MOVERESULT_SWIMVIEW)) { - VectorCopy(moveresult.ideal_viewangles, bs->ideal_viewangles); - } - //if waiting for something - else if (moveresult.flags & MOVERESULT_WAITING) { - if (random() < bs->thinktime * 0.8) { - BotRoamGoal(bs, target); - VectorSubtract(target, bs->origin, dir); - vectoangles(dir, bs->ideal_viewangles); - bs->ideal_viewangles[2] *= 0.5; - } - } - else if (!(bs->flags & BFL_IDEALVIEWSET)) { - if (trap_BotMovementViewTarget(bs->ms, &goal, bs->tfl, 300, target)) { - VectorSubtract(target, bs->origin, dir); - vectoangles(dir, bs->ideal_viewangles); - } - //FIXME: look at cluster portals? - else if (VectorLengthSquared(moveresult.movedir)) { - vectoangles(moveresult.movedir, bs->ideal_viewangles); - } - else if (random() < bs->thinktime * 0.8) { - BotRoamGoal(bs, target); - VectorSubtract(target, bs->origin, dir); - vectoangles(dir, bs->ideal_viewangles); - bs->ideal_viewangles[2] *= 0.5; - } - bs->ideal_viewangles[2] *= 0.5; - } - //if the weapon is used for the bot movement - if (moveresult.flags & MOVERESULT_MOVEMENTWEAPON) bs->weaponnum = moveresult.weapon; - // - return qtrue; -} - -/* -================== -AIEnter_Battle_Fight -================== -*/ -void AIEnter_Battle_Fight(bot_state_t *bs, char *s) { - BotRecordNodeSwitch(bs, "battle fight", "", s); - trap_BotResetLastAvoidReach(bs->ms); - bs->ainode = AINode_Battle_Fight; -} - -/* -================== -AIEnter_Battle_Fight -================== -*/ -void AIEnter_Battle_SuicidalFight(bot_state_t *bs, char *s) { - BotRecordNodeSwitch(bs, "battle fight", "", s); - trap_BotResetLastAvoidReach(bs->ms); - bs->ainode = AINode_Battle_Fight; - bs->flags |= BFL_FIGHTSUICIDAL; -} - -/* -================== -AINode_Battle_Fight -================== -*/ -int AINode_Battle_Fight(bot_state_t *bs) { - int areanum; - vec3_t target; - aas_entityinfo_t entinfo; - bot_moveresult_t moveresult; - - if (BotIsObserver(bs)) { - AIEnter_Observer(bs, "battle fight: observer"); - return qfalse; - } - - //if in the intermission - if (BotIntermission(bs)) { - AIEnter_Intermission(bs, "battle fight: intermission"); - return qfalse; - } - //respawn if dead - if (BotIsDead(bs)) { - AIEnter_Respawn(bs, "battle fight: bot dead"); - return qfalse; - } - //if there is another better enemy - if (BotFindEnemy(bs, bs->enemy)) { -#ifdef DEBUG - BotAI_Print(PRT_MESSAGE, "found new better enemy\n"); -#endif - } - //if no enemy - if (bs->enemy < 0) { - AIEnter_Seek_LTG(bs, "battle fight: no enemy"); - return qfalse; - } - // - BotEntityInfo(bs->enemy, &entinfo); - //if the enemy is dead - if (bs->enemydeath_time) { - if (bs->enemydeath_time < FloatTime() - 1.0) { - bs->enemydeath_time = 0; - if (bs->enemysuicide) { - BotChat_EnemySuicide(bs); - } - if (bs->lastkilledplayer == bs->enemy && BotChat_Kill(bs)) { - bs->stand_time = FloatTime() + BotChatTime(bs); - AIEnter_Stand(bs, "battle fight: enemy dead"); - } - else { - bs->ltg_time = 0; - AIEnter_Seek_LTG(bs, "battle fight: enemy dead"); - } - return qfalse; - } - } - else { - if (EntityIsDead(&entinfo)) { - bs->enemydeath_time = FloatTime(); - } - } - //if the enemy is invisible and not shooting the bot looses track easily - if (EntityIsInvisible(&entinfo) && !EntityIsShooting(&entinfo)) { - if (random() < 0.2) { - AIEnter_Seek_LTG(bs, "battle fight: invisible"); - return qfalse; - } - } - // - VectorCopy(entinfo.origin, target); - // if not a player enemy - if (bs->enemy >= MAX_CLIENTS) { -#ifdef MISSIONPACK - // if attacking an obelisk - if ( bs->enemy == redobelisk.entitynum || - bs->enemy == blueobelisk.entitynum ) { - target[2] += 16; - } -#endif - } - //update the reachability area and origin if possible - areanum = BotPointAreaNum(target); - if (areanum && trap_AAS_AreaReachability(areanum)) { - VectorCopy(target, bs->lastenemyorigin); - bs->lastenemyareanum = areanum; - } - //update the attack inventory values - BotUpdateBattleInventory(bs, bs->enemy); - //if the bot's health decreased - if (bs->lastframe_health > bs->inventory[INVENTORY_HEALTH]) { - if (BotChat_HitNoDeath(bs)) { - bs->stand_time = FloatTime() + BotChatTime(bs); - AIEnter_Stand(bs, "battle fight: chat health decreased"); - return qfalse; - } - } - //if the bot hit someone - if (bs->cur_ps.persistant[PERS_HITS] > bs->lasthitcount) { - if (BotChat_HitNoKill(bs)) { - bs->stand_time = FloatTime() + BotChatTime(bs); - AIEnter_Stand(bs, "battle fight: chat hit someone"); - return qfalse; - } - } - //if the enemy is not visible - if (!BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy)) { - if (BotWantsToChase(bs)) { - AIEnter_Battle_Chase(bs, "battle fight: enemy out of sight"); - return qfalse; - } - else { - AIEnter_Seek_LTG(bs, "battle fight: enemy out of sight"); - return qfalse; - } - } - //use holdable items - BotBattleUseItems(bs); - // - bs->tfl = TFL_DEFAULT; - if (bot_grapple.integer) bs->tfl |= TFL_GRAPPLEHOOK; - //if in lava or slime the bot should be able to get out - if (BotInLavaOrSlime(bs)) bs->tfl |= TFL_LAVA|TFL_SLIME; - // - if (BotCanAndWantsToRocketJump(bs)) { - bs->tfl |= TFL_ROCKETJUMP; - } - //choose the best weapon to fight with - BotChooseWeapon(bs); - //do attack movements - moveresult = BotAttackMove(bs, bs->tfl); - //if the movement failed - if (moveresult.failure) { - //reset the avoid reach, otherwise bot is stuck in current area - trap_BotResetAvoidReach(bs->ms); - //BotAI_Print(PRT_MESSAGE, "movement failure %d\n", moveresult.traveltype); - bs->ltg_time = 0; - } - // - BotAIBlocked(bs, &moveresult, qfalse); - //aim at the enemy - BotAimAtEnemy(bs); - //attack the enemy if possible - BotCheckAttack(bs); - //if the bot wants to retreat - if (!(bs->flags & BFL_FIGHTSUICIDAL)) { - if (BotWantsToRetreat(bs)) { - AIEnter_Battle_Retreat(bs, "battle fight: wants to retreat"); - return qtrue; - } - } - return qtrue; -} - -/* -================== -AIEnter_Battle_Chase -================== -*/ -void AIEnter_Battle_Chase(bot_state_t *bs, char *s) { - BotRecordNodeSwitch(bs, "battle chase", "", s); - bs->chase_time = FloatTime(); - bs->ainode = AINode_Battle_Chase; -} - -/* -================== -AINode_Battle_Chase -================== -*/ -int AINode_Battle_Chase(bot_state_t *bs) -{ - bot_goal_t goal; - vec3_t target, dir; - bot_moveresult_t moveresult; - float range; - - if (BotIsObserver(bs)) { - AIEnter_Observer(bs, "battle chase: observer"); - return qfalse; - } - //if in the intermission - if (BotIntermission(bs)) { - AIEnter_Intermission(bs, "battle chase: intermission"); - return qfalse; - } - //respawn if dead - if (BotIsDead(bs)) { - AIEnter_Respawn(bs, "battle chase: bot dead"); - return qfalse; - } - //if no enemy - if (bs->enemy < 0) { - AIEnter_Seek_LTG(bs, "battle chase: no enemy"); - return qfalse; - } - //if the enemy is visible - if (BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy)) { - AIEnter_Battle_Fight(bs, "battle chase"); - return qfalse; - } - //if there is another enemy - if (BotFindEnemy(bs, -1)) { - AIEnter_Battle_Fight(bs, "battle chase: better enemy"); - return qfalse; - } - //there is no last enemy area - if (!bs->lastenemyareanum) { - AIEnter_Seek_LTG(bs, "battle chase: no enemy area"); - return qfalse; - } - // - bs->tfl = TFL_DEFAULT; - if (bot_grapple.integer) bs->tfl |= TFL_GRAPPLEHOOK; - //if in lava or slime the bot should be able to get out - if (BotInLavaOrSlime(bs)) bs->tfl |= TFL_LAVA|TFL_SLIME; - // - if (BotCanAndWantsToRocketJump(bs)) { - bs->tfl |= TFL_ROCKETJUMP; - } - //map specific code - BotMapScripts(bs); - //create the chase goal - goal.entitynum = bs->enemy; - goal.areanum = bs->lastenemyareanum; - VectorCopy(bs->lastenemyorigin, goal.origin); - VectorSet(goal.mins, -8, -8, -8); - VectorSet(goal.maxs, 8, 8, 8); - //if the last seen enemy spot is reached the enemy could not be found - if (trap_BotTouchingGoal(bs->origin, &goal)) bs->chase_time = 0; - //if there's no chase time left - if (!bs->chase_time || bs->chase_time < FloatTime() - 10) { - AIEnter_Seek_LTG(bs, "battle chase: time out"); - return qfalse; - } - //check for nearby goals periodicly - if (bs->check_time < FloatTime()) { - bs->check_time = FloatTime() + 1; - range = 150; - // - if (BotNearbyGoal(bs, bs->tfl, &goal, range)) { - //the bot gets 5 seconds to pick up the nearby goal item - bs->nbg_time = FloatTime() + 0.1 * range + 1; - trap_BotResetLastAvoidReach(bs->ms); - AIEnter_Battle_NBG(bs, "battle chase: nbg"); - return qfalse; - } - } - // - BotUpdateBattleInventory(bs, bs->enemy); - //initialize the movement state - BotSetupForMovement(bs); - //move towards the goal - trap_BotMoveToGoal(&moveresult, bs->ms, &goal, bs->tfl); - //if the movement failed - if (moveresult.failure) { - //reset the avoid reach, otherwise bot is stuck in current area - trap_BotResetAvoidReach(bs->ms); - //BotAI_Print(PRT_MESSAGE, "movement failure %d\n", moveresult.traveltype); - bs->ltg_time = 0; - } - // - BotAIBlocked(bs, &moveresult, qfalse); - // - if (moveresult.flags & (MOVERESULT_MOVEMENTVIEWSET|MOVERESULT_MOVEMENTVIEW|MOVERESULT_SWIMVIEW)) { - VectorCopy(moveresult.ideal_viewangles, bs->ideal_viewangles); - } - else if (!(bs->flags & BFL_IDEALVIEWSET)) { - if (bs->chase_time > FloatTime() - 2) { - BotAimAtEnemy(bs); - } - else { - if (trap_BotMovementViewTarget(bs->ms, &goal, bs->tfl, 300, target)) { - VectorSubtract(target, bs->origin, dir); - vectoangles(dir, bs->ideal_viewangles); - } - else { - vectoangles(moveresult.movedir, bs->ideal_viewangles); - } - } - bs->ideal_viewangles[2] *= 0.5; - } - //if the weapon is used for the bot movement - if (moveresult.flags & MOVERESULT_MOVEMENTWEAPON) bs->weaponnum = moveresult.weapon; - //if the bot is in the area the enemy was last seen in - if (bs->areanum == bs->lastenemyareanum) bs->chase_time = 0; - //if the bot wants to retreat (the bot could have been damage during the chase) - if (BotWantsToRetreat(bs)) { - AIEnter_Battle_Retreat(bs, "battle chase: wants to retreat"); - return qtrue; - } - return qtrue; -} - -/* -================== -AIEnter_Battle_Retreat -================== -*/ -void AIEnter_Battle_Retreat(bot_state_t *bs, char *s) { - BotRecordNodeSwitch(bs, "battle retreat", "", s); - bs->ainode = AINode_Battle_Retreat; -} - -/* -================== -AINode_Battle_Retreat -================== -*/ -int AINode_Battle_Retreat(bot_state_t *bs) { - bot_goal_t goal; - aas_entityinfo_t entinfo; - bot_moveresult_t moveresult; - vec3_t target, dir; - float attack_skill, range; - int areanum; - - if (BotIsObserver(bs)) { - AIEnter_Observer(bs, "battle retreat: observer"); - return qfalse; - } - //if in the intermission - if (BotIntermission(bs)) { - AIEnter_Intermission(bs, "battle retreat: intermission"); - return qfalse; - } - //respawn if dead - if (BotIsDead(bs)) { - AIEnter_Respawn(bs, "battle retreat: bot dead"); - return qfalse; - } - //if no enemy - if (bs->enemy < 0) { - AIEnter_Seek_LTG(bs, "battle retreat: no enemy"); - return qfalse; - } - // - BotEntityInfo(bs->enemy, &entinfo); - if (EntityIsDead(&entinfo)) { - AIEnter_Seek_LTG(bs, "battle retreat: enemy dead"); - return qfalse; - } - //if there is another better enemy - if (BotFindEnemy(bs, bs->enemy)) { -#ifdef DEBUG - BotAI_Print(PRT_MESSAGE, "found new better enemy\n"); -#endif - } - // - bs->tfl = TFL_DEFAULT; - if (bot_grapple.integer) bs->tfl |= TFL_GRAPPLEHOOK; - //if in lava or slime the bot should be able to get out - if (BotInLavaOrSlime(bs)) bs->tfl |= TFL_LAVA|TFL_SLIME; - //map specific code - BotMapScripts(bs); - //update the attack inventory values - BotUpdateBattleInventory(bs, bs->enemy); - //if the bot doesn't want to retreat anymore... probably picked up some nice items - if (BotWantsToChase(bs)) { - //empty the goal stack, when chasing, only the enemy is the goal - trap_BotEmptyGoalStack(bs->gs); - //go chase the enemy - AIEnter_Battle_Chase(bs, "battle retreat: wants to chase"); - return qfalse; - } - //update the last time the enemy was visible - if (BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy)) { - bs->enemyvisible_time = FloatTime(); - VectorCopy(entinfo.origin, target); - // if not a player enemy - if (bs->enemy >= MAX_CLIENTS) { -#ifdef MISSIONPACK - // if attacking an obelisk - if ( bs->enemy == redobelisk.entitynum || - bs->enemy == blueobelisk.entitynum ) { - target[2] += 16; - } -#endif - } - //update the reachability area and origin if possible - areanum = BotPointAreaNum(target); - if (areanum && trap_AAS_AreaReachability(areanum)) { - VectorCopy(target, bs->lastenemyorigin); - bs->lastenemyareanum = areanum; - } - } - //if the enemy is NOT visible for 4 seconds - if (bs->enemyvisible_time < FloatTime() - 4) { - AIEnter_Seek_LTG(bs, "battle retreat: lost enemy"); - return qfalse; - } - //else if the enemy is NOT visible - else if (bs->enemyvisible_time < FloatTime()) { - //if there is another enemy - if (BotFindEnemy(bs, -1)) { - AIEnter_Battle_Fight(bs, "battle retreat: another enemy"); - return qfalse; - } - } - // - BotTeamGoals(bs, qtrue); - //use holdable items - BotBattleUseItems(bs); - //get the current long term goal while retreating - if (!BotLongTermGoal(bs, bs->tfl, qtrue, &goal)) { - AIEnter_Battle_SuicidalFight(bs, "battle retreat: no way out"); - return qfalse; - } - //check for nearby goals periodicly - if (bs->check_time < FloatTime()) { - bs->check_time = FloatTime() + 1; - range = 150; -#ifdef CTF - if (gametype == GT_CTF) { - //if carrying a flag the bot shouldn't be distracted too much - if (BotCTFCarryingFlag(bs)) - range = 50; - } -#endif //CTF -#ifdef MISSIONPACK - else if (gametype == GT_1FCTF) { - if (Bot1FCTFCarryingFlag(bs)) - range = 50; - } - else if (gametype == GT_HARVESTER) { - if (BotHarvesterCarryingCubes(bs)) - range = 80; - } -#endif - // - if (BotNearbyGoal(bs, bs->tfl, &goal, range)) { - trap_BotResetLastAvoidReach(bs->ms); - //time the bot gets to pick up the nearby goal item - bs->nbg_time = FloatTime() + range / 100 + 1; - AIEnter_Battle_NBG(bs, "battle retreat: nbg"); - return qfalse; - } - } - //initialize the movement state - BotSetupForMovement(bs); - //move towards the goal - trap_BotMoveToGoal(&moveresult, bs->ms, &goal, bs->tfl); - //if the movement failed - if (moveresult.failure) { - //reset the avoid reach, otherwise bot is stuck in current area - trap_BotResetAvoidReach(bs->ms); - //BotAI_Print(PRT_MESSAGE, "movement failure %d\n", moveresult.traveltype); - bs->ltg_time = 0; - } - // - BotAIBlocked(bs, &moveresult, qfalse); - //choose the best weapon to fight with - BotChooseWeapon(bs); - //if the view is fixed for the movement - if (moveresult.flags & (MOVERESULT_MOVEMENTVIEW|MOVERESULT_SWIMVIEW)) { - VectorCopy(moveresult.ideal_viewangles, bs->ideal_viewangles); - } - else if (!(moveresult.flags & MOVERESULT_MOVEMENTVIEWSET) - && !(bs->flags & BFL_IDEALVIEWSET) ) { - attack_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_ATTACK_SKILL, 0, 1); - //if the bot is skilled anough - if (attack_skill > 0.3) { - BotAimAtEnemy(bs); - } - else { - if (trap_BotMovementViewTarget(bs->ms, &goal, bs->tfl, 300, target)) { - VectorSubtract(target, bs->origin, dir); - vectoangles(dir, bs->ideal_viewangles); - } - else { - vectoangles(moveresult.movedir, bs->ideal_viewangles); - } - bs->ideal_viewangles[2] *= 0.5; - } - } - //if the weapon is used for the bot movement - if (moveresult.flags & MOVERESULT_MOVEMENTWEAPON) bs->weaponnum = moveresult.weapon; - //attack the enemy if possible - BotCheckAttack(bs); - // - return qtrue; -} - -/* -================== -AIEnter_Battle_NBG -================== -*/ -void AIEnter_Battle_NBG(bot_state_t *bs, char *s) { - BotRecordNodeSwitch(bs, "battle NBG", "", s); - bs->ainode = AINode_Battle_NBG; -} - -/* -================== -AINode_Battle_NBG -================== -*/ -int AINode_Battle_NBG(bot_state_t *bs) { - int areanum; - bot_goal_t goal; - aas_entityinfo_t entinfo; - bot_moveresult_t moveresult; - float attack_skill; - vec3_t target, dir; - - if (BotIsObserver(bs)) { - AIEnter_Observer(bs, "battle nbg: observer"); - return qfalse; - } - //if in the intermission - if (BotIntermission(bs)) { - AIEnter_Intermission(bs, "battle nbg: intermission"); - return qfalse; - } - //respawn if dead - if (BotIsDead(bs)) { - AIEnter_Respawn(bs, "battle nbg: bot dead"); - return qfalse; - } - //if no enemy - if (bs->enemy < 0) { - AIEnter_Seek_NBG(bs, "battle nbg: no enemy"); - return qfalse; - } - // - BotEntityInfo(bs->enemy, &entinfo); - if (EntityIsDead(&entinfo)) { - AIEnter_Seek_NBG(bs, "battle nbg: enemy dead"); - return qfalse; - } - // - bs->tfl = TFL_DEFAULT; - if (bot_grapple.integer) bs->tfl |= TFL_GRAPPLEHOOK; - //if in lava or slime the bot should be able to get out - if (BotInLavaOrSlime(bs)) bs->tfl |= TFL_LAVA|TFL_SLIME; - // - if (BotCanAndWantsToRocketJump(bs)) { - bs->tfl |= TFL_ROCKETJUMP; - } - //map specific code - BotMapScripts(bs); - //update the last time the enemy was visible - if (BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy)) { - bs->enemyvisible_time = FloatTime(); - VectorCopy(entinfo.origin, target); - // if not a player enemy - if (bs->enemy >= MAX_CLIENTS) { -#ifdef MISSIONPACK - // if attacking an obelisk - if ( bs->enemy == redobelisk.entitynum || - bs->enemy == blueobelisk.entitynum ) { - target[2] += 16; - } -#endif - } - //update the reachability area and origin if possible - areanum = BotPointAreaNum(target); - if (areanum && trap_AAS_AreaReachability(areanum)) { - VectorCopy(target, bs->lastenemyorigin); - bs->lastenemyareanum = areanum; - } - } - //if the bot has no goal or touches the current goal - if (!trap_BotGetTopGoal(bs->gs, &goal)) { - bs->nbg_time = 0; - } - else if (BotReachedGoal(bs, &goal)) { - bs->nbg_time = 0; - } - // - if (bs->nbg_time < FloatTime()) { - //pop the current goal from the stack - trap_BotPopGoal(bs->gs); - //if the bot still has a goal - if (trap_BotGetTopGoal(bs->gs, &goal)) - AIEnter_Battle_Retreat(bs, "battle nbg: time out"); - else - AIEnter_Battle_Fight(bs, "battle nbg: time out"); - // - return qfalse; - } - //initialize the movement state - BotSetupForMovement(bs); - //move towards the goal - trap_BotMoveToGoal(&moveresult, bs->ms, &goal, bs->tfl); - //if the movement failed - if (moveresult.failure) { - //reset the avoid reach, otherwise bot is stuck in current area - trap_BotResetAvoidReach(bs->ms); - //BotAI_Print(PRT_MESSAGE, "movement failure %d\n", moveresult.traveltype); - bs->nbg_time = 0; - } - // - BotAIBlocked(bs, &moveresult, qfalse); - //update the attack inventory values - BotUpdateBattleInventory(bs, bs->enemy); - //choose the best weapon to fight with - BotChooseWeapon(bs); - //if the view is fixed for the movement - if (moveresult.flags & (MOVERESULT_MOVEMENTVIEW|MOVERESULT_SWIMVIEW)) { - VectorCopy(moveresult.ideal_viewangles, bs->ideal_viewangles); - } - else if (!(moveresult.flags & MOVERESULT_MOVEMENTVIEWSET) - && !(bs->flags & BFL_IDEALVIEWSET)) { - attack_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_ATTACK_SKILL, 0, 1); - //if the bot is skilled anough and the enemy is visible - if (attack_skill > 0.3) { - //&& BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy) - BotAimAtEnemy(bs); - } - else { - if (trap_BotMovementViewTarget(bs->ms, &goal, bs->tfl, 300, target)) { - VectorSubtract(target, bs->origin, dir); - vectoangles(dir, bs->ideal_viewangles); - } - else { - vectoangles(moveresult.movedir, bs->ideal_viewangles); - } - bs->ideal_viewangles[2] *= 0.5; - } - } - //if the weapon is used for the bot movement - if (moveresult.flags & MOVERESULT_MOVEMENTWEAPON) bs->weaponnum = moveresult.weapon; - //attack the enemy if possible - BotCheckAttack(bs); - // - return qtrue; -} - +/* +=========================================================================== +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 Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// + +/***************************************************************************** + * name: ai_dmnet.c + * + * desc: Quake3 bot AI + * + * $Archive: /MissionPack/code/game/ai_dmnet.c $ + * + *****************************************************************************/ + +#include "g_local.h" +#include "botlib.h" +#include "be_aas.h" +#include "be_ea.h" +#include "be_ai_char.h" +#include "be_ai_chat.h" +#include "be_ai_gen.h" +#include "be_ai_goal.h" +#include "be_ai_move.h" +#include "be_ai_weap.h" +// +#include "ai_main.h" +#include "ai_dmq3.h" +#include "ai_chat.h" +#include "ai_cmd.h" +#include "ai_dmnet.h" +#include "ai_team.h" +//data file headers +#include "chars.h" //characteristics +#include "inv.h" //indexes into the inventory +#include "syn.h" //synonyms +#include "match.h" //string matching types and vars + +// for the voice chats +#include "../../ui/menudef.h" + +//goal flag, see be_ai_goal.h for the other GFL_* +#define GFL_AIR 128 + +int numnodeswitches; +char nodeswitch[MAX_NODESWITCHES+1][144]; + +#define LOOKAHEAD_DISTANCE 300 + +/* +================== +BotResetNodeSwitches +================== +*/ +void BotResetNodeSwitches(void) { + numnodeswitches = 0; +} + +/* +================== +BotDumpNodeSwitches +================== +*/ +void BotDumpNodeSwitches(bot_state_t *bs) { + int i; + char netname[MAX_NETNAME]; + + ClientName(bs->client, netname, sizeof(netname)); + BotAI_Print(PRT_MESSAGE, "%s at %1.1f switched more than %d AI nodes\n", netname, FloatTime(), MAX_NODESWITCHES); + for (i = 0; i < numnodeswitches; i++) { + BotAI_Print(PRT_MESSAGE, nodeswitch[i]); + } + BotAI_Print(PRT_FATAL, ""); +} + +/* +================== +BotRecordNodeSwitch +================== +*/ +void BotRecordNodeSwitch(bot_state_t *bs, char *node, char *str, char *s) { + char netname[MAX_NETNAME]; + + ClientName(bs->client, netname, sizeof(netname)); + Com_sprintf(nodeswitch[numnodeswitches], 144, "%s at %2.1f entered %s: %s from %s\n", netname, FloatTime(), node, str, s); +#ifdef DEBUG + if (0) { + BotAI_Print(PRT_MESSAGE, nodeswitch[numnodeswitches]); + } +#endif //DEBUG + numnodeswitches++; +} + +/* +================== +BotGetAirGoal +================== +*/ +int BotGetAirGoal(bot_state_t *bs, bot_goal_t *goal) { + bsp_trace_t bsptrace; + vec3_t end, mins = {-15, -15, -2}, maxs = {15, 15, 2}; + int areanum; + + //trace up until we hit solid + VectorCopy(bs->origin, end); + end[2] += 1000; + BotAI_Trace(&bsptrace, bs->origin, mins, maxs, end, bs->entitynum, CONTENTS_SOLID|CONTENTS_PLAYERCLIP); + //trace down until we hit water + VectorCopy(bsptrace.endpos, end); + BotAI_Trace(&bsptrace, end, mins, maxs, bs->origin, bs->entitynum, CONTENTS_WATER|CONTENTS_SLIME|CONTENTS_LAVA); + //if we found the water surface + if (bsptrace.fraction > 0) { + areanum = BotPointAreaNum(bsptrace.endpos); + if (areanum) { + VectorCopy(bsptrace.endpos, goal->origin); + goal->origin[2] -= 2; + goal->areanum = areanum; + goal->mins[0] = -15; + goal->mins[1] = -15; + goal->mins[2] = -1; + goal->maxs[0] = 15; + goal->maxs[1] = 15; + goal->maxs[2] = 1; + goal->flags = GFL_AIR; + goal->number = 0; + goal->iteminfo = 0; + goal->entitynum = 0; + return qtrue; + } + } + return qfalse; +} + +/* +================== +BotGoForAir +================== +*/ +int BotGoForAir(bot_state_t *bs, int tfl, bot_goal_t *ltg, float range) { + bot_goal_t goal; + + //if the bot needs air + if (bs->lastair_time < FloatTime() - 6) { + // +#ifdef DEBUG + //BotAI_Print(PRT_MESSAGE, "going for air\n"); +#endif //DEBUG + //if we can find an air goal + if (BotGetAirGoal(bs, &goal)) { + trap_BotPushGoal(bs->gs, &goal); + return qtrue; + } + else { + //get a nearby goal outside the water + while(trap_BotChooseNBGItem(bs->gs, bs->origin, bs->inventory, tfl, ltg, range)) { + trap_BotGetTopGoal(bs->gs, &goal); + //if the goal is not in water + if (!(trap_AAS_PointContents(goal.origin) & (CONTENTS_WATER|CONTENTS_SLIME|CONTENTS_LAVA))) { + return qtrue; + } + trap_BotPopGoal(bs->gs); + } + trap_BotResetAvoidGoals(bs->gs); + } + } + return qfalse; +} + +/* +================== +BotNearbyGoal +================== +*/ +int BotNearbyGoal(bot_state_t *bs, int tfl, bot_goal_t *ltg, float range) { + int ret; + + //check if the bot should go for air + if (BotGoForAir(bs, tfl, ltg, range)) return qtrue; + //if the bot is carrying the enemy flag + if (BotCTFCarryingFlag(bs)) { + //if the bot is just a few secs away from the base + if (trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, + bs->teamgoal.areanum, TFL_DEFAULT) < 300) { + //make the range really small + range = 50; + } + } + // + ret = trap_BotChooseNBGItem(bs->gs, bs->origin, bs->inventory, tfl, ltg, range); + /* + if (ret) + { + char buf[128]; + //get the goal at the top of the stack + trap_BotGetTopGoal(bs->gs, &goal); + trap_BotGoalName(goal.number, buf, sizeof(buf)); + BotAI_Print(PRT_MESSAGE, "%1.1f: new nearby goal %s\n", FloatTime(), buf); + } + */ + return ret; +} + +/* +================== +BotReachedGoal +================== +*/ +int BotReachedGoal(bot_state_t *bs, bot_goal_t *goal) { + if (goal->flags & GFL_ITEM) { + //if touching the goal + if (trap_BotTouchingGoal(bs->origin, goal)) { + if (!(goal->flags & GFL_DROPPED)) { + trap_BotSetAvoidGoalTime(bs->gs, goal->number, -1); + } + return qtrue; + } + //if the goal isn't there + if (trap_BotItemGoalInVisButNotVisible(bs->entitynum, bs->eye, bs->viewangles, goal)) { + /* + float avoidtime; + int t; + + avoidtime = trap_BotAvoidGoalTime(bs->gs, goal->number); + if (avoidtime > 0) { + t = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, goal->areanum, bs->tfl); + if ((float) t * 0.009 < avoidtime) + return qtrue; + } + */ + return qtrue; + } + //if in the goal area and below or above the goal and not swimming + if (bs->areanum == goal->areanum) { + if (bs->origin[0] > goal->origin[0] + goal->mins[0] && bs->origin[0] < goal->origin[0] + goal->maxs[0]) { + if (bs->origin[1] > goal->origin[1] + goal->mins[1] && bs->origin[1] < goal->origin[1] + goal->maxs[1]) { + if (!trap_AAS_Swimming(bs->origin)) { + return qtrue; + } + } + } + } + } + else if (goal->flags & GFL_AIR) { + //if touching the goal + if (trap_BotTouchingGoal(bs->origin, goal)) return qtrue; + //if the bot got air + if (bs->lastair_time > FloatTime() - 1) return qtrue; + } + else { + //if touching the goal + if (trap_BotTouchingGoal(bs->origin, goal)) return qtrue; + } + return qfalse; +} + +/* +================== +BotGetItemLongTermGoal +================== +*/ +int BotGetItemLongTermGoal(bot_state_t *bs, int tfl, bot_goal_t *goal) { + //if the bot has no goal + if (!trap_BotGetTopGoal(bs->gs, goal)) { + //BotAI_Print(PRT_MESSAGE, "no ltg on stack\n"); + bs->ltg_time = 0; + } + //if the bot touches the current goal + else if (BotReachedGoal(bs, goal)) { + BotChooseWeapon(bs); + bs->ltg_time = 0; + } + //if it is time to find a new long term goal + if (bs->ltg_time < FloatTime()) { + //pop the current goal from the stack + trap_BotPopGoal(bs->gs); + //BotAI_Print(PRT_MESSAGE, "%s: choosing new ltg\n", ClientName(bs->client, netname, sizeof(netname))); + //choose a new goal + //BotAI_Print(PRT_MESSAGE, "%6.1f client %d: BotChooseLTGItem\n", FloatTime(), bs->client); + if (trap_BotChooseLTGItem(bs->gs, bs->origin, bs->inventory, tfl)) { + /* + char buf[128]; + //get the goal at the top of the stack + trap_BotGetTopGoal(bs->gs, goal); + trap_BotGoalName(goal->number, buf, sizeof(buf)); + BotAI_Print(PRT_MESSAGE, "%1.1f: new long term goal %s\n", FloatTime(), buf); + */ + bs->ltg_time = FloatTime() + 20; + } + else {//the bot gets sorta stuck with all the avoid timings, shouldn't happen though + // +#ifdef DEBUG + char netname[128]; + + BotAI_Print(PRT_MESSAGE, "%s: no valid ltg (probably stuck)\n", ClientName(bs->client, netname, sizeof(netname))); +#endif + //trap_BotDumpAvoidGoals(bs->gs); + //reset the avoid goals and the avoid reach + trap_BotResetAvoidGoals(bs->gs); + trap_BotResetAvoidReach(bs->ms); + } + //get the goal at the top of the stack + return trap_BotGetTopGoal(bs->gs, goal); + } + return qtrue; +} + +/* +================== +BotGetLongTermGoal + +we could also create a seperate AI node for every long term goal type +however this saves us a lot of code +================== +*/ +int BotGetLongTermGoal(bot_state_t *bs, int tfl, int retreat, bot_goal_t *goal) { + vec3_t target, dir, dir2; + char netname[MAX_NETNAME]; + char buf[MAX_MESSAGE_SIZE]; + int areanum; + float croucher; + aas_entityinfo_t entinfo, botinfo; + bot_waypoint_t *wp; + + if (bs->ltgtype == LTG_TEAMHELP && !retreat) { + //check for bot typing status message + if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { + BotAI_BotInitialChat(bs, "help_start", EasyClientName(bs->teammate, netname, sizeof(netname)), NULL); + trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); + BotVoiceChatOnly(bs, bs->decisionmaker, VOICECHAT_YES); + trap_EA_Action(bs->client, ACTION_AFFIRMATIVE); + bs->teammessage_time = 0; + } + //if trying to help the team mate for more than a minute + if (bs->teamgoal_time < FloatTime()) + bs->ltgtype = 0; + //if the team mate IS visible for quite some time + if (bs->teammatevisible_time < FloatTime() - 10) bs->ltgtype = 0; + //get entity information of the companion + BotEntityInfo(bs->teammate, &entinfo); + //if the team mate is visible + if (BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->teammate)) { + //if close just stand still there + VectorSubtract(entinfo.origin, bs->origin, dir); + if (VectorLengthSquared(dir) < Square(100)) { + trap_BotResetAvoidReach(bs->ms); + return qfalse; + } + } + else { + //last time the bot was NOT visible + bs->teammatevisible_time = FloatTime(); + } + //if the entity information is valid (entity in PVS) + if (entinfo.valid) { + areanum = BotPointAreaNum(entinfo.origin); + if (areanum && trap_AAS_AreaReachability(areanum)) { + //update team goal + bs->teamgoal.entitynum = bs->teammate; + bs->teamgoal.areanum = areanum; + VectorCopy(entinfo.origin, bs->teamgoal.origin); + VectorSet(bs->teamgoal.mins, -8, -8, -8); + VectorSet(bs->teamgoal.maxs, 8, 8, 8); + } + } + memcpy(goal, &bs->teamgoal, sizeof(bot_goal_t)); + return qtrue; + } + //if the bot accompanies someone + if (bs->ltgtype == LTG_TEAMACCOMPANY && !retreat) { + //check for bot typing status message + if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { + BotAI_BotInitialChat(bs, "accompany_start", EasyClientName(bs->teammate, netname, sizeof(netname)), NULL); + trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); + BotVoiceChatOnly(bs, bs->decisionmaker, VOICECHAT_YES); + trap_EA_Action(bs->client, ACTION_AFFIRMATIVE); + bs->teammessage_time = 0; + } + //if accompanying the companion for 3 minutes + if (bs->teamgoal_time < FloatTime()) { + BotAI_BotInitialChat(bs, "accompany_stop", EasyClientName(bs->teammate, netname, sizeof(netname)), NULL); + trap_BotEnterChat(bs->cs, bs->teammate, CHAT_TELL); + bs->ltgtype = 0; + } + //get entity information of the companion + BotEntityInfo(bs->teammate, &entinfo); + //if the companion is visible + if (BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->teammate)) { + //update visible time + bs->teammatevisible_time = FloatTime(); + VectorSubtract(entinfo.origin, bs->origin, dir); + if (VectorLengthSquared(dir) < Square(bs->formation_dist)) { + // + // if the client being followed bumps into this bot then + // the bot should back up + BotEntityInfo(bs->entitynum, &botinfo); + // if the followed client is not standing ontop of the bot + if (botinfo.origin[2] + botinfo.maxs[2] > entinfo.origin[2] + entinfo.mins[2]) { + // if the bounding boxes touch each other + if (botinfo.origin[0] + botinfo.maxs[0] > entinfo.origin[0] + entinfo.mins[0] - 4&& + botinfo.origin[0] + botinfo.mins[0] < entinfo.origin[0] + entinfo.maxs[0] + 4) { + if (botinfo.origin[1] + botinfo.maxs[1] > entinfo.origin[1] + entinfo.mins[1] - 4 && + botinfo.origin[1] + botinfo.mins[1] < entinfo.origin[1] + entinfo.maxs[1] + 4) { + if (botinfo.origin[2] + botinfo.maxs[2] > entinfo.origin[2] + entinfo.mins[2] - 4 && + botinfo.origin[2] + botinfo.mins[2] < entinfo.origin[2] + entinfo.maxs[2] + 4) { + // if the followed client looks in the direction of this bot + AngleVectors(entinfo.angles, dir, NULL, NULL); + dir[2] = 0; + VectorNormalize(dir); + //VectorSubtract(entinfo.origin, entinfo.lastvisorigin, dir); + VectorSubtract(bs->origin, entinfo.origin, dir2); + VectorNormalize(dir2); + if (DotProduct(dir, dir2) > 0.7) { + // back up + BotSetupForMovement(bs); + trap_BotMoveInDirection(bs->ms, dir2, 400, MOVE_WALK); + } + } + } + } + } + //check if the bot wants to crouch + //don't crouch if crouched less than 5 seconds ago + if (bs->attackcrouch_time < FloatTime() - 5) { + croucher = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CROUCHER, 0, 1); + if (random() < bs->thinktime * croucher) { + bs->attackcrouch_time = FloatTime() + 5 + croucher * 15; + } + } + //don't crouch when swimming + if (trap_AAS_Swimming(bs->origin)) bs->attackcrouch_time = FloatTime() - 1; + //if not arrived yet or arived some time ago + if (bs->arrive_time < FloatTime() - 2) { + //if not arrived yet + if (!bs->arrive_time) { + trap_EA_Gesture(bs->client); + BotAI_BotInitialChat(bs, "accompany_arrive", EasyClientName(bs->teammate, netname, sizeof(netname)), NULL); + trap_BotEnterChat(bs->cs, bs->teammate, CHAT_TELL); + bs->arrive_time = FloatTime(); + } + //if the bot wants to crouch + else if (bs->attackcrouch_time > FloatTime()) { + trap_EA_Crouch(bs->client); + } + //else do some model taunts + else if (random() < bs->thinktime * 0.05) { + //do a gesture :) + trap_EA_Gesture(bs->client); + } + } + //if just arrived look at the companion + if (bs->arrive_time > FloatTime() - 2) { + VectorSubtract(entinfo.origin, bs->origin, dir); + vectoangles(dir, bs->ideal_viewangles); + bs->ideal_viewangles[2] *= 0.5; + } + //else look strategically around for enemies + else if (random() < bs->thinktime * 0.8) { + BotRoamGoal(bs, target); + VectorSubtract(target, bs->origin, dir); + vectoangles(dir, bs->ideal_viewangles); + bs->ideal_viewangles[2] *= 0.5; + } + //check if the bot wants to go for air + if (BotGoForAir(bs, bs->tfl, &bs->teamgoal, 400)) { + trap_BotResetLastAvoidReach(bs->ms); + //get the goal at the top of the stack + //trap_BotGetTopGoal(bs->gs, &tmpgoal); + //trap_BotGoalName(tmpgoal.number, buf, 144); + //BotAI_Print(PRT_MESSAGE, "new nearby goal %s\n", buf); + //time the bot gets to pick up the nearby goal item + bs->nbg_time = FloatTime() + 8; + AIEnter_Seek_NBG(bs, "BotLongTermGoal: go for air"); + return qfalse; + } + // + trap_BotResetAvoidReach(bs->ms); + return qfalse; + } + } + //if the entity information is valid (entity in PVS) + if (entinfo.valid) { + areanum = BotPointAreaNum(entinfo.origin); + if (areanum && trap_AAS_AreaReachability(areanum)) { + //update team goal + bs->teamgoal.entitynum = bs->teammate; + bs->teamgoal.areanum = areanum; + VectorCopy(entinfo.origin, bs->teamgoal.origin); + VectorSet(bs->teamgoal.mins, -8, -8, -8); + VectorSet(bs->teamgoal.maxs, 8, 8, 8); + } + } + //the goal the bot should go for + memcpy(goal, &bs->teamgoal, sizeof(bot_goal_t)); + //if the companion is NOT visible for too long + if (bs->teammatevisible_time < FloatTime() - 60) { + BotAI_BotInitialChat(bs, "accompany_cannotfind", EasyClientName(bs->teammate, netname, sizeof(netname)), NULL); + trap_BotEnterChat(bs->cs, bs->teammate, CHAT_TELL); + bs->ltgtype = 0; + // just to make sure the bot won't spam this message + bs->teammatevisible_time = FloatTime(); + } + return qtrue; + } + // + if (bs->ltgtype == LTG_DEFENDKEYAREA) { + if (trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, + bs->teamgoal.areanum, TFL_DEFAULT) > bs->defendaway_range) { + bs->defendaway_time = 0; + } + } + //if defending a key area + if (bs->ltgtype == LTG_DEFENDKEYAREA && !retreat && + bs->defendaway_time < FloatTime()) { + //check for bot typing status message + if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { + trap_BotGoalName(bs->teamgoal.number, buf, sizeof(buf)); + BotAI_BotInitialChat(bs, "defend_start", buf, NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_TEAM); + BotVoiceChatOnly(bs, -1, VOICECHAT_ONDEFENSE); + bs->teammessage_time = 0; + } + //set the bot goal + memcpy(goal, &bs->teamgoal, sizeof(bot_goal_t)); + //stop after 2 minutes + if (bs->teamgoal_time < FloatTime()) { + trap_BotGoalName(bs->teamgoal.number, buf, sizeof(buf)); + BotAI_BotInitialChat(bs, "defend_stop", buf, NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_TEAM); + bs->ltgtype = 0; + } + //if very close... go away for some time + VectorSubtract(goal->origin, bs->origin, dir); + if (VectorLengthSquared(dir) < Square(70)) { + trap_BotResetAvoidReach(bs->ms); + bs->defendaway_time = FloatTime() + 3 + 3 * random(); + if (BotHasPersistantPowerupAndWeapon(bs)) { + bs->defendaway_range = 100; + } + else { + bs->defendaway_range = 350; + } + } + return qtrue; + } + //going to kill someone + if (bs->ltgtype == LTG_KILL && !retreat) { + //check for bot typing status message + if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { + EasyClientName(bs->teamgoal.entitynum, buf, sizeof(buf)); + BotAI_BotInitialChat(bs, "kill_start", buf, NULL); + trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); + bs->teammessage_time = 0; + } + // + if (bs->lastkilledplayer == bs->teamgoal.entitynum) { + EasyClientName(bs->teamgoal.entitynum, buf, sizeof(buf)); + BotAI_BotInitialChat(bs, "kill_done", buf, NULL); + trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); + bs->lastkilledplayer = -1; + bs->ltgtype = 0; + } + // + if (bs->teamgoal_time < FloatTime()) { + bs->ltgtype = 0; + } + //just roam around + return BotGetItemLongTermGoal(bs, tfl, goal); + } + //get an item + if (bs->ltgtype == LTG_GETITEM && !retreat) { + //check for bot typing status message + if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { + trap_BotGoalName(bs->teamgoal.number, buf, sizeof(buf)); + BotAI_BotInitialChat(bs, "getitem_start", buf, NULL); + trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); + BotVoiceChatOnly(bs, bs->decisionmaker, VOICECHAT_YES); + trap_EA_Action(bs->client, ACTION_AFFIRMATIVE); + bs->teammessage_time = 0; + } + //set the bot goal + memcpy(goal, &bs->teamgoal, sizeof(bot_goal_t)); + //stop after some time + if (bs->teamgoal_time < FloatTime()) { + bs->ltgtype = 0; + } + // + if (trap_BotItemGoalInVisButNotVisible(bs->entitynum, bs->eye, bs->viewangles, goal)) { + trap_BotGoalName(bs->teamgoal.number, buf, sizeof(buf)); + BotAI_BotInitialChat(bs, "getitem_notthere", buf, NULL); + trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); + bs->ltgtype = 0; + } + else if (BotReachedGoal(bs, goal)) { + trap_BotGoalName(bs->teamgoal.number, buf, sizeof(buf)); + BotAI_BotInitialChat(bs, "getitem_gotit", buf, NULL); + trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); + bs->ltgtype = 0; + } + return qtrue; + } + //if camping somewhere + if ((bs->ltgtype == LTG_CAMP || bs->ltgtype == LTG_CAMPORDER) && !retreat) { + //check for bot typing status message + if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { + if (bs->ltgtype == LTG_CAMPORDER) { + BotAI_BotInitialChat(bs, "camp_start", EasyClientName(bs->teammate, netname, sizeof(netname)), NULL); + trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); + BotVoiceChatOnly(bs, bs->decisionmaker, VOICECHAT_YES); + trap_EA_Action(bs->client, ACTION_AFFIRMATIVE); + } + bs->teammessage_time = 0; + } + //set the bot goal + memcpy(goal, &bs->teamgoal, sizeof(bot_goal_t)); + // + if (bs->teamgoal_time < FloatTime()) { + if (bs->ltgtype == LTG_CAMPORDER) { + BotAI_BotInitialChat(bs, "camp_stop", NULL); + trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); + } + bs->ltgtype = 0; + } + //if really near the camp spot + VectorSubtract(goal->origin, bs->origin, dir); + if (VectorLengthSquared(dir) < Square(60)) + { + //if not arrived yet + if (!bs->arrive_time) { + if (bs->ltgtype == LTG_CAMPORDER) { + BotAI_BotInitialChat(bs, "camp_arrive", EasyClientName(bs->teammate, netname, sizeof(netname)), NULL); + trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); + BotVoiceChatOnly(bs, bs->decisionmaker, VOICECHAT_INPOSITION); + } + bs->arrive_time = FloatTime(); + } + //look strategically around for enemies + if (random() < bs->thinktime * 0.8) { + BotRoamGoal(bs, target); + VectorSubtract(target, bs->origin, dir); + vectoangles(dir, bs->ideal_viewangles); + bs->ideal_viewangles[2] *= 0.5; + } + //check if the bot wants to crouch + //don't crouch if crouched less than 5 seconds ago + if (bs->attackcrouch_time < FloatTime() - 5) { + croucher = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CROUCHER, 0, 1); + if (random() < bs->thinktime * croucher) { + bs->attackcrouch_time = FloatTime() + 5 + croucher * 15; + } + } + //if the bot wants to crouch + if (bs->attackcrouch_time > FloatTime()) { + trap_EA_Crouch(bs->client); + } + //don't crouch when swimming + if (trap_AAS_Swimming(bs->origin)) bs->attackcrouch_time = FloatTime() - 1; + //make sure the bot is not gonna drown + if (trap_PointContents(bs->eye,bs->entitynum) & (CONTENTS_WATER|CONTENTS_SLIME|CONTENTS_LAVA)) { + if (bs->ltgtype == LTG_CAMPORDER) { + BotAI_BotInitialChat(bs, "camp_stop", NULL); + trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); + // + if (bs->lastgoal_ltgtype == LTG_CAMPORDER) { + bs->lastgoal_ltgtype = 0; + } + } + bs->ltgtype = 0; + } + // + if (bs->camp_range > 0) { + //FIXME: move around a bit + } + // + trap_BotResetAvoidReach(bs->ms); + return qfalse; + } + return qtrue; + } + //patrolling along several waypoints + if (bs->ltgtype == LTG_PATROL && !retreat) { + //check for bot typing status message + if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { + strcpy(buf, ""); + for (wp = bs->patrolpoints; wp; wp = wp->next) { + strcat(buf, wp->name); + if (wp->next) strcat(buf, " to "); + } + BotAI_BotInitialChat(bs, "patrol_start", buf, NULL); + trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); + BotVoiceChatOnly(bs, bs->decisionmaker, VOICECHAT_YES); + trap_EA_Action(bs->client, ACTION_AFFIRMATIVE); + bs->teammessage_time = 0; + } + // + if (!bs->curpatrolpoint) { + bs->ltgtype = 0; + return qfalse; + } + //if the bot touches the current goal + if (trap_BotTouchingGoal(bs->origin, &bs->curpatrolpoint->goal)) { + if (bs->patrolflags & PATROL_BACK) { + if (bs->curpatrolpoint->prev) { + bs->curpatrolpoint = bs->curpatrolpoint->prev; + } + else { + bs->curpatrolpoint = bs->curpatrolpoint->next; + bs->patrolflags &= ~PATROL_BACK; + } + } + else { + if (bs->curpatrolpoint->next) { + bs->curpatrolpoint = bs->curpatrolpoint->next; + } + else { + bs->curpatrolpoint = bs->curpatrolpoint->prev; + bs->patrolflags |= PATROL_BACK; + } + } + } + //stop after 5 minutes + if (bs->teamgoal_time < FloatTime()) { + BotAI_BotInitialChat(bs, "patrol_stop", NULL); + trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); + bs->ltgtype = 0; + } + if (!bs->curpatrolpoint) { + bs->ltgtype = 0; + return qfalse; + } + memcpy(goal, &bs->curpatrolpoint->goal, sizeof(bot_goal_t)); + return qtrue; + } +#ifdef CTF + if (gametype == GT_CTF) { + //if going for enemy flag + if (bs->ltgtype == LTG_GETFLAG) { + //check for bot typing status message + if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { + BotAI_BotInitialChat(bs, "captureflag_start", NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_TEAM); + BotVoiceChatOnly(bs, -1, VOICECHAT_ONGETFLAG); + bs->teammessage_time = 0; + } + // + switch(BotTeam(bs)) { + case TEAM_RED: memcpy(goal, &ctf_blueflag, sizeof(bot_goal_t)); break; + case TEAM_BLUE: memcpy(goal, &ctf_redflag, sizeof(bot_goal_t)); break; + default: bs->ltgtype = 0; return qfalse; + } + //if touching the flag + if (trap_BotTouchingGoal(bs->origin, goal)) { + // make sure the bot knows the flag isn't there anymore + switch(BotTeam(bs)) { + case TEAM_RED: bs->blueflagstatus = 1; break; + case TEAM_BLUE: bs->redflagstatus = 1; break; + } + bs->ltgtype = 0; + } + //stop after 3 minutes + if (bs->teamgoal_time < FloatTime()) { + bs->ltgtype = 0; + } + BotAlternateRoute(bs, goal); + return qtrue; + } + //if rushing to the base + if (bs->ltgtype == LTG_RUSHBASE && bs->rushbaseaway_time < FloatTime()) { + switch(BotTeam(bs)) { + case TEAM_RED: memcpy(goal, &ctf_redflag, sizeof(bot_goal_t)); break; + case TEAM_BLUE: memcpy(goal, &ctf_blueflag, sizeof(bot_goal_t)); break; + default: bs->ltgtype = 0; return qfalse; + } + //if not carrying the flag anymore + if (!BotCTFCarryingFlag(bs)) bs->ltgtype = 0; + //quit rushing after 2 minutes + if (bs->teamgoal_time < FloatTime()) bs->ltgtype = 0; + //if touching the base flag the bot should loose the enemy flag + if (trap_BotTouchingGoal(bs->origin, goal)) { + //if the bot is still carrying the enemy flag then the + //base flag is gone, now just walk near the base a bit + if (BotCTFCarryingFlag(bs)) { + trap_BotResetAvoidReach(bs->ms); + bs->rushbaseaway_time = FloatTime() + 5 + 10 * random(); + //FIXME: add chat to tell the others to get back the flag + } + else { + bs->ltgtype = 0; + } + } + BotAlternateRoute(bs, goal); + return qtrue; + } + //returning flag + if (bs->ltgtype == LTG_RETURNFLAG) { + //check for bot typing status message + if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { + BotAI_BotInitialChat(bs, "returnflag_start", NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_TEAM); + BotVoiceChatOnly(bs, -1, VOICECHAT_ONRETURNFLAG); + bs->teammessage_time = 0; + } + // + switch(BotTeam(bs)) { + case TEAM_RED: memcpy(goal, &ctf_blueflag, sizeof(bot_goal_t)); break; + case TEAM_BLUE: memcpy(goal, &ctf_redflag, sizeof(bot_goal_t)); break; + default: bs->ltgtype = 0; return qfalse; + } + //if touching the flag + if (trap_BotTouchingGoal(bs->origin, goal)) bs->ltgtype = 0; + //stop after 3 minutes + if (bs->teamgoal_time < FloatTime()) { + bs->ltgtype = 0; + } + BotAlternateRoute(bs, goal); + return qtrue; + } + } +#endif //CTF +#ifdef MISSIONPACK + else if (gametype == GT_1FCTF) { + if (bs->ltgtype == LTG_GETFLAG) { + //check for bot typing status message + if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { + BotAI_BotInitialChat(bs, "captureflag_start", NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_TEAM); + BotVoiceChatOnly(bs, -1, VOICECHAT_ONGETFLAG); + bs->teammessage_time = 0; + } + memcpy(goal, &ctf_neutralflag, sizeof(bot_goal_t)); + //if touching the flag + if (trap_BotTouchingGoal(bs->origin, goal)) { + bs->ltgtype = 0; + } + //stop after 3 minutes + if (bs->teamgoal_time < FloatTime()) { + bs->ltgtype = 0; + } + return qtrue; + } + //if rushing to the base + if (bs->ltgtype == LTG_RUSHBASE) { + switch(BotTeam(bs)) { + case TEAM_RED: memcpy(goal, &ctf_blueflag, sizeof(bot_goal_t)); break; + case TEAM_BLUE: memcpy(goal, &ctf_redflag, sizeof(bot_goal_t)); break; + default: bs->ltgtype = 0; return qfalse; + } + //if not carrying the flag anymore + if (!Bot1FCTFCarryingFlag(bs)) { + bs->ltgtype = 0; + } + //quit rushing after 2 minutes + if (bs->teamgoal_time < FloatTime()) { + bs->ltgtype = 0; + } + //if touching the base flag the bot should loose the enemy flag + if (trap_BotTouchingGoal(bs->origin, goal)) { + bs->ltgtype = 0; + } + BotAlternateRoute(bs, goal); + return qtrue; + } + //attack the enemy base + if (bs->ltgtype == LTG_ATTACKENEMYBASE && + bs->attackaway_time < FloatTime()) { + //check for bot typing status message + if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { + BotAI_BotInitialChat(bs, "attackenemybase_start", NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_TEAM); + BotVoiceChatOnly(bs, -1, VOICECHAT_ONOFFENSE); + bs->teammessage_time = 0; + } + switch(BotTeam(bs)) { + case TEAM_RED: memcpy(goal, &ctf_blueflag, sizeof(bot_goal_t)); break; + case TEAM_BLUE: memcpy(goal, &ctf_redflag, sizeof(bot_goal_t)); break; + default: bs->ltgtype = 0; return qfalse; + } + //quit rushing after 2 minutes + if (bs->teamgoal_time < FloatTime()) { + bs->ltgtype = 0; + } + //if touching the base flag the bot should loose the enemy flag + if (trap_BotTouchingGoal(bs->origin, goal)) { + bs->attackaway_time = FloatTime() + 2 + 5 * random(); + } + return qtrue; + } + //returning flag + if (bs->ltgtype == LTG_RETURNFLAG) { + //check for bot typing status message + if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { + BotAI_BotInitialChat(bs, "returnflag_start", NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_TEAM); + BotVoiceChatOnly(bs, -1, VOICECHAT_ONRETURNFLAG); + bs->teammessage_time = 0; + } + // + if (bs->teamgoal_time < FloatTime()) { + bs->ltgtype = 0; + } + //just roam around + return BotGetItemLongTermGoal(bs, tfl, goal); + } + } + else if (gametype == GT_OBELISK) { + if (bs->ltgtype == LTG_ATTACKENEMYBASE && + bs->attackaway_time < FloatTime()) { + + //check for bot typing status message + if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { + BotAI_BotInitialChat(bs, "attackenemybase_start", NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_TEAM); + BotVoiceChatOnly(bs, -1, VOICECHAT_ONOFFENSE); + bs->teammessage_time = 0; + } + switch(BotTeam(bs)) { + case TEAM_RED: memcpy(goal, &blueobelisk, sizeof(bot_goal_t)); break; + case TEAM_BLUE: memcpy(goal, &redobelisk, sizeof(bot_goal_t)); break; + default: bs->ltgtype = 0; return qfalse; + } + //if the bot no longer wants to attack the obelisk + if (BotFeelingBad(bs) > 50) { + return BotGetItemLongTermGoal(bs, tfl, goal); + } + //if touching the obelisk + if (trap_BotTouchingGoal(bs->origin, goal)) { + bs->attackaway_time = FloatTime() + 3 + 5 * random(); + } + // or very close to the obelisk + VectorSubtract(bs->origin, goal->origin, dir); + if (VectorLengthSquared(dir) < Square(60)) { + bs->attackaway_time = FloatTime() + 3 + 5 * random(); + } + //quit rushing after 2 minutes + if (bs->teamgoal_time < FloatTime()) { + bs->ltgtype = 0; + } + BotAlternateRoute(bs, goal); + //just move towards the obelisk + return qtrue; + } + } + else if (gametype == GT_HARVESTER) { + //if rushing to the base + if (bs->ltgtype == LTG_RUSHBASE) { + switch(BotTeam(bs)) { + case TEAM_RED: memcpy(goal, &blueobelisk, sizeof(bot_goal_t)); break; + case TEAM_BLUE: memcpy(goal, &redobelisk, sizeof(bot_goal_t)); break; + default: BotGoHarvest(bs); return qfalse; + } + //if not carrying any cubes + if (!BotHarvesterCarryingCubes(bs)) { + BotGoHarvest(bs); + return qfalse; + } + //quit rushing after 2 minutes + if (bs->teamgoal_time < FloatTime()) { + BotGoHarvest(bs); + return qfalse; + } + //if touching the base flag the bot should loose the enemy flag + if (trap_BotTouchingGoal(bs->origin, goal)) { + BotGoHarvest(bs); + return qfalse; + } + BotAlternateRoute(bs, goal); + return qtrue; + } + //attack the enemy base + if (bs->ltgtype == LTG_ATTACKENEMYBASE && + bs->attackaway_time < FloatTime()) { + //check for bot typing status message + if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { + BotAI_BotInitialChat(bs, "attackenemybase_start", NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_TEAM); + BotVoiceChatOnly(bs, -1, VOICECHAT_ONOFFENSE); + bs->teammessage_time = 0; + } + switch(BotTeam(bs)) { + case TEAM_RED: memcpy(goal, &blueobelisk, sizeof(bot_goal_t)); break; + case TEAM_BLUE: memcpy(goal, &redobelisk, sizeof(bot_goal_t)); break; + default: bs->ltgtype = 0; return qfalse; + } + //quit rushing after 2 minutes + if (bs->teamgoal_time < FloatTime()) { + bs->ltgtype = 0; + } + //if touching the base flag the bot should loose the enemy flag + if (trap_BotTouchingGoal(bs->origin, goal)) { + bs->attackaway_time = FloatTime() + 2 + 5 * random(); + } + return qtrue; + } + //harvest cubes + if (bs->ltgtype == LTG_HARVEST && + bs->harvestaway_time < FloatTime()) { + //check for bot typing status message + if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { + BotAI_BotInitialChat(bs, "harvest_start", NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_TEAM); + BotVoiceChatOnly(bs, -1, VOICECHAT_ONOFFENSE); + bs->teammessage_time = 0; + } + memcpy(goal, &neutralobelisk, sizeof(bot_goal_t)); + // + if (bs->teamgoal_time < FloatTime()) { + bs->ltgtype = 0; + } + // + if (trap_BotTouchingGoal(bs->origin, goal)) { + bs->harvestaway_time = FloatTime() + 4 + 3 * random(); + } + return qtrue; + } + } +#endif + //normal goal stuff + return BotGetItemLongTermGoal(bs, tfl, goal); +} + +/* +================== +BotLongTermGoal +================== +*/ +int BotLongTermGoal(bot_state_t *bs, int tfl, int retreat, bot_goal_t *goal) { + aas_entityinfo_t entinfo; + char teammate[MAX_MESSAGE_SIZE]; + float squaredist; + int areanum; + vec3_t dir; + + //FIXME: also have air long term goals? + // + //if the bot is leading someone and not retreating + if (bs->lead_time > 0 && !retreat) { + if (bs->lead_time < FloatTime()) { + BotAI_BotInitialChat(bs, "lead_stop", EasyClientName(bs->lead_teammate, teammate, sizeof(teammate)), NULL); + trap_BotEnterChat(bs->cs, bs->teammate, CHAT_TELL); + bs->lead_time = 0; + return BotGetLongTermGoal(bs, tfl, retreat, goal); + } + // + if (bs->leadmessage_time < 0 && -bs->leadmessage_time < FloatTime()) { + BotAI_BotInitialChat(bs, "followme", EasyClientName(bs->lead_teammate, teammate, sizeof(teammate)), NULL); + trap_BotEnterChat(bs->cs, bs->teammate, CHAT_TELL); + bs->leadmessage_time = FloatTime(); + } + //get entity information of the companion + BotEntityInfo(bs->lead_teammate, &entinfo); + // + if (entinfo.valid) { + areanum = BotPointAreaNum(entinfo.origin); + if (areanum && trap_AAS_AreaReachability(areanum)) { + //update team goal + bs->lead_teamgoal.entitynum = bs->lead_teammate; + bs->lead_teamgoal.areanum = areanum; + VectorCopy(entinfo.origin, bs->lead_teamgoal.origin); + VectorSet(bs->lead_teamgoal.mins, -8, -8, -8); + VectorSet(bs->lead_teamgoal.maxs, 8, 8, 8); + } + } + //if the team mate is visible + if (BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->lead_teammate)) { + bs->leadvisible_time = FloatTime(); + } + //if the team mate is not visible for 1 seconds + if (bs->leadvisible_time < FloatTime() - 1) { + bs->leadbackup_time = FloatTime() + 2; + } + //distance towards the team mate + VectorSubtract(bs->origin, bs->lead_teamgoal.origin, dir); + squaredist = VectorLengthSquared(dir); + //if backing up towards the team mate + if (bs->leadbackup_time > FloatTime()) { + if (bs->leadmessage_time < FloatTime() - 20) { + BotAI_BotInitialChat(bs, "followme", EasyClientName(bs->lead_teammate, teammate, sizeof(teammate)), NULL); + trap_BotEnterChat(bs->cs, bs->teammate, CHAT_TELL); + bs->leadmessage_time = FloatTime(); + } + //if very close to the team mate + if (squaredist < Square(100)) { + bs->leadbackup_time = 0; + } + //the bot should go back to the team mate + memcpy(goal, &bs->lead_teamgoal, sizeof(bot_goal_t)); + return qtrue; + } + else { + //if quite distant from the team mate + if (squaredist > Square(500)) { + if (bs->leadmessage_time < FloatTime() - 20) { + BotAI_BotInitialChat(bs, "followme", EasyClientName(bs->lead_teammate, teammate, sizeof(teammate)), NULL); + trap_BotEnterChat(bs->cs, bs->teammate, CHAT_TELL); + bs->leadmessage_time = FloatTime(); + } + //look at the team mate + VectorSubtract(entinfo.origin, bs->origin, dir); + vectoangles(dir, bs->ideal_viewangles); + bs->ideal_viewangles[2] *= 0.5; + //just wait for the team mate + return qfalse; + } + } + } + return BotGetLongTermGoal(bs, tfl, retreat, goal); +} + +/* +================== +AIEnter_Intermission +================== +*/ +void AIEnter_Intermission(bot_state_t *bs, char *s) { + BotRecordNodeSwitch(bs, "intermission", "", s); + //reset the bot state + BotResetState(bs); + //check for end level chat + if (BotChat_EndLevel(bs)) { + trap_BotEnterChat(bs->cs, 0, bs->chatto); + } + bs->ainode = AINode_Intermission; +} + +/* +================== +AINode_Intermission +================== +*/ +int AINode_Intermission(bot_state_t *bs) { + //if the intermission ended + if (!BotIntermission(bs)) { + if (BotChat_StartLevel(bs)) { + bs->stand_time = FloatTime() + BotChatTime(bs); + } + else { + bs->stand_time = FloatTime() + 2; + } + AIEnter_Stand(bs, "intermission: chat"); + } + return qtrue; +} + +/* +================== +AIEnter_Observer +================== +*/ +void AIEnter_Observer(bot_state_t *bs, char *s) { + BotRecordNodeSwitch(bs, "observer", "", s); + //reset the bot state + BotResetState(bs); + bs->ainode = AINode_Observer; +} + +/* +================== +AINode_Observer +================== +*/ +int AINode_Observer(bot_state_t *bs) { + //if the bot left observer mode + if (!BotIsObserver(bs)) { + AIEnter_Stand(bs, "observer: left observer"); + } + return qtrue; +} + +/* +================== +AIEnter_Stand +================== +*/ +void AIEnter_Stand(bot_state_t *bs, char *s) { + BotRecordNodeSwitch(bs, "stand", "", s); + bs->standfindenemy_time = FloatTime() + 1; + bs->ainode = AINode_Stand; +} + +/* +================== +AINode_Stand +================== +*/ +int AINode_Stand(bot_state_t *bs) { + + //if the bot's health decreased + if (bs->lastframe_health > bs->inventory[INVENTORY_HEALTH]) { + if (BotChat_HitTalking(bs)) { + bs->standfindenemy_time = FloatTime() + BotChatTime(bs) + 0.1; + bs->stand_time = FloatTime() + BotChatTime(bs) + 0.1; + } + } + if (bs->standfindenemy_time < FloatTime()) { + if (BotFindEnemy(bs, -1)) { + AIEnter_Battle_Fight(bs, "stand: found enemy"); + return qfalse; + } + bs->standfindenemy_time = FloatTime() + 1; + } + // put up chat icon + trap_EA_Talk(bs->client); + // when done standing + if (bs->stand_time < FloatTime()) { + trap_BotEnterChat(bs->cs, 0, bs->chatto); + AIEnter_Seek_LTG(bs, "stand: time out"); + return qfalse; + } + // + return qtrue; +} + +/* +================== +AIEnter_Respawn +================== +*/ +void AIEnter_Respawn(bot_state_t *bs, char *s) { + BotRecordNodeSwitch(bs, "respawn", "", s); + //reset some states + trap_BotResetMoveState(bs->ms); + trap_BotResetGoalState(bs->gs); + trap_BotResetAvoidGoals(bs->gs); + trap_BotResetAvoidReach(bs->ms); + //if the bot wants to chat + if (BotChat_Death(bs)) { + bs->respawn_time = FloatTime() + BotChatTime(bs); + bs->respawnchat_time = FloatTime(); + } + else { + bs->respawn_time = FloatTime() + 1 + random(); + bs->respawnchat_time = 0; + } + //set respawn state + bs->respawn_wait = qfalse; + bs->ainode = AINode_Respawn; +} + +/* +================== +AINode_Respawn +================== +*/ +int AINode_Respawn(bot_state_t *bs) { + // if waiting for the actual respawn + if (bs->respawn_wait) { + if (!BotIsDead(bs)) { + AIEnter_Seek_LTG(bs, "respawn: respawned"); + } + else { + trap_EA_Respawn(bs->client); + } + } + else if (bs->respawn_time < FloatTime()) { + // wait until respawned + bs->respawn_wait = qtrue; + // elementary action respawn + trap_EA_Respawn(bs->client); + // + if (bs->respawnchat_time) { + trap_BotEnterChat(bs->cs, 0, bs->chatto); + bs->enemy = -1; + } + } + if (bs->respawnchat_time && bs->respawnchat_time < FloatTime() - 0.5) { + trap_EA_Talk(bs->client); + } + // + return qtrue; +} + +/* +================== +BotSelectActivateWeapon +================== +*/ +int BotSelectActivateWeapon(bot_state_t *bs) { + // + if (bs->inventory[INVENTORY_MACHINEGUN] > 0 && bs->inventory[INVENTORY_BULLETS] > 0) + return WEAPONINDEX_MACHINEGUN; + else if (bs->inventory[INVENTORY_SHOTGUN] > 0 && bs->inventory[INVENTORY_SHELLS] > 0) + return WEAPONINDEX_SHOTGUN; + else if (bs->inventory[INVENTORY_PLASMAGUN] > 0 && bs->inventory[INVENTORY_CELLS] > 0) + return WEAPONINDEX_PLASMAGUN; + else if (bs->inventory[INVENTORY_LIGHTNING] > 0 && bs->inventory[INVENTORY_LIGHTNINGAMMO] > 0) + return WEAPONINDEX_LIGHTNING; +#ifdef MISSIONPACK + else if (bs->inventory[INVENTORY_CHAINGUN] > 0 && bs->inventory[INVENTORY_BELT] > 0) + return WEAPONINDEX_CHAINGUN; + else if (bs->inventory[INVENTORY_NAILGUN] > 0 && bs->inventory[INVENTORY_NAILS] > 0) + return WEAPONINDEX_NAILGUN; +#endif + else if (bs->inventory[INVENTORY_RAILGUN] > 0 && bs->inventory[INVENTORY_SLUGS] > 0) + return WEAPONINDEX_RAILGUN; + else if (bs->inventory[INVENTORY_ROCKETLAUNCHER] > 0 && bs->inventory[INVENTORY_ROCKETS] > 0) + return WEAPONINDEX_ROCKET_LAUNCHER; + else if (bs->inventory[INVENTORY_BFG10K] > 0 && bs->inventory[INVENTORY_BFGAMMO] > 0) + return WEAPONINDEX_BFG; + else { + return -1; + } +} + +/* +================== +BotClearPath + + try to deactivate obstacles like proximity mines on the bot's path +================== +*/ +void BotClearPath(bot_state_t *bs, bot_moveresult_t *moveresult) { + int i, bestmine; + float dist, bestdist; + vec3_t target, dir; + bsp_trace_t bsptrace; + entityState_t state; + + // if there is a dead body wearing kamikze nearby + if (bs->kamikazebody) { + // if the bot's view angles and weapon are not used for movement + if ( !(moveresult->flags & (MOVERESULT_MOVEMENTVIEW | MOVERESULT_MOVEMENTWEAPON)) ) { + // + BotAI_GetEntityState(bs->kamikazebody, &state); + VectorCopy(state.pos.trBase, target); + target[2] += 8; + VectorSubtract(target, bs->eye, dir); + vectoangles(dir, moveresult->ideal_viewangles); + // + moveresult->weapon = BotSelectActivateWeapon(bs); + if (moveresult->weapon == -1) { + // FIXME: run away! + moveresult->weapon = 0; + } + if (moveresult->weapon) { + // + moveresult->flags |= MOVERESULT_MOVEMENTWEAPON | MOVERESULT_MOVEMENTVIEW; + // if holding the right weapon + if (bs->cur_ps.weapon == moveresult->weapon) { + // if the bot is pretty close with it's aim + if (InFieldOfVision(bs->viewangles, 20, moveresult->ideal_viewangles)) { + // + BotAI_Trace(&bsptrace, bs->eye, NULL, NULL, target, bs->entitynum, MASK_SHOT); + // if the mine is visible from the current position + if (bsptrace.fraction >= 1.0 || bsptrace.ent == state.number) { + // shoot at the mine + trap_EA_Attack(bs->client); + } + } + } + } + } + } + if (moveresult->flags & MOVERESULT_BLOCKEDBYAVOIDSPOT) { + bs->blockedbyavoidspot_time = FloatTime() + 5; + } + // if blocked by an avoid spot and the view angles and weapon are used for movement + if (bs->blockedbyavoidspot_time > FloatTime() && + !(moveresult->flags & (MOVERESULT_MOVEMENTVIEW | MOVERESULT_MOVEMENTWEAPON)) ) { + bestdist = 300; + bestmine = -1; + for (i = 0; i < bs->numproxmines; i++) { + BotAI_GetEntityState(bs->proxmines[i], &state); + VectorSubtract(state.pos.trBase, bs->origin, dir); + dist = VectorLength(dir); + if (dist < bestdist) { + bestdist = dist; + bestmine = i; + } + } + if (bestmine != -1) { + // + // state->generic1 == TEAM_RED || state->generic1 == TEAM_BLUE + // + // deactivate prox mines in the bot's path by shooting + // rockets or plasma cells etc. at them + BotAI_GetEntityState(bs->proxmines[bestmine], &state); + VectorCopy(state.pos.trBase, target); + target[2] += 2; + VectorSubtract(target, bs->eye, dir); + vectoangles(dir, moveresult->ideal_viewangles); + // if the bot has a weapon that does splash damage + if (bs->inventory[INVENTORY_PLASMAGUN] > 0 && bs->inventory[INVENTORY_CELLS] > 0) + moveresult->weapon = WEAPONINDEX_PLASMAGUN; + else if (bs->inventory[INVENTORY_ROCKETLAUNCHER] > 0 && bs->inventory[INVENTORY_ROCKETS] > 0) + moveresult->weapon = WEAPONINDEX_ROCKET_LAUNCHER; + else if (bs->inventory[INVENTORY_BFG10K] > 0 && bs->inventory[INVENTORY_BFGAMMO] > 0) + moveresult->weapon = WEAPONINDEX_BFG; + else { + moveresult->weapon = 0; + } + if (moveresult->weapon) { + // + moveresult->flags |= MOVERESULT_MOVEMENTWEAPON | MOVERESULT_MOVEMENTVIEW; + // if holding the right weapon + if (bs->cur_ps.weapon == moveresult->weapon) { + // if the bot is pretty close with it's aim + if (InFieldOfVision(bs->viewangles, 20, moveresult->ideal_viewangles)) { + // + BotAI_Trace(&bsptrace, bs->eye, NULL, NULL, target, bs->entitynum, MASK_SHOT); + // if the mine is visible from the current position + if (bsptrace.fraction >= 1.0 || bsptrace.ent == state.number) { + // shoot at the mine + trap_EA_Attack(bs->client); + } + } + } + } + } + } +} + +/* +================== +AIEnter_Seek_ActivateEntity +================== +*/ +void AIEnter_Seek_ActivateEntity(bot_state_t *bs, char *s) { + BotRecordNodeSwitch(bs, "activate entity", "", s); + bs->ainode = AINode_Seek_ActivateEntity; +} + +/* +================== +AINode_Seek_Activate_Entity +================== +*/ +int AINode_Seek_ActivateEntity(bot_state_t *bs) { + bot_goal_t *goal; + vec3_t target, dir, ideal_viewangles; + bot_moveresult_t moveresult; + int targetvisible; + bsp_trace_t bsptrace; + aas_entityinfo_t entinfo; + + if (BotIsObserver(bs)) { + BotClearActivateGoalStack(bs); + AIEnter_Observer(bs, "active entity: observer"); + return qfalse; + } + //if in the intermission + if (BotIntermission(bs)) { + BotClearActivateGoalStack(bs); + AIEnter_Intermission(bs, "activate entity: intermission"); + return qfalse; + } + //respawn if dead + if (BotIsDead(bs)) { + BotClearActivateGoalStack(bs); + AIEnter_Respawn(bs, "activate entity: bot dead"); + return qfalse; + } + // + bs->tfl = TFL_DEFAULT; + if (bot_grapple.integer) bs->tfl |= TFL_GRAPPLEHOOK; + // if in lava or slime the bot should be able to get out + if (BotInLavaOrSlime(bs)) bs->tfl |= TFL_LAVA|TFL_SLIME; + // map specific code + BotMapScripts(bs); + // no enemy + bs->enemy = -1; + // if the bot has no activate goal + if (!bs->activatestack) { + BotClearActivateGoalStack(bs); + AIEnter_Seek_NBG(bs, "activate entity: no goal"); + return qfalse; + } + // + goal = &bs->activatestack->goal; + // initialize target being visible to false + targetvisible = qfalse; + // if the bot has to shoot at a target to activate something + if (bs->activatestack->shoot) { + // + BotAI_Trace(&bsptrace, bs->eye, NULL, NULL, bs->activatestack->target, bs->entitynum, MASK_SHOT); + // if the shootable entity is visible from the current position + if (bsptrace.fraction >= 1.0 || bsptrace.ent == goal->entitynum) { + targetvisible = qtrue; + // if holding the right weapon + if (bs->cur_ps.weapon == bs->activatestack->weapon) { + VectorSubtract(bs->activatestack->target, bs->eye, dir); + vectoangles(dir, ideal_viewangles); + // if the bot is pretty close with it's aim + if (InFieldOfVision(bs->viewangles, 20, ideal_viewangles)) { + trap_EA_Attack(bs->client); + } + } + } + } + // if the shoot target is visible + if (targetvisible) { + // get the entity info of the entity the bot is shooting at + BotEntityInfo(goal->entitynum, &entinfo); + // if the entity the bot shoots at moved + if (!VectorCompare(bs->activatestack->origin, entinfo.origin)) { +#ifdef DEBUG + BotAI_Print(PRT_MESSAGE, "hit shootable button or trigger\n"); +#endif //DEBUG + bs->activatestack->time = 0; + } + // if the activate goal has been activated or the bot takes too long + if (bs->activatestack->time < FloatTime()) { + BotPopFromActivateGoalStack(bs); + // if there are more activate goals on the stack + if (bs->activatestack) { + bs->activatestack->time = FloatTime() + 10; + return qfalse; + } + AIEnter_Seek_NBG(bs, "activate entity: time out"); + return qfalse; + } + memset(&moveresult, 0, sizeof(bot_moveresult_t)); + } + else { + // if the bot has no goal + if (!goal) { + bs->activatestack->time = 0; + } + // if the bot does not have a shoot goal + else if (!bs->activatestack->shoot) { + //if the bot touches the current goal + if (trap_BotTouchingGoal(bs->origin, goal)) { +#ifdef DEBUG + BotAI_Print(PRT_MESSAGE, "touched button or trigger\n"); +#endif //DEBUG + bs->activatestack->time = 0; + } + } + // if the activate goal has been activated or the bot takes too long + if (bs->activatestack->time < FloatTime()) { + BotPopFromActivateGoalStack(bs); + // if there are more activate goals on the stack + if (bs->activatestack) { + bs->activatestack->time = FloatTime() + 10; + return qfalse; + } + AIEnter_Seek_NBG(bs, "activate entity: activated"); + return qfalse; + } + //predict obstacles + if (BotAIPredictObstacles(bs, goal)) + return qfalse; + //initialize the movement state + BotSetupForMovement(bs); + //move towards the goal + trap_BotMoveToGoal(&moveresult, bs->ms, goal, bs->tfl); + //if the movement failed + if (moveresult.failure) { + //reset the avoid reach, otherwise bot is stuck in current area + trap_BotResetAvoidReach(bs->ms); + // + bs->activatestack->time = 0; + } + //check if the bot is blocked + BotAIBlocked(bs, &moveresult, qtrue); + } + // + BotClearPath(bs, &moveresult); + // if the bot has to shoot to activate + if (bs->activatestack->shoot) { + // if the view angles aren't yet used for the movement + if (!(moveresult.flags & MOVERESULT_MOVEMENTVIEW)) { + VectorSubtract(bs->activatestack->target, bs->eye, dir); + vectoangles(dir, moveresult.ideal_viewangles); + moveresult.flags |= MOVERESULT_MOVEMENTVIEW; + } + // if there's no weapon yet used for the movement + if (!(moveresult.flags & MOVERESULT_MOVEMENTWEAPON)) { + moveresult.flags |= MOVERESULT_MOVEMENTWEAPON; + // + bs->activatestack->weapon = BotSelectActivateWeapon(bs); + if (bs->activatestack->weapon == -1) { + //FIXME: find a decent weapon first + bs->activatestack->weapon = 0; + } + moveresult.weapon = bs->activatestack->weapon; + } + } + // if the ideal view angles are set for movement + if (moveresult.flags & (MOVERESULT_MOVEMENTVIEWSET|MOVERESULT_MOVEMENTVIEW|MOVERESULT_SWIMVIEW)) { + VectorCopy(moveresult.ideal_viewangles, bs->ideal_viewangles); + } + // if waiting for something + else if (moveresult.flags & MOVERESULT_WAITING) { + if (random() < bs->thinktime * 0.8) { + BotRoamGoal(bs, target); + VectorSubtract(target, bs->origin, dir); + vectoangles(dir, bs->ideal_viewangles); + bs->ideal_viewangles[2] *= 0.5; + } + } + else if (!(bs->flags & BFL_IDEALVIEWSET)) { + if (trap_BotMovementViewTarget(bs->ms, goal, bs->tfl, 300, target)) { + VectorSubtract(target, bs->origin, dir); + vectoangles(dir, bs->ideal_viewangles); + } + else { + vectoangles(moveresult.movedir, bs->ideal_viewangles); + } + bs->ideal_viewangles[2] *= 0.5; + } + // if the weapon is used for the bot movement + if (moveresult.flags & MOVERESULT_MOVEMENTWEAPON) + bs->weaponnum = moveresult.weapon; + // if there is an enemy + if (BotFindEnemy(bs, -1)) { + if (BotWantsToRetreat(bs)) { + //keep the current long term goal and retreat + AIEnter_Battle_NBG(bs, "activate entity: found enemy"); + } + else { + trap_BotResetLastAvoidReach(bs->ms); + //empty the goal stack + trap_BotEmptyGoalStack(bs->gs); + //go fight + AIEnter_Battle_Fight(bs, "activate entity: found enemy"); + } + BotClearActivateGoalStack(bs); + } + return qtrue; +} + +/* +================== +AIEnter_Seek_NBG +================== +*/ +void AIEnter_Seek_NBG(bot_state_t *bs, char *s) { + bot_goal_t goal; + char buf[144]; + + if (trap_BotGetTopGoal(bs->gs, &goal)) { + trap_BotGoalName(goal.number, buf, 144); + BotRecordNodeSwitch(bs, "seek NBG", buf, s); + } + else { + BotRecordNodeSwitch(bs, "seek NBG", "no goal", s); + } + bs->ainode = AINode_Seek_NBG; +} + +/* +================== +AINode_Seek_NBG +================== +*/ +int AINode_Seek_NBG(bot_state_t *bs) { + bot_goal_t goal; + vec3_t target, dir; + bot_moveresult_t moveresult; + + if (BotIsObserver(bs)) { + AIEnter_Observer(bs, "seek nbg: observer"); + return qfalse; + } + //if in the intermission + if (BotIntermission(bs)) { + AIEnter_Intermission(bs, "seek nbg: intermision"); + return qfalse; + } + //respawn if dead + if (BotIsDead(bs)) { + AIEnter_Respawn(bs, "seek nbg: bot dead"); + return qfalse; + } + // + bs->tfl = TFL_DEFAULT; + if (bot_grapple.integer) bs->tfl |= TFL_GRAPPLEHOOK; + //if in lava or slime the bot should be able to get out + if (BotInLavaOrSlime(bs)) bs->tfl |= TFL_LAVA|TFL_SLIME; + // + if (BotCanAndWantsToRocketJump(bs)) { + bs->tfl |= TFL_ROCKETJUMP; + } + //map specific code + BotMapScripts(bs); + //no enemy + bs->enemy = -1; + //if the bot has no goal + if (!trap_BotGetTopGoal(bs->gs, &goal)) bs->nbg_time = 0; + //if the bot touches the current goal + else if (BotReachedGoal(bs, &goal)) { + BotChooseWeapon(bs); + bs->nbg_time = 0; + } + // + if (bs->nbg_time < FloatTime()) { + //pop the current goal from the stack + trap_BotPopGoal(bs->gs); + //check for new nearby items right away + //NOTE: we canNOT reset the check_time to zero because it would create an endless loop of node switches + bs->check_time = FloatTime() + 0.05; + //go back to seek ltg + AIEnter_Seek_LTG(bs, "seek nbg: time out"); + return qfalse; + } + //predict obstacles + if (BotAIPredictObstacles(bs, &goal)) + return qfalse; + //initialize the movement state + BotSetupForMovement(bs); + //move towards the goal + trap_BotMoveToGoal(&moveresult, bs->ms, &goal, bs->tfl); + //if the movement failed + if (moveresult.failure) { + //reset the avoid reach, otherwise bot is stuck in current area + trap_BotResetAvoidReach(bs->ms); + bs->nbg_time = 0; + } + //check if the bot is blocked + BotAIBlocked(bs, &moveresult, qtrue); + // + BotClearPath(bs, &moveresult); + //if the viewangles are used for the movement + if (moveresult.flags & (MOVERESULT_MOVEMENTVIEWSET|MOVERESULT_MOVEMENTVIEW|MOVERESULT_SWIMVIEW)) { + VectorCopy(moveresult.ideal_viewangles, bs->ideal_viewangles); + } + //if waiting for something + else if (moveresult.flags & MOVERESULT_WAITING) { + if (random() < bs->thinktime * 0.8) { + BotRoamGoal(bs, target); + VectorSubtract(target, bs->origin, dir); + vectoangles(dir, bs->ideal_viewangles); + bs->ideal_viewangles[2] *= 0.5; + } + } + else if (!(bs->flags & BFL_IDEALVIEWSET)) { + if (!trap_BotGetSecondGoal(bs->gs, &goal)) trap_BotGetTopGoal(bs->gs, &goal); + if (trap_BotMovementViewTarget(bs->ms, &goal, bs->tfl, 300, target)) { + VectorSubtract(target, bs->origin, dir); + vectoangles(dir, bs->ideal_viewangles); + } + //FIXME: look at cluster portals? + else vectoangles(moveresult.movedir, bs->ideal_viewangles); + bs->ideal_viewangles[2] *= 0.5; + } + //if the weapon is used for the bot movement + if (moveresult.flags & MOVERESULT_MOVEMENTWEAPON) bs->weaponnum = moveresult.weapon; + //if there is an enemy + if (BotFindEnemy(bs, -1)) { + if (BotWantsToRetreat(bs)) { + //keep the current long term goal and retreat + AIEnter_Battle_NBG(bs, "seek nbg: found enemy"); + } + else { + trap_BotResetLastAvoidReach(bs->ms); + //empty the goal stack + trap_BotEmptyGoalStack(bs->gs); + //go fight + AIEnter_Battle_Fight(bs, "seek nbg: found enemy"); + } + } + return qtrue; +} + +/* +================== +AIEnter_Seek_LTG +================== +*/ +void AIEnter_Seek_LTG(bot_state_t *bs, char *s) { + bot_goal_t goal; + char buf[144]; + + if (trap_BotGetTopGoal(bs->gs, &goal)) { + trap_BotGoalName(goal.number, buf, 144); + BotRecordNodeSwitch(bs, "seek LTG", buf, s); + } + else { + BotRecordNodeSwitch(bs, "seek LTG", "no goal", s); + } + bs->ainode = AINode_Seek_LTG; +} + +/* +================== +AINode_Seek_LTG +================== +*/ +int AINode_Seek_LTG(bot_state_t *bs) +{ + bot_goal_t goal; + vec3_t target, dir; + bot_moveresult_t moveresult; + int range; + //char buf[128]; + //bot_goal_t tmpgoal; + + if (BotIsObserver(bs)) { + AIEnter_Observer(bs, "seek ltg: observer"); + return qfalse; + } + //if in the intermission + if (BotIntermission(bs)) { + AIEnter_Intermission(bs, "seek ltg: intermission"); + return qfalse; + } + //respawn if dead + if (BotIsDead(bs)) { + AIEnter_Respawn(bs, "seek ltg: bot dead"); + return qfalse; + } + // + if (BotChat_Random(bs)) { + bs->stand_time = FloatTime() + BotChatTime(bs); + AIEnter_Stand(bs, "seek ltg: random chat"); + return qfalse; + } + // + bs->tfl = TFL_DEFAULT; + if (bot_grapple.integer) bs->tfl |= TFL_GRAPPLEHOOK; + //if in lava or slime the bot should be able to get out + if (BotInLavaOrSlime(bs)) bs->tfl |= TFL_LAVA|TFL_SLIME; + // + if (BotCanAndWantsToRocketJump(bs)) { + bs->tfl |= TFL_ROCKETJUMP; + } + //map specific code + BotMapScripts(bs); + //no enemy + bs->enemy = -1; + // + if (bs->killedenemy_time > FloatTime() - 2) { + if (random() < bs->thinktime * 1) { + trap_EA_Gesture(bs->client); + } + } + //if there is an enemy + if (BotFindEnemy(bs, -1)) { + if (BotWantsToRetreat(bs)) { + //keep the current long term goal and retreat + AIEnter_Battle_Retreat(bs, "seek ltg: found enemy"); + return qfalse; + } + else { + trap_BotResetLastAvoidReach(bs->ms); + //empty the goal stack + trap_BotEmptyGoalStack(bs->gs); + //go fight + AIEnter_Battle_Fight(bs, "seek ltg: found enemy"); + return qfalse; + } + } + // + BotTeamGoals(bs, qfalse); + //get the current long term goal + if (!BotLongTermGoal(bs, bs->tfl, qfalse, &goal)) { + return qtrue; + } + //check for nearby goals periodicly + if (bs->check_time < FloatTime()) { + bs->check_time = FloatTime() + 0.5; + //check if the bot wants to camp + BotWantsToCamp(bs); + // + if (bs->ltgtype == LTG_DEFENDKEYAREA) range = 400; + else range = 150; + // +#ifdef CTF + if (gametype == GT_CTF) { + //if carrying a flag the bot shouldn't be distracted too much + if (BotCTFCarryingFlag(bs)) + range = 50; + } +#endif //CTF +#ifdef MISSIONPACK + else if (gametype == GT_1FCTF) { + if (Bot1FCTFCarryingFlag(bs)) + range = 50; + } + else if (gametype == GT_HARVESTER) { + if (BotHarvesterCarryingCubes(bs)) + range = 80; + } +#endif + // + if (BotNearbyGoal(bs, bs->tfl, &goal, range)) { + trap_BotResetLastAvoidReach(bs->ms); + //get the goal at the top of the stack + //trap_BotGetTopGoal(bs->gs, &tmpgoal); + //trap_BotGoalName(tmpgoal.number, buf, 144); + //BotAI_Print(PRT_MESSAGE, "new nearby goal %s\n", buf); + //time the bot gets to pick up the nearby goal item + bs->nbg_time = FloatTime() + 4 + range * 0.01; + AIEnter_Seek_NBG(bs, "ltg seek: nbg"); + return qfalse; + } + } + //predict obstacles + if (BotAIPredictObstacles(bs, &goal)) + return qfalse; + //initialize the movement state + BotSetupForMovement(bs); + //move towards the goal + trap_BotMoveToGoal(&moveresult, bs->ms, &goal, bs->tfl); + //if the movement failed + if (moveresult.failure) { + //reset the avoid reach, otherwise bot is stuck in current area + trap_BotResetAvoidReach(bs->ms); + //BotAI_Print(PRT_MESSAGE, "movement failure %d\n", moveresult.traveltype); + bs->ltg_time = 0; + } + // + BotAIBlocked(bs, &moveresult, qtrue); + // + BotClearPath(bs, &moveresult); + //if the viewangles are used for the movement + if (moveresult.flags & (MOVERESULT_MOVEMENTVIEWSET|MOVERESULT_MOVEMENTVIEW|MOVERESULT_SWIMVIEW)) { + VectorCopy(moveresult.ideal_viewangles, bs->ideal_viewangles); + } + //if waiting for something + else if (moveresult.flags & MOVERESULT_WAITING) { + if (random() < bs->thinktime * 0.8) { + BotRoamGoal(bs, target); + VectorSubtract(target, bs->origin, dir); + vectoangles(dir, bs->ideal_viewangles); + bs->ideal_viewangles[2] *= 0.5; + } + } + else if (!(bs->flags & BFL_IDEALVIEWSET)) { + if (trap_BotMovementViewTarget(bs->ms, &goal, bs->tfl, 300, target)) { + VectorSubtract(target, bs->origin, dir); + vectoangles(dir, bs->ideal_viewangles); + } + //FIXME: look at cluster portals? + else if (VectorLengthSquared(moveresult.movedir)) { + vectoangles(moveresult.movedir, bs->ideal_viewangles); + } + else if (random() < bs->thinktime * 0.8) { + BotRoamGoal(bs, target); + VectorSubtract(target, bs->origin, dir); + vectoangles(dir, bs->ideal_viewangles); + bs->ideal_viewangles[2] *= 0.5; + } + bs->ideal_viewangles[2] *= 0.5; + } + //if the weapon is used for the bot movement + if (moveresult.flags & MOVERESULT_MOVEMENTWEAPON) bs->weaponnum = moveresult.weapon; + // + return qtrue; +} + +/* +================== +AIEnter_Battle_Fight +================== +*/ +void AIEnter_Battle_Fight(bot_state_t *bs, char *s) { + BotRecordNodeSwitch(bs, "battle fight", "", s); + trap_BotResetLastAvoidReach(bs->ms); + bs->ainode = AINode_Battle_Fight; +} + +/* +================== +AIEnter_Battle_Fight +================== +*/ +void AIEnter_Battle_SuicidalFight(bot_state_t *bs, char *s) { + BotRecordNodeSwitch(bs, "battle fight", "", s); + trap_BotResetLastAvoidReach(bs->ms); + bs->ainode = AINode_Battle_Fight; + bs->flags |= BFL_FIGHTSUICIDAL; +} + +/* +================== +AINode_Battle_Fight +================== +*/ +int AINode_Battle_Fight(bot_state_t *bs) { + int areanum; + vec3_t target; + aas_entityinfo_t entinfo; + bot_moveresult_t moveresult; + + if (BotIsObserver(bs)) { + AIEnter_Observer(bs, "battle fight: observer"); + return qfalse; + } + + //if in the intermission + if (BotIntermission(bs)) { + AIEnter_Intermission(bs, "battle fight: intermission"); + return qfalse; + } + //respawn if dead + if (BotIsDead(bs)) { + AIEnter_Respawn(bs, "battle fight: bot dead"); + return qfalse; + } + //if there is another better enemy + if (BotFindEnemy(bs, bs->enemy)) { +#ifdef DEBUG + BotAI_Print(PRT_MESSAGE, "found new better enemy\n"); +#endif + } + //if no enemy + if (bs->enemy < 0) { + AIEnter_Seek_LTG(bs, "battle fight: no enemy"); + return qfalse; + } + // + BotEntityInfo(bs->enemy, &entinfo); + //if the enemy is dead + if (bs->enemydeath_time) { + if (bs->enemydeath_time < FloatTime() - 1.0) { + bs->enemydeath_time = 0; + if (bs->enemysuicide) { + BotChat_EnemySuicide(bs); + } + if (bs->lastkilledplayer == bs->enemy && BotChat_Kill(bs)) { + bs->stand_time = FloatTime() + BotChatTime(bs); + AIEnter_Stand(bs, "battle fight: enemy dead"); + } + else { + bs->ltg_time = 0; + AIEnter_Seek_LTG(bs, "battle fight: enemy dead"); + } + return qfalse; + } + } + else { + if (EntityIsDead(&entinfo)) { + bs->enemydeath_time = FloatTime(); + } + } + //if the enemy is invisible and not shooting the bot looses track easily + if (EntityIsInvisible(&entinfo) && !EntityIsShooting(&entinfo)) { + if (random() < 0.2) { + AIEnter_Seek_LTG(bs, "battle fight: invisible"); + return qfalse; + } + } + // + VectorCopy(entinfo.origin, target); + // if not a player enemy + if (bs->enemy >= MAX_CLIENTS) { +#ifdef MISSIONPACK + // if attacking an obelisk + if ( bs->enemy == redobelisk.entitynum || + bs->enemy == blueobelisk.entitynum ) { + target[2] += 16; + } +#endif + } + //update the reachability area and origin if possible + areanum = BotPointAreaNum(target); + if (areanum && trap_AAS_AreaReachability(areanum)) { + VectorCopy(target, bs->lastenemyorigin); + bs->lastenemyareanum = areanum; + } + //update the attack inventory values + BotUpdateBattleInventory(bs, bs->enemy); + //if the bot's health decreased + if (bs->lastframe_health > bs->inventory[INVENTORY_HEALTH]) { + if (BotChat_HitNoDeath(bs)) { + bs->stand_time = FloatTime() + BotChatTime(bs); + AIEnter_Stand(bs, "battle fight: chat health decreased"); + return qfalse; + } + } + //if the bot hit someone + if (bs->cur_ps.persistant[PERS_HITS] > bs->lasthitcount) { + if (BotChat_HitNoKill(bs)) { + bs->stand_time = FloatTime() + BotChatTime(bs); + AIEnter_Stand(bs, "battle fight: chat hit someone"); + return qfalse; + } + } + //if the enemy is not visible + if (!BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy)) { + if (BotWantsToChase(bs)) { + AIEnter_Battle_Chase(bs, "battle fight: enemy out of sight"); + return qfalse; + } + else { + AIEnter_Seek_LTG(bs, "battle fight: enemy out of sight"); + return qfalse; + } + } + //use holdable items + BotBattleUseItems(bs); + // + bs->tfl = TFL_DEFAULT; + if (bot_grapple.integer) bs->tfl |= TFL_GRAPPLEHOOK; + //if in lava or slime the bot should be able to get out + if (BotInLavaOrSlime(bs)) bs->tfl |= TFL_LAVA|TFL_SLIME; + // + if (BotCanAndWantsToRocketJump(bs)) { + bs->tfl |= TFL_ROCKETJUMP; + } + //choose the best weapon to fight with + BotChooseWeapon(bs); + //do attack movements + moveresult = BotAttackMove(bs, bs->tfl); + //if the movement failed + if (moveresult.failure) { + //reset the avoid reach, otherwise bot is stuck in current area + trap_BotResetAvoidReach(bs->ms); + //BotAI_Print(PRT_MESSAGE, "movement failure %d\n", moveresult.traveltype); + bs->ltg_time = 0; + } + // + BotAIBlocked(bs, &moveresult, qfalse); + //aim at the enemy + BotAimAtEnemy(bs); + //attack the enemy if possible + BotCheckAttack(bs); + //if the bot wants to retreat + if (!(bs->flags & BFL_FIGHTSUICIDAL)) { + if (BotWantsToRetreat(bs)) { + AIEnter_Battle_Retreat(bs, "battle fight: wants to retreat"); + return qtrue; + } + } + return qtrue; +} + +/* +================== +AIEnter_Battle_Chase +================== +*/ +void AIEnter_Battle_Chase(bot_state_t *bs, char *s) { + BotRecordNodeSwitch(bs, "battle chase", "", s); + bs->chase_time = FloatTime(); + bs->ainode = AINode_Battle_Chase; +} + +/* +================== +AINode_Battle_Chase +================== +*/ +int AINode_Battle_Chase(bot_state_t *bs) +{ + bot_goal_t goal; + vec3_t target, dir; + bot_moveresult_t moveresult; + float range; + + if (BotIsObserver(bs)) { + AIEnter_Observer(bs, "battle chase: observer"); + return qfalse; + } + //if in the intermission + if (BotIntermission(bs)) { + AIEnter_Intermission(bs, "battle chase: intermission"); + return qfalse; + } + //respawn if dead + if (BotIsDead(bs)) { + AIEnter_Respawn(bs, "battle chase: bot dead"); + return qfalse; + } + //if no enemy + if (bs->enemy < 0) { + AIEnter_Seek_LTG(bs, "battle chase: no enemy"); + return qfalse; + } + //if the enemy is visible + if (BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy)) { + AIEnter_Battle_Fight(bs, "battle chase"); + return qfalse; + } + //if there is another enemy + if (BotFindEnemy(bs, -1)) { + AIEnter_Battle_Fight(bs, "battle chase: better enemy"); + return qfalse; + } + //there is no last enemy area + if (!bs->lastenemyareanum) { + AIEnter_Seek_LTG(bs, "battle chase: no enemy area"); + return qfalse; + } + // + bs->tfl = TFL_DEFAULT; + if (bot_grapple.integer) bs->tfl |= TFL_GRAPPLEHOOK; + //if in lava or slime the bot should be able to get out + if (BotInLavaOrSlime(bs)) bs->tfl |= TFL_LAVA|TFL_SLIME; + // + if (BotCanAndWantsToRocketJump(bs)) { + bs->tfl |= TFL_ROCKETJUMP; + } + //map specific code + BotMapScripts(bs); + //create the chase goal + goal.entitynum = bs->enemy; + goal.areanum = bs->lastenemyareanum; + VectorCopy(bs->lastenemyorigin, goal.origin); + VectorSet(goal.mins, -8, -8, -8); + VectorSet(goal.maxs, 8, 8, 8); + //if the last seen enemy spot is reached the enemy could not be found + if (trap_BotTouchingGoal(bs->origin, &goal)) bs->chase_time = 0; + //if there's no chase time left + if (!bs->chase_time || bs->chase_time < FloatTime() - 10) { + AIEnter_Seek_LTG(bs, "battle chase: time out"); + return qfalse; + } + //check for nearby goals periodicly + if (bs->check_time < FloatTime()) { + bs->check_time = FloatTime() + 1; + range = 150; + // + if (BotNearbyGoal(bs, bs->tfl, &goal, range)) { + //the bot gets 5 seconds to pick up the nearby goal item + bs->nbg_time = FloatTime() + 0.1 * range + 1; + trap_BotResetLastAvoidReach(bs->ms); + AIEnter_Battle_NBG(bs, "battle chase: nbg"); + return qfalse; + } + } + // + BotUpdateBattleInventory(bs, bs->enemy); + //initialize the movement state + BotSetupForMovement(bs); + //move towards the goal + trap_BotMoveToGoal(&moveresult, bs->ms, &goal, bs->tfl); + //if the movement failed + if (moveresult.failure) { + //reset the avoid reach, otherwise bot is stuck in current area + trap_BotResetAvoidReach(bs->ms); + //BotAI_Print(PRT_MESSAGE, "movement failure %d\n", moveresult.traveltype); + bs->ltg_time = 0; + } + // + BotAIBlocked(bs, &moveresult, qfalse); + // + if (moveresult.flags & (MOVERESULT_MOVEMENTVIEWSET|MOVERESULT_MOVEMENTVIEW|MOVERESULT_SWIMVIEW)) { + VectorCopy(moveresult.ideal_viewangles, bs->ideal_viewangles); + } + else if (!(bs->flags & BFL_IDEALVIEWSET)) { + if (bs->chase_time > FloatTime() - 2) { + BotAimAtEnemy(bs); + } + else { + if (trap_BotMovementViewTarget(bs->ms, &goal, bs->tfl, 300, target)) { + VectorSubtract(target, bs->origin, dir); + vectoangles(dir, bs->ideal_viewangles); + } + else { + vectoangles(moveresult.movedir, bs->ideal_viewangles); + } + } + bs->ideal_viewangles[2] *= 0.5; + } + //if the weapon is used for the bot movement + if (moveresult.flags & MOVERESULT_MOVEMENTWEAPON) bs->weaponnum = moveresult.weapon; + //if the bot is in the area the enemy was last seen in + if (bs->areanum == bs->lastenemyareanum) bs->chase_time = 0; + //if the bot wants to retreat (the bot could have been damage during the chase) + if (BotWantsToRetreat(bs)) { + AIEnter_Battle_Retreat(bs, "battle chase: wants to retreat"); + return qtrue; + } + return qtrue; +} + +/* +================== +AIEnter_Battle_Retreat +================== +*/ +void AIEnter_Battle_Retreat(bot_state_t *bs, char *s) { + BotRecordNodeSwitch(bs, "battle retreat", "", s); + bs->ainode = AINode_Battle_Retreat; +} + +/* +================== +AINode_Battle_Retreat +================== +*/ +int AINode_Battle_Retreat(bot_state_t *bs) { + bot_goal_t goal; + aas_entityinfo_t entinfo; + bot_moveresult_t moveresult; + vec3_t target, dir; + float attack_skill, range; + int areanum; + + if (BotIsObserver(bs)) { + AIEnter_Observer(bs, "battle retreat: observer"); + return qfalse; + } + //if in the intermission + if (BotIntermission(bs)) { + AIEnter_Intermission(bs, "battle retreat: intermission"); + return qfalse; + } + //respawn if dead + if (BotIsDead(bs)) { + AIEnter_Respawn(bs, "battle retreat: bot dead"); + return qfalse; + } + //if no enemy + if (bs->enemy < 0) { + AIEnter_Seek_LTG(bs, "battle retreat: no enemy"); + return qfalse; + } + // + BotEntityInfo(bs->enemy, &entinfo); + if (EntityIsDead(&entinfo)) { + AIEnter_Seek_LTG(bs, "battle retreat: enemy dead"); + return qfalse; + } + //if there is another better enemy + if (BotFindEnemy(bs, bs->enemy)) { +#ifdef DEBUG + BotAI_Print(PRT_MESSAGE, "found new better enemy\n"); +#endif + } + // + bs->tfl = TFL_DEFAULT; + if (bot_grapple.integer) bs->tfl |= TFL_GRAPPLEHOOK; + //if in lava or slime the bot should be able to get out + if (BotInLavaOrSlime(bs)) bs->tfl |= TFL_LAVA|TFL_SLIME; + //map specific code + BotMapScripts(bs); + //update the attack inventory values + BotUpdateBattleInventory(bs, bs->enemy); + //if the bot doesn't want to retreat anymore... probably picked up some nice items + if (BotWantsToChase(bs)) { + //empty the goal stack, when chasing, only the enemy is the goal + trap_BotEmptyGoalStack(bs->gs); + //go chase the enemy + AIEnter_Battle_Chase(bs, "battle retreat: wants to chase"); + return qfalse; + } + //update the last time the enemy was visible + if (BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy)) { + bs->enemyvisible_time = FloatTime(); + VectorCopy(entinfo.origin, target); + // if not a player enemy + if (bs->enemy >= MAX_CLIENTS) { +#ifdef MISSIONPACK + // if attacking an obelisk + if ( bs->enemy == redobelisk.entitynum || + bs->enemy == blueobelisk.entitynum ) { + target[2] += 16; + } +#endif + } + //update the reachability area and origin if possible + areanum = BotPointAreaNum(target); + if (areanum && trap_AAS_AreaReachability(areanum)) { + VectorCopy(target, bs->lastenemyorigin); + bs->lastenemyareanum = areanum; + } + } + //if the enemy is NOT visible for 4 seconds + if (bs->enemyvisible_time < FloatTime() - 4) { + AIEnter_Seek_LTG(bs, "battle retreat: lost enemy"); + return qfalse; + } + //else if the enemy is NOT visible + else if (bs->enemyvisible_time < FloatTime()) { + //if there is another enemy + if (BotFindEnemy(bs, -1)) { + AIEnter_Battle_Fight(bs, "battle retreat: another enemy"); + return qfalse; + } + } + // + BotTeamGoals(bs, qtrue); + //use holdable items + BotBattleUseItems(bs); + //get the current long term goal while retreating + if (!BotLongTermGoal(bs, bs->tfl, qtrue, &goal)) { + AIEnter_Battle_SuicidalFight(bs, "battle retreat: no way out"); + return qfalse; + } + //check for nearby goals periodicly + if (bs->check_time < FloatTime()) { + bs->check_time = FloatTime() + 1; + range = 150; +#ifdef CTF + if (gametype == GT_CTF) { + //if carrying a flag the bot shouldn't be distracted too much + if (BotCTFCarryingFlag(bs)) + range = 50; + } +#endif //CTF +#ifdef MISSIONPACK + else if (gametype == GT_1FCTF) { + if (Bot1FCTFCarryingFlag(bs)) + range = 50; + } + else if (gametype == GT_HARVESTER) { + if (BotHarvesterCarryingCubes(bs)) + range = 80; + } +#endif + // + if (BotNearbyGoal(bs, bs->tfl, &goal, range)) { + trap_BotResetLastAvoidReach(bs->ms); + //time the bot gets to pick up the nearby goal item + bs->nbg_time = FloatTime() + range / 100 + 1; + AIEnter_Battle_NBG(bs, "battle retreat: nbg"); + return qfalse; + } + } + //initialize the movement state + BotSetupForMovement(bs); + //move towards the goal + trap_BotMoveToGoal(&moveresult, bs->ms, &goal, bs->tfl); + //if the movement failed + if (moveresult.failure) { + //reset the avoid reach, otherwise bot is stuck in current area + trap_BotResetAvoidReach(bs->ms); + //BotAI_Print(PRT_MESSAGE, "movement failure %d\n", moveresult.traveltype); + bs->ltg_time = 0; + } + // + BotAIBlocked(bs, &moveresult, qfalse); + //choose the best weapon to fight with + BotChooseWeapon(bs); + //if the view is fixed for the movement + if (moveresult.flags & (MOVERESULT_MOVEMENTVIEW|MOVERESULT_SWIMVIEW)) { + VectorCopy(moveresult.ideal_viewangles, bs->ideal_viewangles); + } + else if (!(moveresult.flags & MOVERESULT_MOVEMENTVIEWSET) + && !(bs->flags & BFL_IDEALVIEWSET) ) { + attack_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_ATTACK_SKILL, 0, 1); + //if the bot is skilled anough + if (attack_skill > 0.3) { + BotAimAtEnemy(bs); + } + else { + if (trap_BotMovementViewTarget(bs->ms, &goal, bs->tfl, 300, target)) { + VectorSubtract(target, bs->origin, dir); + vectoangles(dir, bs->ideal_viewangles); + } + else { + vectoangles(moveresult.movedir, bs->ideal_viewangles); + } + bs->ideal_viewangles[2] *= 0.5; + } + } + //if the weapon is used for the bot movement + if (moveresult.flags & MOVERESULT_MOVEMENTWEAPON) bs->weaponnum = moveresult.weapon; + //attack the enemy if possible + BotCheckAttack(bs); + // + return qtrue; +} + +/* +================== +AIEnter_Battle_NBG +================== +*/ +void AIEnter_Battle_NBG(bot_state_t *bs, char *s) { + BotRecordNodeSwitch(bs, "battle NBG", "", s); + bs->ainode = AINode_Battle_NBG; +} + +/* +================== +AINode_Battle_NBG +================== +*/ +int AINode_Battle_NBG(bot_state_t *bs) { + int areanum; + bot_goal_t goal; + aas_entityinfo_t entinfo; + bot_moveresult_t moveresult; + float attack_skill; + vec3_t target, dir; + + if (BotIsObserver(bs)) { + AIEnter_Observer(bs, "battle nbg: observer"); + return qfalse; + } + //if in the intermission + if (BotIntermission(bs)) { + AIEnter_Intermission(bs, "battle nbg: intermission"); + return qfalse; + } + //respawn if dead + if (BotIsDead(bs)) { + AIEnter_Respawn(bs, "battle nbg: bot dead"); + return qfalse; + } + //if no enemy + if (bs->enemy < 0) { + AIEnter_Seek_NBG(bs, "battle nbg: no enemy"); + return qfalse; + } + // + BotEntityInfo(bs->enemy, &entinfo); + if (EntityIsDead(&entinfo)) { + AIEnter_Seek_NBG(bs, "battle nbg: enemy dead"); + return qfalse; + } + // + bs->tfl = TFL_DEFAULT; + if (bot_grapple.integer) bs->tfl |= TFL_GRAPPLEHOOK; + //if in lava or slime the bot should be able to get out + if (BotInLavaOrSlime(bs)) bs->tfl |= TFL_LAVA|TFL_SLIME; + // + if (BotCanAndWantsToRocketJump(bs)) { + bs->tfl |= TFL_ROCKETJUMP; + } + //map specific code + BotMapScripts(bs); + //update the last time the enemy was visible + if (BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy)) { + bs->enemyvisible_time = FloatTime(); + VectorCopy(entinfo.origin, target); + // if not a player enemy + if (bs->enemy >= MAX_CLIENTS) { +#ifdef MISSIONPACK + // if attacking an obelisk + if ( bs->enemy == redobelisk.entitynum || + bs->enemy == blueobelisk.entitynum ) { + target[2] += 16; + } +#endif + } + //update the reachability area and origin if possible + areanum = BotPointAreaNum(target); + if (areanum && trap_AAS_AreaReachability(areanum)) { + VectorCopy(target, bs->lastenemyorigin); + bs->lastenemyareanum = areanum; + } + } + //if the bot has no goal or touches the current goal + if (!trap_BotGetTopGoal(bs->gs, &goal)) { + bs->nbg_time = 0; + } + else if (BotReachedGoal(bs, &goal)) { + bs->nbg_time = 0; + } + // + if (bs->nbg_time < FloatTime()) { + //pop the current goal from the stack + trap_BotPopGoal(bs->gs); + //if the bot still has a goal + if (trap_BotGetTopGoal(bs->gs, &goal)) + AIEnter_Battle_Retreat(bs, "battle nbg: time out"); + else + AIEnter_Battle_Fight(bs, "battle nbg: time out"); + // + return qfalse; + } + //initialize the movement state + BotSetupForMovement(bs); + //move towards the goal + trap_BotMoveToGoal(&moveresult, bs->ms, &goal, bs->tfl); + //if the movement failed + if (moveresult.failure) { + //reset the avoid reach, otherwise bot is stuck in current area + trap_BotResetAvoidReach(bs->ms); + //BotAI_Print(PRT_MESSAGE, "movement failure %d\n", moveresult.traveltype); + bs->nbg_time = 0; + } + // + BotAIBlocked(bs, &moveresult, qfalse); + //update the attack inventory values + BotUpdateBattleInventory(bs, bs->enemy); + //choose the best weapon to fight with + BotChooseWeapon(bs); + //if the view is fixed for the movement + if (moveresult.flags & (MOVERESULT_MOVEMENTVIEW|MOVERESULT_SWIMVIEW)) { + VectorCopy(moveresult.ideal_viewangles, bs->ideal_viewangles); + } + else if (!(moveresult.flags & MOVERESULT_MOVEMENTVIEWSET) + && !(bs->flags & BFL_IDEALVIEWSET)) { + attack_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_ATTACK_SKILL, 0, 1); + //if the bot is skilled anough and the enemy is visible + if (attack_skill > 0.3) { + //&& BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy) + BotAimAtEnemy(bs); + } + else { + if (trap_BotMovementViewTarget(bs->ms, &goal, bs->tfl, 300, target)) { + VectorSubtract(target, bs->origin, dir); + vectoangles(dir, bs->ideal_viewangles); + } + else { + vectoangles(moveresult.movedir, bs->ideal_viewangles); + } + bs->ideal_viewangles[2] *= 0.5; + } + } + //if the weapon is used for the bot movement + if (moveresult.flags & MOVERESULT_MOVEMENTWEAPON) bs->weaponnum = moveresult.weapon; + //attack the enemy if possible + BotCheckAttack(bs); + // + return qtrue; +} + diff --git a/code/game/ai_dmnet.h b/code/game/ai_dmnet.h index a5c4c9a..0dbd6de 100755 --- a/code/game/ai_dmnet.h +++ b/code/game/ai_dmnet.h @@ -1,61 +1,61 @@ -/* -=========================================================================== -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 Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// - -/***************************************************************************** - * name: ai_dmnet.h - * - * desc: Quake3 bot AI - * - * $Archive: /source/code/botai/ai_chat.c $ - * - *****************************************************************************/ - -#define MAX_NODESWITCHES 50 - -void AIEnter_Intermission(bot_state_t *bs, char *s); -void AIEnter_Observer(bot_state_t *bs, char *s); -void AIEnter_Respawn(bot_state_t *bs, char *s); -void AIEnter_Stand(bot_state_t *bs, char *s); -void AIEnter_Seek_ActivateEntity(bot_state_t *bs, char *s); -void AIEnter_Seek_NBG(bot_state_t *bs, char *s); -void AIEnter_Seek_LTG(bot_state_t *bs, char *s); -void AIEnter_Seek_Camp(bot_state_t *bs, char *s); -void AIEnter_Battle_Fight(bot_state_t *bs, char *s); -void AIEnter_Battle_Chase(bot_state_t *bs, char *s); -void AIEnter_Battle_Retreat(bot_state_t *bs, char *s); -void AIEnter_Battle_NBG(bot_state_t *bs, char *s); -int AINode_Intermission(bot_state_t *bs); -int AINode_Observer(bot_state_t *bs); -int AINode_Respawn(bot_state_t *bs); -int AINode_Stand(bot_state_t *bs); -int AINode_Seek_ActivateEntity(bot_state_t *bs); -int AINode_Seek_NBG(bot_state_t *bs); -int AINode_Seek_LTG(bot_state_t *bs); -int AINode_Battle_Fight(bot_state_t *bs); -int AINode_Battle_Chase(bot_state_t *bs); -int AINode_Battle_Retreat(bot_state_t *bs); -int AINode_Battle_NBG(bot_state_t *bs); - -void BotResetNodeSwitches(void); -void BotDumpNodeSwitches(bot_state_t *bs); - +/* +=========================================================================== +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 Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// + +/***************************************************************************** + * name: ai_dmnet.h + * + * desc: Quake3 bot AI + * + * $Archive: /source/code/botai/ai_chat.c $ + * + *****************************************************************************/ + +#define MAX_NODESWITCHES 50 + +void AIEnter_Intermission(bot_state_t *bs, char *s); +void AIEnter_Observer(bot_state_t *bs, char *s); +void AIEnter_Respawn(bot_state_t *bs, char *s); +void AIEnter_Stand(bot_state_t *bs, char *s); +void AIEnter_Seek_ActivateEntity(bot_state_t *bs, char *s); +void AIEnter_Seek_NBG(bot_state_t *bs, char *s); +void AIEnter_Seek_LTG(bot_state_t *bs, char *s); +void AIEnter_Seek_Camp(bot_state_t *bs, char *s); +void AIEnter_Battle_Fight(bot_state_t *bs, char *s); +void AIEnter_Battle_Chase(bot_state_t *bs, char *s); +void AIEnter_Battle_Retreat(bot_state_t *bs, char *s); +void AIEnter_Battle_NBG(bot_state_t *bs, char *s); +int AINode_Intermission(bot_state_t *bs); +int AINode_Observer(bot_state_t *bs); +int AINode_Respawn(bot_state_t *bs); +int AINode_Stand(bot_state_t *bs); +int AINode_Seek_ActivateEntity(bot_state_t *bs); +int AINode_Seek_NBG(bot_state_t *bs); +int AINode_Seek_LTG(bot_state_t *bs); +int AINode_Battle_Fight(bot_state_t *bs); +int AINode_Battle_Chase(bot_state_t *bs); +int AINode_Battle_Retreat(bot_state_t *bs); +int AINode_Battle_NBG(bot_state_t *bs); + +void BotResetNodeSwitches(void); +void BotDumpNodeSwitches(bot_state_t *bs); + diff --git a/code/game/ai_dmq3.c b/code/game/ai_dmq3.c index ed7f4c1..c373df8 100755 --- a/code/game/ai_dmq3.c +++ b/code/game/ai_dmq3.c @@ -1,5461 +1,5461 @@ -/* -=========================================================================== -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 Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// - -/***************************************************************************** - * name: ai_dmq3.c - * - * desc: Quake3 bot AI - * - * $Archive: /MissionPack/code/game/ai_dmq3.c $ - * - *****************************************************************************/ - - -#include "g_local.h" -#include "botlib.h" -#include "be_aas.h" -#include "be_ea.h" -#include "be_ai_char.h" -#include "be_ai_chat.h" -#include "be_ai_gen.h" -#include "be_ai_goal.h" -#include "be_ai_move.h" -#include "be_ai_weap.h" -// -#include "ai_main.h" -#include "ai_dmq3.h" -#include "ai_chat.h" -#include "ai_cmd.h" -#include "ai_dmnet.h" -#include "ai_team.h" -// -#include "chars.h" //characteristics -#include "inv.h" //indexes into the inventory -#include "syn.h" //synonyms -#include "match.h" //string matching types and vars - -// for the voice chats -#include "../../ui/menudef.h" // sos001205 - for q3_ui also - -// from aasfile.h -#define AREACONTENTS_MOVER 1024 -#define AREACONTENTS_MODELNUMSHIFT 24 -#define AREACONTENTS_MAXMODELNUM 0xFF -#define AREACONTENTS_MODELNUM (AREACONTENTS_MAXMODELNUM << AREACONTENTS_MODELNUMSHIFT) - -#define IDEAL_ATTACKDIST 140 - -#define MAX_WAYPOINTS 128 -// -bot_waypoint_t botai_waypoints[MAX_WAYPOINTS]; -bot_waypoint_t *botai_freewaypoints; - -//NOTE: not using a cvars which can be updated because the game should be reloaded anyway -int gametype; //game type -int maxclients; //maximum number of clients - -vmCvar_t bot_grapple; -vmCvar_t bot_rocketjump; -vmCvar_t bot_fastchat; -vmCvar_t bot_nochat; -vmCvar_t bot_testrchat; -vmCvar_t bot_challenge; -vmCvar_t bot_predictobstacles; -vmCvar_t g_spSkill; - -extern vmCvar_t bot_developer; - -vec3_t lastteleport_origin; //last teleport event origin -float lastteleport_time; //last teleport event time -int max_bspmodelindex; //maximum BSP model index - -//CTF flag goals -bot_goal_t ctf_redflag; -bot_goal_t ctf_blueflag; -#ifdef MISSIONPACK -bot_goal_t ctf_neutralflag; -bot_goal_t redobelisk; -bot_goal_t blueobelisk; -bot_goal_t neutralobelisk; -#endif - -#define MAX_ALTROUTEGOALS 32 - -int altroutegoals_setup; -aas_altroutegoal_t red_altroutegoals[MAX_ALTROUTEGOALS]; -int red_numaltroutegoals; -aas_altroutegoal_t blue_altroutegoals[MAX_ALTROUTEGOALS]; -int blue_numaltroutegoals; - - -/* -================== -BotSetUserInfo -================== -*/ -void BotSetUserInfo(bot_state_t *bs, char *key, char *value) { - char userinfo[MAX_INFO_STRING]; - - trap_GetUserinfo(bs->client, userinfo, sizeof(userinfo)); - Info_SetValueForKey(userinfo, key, value); - trap_SetUserinfo(bs->client, userinfo); - ClientUserinfoChanged( bs->client ); -} - -/* -================== -BotCTFCarryingFlag -================== -*/ -int BotCTFCarryingFlag(bot_state_t *bs) { - if (gametype != GT_CTF) return CTF_FLAG_NONE; - - if (bs->inventory[INVENTORY_REDFLAG] > 0) return CTF_FLAG_RED; - else if (bs->inventory[INVENTORY_BLUEFLAG] > 0) return CTF_FLAG_BLUE; - return CTF_FLAG_NONE; -} - -/* -================== -BotTeam -================== -*/ -int BotTeam(bot_state_t *bs) { - char info[1024]; - - if (bs->client < 0 || bs->client >= MAX_CLIENTS) { - //BotAI_Print(PRT_ERROR, "BotCTFTeam: client out of range\n"); - return qfalse; - } - trap_GetConfigstring(CS_PLAYERS+bs->client, info, sizeof(info)); - // - if (atoi(Info_ValueForKey(info, "t")) == TEAM_RED) return TEAM_RED; - else if (atoi(Info_ValueForKey(info, "t")) == TEAM_BLUE) return TEAM_BLUE; - return TEAM_FREE; -} - -/* -================== -BotOppositeTeam -================== -*/ -int BotOppositeTeam(bot_state_t *bs) { - switch(BotTeam(bs)) { - case TEAM_RED: return TEAM_BLUE; - case TEAM_BLUE: return TEAM_RED; - default: return TEAM_FREE; - } -} - -/* -================== -BotEnemyFlag -================== -*/ -bot_goal_t *BotEnemyFlag(bot_state_t *bs) { - if (BotTeam(bs) == TEAM_RED) { - return &ctf_blueflag; - } - else { - return &ctf_redflag; - } -} - -/* -================== -BotTeamFlag -================== -*/ -bot_goal_t *BotTeamFlag(bot_state_t *bs) { - if (BotTeam(bs) == TEAM_RED) { - return &ctf_redflag; - } - else { - return &ctf_blueflag; - } -} - - -/* -================== -EntityIsDead -================== -*/ -qboolean EntityIsDead(aas_entityinfo_t *entinfo) { - playerState_t ps; - - if (entinfo->number >= 0 && entinfo->number < MAX_CLIENTS) { - //retrieve the current client state - BotAI_GetClientState( entinfo->number, &ps ); - if (ps.pm_type != PM_NORMAL) return qtrue; - } - return qfalse; -} - -/* -================== -EntityCarriesFlag -================== -*/ -qboolean EntityCarriesFlag(aas_entityinfo_t *entinfo) { - if ( entinfo->powerups & ( 1 << PW_REDFLAG ) ) - return qtrue; - if ( entinfo->powerups & ( 1 << PW_BLUEFLAG ) ) - return qtrue; -#ifdef MISSIONPACK - if ( entinfo->powerups & ( 1 << PW_NEUTRALFLAG ) ) - return qtrue; -#endif - return qfalse; -} - -/* -================== -EntityIsInvisible -================== -*/ -qboolean EntityIsInvisible(aas_entityinfo_t *entinfo) { - // the flag is always visible - if (EntityCarriesFlag(entinfo)) { - return qfalse; - } - if (entinfo->powerups & (1 << PW_INVIS)) { - return qtrue; - } - return qfalse; -} - -/* -================== -EntityIsShooting -================== -*/ -qboolean EntityIsShooting(aas_entityinfo_t *entinfo) { - if (entinfo->flags & EF_FIRING) { - return qtrue; - } - return qfalse; -} - -/* -================== -EntityIsChatting -================== -*/ -qboolean EntityIsChatting(aas_entityinfo_t *entinfo) { - if (entinfo->flags & EF_TALK) { - return qtrue; - } - return qfalse; -} - -/* -================== -EntityHasQuad -================== -*/ -qboolean EntityHasQuad(aas_entityinfo_t *entinfo) { - if (entinfo->powerups & (1 << PW_QUAD)) { - return qtrue; - } - return qfalse; -} - -#ifdef MISSIONPACK -/* -================== -EntityHasKamikze -================== -*/ -qboolean EntityHasKamikaze(aas_entityinfo_t *entinfo) { - if (entinfo->flags & EF_KAMIKAZE) { - return qtrue; - } - return qfalse; -} - -/* -================== -EntityCarriesCubes -================== -*/ -qboolean EntityCarriesCubes(aas_entityinfo_t *entinfo) { - entityState_t state; - - if (gametype != GT_HARVESTER) - return qfalse; - //FIXME: get this info from the aas_entityinfo_t ? - BotAI_GetEntityState(entinfo->number, &state); - if (state.generic1 > 0) - return qtrue; - return qfalse; -} - -/* -================== -Bot1FCTFCarryingFlag -================== -*/ -int Bot1FCTFCarryingFlag(bot_state_t *bs) { - if (gametype != GT_1FCTF) return qfalse; - - if (bs->inventory[INVENTORY_NEUTRALFLAG] > 0) return qtrue; - return qfalse; -} - -/* -================== -BotHarvesterCarryingCubes -================== -*/ -int BotHarvesterCarryingCubes(bot_state_t *bs) { - if (gametype != GT_HARVESTER) return qfalse; - - if (bs->inventory[INVENTORY_REDCUBE] > 0) return qtrue; - if (bs->inventory[INVENTORY_BLUECUBE] > 0) return qtrue; - return qfalse; -} -#endif - -/* -================== -BotRememberLastOrderedTask -================== -*/ -void BotRememberLastOrderedTask(bot_state_t *bs) { - if (!bs->ordered) { - return; - } - bs->lastgoal_decisionmaker = bs->decisionmaker; - bs->lastgoal_ltgtype = bs->ltgtype; - memcpy(&bs->lastgoal_teamgoal, &bs->teamgoal, sizeof(bot_goal_t)); - bs->lastgoal_teammate = bs->teammate; -} - -/* -================== -BotSetTeamStatus -================== -*/ -void BotSetTeamStatus(bot_state_t *bs) { -#ifdef MISSIONPACK - int teamtask; - aas_entityinfo_t entinfo; - - teamtask = TEAMTASK_PATROL; - - switch(bs->ltgtype) { - case LTG_TEAMHELP: - break; - case LTG_TEAMACCOMPANY: - BotEntityInfo(bs->teammate, &entinfo); - if ( ( (gametype == GT_CTF || gametype == GT_1FCTF) && EntityCarriesFlag(&entinfo)) - || ( gametype == GT_HARVESTER && EntityCarriesCubes(&entinfo)) ) { - teamtask = TEAMTASK_ESCORT; - } - else { - teamtask = TEAMTASK_FOLLOW; - } - break; - case LTG_DEFENDKEYAREA: - teamtask = TEAMTASK_DEFENSE; - break; - case LTG_GETFLAG: - teamtask = TEAMTASK_OFFENSE; - break; - case LTG_RUSHBASE: - teamtask = TEAMTASK_DEFENSE; - break; - case LTG_RETURNFLAG: - teamtask = TEAMTASK_RETRIEVE; - break; - case LTG_CAMP: - case LTG_CAMPORDER: - teamtask = TEAMTASK_CAMP; - break; - case LTG_PATROL: - teamtask = TEAMTASK_PATROL; - break; - case LTG_GETITEM: - teamtask = TEAMTASK_PATROL; - break; - case LTG_KILL: - teamtask = TEAMTASK_PATROL; - break; - case LTG_HARVEST: - teamtask = TEAMTASK_OFFENSE; - break; - case LTG_ATTACKENEMYBASE: - teamtask = TEAMTASK_OFFENSE; - break; - default: - teamtask = TEAMTASK_PATROL; - break; - } - BotSetUserInfo(bs, "teamtask", va("%d", teamtask)); -#endif -} - -/* -================== -BotSetLastOrderedTask -================== -*/ -int BotSetLastOrderedTask(bot_state_t *bs) { - - if (gametype == GT_CTF) { - // don't go back to returning the flag if it's at the base - if ( bs->lastgoal_ltgtype == LTG_RETURNFLAG ) { - if ( BotTeam(bs) == TEAM_RED ) { - if ( bs->redflagstatus == 0 ) { - bs->lastgoal_ltgtype = 0; - } - } - else { - if ( bs->blueflagstatus == 0 ) { - bs->lastgoal_ltgtype = 0; - } - } - } - } - - if ( bs->lastgoal_ltgtype ) { - bs->decisionmaker = bs->lastgoal_decisionmaker; - bs->ordered = qtrue; - bs->ltgtype = bs->lastgoal_ltgtype; - memcpy(&bs->teamgoal, &bs->lastgoal_teamgoal, sizeof(bot_goal_t)); - bs->teammate = bs->lastgoal_teammate; - bs->teamgoal_time = FloatTime() + 300; - BotSetTeamStatus(bs); - // - if ( gametype == GT_CTF ) { - if ( bs->ltgtype == LTG_GETFLAG ) { - bot_goal_t *tb, *eb; - int tt, et; - - tb = BotTeamFlag(bs); - eb = BotEnemyFlag(bs); - tt = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, tb->areanum, TFL_DEFAULT); - et = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, eb->areanum, TFL_DEFAULT); - // if the travel time towards the enemy base is larger than towards our base - if (et > tt) { - //get an alternative route goal towards the enemy base - BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs)); - } - } - } - return qtrue; - } - return qfalse; -} - -/* -================== -BotRefuseOrder -================== -*/ -void BotRefuseOrder(bot_state_t *bs) { - if (!bs->ordered) - return; - // if the bot was ordered to do something - if ( bs->order_time && bs->order_time > FloatTime() - 10 ) { - trap_EA_Action(bs->client, ACTION_NEGATIVE); - BotVoiceChat(bs, bs->decisionmaker, VOICECHAT_NO); - bs->order_time = 0; - } -} - -/* -================== -BotCTFSeekGoals -================== -*/ -void BotCTFSeekGoals(bot_state_t *bs) { - float rnd, l1, l2; - int flagstatus, c; - vec3_t dir; - aas_entityinfo_t entinfo; - - //when carrying a flag in ctf the bot should rush to the base - if (BotCTFCarryingFlag(bs)) { - //if not already rushing to the base - if (bs->ltgtype != LTG_RUSHBASE) { - BotRefuseOrder(bs); - bs->ltgtype = LTG_RUSHBASE; - bs->teamgoal_time = FloatTime() + CTF_RUSHBASE_TIME; - bs->rushbaseaway_time = 0; - bs->decisionmaker = bs->client; - bs->ordered = qfalse; - // - switch(BotTeam(bs)) { - case TEAM_RED: VectorSubtract(bs->origin, ctf_blueflag.origin, dir); break; - case TEAM_BLUE: VectorSubtract(bs->origin, ctf_redflag.origin, dir); break; - default: VectorSet(dir, 999, 999, 999); break; - } - // if the bot picked up the flag very close to the enemy base - if ( VectorLength(dir) < 128 ) { - // get an alternative route goal through the enemy base - BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs)); - } else { - // don't use any alt route goal, just get the hell out of the base - bs->altroutegoal.areanum = 0; - } - BotSetUserInfo(bs, "teamtask", va("%d", TEAMTASK_OFFENSE)); - BotVoiceChat(bs, -1, VOICECHAT_IHAVEFLAG); - } - else if (bs->rushbaseaway_time > FloatTime()) { - if (BotTeam(bs) == TEAM_RED) flagstatus = bs->redflagstatus; - else flagstatus = bs->blueflagstatus; - //if the flag is back - if (flagstatus == 0) { - bs->rushbaseaway_time = 0; - } - } - return; - } - // if the bot decided to follow someone - if ( bs->ltgtype == LTG_TEAMACCOMPANY && !bs->ordered ) { - // if the team mate being accompanied no longer carries the flag - BotEntityInfo(bs->teammate, &entinfo); - if (!EntityCarriesFlag(&entinfo)) { - bs->ltgtype = 0; - } - } - // - if (BotTeam(bs) == TEAM_RED) flagstatus = bs->redflagstatus * 2 + bs->blueflagstatus; - else flagstatus = bs->blueflagstatus * 2 + bs->redflagstatus; - //if our team has the enemy flag and our flag is at the base - if (flagstatus == 1) { - // - if (bs->owndecision_time < FloatTime()) { - //if Not defending the base already - if (!(bs->ltgtype == LTG_DEFENDKEYAREA && - (bs->teamgoal.number == ctf_redflag.number || - bs->teamgoal.number == ctf_blueflag.number))) { - //if there is a visible team mate flag carrier - c = BotTeamFlagCarrierVisible(bs); - if (c >= 0 && - // and not already following the team mate flag carrier - (bs->ltgtype != LTG_TEAMACCOMPANY || bs->teammate != c)) { - // - BotRefuseOrder(bs); - //follow the flag carrier - bs->decisionmaker = bs->client; - bs->ordered = qfalse; - //the team mate - bs->teammate = c; - //last time the team mate was visible - bs->teammatevisible_time = FloatTime(); - //no message - bs->teammessage_time = 0; - //no arrive message - bs->arrive_time = 1; - // - BotVoiceChat(bs, bs->teammate, VOICECHAT_ONFOLLOW); - //get the team goal time - bs->teamgoal_time = FloatTime() + TEAM_ACCOMPANY_TIME; - bs->ltgtype = LTG_TEAMACCOMPANY; - bs->formation_dist = 3.5 * 32; //3.5 meter - BotSetTeamStatus(bs); - bs->owndecision_time = FloatTime() + 5; - } - } - } - return; - } - //if the enemy has our flag - else if (flagstatus == 2) { - // - if (bs->owndecision_time < FloatTime()) { - //if enemy flag carrier is visible - c = BotEnemyFlagCarrierVisible(bs); - if (c >= 0) { - //FIXME: fight enemy flag carrier - } - //if not already doing something important - if (bs->ltgtype != LTG_GETFLAG && - bs->ltgtype != LTG_RETURNFLAG && - bs->ltgtype != LTG_TEAMHELP && - bs->ltgtype != LTG_TEAMACCOMPANY && - bs->ltgtype != LTG_CAMPORDER && - bs->ltgtype != LTG_PATROL && - bs->ltgtype != LTG_GETITEM) { - - BotRefuseOrder(bs); - bs->decisionmaker = bs->client; - bs->ordered = qfalse; - // - if (random() < 0.5) { - //go for the enemy flag - bs->ltgtype = LTG_GETFLAG; - } - else { - bs->ltgtype = LTG_RETURNFLAG; - } - //no team message - bs->teammessage_time = 0; - //set the time the bot will stop getting the flag - bs->teamgoal_time = FloatTime() + CTF_GETFLAG_TIME; - //get an alternative route goal towards the enemy base - BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs)); - // - BotSetTeamStatus(bs); - bs->owndecision_time = FloatTime() + 5; - } - } - return; - } - //if both flags Not at their bases - else if (flagstatus == 3) { - // - if (bs->owndecision_time < FloatTime()) { - // if not trying to return the flag and not following the team flag carrier - if ( bs->ltgtype != LTG_RETURNFLAG && bs->ltgtype != LTG_TEAMACCOMPANY ) { - // - c = BotTeamFlagCarrierVisible(bs); - // if there is a visible team mate flag carrier - if (c >= 0) { - BotRefuseOrder(bs); - //follow the flag carrier - bs->decisionmaker = bs->client; - bs->ordered = qfalse; - //the team mate - bs->teammate = c; - //last time the team mate was visible - bs->teammatevisible_time = FloatTime(); - //no message - bs->teammessage_time = 0; - //no arrive message - bs->arrive_time = 1; - // - BotVoiceChat(bs, bs->teammate, VOICECHAT_ONFOLLOW); - //get the team goal time - bs->teamgoal_time = FloatTime() + TEAM_ACCOMPANY_TIME; - bs->ltgtype = LTG_TEAMACCOMPANY; - bs->formation_dist = 3.5 * 32; //3.5 meter - // - BotSetTeamStatus(bs); - bs->owndecision_time = FloatTime() + 5; - } - else { - BotRefuseOrder(bs); - bs->decisionmaker = bs->client; - bs->ordered = qfalse; - //get the enemy flag - bs->teammessage_time = FloatTime() + 2 * random(); - //get the flag - bs->ltgtype = LTG_RETURNFLAG; - //set the time the bot will stop getting the flag - bs->teamgoal_time = FloatTime() + CTF_RETURNFLAG_TIME; - //get an alternative route goal towards the enemy base - BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs)); - // - BotSetTeamStatus(bs); - bs->owndecision_time = FloatTime() + 5; - } - } - } - return; - } - // don't just do something wait for the bot team leader to give orders - if (BotTeamLeader(bs)) { - return; - } - // if the bot is ordered to do something - if ( bs->lastgoal_ltgtype ) { - bs->teamgoal_time += 60; - } - // if the bot decided to do something on it's own and has a last ordered goal - if ( !bs->ordered && bs->lastgoal_ltgtype ) { - bs->ltgtype = 0; - } - //if already a CTF or team goal - if (bs->ltgtype == LTG_TEAMHELP || - bs->ltgtype == LTG_TEAMACCOMPANY || - bs->ltgtype == LTG_DEFENDKEYAREA || - bs->ltgtype == LTG_GETFLAG || - bs->ltgtype == LTG_RUSHBASE || - bs->ltgtype == LTG_RETURNFLAG || - bs->ltgtype == LTG_CAMPORDER || - bs->ltgtype == LTG_PATROL || - bs->ltgtype == LTG_GETITEM || - bs->ltgtype == LTG_MAKELOVE_UNDER || - bs->ltgtype == LTG_MAKELOVE_ONTOP) { - return; - } - // - if (BotSetLastOrderedTask(bs)) - return; - // - if (bs->owndecision_time > FloatTime()) - return;; - //if the bot is roaming - if (bs->ctfroam_time > FloatTime()) - return; - //if the bot has anough aggression to decide what to do - if (BotAggression(bs) < 50) - return; - //set the time to send a message to the team mates - bs->teammessage_time = FloatTime() + 2 * random(); - // - if (bs->teamtaskpreference & (TEAMTP_ATTACKER|TEAMTP_DEFENDER)) { - if (bs->teamtaskpreference & TEAMTP_ATTACKER) { - l1 = 0.7f; - } - else { - l1 = 0.2f; - } - l2 = 0.9f; - } - else { - l1 = 0.4f; - l2 = 0.7f; - } - //get the flag or defend the base - rnd = random(); - if (rnd < l1 && ctf_redflag.areanum && ctf_blueflag.areanum) { - bs->decisionmaker = bs->client; - bs->ordered = qfalse; - bs->ltgtype = LTG_GETFLAG; - //set the time the bot will stop getting the flag - bs->teamgoal_time = FloatTime() + CTF_GETFLAG_TIME; - //get an alternative route goal towards the enemy base - BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs)); - BotSetTeamStatus(bs); - } - else if (rnd < l2 && ctf_redflag.areanum && ctf_blueflag.areanum) { - bs->decisionmaker = bs->client; - bs->ordered = qfalse; - // - if (BotTeam(bs) == TEAM_RED) memcpy(&bs->teamgoal, &ctf_redflag, sizeof(bot_goal_t)); - else memcpy(&bs->teamgoal, &ctf_blueflag, sizeof(bot_goal_t)); - //set the ltg type - bs->ltgtype = LTG_DEFENDKEYAREA; - //set the time the bot stops defending the base - bs->teamgoal_time = FloatTime() + TEAM_DEFENDKEYAREA_TIME; - bs->defendaway_time = 0; - BotSetTeamStatus(bs); - } - else { - bs->ltgtype = 0; - //set the time the bot will stop roaming - bs->ctfroam_time = FloatTime() + CTF_ROAM_TIME; - BotSetTeamStatus(bs); - } - bs->owndecision_time = FloatTime() + 5; -#ifdef DEBUG - BotPrintTeamGoal(bs); -#endif //DEBUG -} - -/* -================== -BotCTFRetreatGoals -================== -*/ -void BotCTFRetreatGoals(bot_state_t *bs) { - //when carrying a flag in ctf the bot should rush to the base - if (BotCTFCarryingFlag(bs)) { - //if not already rushing to the base - if (bs->ltgtype != LTG_RUSHBASE) { - BotRefuseOrder(bs); - bs->ltgtype = LTG_RUSHBASE; - bs->teamgoal_time = FloatTime() + CTF_RUSHBASE_TIME; - bs->rushbaseaway_time = 0; - bs->decisionmaker = bs->client; - bs->ordered = qfalse; - BotSetTeamStatus(bs); - } - } -} - -#ifdef MISSIONPACK -/* -================== -Bot1FCTFSeekGoals -================== -*/ -void Bot1FCTFSeekGoals(bot_state_t *bs) { - aas_entityinfo_t entinfo; - float rnd, l1, l2; - int c; - - //when carrying a flag in ctf the bot should rush to the base - if (Bot1FCTFCarryingFlag(bs)) { - //if not already rushing to the base - if (bs->ltgtype != LTG_RUSHBASE) { - BotRefuseOrder(bs); - bs->ltgtype = LTG_RUSHBASE; - bs->teamgoal_time = FloatTime() + CTF_RUSHBASE_TIME; - bs->rushbaseaway_time = 0; - bs->decisionmaker = bs->client; - bs->ordered = qfalse; - //get an alternative route goal towards the enemy base - BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs)); - // - BotSetTeamStatus(bs); - BotVoiceChat(bs, -1, VOICECHAT_IHAVEFLAG); - } - return; - } - // if the bot decided to follow someone - if ( bs->ltgtype == LTG_TEAMACCOMPANY && !bs->ordered ) { - // if the team mate being accompanied no longer carries the flag - BotEntityInfo(bs->teammate, &entinfo); - if (!EntityCarriesFlag(&entinfo)) { - bs->ltgtype = 0; - } - } - //our team has the flag - if (bs->neutralflagstatus == 1) { - if (bs->owndecision_time < FloatTime()) { - // if not already following someone - if (bs->ltgtype != LTG_TEAMACCOMPANY) { - //if there is a visible team mate flag carrier - c = BotTeamFlagCarrierVisible(bs); - if (c >= 0) { - BotRefuseOrder(bs); - //follow the flag carrier - bs->decisionmaker = bs->client; - bs->ordered = qfalse; - //the team mate - bs->teammate = c; - //last time the team mate was visible - bs->teammatevisible_time = FloatTime(); - //no message - bs->teammessage_time = 0; - //no arrive message - bs->arrive_time = 1; - // - BotVoiceChat(bs, bs->teammate, VOICECHAT_ONFOLLOW); - //get the team goal time - bs->teamgoal_time = FloatTime() + TEAM_ACCOMPANY_TIME; - bs->ltgtype = LTG_TEAMACCOMPANY; - bs->formation_dist = 3.5 * 32; //3.5 meter - BotSetTeamStatus(bs); - bs->owndecision_time = FloatTime() + 5; - return; - } - } - //if already a CTF or team goal - if (bs->ltgtype == LTG_TEAMHELP || - bs->ltgtype == LTG_TEAMACCOMPANY || - bs->ltgtype == LTG_DEFENDKEYAREA || - bs->ltgtype == LTG_GETFLAG || - bs->ltgtype == LTG_RUSHBASE || - bs->ltgtype == LTG_CAMPORDER || - bs->ltgtype == LTG_PATROL || - bs->ltgtype == LTG_ATTACKENEMYBASE || - bs->ltgtype == LTG_GETITEM || - bs->ltgtype == LTG_MAKELOVE_UNDER || - bs->ltgtype == LTG_MAKELOVE_ONTOP) { - return; - } - //if not already attacking the enemy base - if (bs->ltgtype != LTG_ATTACKENEMYBASE) { - BotRefuseOrder(bs); - bs->decisionmaker = bs->client; - bs->ordered = qfalse; - // - if (BotTeam(bs) == TEAM_RED) memcpy(&bs->teamgoal, &ctf_blueflag, sizeof(bot_goal_t)); - else memcpy(&bs->teamgoal, &ctf_redflag, sizeof(bot_goal_t)); - //set the ltg type - bs->ltgtype = LTG_ATTACKENEMYBASE; - //set the time the bot will stop getting the flag - bs->teamgoal_time = FloatTime() + TEAM_ATTACKENEMYBASE_TIME; - BotSetTeamStatus(bs); - bs->owndecision_time = FloatTime() + 5; - } - } - return; - } - //enemy team has the flag - else if (bs->neutralflagstatus == 2) { - if (bs->owndecision_time < FloatTime()) { - c = BotEnemyFlagCarrierVisible(bs); - if (c >= 0) { - //FIXME: attack enemy flag carrier - } - //if already a CTF or team goal - if (bs->ltgtype == LTG_TEAMHELP || - bs->ltgtype == LTG_TEAMACCOMPANY || - bs->ltgtype == LTG_CAMPORDER || - bs->ltgtype == LTG_PATROL || - bs->ltgtype == LTG_GETITEM) { - return; - } - // if not already defending the base - if (bs->ltgtype != LTG_DEFENDKEYAREA) { - BotRefuseOrder(bs); - bs->decisionmaker = bs->client; - bs->ordered = qfalse; - // - if (BotTeam(bs) == TEAM_RED) memcpy(&bs->teamgoal, &ctf_redflag, sizeof(bot_goal_t)); - else memcpy(&bs->teamgoal, &ctf_blueflag, sizeof(bot_goal_t)); - //set the ltg type - bs->ltgtype = LTG_DEFENDKEYAREA; - //set the time the bot stops defending the base - bs->teamgoal_time = FloatTime() + TEAM_DEFENDKEYAREA_TIME; - bs->defendaway_time = 0; - BotSetTeamStatus(bs); - bs->owndecision_time = FloatTime() + 5; - } - } - return; - } - // don't just do something wait for the bot team leader to give orders - if (BotTeamLeader(bs)) { - return; - } - // if the bot is ordered to do something - if ( bs->lastgoal_ltgtype ) { - bs->teamgoal_time += 60; - } - // if the bot decided to do something on it's own and has a last ordered goal - if ( !bs->ordered && bs->lastgoal_ltgtype ) { - bs->ltgtype = 0; - } - //if already a CTF or team goal - if (bs->ltgtype == LTG_TEAMHELP || - bs->ltgtype == LTG_TEAMACCOMPANY || - bs->ltgtype == LTG_DEFENDKEYAREA || - bs->ltgtype == LTG_GETFLAG || - bs->ltgtype == LTG_RUSHBASE || - bs->ltgtype == LTG_RETURNFLAG || - bs->ltgtype == LTG_CAMPORDER || - bs->ltgtype == LTG_PATROL || - bs->ltgtype == LTG_ATTACKENEMYBASE || - bs->ltgtype == LTG_GETITEM || - bs->ltgtype == LTG_MAKELOVE_UNDER || - bs->ltgtype == LTG_MAKELOVE_ONTOP) { - return; - } - // - if (BotSetLastOrderedTask(bs)) - return; - // - if (bs->owndecision_time > FloatTime()) - return;; - //if the bot is roaming - if (bs->ctfroam_time > FloatTime()) - return; - //if the bot has anough aggression to decide what to do - if (BotAggression(bs) < 50) - return; - //set the time to send a message to the team mates - bs->teammessage_time = FloatTime() + 2 * random(); - // - if (bs->teamtaskpreference & (TEAMTP_ATTACKER|TEAMTP_DEFENDER)) { - if (bs->teamtaskpreference & TEAMTP_ATTACKER) { - l1 = 0.7f; - } - else { - l1 = 0.2f; - } - l2 = 0.9f; - } - else { - l1 = 0.4f; - l2 = 0.7f; - } - //get the flag or defend the base - rnd = random(); - if (rnd < l1 && ctf_neutralflag.areanum) { - bs->decisionmaker = bs->client; - bs->ordered = qfalse; - bs->ltgtype = LTG_GETFLAG; - //set the time the bot will stop getting the flag - bs->teamgoal_time = FloatTime() + CTF_GETFLAG_TIME; - BotSetTeamStatus(bs); - } - else if (rnd < l2 && ctf_redflag.areanum && ctf_blueflag.areanum) { - bs->decisionmaker = bs->client; - bs->ordered = qfalse; - // - if (BotTeam(bs) == TEAM_RED) memcpy(&bs->teamgoal, &ctf_redflag, sizeof(bot_goal_t)); - else memcpy(&bs->teamgoal, &ctf_blueflag, sizeof(bot_goal_t)); - //set the ltg type - bs->ltgtype = LTG_DEFENDKEYAREA; - //set the time the bot stops defending the base - bs->teamgoal_time = FloatTime() + TEAM_DEFENDKEYAREA_TIME; - bs->defendaway_time = 0; - BotSetTeamStatus(bs); - } - else { - bs->ltgtype = 0; - //set the time the bot will stop roaming - bs->ctfroam_time = FloatTime() + CTF_ROAM_TIME; - BotSetTeamStatus(bs); - } - bs->owndecision_time = FloatTime() + 5; -#ifdef DEBUG - BotPrintTeamGoal(bs); -#endif //DEBUG -} - -/* -================== -Bot1FCTFRetreatGoals -================== -*/ -void Bot1FCTFRetreatGoals(bot_state_t *bs) { - //when carrying a flag in ctf the bot should rush to the enemy base - if (Bot1FCTFCarryingFlag(bs)) { - //if not already rushing to the base - if (bs->ltgtype != LTG_RUSHBASE) { - BotRefuseOrder(bs); - bs->ltgtype = LTG_RUSHBASE; - bs->teamgoal_time = FloatTime() + CTF_RUSHBASE_TIME; - bs->rushbaseaway_time = 0; - bs->decisionmaker = bs->client; - bs->ordered = qfalse; - //get an alternative route goal towards the enemy base - BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs)); - BotSetTeamStatus(bs); - } - } -} - -/* -================== -BotObeliskSeekGoals -================== -*/ -void BotObeliskSeekGoals(bot_state_t *bs) { - float rnd, l1, l2; - - // don't just do something wait for the bot team leader to give orders - if (BotTeamLeader(bs)) { - return; - } - // if the bot is ordered to do something - if ( bs->lastgoal_ltgtype ) { - bs->teamgoal_time += 60; - } - //if already a team goal - if (bs->ltgtype == LTG_TEAMHELP || - bs->ltgtype == LTG_TEAMACCOMPANY || - bs->ltgtype == LTG_DEFENDKEYAREA || - bs->ltgtype == LTG_GETFLAG || - bs->ltgtype == LTG_RUSHBASE || - bs->ltgtype == LTG_RETURNFLAG || - bs->ltgtype == LTG_CAMPORDER || - bs->ltgtype == LTG_PATROL || - bs->ltgtype == LTG_ATTACKENEMYBASE || - bs->ltgtype == LTG_GETITEM || - bs->ltgtype == LTG_MAKELOVE_UNDER || - bs->ltgtype == LTG_MAKELOVE_ONTOP) { - return; - } - // - if (BotSetLastOrderedTask(bs)) - return; - //if the bot is roaming - if (bs->ctfroam_time > FloatTime()) - return; - //if the bot has anough aggression to decide what to do - if (BotAggression(bs) < 50) - return; - //set the time to send a message to the team mates - bs->teammessage_time = FloatTime() + 2 * random(); - // - if (bs->teamtaskpreference & (TEAMTP_ATTACKER|TEAMTP_DEFENDER)) { - if (bs->teamtaskpreference & TEAMTP_ATTACKER) { - l1 = 0.7f; - } - else { - l1 = 0.2f; - } - l2 = 0.9f; - } - else { - l1 = 0.4f; - l2 = 0.7f; - } - //get the flag or defend the base - rnd = random(); - if (rnd < l1 && redobelisk.areanum && blueobelisk.areanum) { - bs->decisionmaker = bs->client; - bs->ordered = qfalse; - // - if (BotTeam(bs) == TEAM_RED) memcpy(&bs->teamgoal, &blueobelisk, sizeof(bot_goal_t)); - else memcpy(&bs->teamgoal, &redobelisk, sizeof(bot_goal_t)); - //set the ltg type - bs->ltgtype = LTG_ATTACKENEMYBASE; - //set the time the bot will stop attacking the enemy base - bs->teamgoal_time = FloatTime() + TEAM_ATTACKENEMYBASE_TIME; - //get an alternate route goal towards the enemy base - BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs)); - BotSetTeamStatus(bs); - } - else if (rnd < l2 && redobelisk.areanum && blueobelisk.areanum) { - bs->decisionmaker = bs->client; - bs->ordered = qfalse; - // - if (BotTeam(bs) == TEAM_RED) memcpy(&bs->teamgoal, &redobelisk, sizeof(bot_goal_t)); - else memcpy(&bs->teamgoal, &blueobelisk, sizeof(bot_goal_t)); - //set the ltg type - bs->ltgtype = LTG_DEFENDKEYAREA; - //set the time the bot stops defending the base - bs->teamgoal_time = FloatTime() + TEAM_DEFENDKEYAREA_TIME; - bs->defendaway_time = 0; - BotSetTeamStatus(bs); - } - else { - bs->ltgtype = 0; - //set the time the bot will stop roaming - bs->ctfroam_time = FloatTime() + CTF_ROAM_TIME; - BotSetTeamStatus(bs); - } -} - -/* -================== -BotGoHarvest -================== -*/ -void BotGoHarvest(bot_state_t *bs) { - // - if (BotTeam(bs) == TEAM_RED) memcpy(&bs->teamgoal, &blueobelisk, sizeof(bot_goal_t)); - else memcpy(&bs->teamgoal, &redobelisk, sizeof(bot_goal_t)); - //set the ltg type - bs->ltgtype = LTG_HARVEST; - //set the time the bot will stop harvesting - bs->teamgoal_time = FloatTime() + TEAM_HARVEST_TIME; - bs->harvestaway_time = 0; - BotSetTeamStatus(bs); -} - -/* -================== -BotObeliskRetreatGoals -================== -*/ -void BotObeliskRetreatGoals(bot_state_t *bs) { - //nothing special -} - -/* -================== -BotHarvesterSeekGoals -================== -*/ -void BotHarvesterSeekGoals(bot_state_t *bs) { - aas_entityinfo_t entinfo; - float rnd, l1, l2; - int c; - - //when carrying cubes in harvester the bot should rush to the base - if (BotHarvesterCarryingCubes(bs)) { - //if not already rushing to the base - if (bs->ltgtype != LTG_RUSHBASE) { - BotRefuseOrder(bs); - bs->ltgtype = LTG_RUSHBASE; - bs->teamgoal_time = FloatTime() + CTF_RUSHBASE_TIME; - bs->rushbaseaway_time = 0; - bs->decisionmaker = bs->client; - bs->ordered = qfalse; - //get an alternative route goal towards the enemy base - BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs)); - // - BotSetTeamStatus(bs); - } - return; - } - // don't just do something wait for the bot team leader to give orders - if (BotTeamLeader(bs)) { - return; - } - // if the bot decided to follow someone - if ( bs->ltgtype == LTG_TEAMACCOMPANY && !bs->ordered ) { - // if the team mate being accompanied no longer carries the flag - BotEntityInfo(bs->teammate, &entinfo); - if (!EntityCarriesCubes(&entinfo)) { - bs->ltgtype = 0; - } - } - // if the bot is ordered to do something - if ( bs->lastgoal_ltgtype ) { - bs->teamgoal_time += 60; - } - //if not yet doing something - if (bs->ltgtype == LTG_TEAMHELP || - bs->ltgtype == LTG_TEAMACCOMPANY || - bs->ltgtype == LTG_DEFENDKEYAREA || - bs->ltgtype == LTG_GETFLAG || - bs->ltgtype == LTG_CAMPORDER || - bs->ltgtype == LTG_PATROL || - bs->ltgtype == LTG_ATTACKENEMYBASE || - bs->ltgtype == LTG_HARVEST || - bs->ltgtype == LTG_GETITEM || - bs->ltgtype == LTG_MAKELOVE_UNDER || - bs->ltgtype == LTG_MAKELOVE_ONTOP) { - return; - } - // - if (BotSetLastOrderedTask(bs)) - return; - //if the bot is roaming - if (bs->ctfroam_time > FloatTime()) - return; - //if the bot has anough aggression to decide what to do - if (BotAggression(bs) < 50) - return; - //set the time to send a message to the team mates - bs->teammessage_time = FloatTime() + 2 * random(); - // - c = BotEnemyCubeCarrierVisible(bs); - if (c >= 0) { - //FIXME: attack enemy cube carrier - } - if (bs->ltgtype != LTG_TEAMACCOMPANY) { - //if there is a visible team mate carrying cubes - c = BotTeamCubeCarrierVisible(bs); - if (c >= 0) { - //follow the team mate carrying cubes - bs->decisionmaker = bs->client; - bs->ordered = qfalse; - //the team mate - bs->teammate = c; - //last time the team mate was visible - bs->teammatevisible_time = FloatTime(); - //no message - bs->teammessage_time = 0; - //no arrive message - bs->arrive_time = 1; - // - BotVoiceChat(bs, bs->teammate, VOICECHAT_ONFOLLOW); - //get the team goal time - bs->teamgoal_time = FloatTime() + TEAM_ACCOMPANY_TIME; - bs->ltgtype = LTG_TEAMACCOMPANY; - bs->formation_dist = 3.5 * 32; //3.5 meter - BotSetTeamStatus(bs); - return; - } - } - // - if (bs->teamtaskpreference & (TEAMTP_ATTACKER|TEAMTP_DEFENDER)) { - if (bs->teamtaskpreference & TEAMTP_ATTACKER) { - l1 = 0.7f; - } - else { - l1 = 0.2f; - } - l2 = 0.9f; - } - else { - l1 = 0.4f; - l2 = 0.7f; - } - // - rnd = random(); - if (rnd < l1 && redobelisk.areanum && blueobelisk.areanum) { - bs->decisionmaker = bs->client; - bs->ordered = qfalse; - BotGoHarvest(bs); - } - else if (rnd < l2 && redobelisk.areanum && blueobelisk.areanum) { - bs->decisionmaker = bs->client; - bs->ordered = qfalse; - // - if (BotTeam(bs) == TEAM_RED) memcpy(&bs->teamgoal, &redobelisk, sizeof(bot_goal_t)); - else memcpy(&bs->teamgoal, &blueobelisk, sizeof(bot_goal_t)); - //set the ltg type - bs->ltgtype = LTG_DEFENDKEYAREA; - //set the time the bot stops defending the base - bs->teamgoal_time = FloatTime() + TEAM_DEFENDKEYAREA_TIME; - bs->defendaway_time = 0; - BotSetTeamStatus(bs); - } - else { - bs->ltgtype = 0; - //set the time the bot will stop roaming - bs->ctfroam_time = FloatTime() + CTF_ROAM_TIME; - BotSetTeamStatus(bs); - } -} - -/* -================== -BotHarvesterRetreatGoals -================== -*/ -void BotHarvesterRetreatGoals(bot_state_t *bs) { - //when carrying cubes in harvester the bot should rush to the base - if (BotHarvesterCarryingCubes(bs)) { - //if not already rushing to the base - if (bs->ltgtype != LTG_RUSHBASE) { - BotRefuseOrder(bs); - bs->ltgtype = LTG_RUSHBASE; - bs->teamgoal_time = FloatTime() + CTF_RUSHBASE_TIME; - bs->rushbaseaway_time = 0; - bs->decisionmaker = bs->client; - bs->ordered = qfalse; - BotSetTeamStatus(bs); - } - return; - } -} -#endif - -/* -================== -BotTeamGoals -================== -*/ -void BotTeamGoals(bot_state_t *bs, int retreat) { - - if ( retreat ) { - if (gametype == GT_CTF) { - BotCTFRetreatGoals(bs); - } -#ifdef MISSIONPACK - else if (gametype == GT_1FCTF) { - Bot1FCTFRetreatGoals(bs); - } - else if (gametype == GT_OBELISK) { - BotObeliskRetreatGoals(bs); - } - else if (gametype == GT_HARVESTER) { - BotHarvesterRetreatGoals(bs); - } -#endif - } - else { - if (gametype == GT_CTF) { - //decide what to do in CTF mode - BotCTFSeekGoals(bs); - } -#ifdef MISSIONPACK - else if (gametype == GT_1FCTF) { - Bot1FCTFSeekGoals(bs); - } - else if (gametype == GT_OBELISK) { - BotObeliskSeekGoals(bs); - } - else if (gametype == GT_HARVESTER) { - BotHarvesterSeekGoals(bs); - } -#endif - } - // reset the order time which is used to see if - // we decided to refuse an order - bs->order_time = 0; -} - -/* -================== -BotPointAreaNum -================== -*/ -int BotPointAreaNum(vec3_t origin) { - int areanum, numareas, areas[10]; - vec3_t end; - - areanum = trap_AAS_PointAreaNum(origin); - if (areanum) return areanum; - VectorCopy(origin, end); - end[2] += 10; - numareas = trap_AAS_TraceAreas(origin, end, areas, NULL, 10); - if (numareas > 0) return areas[0]; - return 0; -} - -/* -================== -ClientName -================== -*/ -char *ClientName(int client, char *name, int size) { - char buf[MAX_INFO_STRING]; - - if (client < 0 || client >= MAX_CLIENTS) { - BotAI_Print(PRT_ERROR, "ClientName: client out of range\n"); - return "[client out of range]"; - } - trap_GetConfigstring(CS_PLAYERS+client, buf, sizeof(buf)); - strncpy(name, Info_ValueForKey(buf, "n"), size-1); - name[size-1] = '\0'; - Q_CleanStr( name ); - return name; -} - -/* -================== -ClientSkin -================== -*/ -char *ClientSkin(int client, char *skin, int size) { - char buf[MAX_INFO_STRING]; - - if (client < 0 || client >= MAX_CLIENTS) { - BotAI_Print(PRT_ERROR, "ClientSkin: client out of range\n"); - return "[client out of range]"; - } - trap_GetConfigstring(CS_PLAYERS+client, buf, sizeof(buf)); - strncpy(skin, Info_ValueForKey(buf, "model"), size-1); - skin[size-1] = '\0'; - return skin; -} - -/* -================== -ClientFromName -================== -*/ -int ClientFromName(char *name) { - int i; - char buf[MAX_INFO_STRING]; - static int maxclients; - - if (!maxclients) - maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { - trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); - Q_CleanStr( buf ); - if (!Q_stricmp(Info_ValueForKey(buf, "n"), name)) return i; - } - return -1; -} - -/* -================== -ClientOnSameTeamFromName -================== -*/ -int ClientOnSameTeamFromName(bot_state_t *bs, char *name) { - int i; - char buf[MAX_INFO_STRING]; - static int maxclients; - - if (!maxclients) - maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { - if (!BotSameTeam(bs, i)) - continue; - trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); - Q_CleanStr( buf ); - if (!Q_stricmp(Info_ValueForKey(buf, "n"), name)) return i; - } - return -1; -} - -/* -================== -stristr -================== -*/ -char *stristr(char *str, char *charset) { - int i; - - while(*str) { - for (i = 0; charset[i] && str[i]; i++) { - if (toupper(charset[i]) != toupper(str[i])) break; - } - if (!charset[i]) return str; - str++; - } - return NULL; -} - -/* -================== -EasyClientName -================== -*/ -char *EasyClientName(int client, char *buf, int size) { - int i; - char *str1, *str2, *ptr, c; - char name[128]; - - strcpy(name, ClientName(client, name, sizeof(name))); - for (i = 0; name[i]; i++) name[i] &= 127; - //remove all spaces - for (ptr = strstr(name, " "); ptr; ptr = strstr(name, " ")) { - memmove(ptr, ptr+1, strlen(ptr+1)+1); - } - //check for [x] and ]x[ clan names - str1 = strstr(name, "["); - str2 = strstr(name, "]"); - if (str1 && str2) { - if (str2 > str1) memmove(str1, str2+1, strlen(str2+1)+1); - else memmove(str2, str1+1, strlen(str1+1)+1); - } - //remove Mr prefix - if ((name[0] == 'm' || name[0] == 'M') && - (name[1] == 'r' || name[1] == 'R')) { - memmove(name, name+2, strlen(name+2)+1); - } - //only allow lower case alphabet characters - ptr = name; - while(*ptr) { - c = *ptr; - if ((c >= 'a' && c <= 'z') || - (c >= '0' && c <= '9') || c == '_') { - ptr++; - } - else if (c >= 'A' && c <= 'Z') { - *ptr += 'a' - 'A'; - ptr++; - } - else { - memmove(ptr, ptr+1, strlen(ptr + 1)+1); - } - } - strncpy(buf, name, size-1); - buf[size-1] = '\0'; - return buf; -} - -/* -================== -BotSynonymContext -================== -*/ -int BotSynonymContext(bot_state_t *bs) { - int context; - - context = CONTEXT_NORMAL|CONTEXT_NEARBYITEM|CONTEXT_NAMES; - // - if (gametype == GT_CTF -#ifdef MISSIONPACK - || gametype == GT_1FCTF -#endif - ) { - if (BotTeam(bs) == TEAM_RED) context |= CONTEXT_CTFREDTEAM; - else context |= CONTEXT_CTFBLUETEAM; - } -#ifdef MISSIONPACK - else if (gametype == GT_OBELISK) { - if (BotTeam(bs) == TEAM_RED) context |= CONTEXT_OBELISKREDTEAM; - else context |= CONTEXT_OBELISKBLUETEAM; - } - else if (gametype == GT_HARVESTER) { - if (BotTeam(bs) == TEAM_RED) context |= CONTEXT_HARVESTERREDTEAM; - else context |= CONTEXT_HARVESTERBLUETEAM; - } -#endif - return context; -} - -/* -================== -BotChooseWeapon -================== -*/ -void BotChooseWeapon(bot_state_t *bs) { - int newweaponnum; - - if (bs->cur_ps.weaponstate == WEAPON_RAISING || - bs->cur_ps.weaponstate == WEAPON_DROPPING) { - trap_EA_SelectWeapon(bs->client, bs->weaponnum); - } - else { - newweaponnum = trap_BotChooseBestFightWeapon(bs->ws, bs->inventory); - if (bs->weaponnum != newweaponnum) bs->weaponchange_time = FloatTime(); - bs->weaponnum = newweaponnum; - //BotAI_Print(PRT_MESSAGE, "bs->weaponnum = %d\n", bs->weaponnum); - trap_EA_SelectWeapon(bs->client, bs->weaponnum); - } -} - -/* -================== -BotSetupForMovement -================== -*/ -void BotSetupForMovement(bot_state_t *bs) { - bot_initmove_t initmove; - - memset(&initmove, 0, sizeof(bot_initmove_t)); - VectorCopy(bs->cur_ps.origin, initmove.origin); - VectorCopy(bs->cur_ps.velocity, initmove.velocity); - VectorClear(initmove.viewoffset); - initmove.viewoffset[2] += bs->cur_ps.viewheight; - initmove.entitynum = bs->entitynum; - initmove.client = bs->client; - initmove.thinktime = bs->thinktime; - //set the onground flag - if (bs->cur_ps.groundEntityNum != ENTITYNUM_NONE) initmove.or_moveflags |= MFL_ONGROUND; - //set the teleported flag - if ((bs->cur_ps.pm_flags & PMF_TIME_KNOCKBACK) && (bs->cur_ps.pm_time > 0)) { - initmove.or_moveflags |= MFL_TELEPORTED; - } - //set the waterjump flag - if ((bs->cur_ps.pm_flags & PMF_TIME_WATERJUMP) && (bs->cur_ps.pm_time > 0)) { - initmove.or_moveflags |= MFL_WATERJUMP; - } - //set presence type - if (bs->cur_ps.pm_flags & PMF_DUCKED) initmove.presencetype = PRESENCE_CROUCH; - else initmove.presencetype = PRESENCE_NORMAL; - // - if (bs->walker > 0.5) initmove.or_moveflags |= MFL_WALK; - // - VectorCopy(bs->viewangles, initmove.viewangles); - // - trap_BotInitMoveState(bs->ms, &initmove); -} - -/* -================== -BotCheckItemPickup -================== -*/ -void BotCheckItemPickup(bot_state_t *bs, int *oldinventory) { -#ifdef MISSIONPACK - int offence, leader; - - if (gametype <= GT_TEAM) - return; - - offence = -1; - // go into offence if picked up the kamikaze or invulnerability - if (!oldinventory[INVENTORY_KAMIKAZE] && bs->inventory[INVENTORY_KAMIKAZE] >= 1) { - offence = qtrue; - } - if (!oldinventory[INVENTORY_INVULNERABILITY] && bs->inventory[INVENTORY_INVULNERABILITY] >= 1) { - offence = qtrue; - } - // if not already wearing the kamikaze or invulnerability - if (!bs->inventory[INVENTORY_KAMIKAZE] && !bs->inventory[INVENTORY_INVULNERABILITY]) { - if (!oldinventory[INVENTORY_SCOUT] && bs->inventory[INVENTORY_SCOUT] >= 1) { - offence = qtrue; - } - if (!oldinventory[INVENTORY_GUARD] && bs->inventory[INVENTORY_GUARD] >= 1) { - offence = qtrue; - } - if (!oldinventory[INVENTORY_DOUBLER] && bs->inventory[INVENTORY_DOUBLER] >= 1) { - offence = qfalse; - } - if (!oldinventory[INVENTORY_AMMOREGEN] && bs->inventory[INVENTORY_AMMOREGEN] >= 1) { - offence = qfalse; - } - } - - if (offence >= 0) { - leader = ClientFromName(bs->teamleader); - if (offence) { - if (!(bs->teamtaskpreference & TEAMTP_ATTACKER)) { - // if we have a bot team leader - if (BotTeamLeader(bs)) { - // tell the leader we want to be on offence - BotVoiceChat(bs, leader, VOICECHAT_WANTONOFFENSE); - //BotAI_BotInitialChat(bs, "wantoffence", NULL); - //trap_BotEnterChat(bs->cs, leader, CHAT_TELL); - } - else if (g_spSkill.integer <= 3) { - if ( bs->ltgtype != LTG_GETFLAG && - bs->ltgtype != LTG_ATTACKENEMYBASE && - bs->ltgtype != LTG_HARVEST ) { - // - if ((gametype != GT_CTF || (bs->redflagstatus == 0 && bs->blueflagstatus == 0)) && - (gametype != GT_1FCTF || bs->neutralflagstatus == 0) ) { - // tell the leader we want to be on offence - BotVoiceChat(bs, leader, VOICECHAT_WANTONOFFENSE); - //BotAI_BotInitialChat(bs, "wantoffence", NULL); - //trap_BotEnterChat(bs->cs, leader, CHAT_TELL); - } - } - bs->teamtaskpreference |= TEAMTP_ATTACKER; - } - } - bs->teamtaskpreference &= ~TEAMTP_DEFENDER; - } - else { - if (!(bs->teamtaskpreference & TEAMTP_DEFENDER)) { - // if we have a bot team leader - if (BotTeamLeader(bs)) { - // tell the leader we want to be on defense - BotVoiceChat(bs, -1, VOICECHAT_WANTONDEFENSE); - //BotAI_BotInitialChat(bs, "wantdefence", NULL); - //trap_BotEnterChat(bs->cs, leader, CHAT_TELL); - } - else if (g_spSkill.integer <= 3) { - if ( bs->ltgtype != LTG_DEFENDKEYAREA ) { - // - if ((gametype != GT_CTF || (bs->redflagstatus == 0 && bs->blueflagstatus == 0)) && - (gametype != GT_1FCTF || bs->neutralflagstatus == 0) ) { - // tell the leader we want to be on defense - BotVoiceChat(bs, -1, VOICECHAT_WANTONDEFENSE); - //BotAI_BotInitialChat(bs, "wantdefence", NULL); - //trap_BotEnterChat(bs->cs, leader, CHAT_TELL); - } - } - } - bs->teamtaskpreference |= TEAMTP_DEFENDER; - } - bs->teamtaskpreference &= ~TEAMTP_ATTACKER; - } - } -#endif -} - -/* -================== -BotUpdateInventory -================== -*/ -void BotUpdateInventory(bot_state_t *bs) { - int oldinventory[MAX_ITEMS]; - - memcpy(oldinventory, bs->inventory, sizeof(oldinventory)); - //armor - bs->inventory[INVENTORY_ARMOR] = bs->cur_ps.stats[STAT_ARMOR]; - //weapons - bs->inventory[INVENTORY_GAUNTLET] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_GAUNTLET)) != 0; - bs->inventory[INVENTORY_SHOTGUN] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_SHOTGUN)) != 0; - bs->inventory[INVENTORY_MACHINEGUN] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_MACHINEGUN)) != 0; - bs->inventory[INVENTORY_GRENADELAUNCHER] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_GRENADE_LAUNCHER)) != 0; - bs->inventory[INVENTORY_ROCKETLAUNCHER] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_ROCKET_LAUNCHER)) != 0; - bs->inventory[INVENTORY_LIGHTNING] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_LIGHTNING)) != 0; - bs->inventory[INVENTORY_RAILGUN] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_RAILGUN)) != 0; - bs->inventory[INVENTORY_PLASMAGUN] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_PLASMAGUN)) != 0; - bs->inventory[INVENTORY_BFG10K] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_BFG)) != 0; - bs->inventory[INVENTORY_GRAPPLINGHOOK] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_GRAPPLING_HOOK)) != 0; -#ifdef MISSIONPACK - bs->inventory[INVENTORY_NAILGUN] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_NAILGUN)) != 0;; - bs->inventory[INVENTORY_PROXLAUNCHER] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_PROX_LAUNCHER)) != 0;; - bs->inventory[INVENTORY_CHAINGUN] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_CHAINGUN)) != 0;; -#endif - //ammo - bs->inventory[INVENTORY_SHELLS] = bs->cur_ps.ammo[WP_SHOTGUN]; - bs->inventory[INVENTORY_BULLETS] = bs->cur_ps.ammo[WP_MACHINEGUN]; - bs->inventory[INVENTORY_GRENADES] = bs->cur_ps.ammo[WP_GRENADE_LAUNCHER]; - bs->inventory[INVENTORY_CELLS] = bs->cur_ps.ammo[WP_PLASMAGUN]; - bs->inventory[INVENTORY_LIGHTNINGAMMO] = bs->cur_ps.ammo[WP_LIGHTNING]; - bs->inventory[INVENTORY_ROCKETS] = bs->cur_ps.ammo[WP_ROCKET_LAUNCHER]; - bs->inventory[INVENTORY_SLUGS] = bs->cur_ps.ammo[WP_RAILGUN]; - bs->inventory[INVENTORY_BFGAMMO] = bs->cur_ps.ammo[WP_BFG]; -#ifdef MISSIONPACK - bs->inventory[INVENTORY_NAILS] = bs->cur_ps.ammo[WP_NAILGUN]; - bs->inventory[INVENTORY_MINES] = bs->cur_ps.ammo[WP_PROX_LAUNCHER]; - bs->inventory[INVENTORY_BELT] = bs->cur_ps.ammo[WP_CHAINGUN]; -#endif - //powerups - bs->inventory[INVENTORY_HEALTH] = bs->cur_ps.stats[STAT_HEALTH]; - bs->inventory[INVENTORY_TELEPORTER] = bs->cur_ps.stats[STAT_HOLDABLE_ITEM] == MODELINDEX_TELEPORTER; - bs->inventory[INVENTORY_MEDKIT] = bs->cur_ps.stats[STAT_HOLDABLE_ITEM] == MODELINDEX_MEDKIT; -#ifdef MISSIONPACK - bs->inventory[INVENTORY_KAMIKAZE] = bs->cur_ps.stats[STAT_HOLDABLE_ITEM] == MODELINDEX_KAMIKAZE; - bs->inventory[INVENTORY_PORTAL] = bs->cur_ps.stats[STAT_HOLDABLE_ITEM] == MODELINDEX_PORTAL; - bs->inventory[INVENTORY_INVULNERABILITY] = bs->cur_ps.stats[STAT_HOLDABLE_ITEM] == MODELINDEX_INVULNERABILITY; -#endif - bs->inventory[INVENTORY_QUAD] = bs->cur_ps.powerups[PW_QUAD] != 0; - bs->inventory[INVENTORY_ENVIRONMENTSUIT] = bs->cur_ps.powerups[PW_BATTLESUIT] != 0; - bs->inventory[INVENTORY_HASTE] = bs->cur_ps.powerups[PW_HASTE] != 0; - bs->inventory[INVENTORY_INVISIBILITY] = bs->cur_ps.powerups[PW_INVIS] != 0; - bs->inventory[INVENTORY_REGEN] = bs->cur_ps.powerups[PW_REGEN] != 0; - bs->inventory[INVENTORY_FLIGHT] = bs->cur_ps.powerups[PW_FLIGHT] != 0; -#ifdef MISSIONPACK - bs->inventory[INVENTORY_SCOUT] = bs->cur_ps.stats[STAT_PERSISTANT_POWERUP] == MODELINDEX_SCOUT; - bs->inventory[INVENTORY_GUARD] = bs->cur_ps.stats[STAT_PERSISTANT_POWERUP] == MODELINDEX_GUARD; - bs->inventory[INVENTORY_DOUBLER] = bs->cur_ps.stats[STAT_PERSISTANT_POWERUP] == MODELINDEX_DOUBLER; - bs->inventory[INVENTORY_AMMOREGEN] = bs->cur_ps.stats[STAT_PERSISTANT_POWERUP] == MODELINDEX_AMMOREGEN; -#endif - bs->inventory[INVENTORY_REDFLAG] = bs->cur_ps.powerups[PW_REDFLAG] != 0; - bs->inventory[INVENTORY_BLUEFLAG] = bs->cur_ps.powerups[PW_BLUEFLAG] != 0; -#ifdef MISSIONPACK - bs->inventory[INVENTORY_NEUTRALFLAG] = bs->cur_ps.powerups[PW_NEUTRALFLAG] != 0; - if (BotTeam(bs) == TEAM_RED) { - bs->inventory[INVENTORY_REDCUBE] = bs->cur_ps.generic1; - bs->inventory[INVENTORY_BLUECUBE] = 0; - } - else { - bs->inventory[INVENTORY_REDCUBE] = 0; - bs->inventory[INVENTORY_BLUECUBE] = bs->cur_ps.generic1; - } -#endif - BotCheckItemPickup(bs, oldinventory); -} - -/* -================== -BotUpdateBattleInventory -================== -*/ -void BotUpdateBattleInventory(bot_state_t *bs, int enemy) { - vec3_t dir; - aas_entityinfo_t entinfo; - - BotEntityInfo(enemy, &entinfo); - VectorSubtract(entinfo.origin, bs->origin, dir); - bs->inventory[ENEMY_HEIGHT] = (int) dir[2]; - dir[2] = 0; - bs->inventory[ENEMY_HORIZONTAL_DIST] = (int) VectorLength(dir); - //FIXME: add num visible enemies and num visible team mates to the inventory -} - -#ifdef MISSIONPACK -/* -================== -BotUseKamikaze -================== -*/ -#define KAMIKAZE_DIST 1024 - -void BotUseKamikaze(bot_state_t *bs) { - int c, teammates, enemies; - aas_entityinfo_t entinfo; - vec3_t dir, target; - bot_goal_t *goal; - bsp_trace_t trace; - - //if the bot has no kamikaze - if (bs->inventory[INVENTORY_KAMIKAZE] <= 0) - return; - if (bs->kamikaze_time > FloatTime()) - return; - bs->kamikaze_time = FloatTime() + 0.2; - if (gametype == GT_CTF) { - //never use kamikaze if the team flag carrier is visible - if (BotCTFCarryingFlag(bs)) - return; - c = BotTeamFlagCarrierVisible(bs); - if (c >= 0) { - BotEntityInfo(c, &entinfo); - VectorSubtract(entinfo.origin, bs->origin, dir); - if (VectorLengthSquared(dir) < Square(KAMIKAZE_DIST)) - return; - } - c = BotEnemyFlagCarrierVisible(bs); - if (c >= 0) { - BotEntityInfo(c, &entinfo); - VectorSubtract(entinfo.origin, bs->origin, dir); - if (VectorLengthSquared(dir) < Square(KAMIKAZE_DIST)) { - trap_EA_Use(bs->client); - return; - } - } - } - else if (gametype == GT_1FCTF) { - //never use kamikaze if the team flag carrier is visible - if (Bot1FCTFCarryingFlag(bs)) - return; - c = BotTeamFlagCarrierVisible(bs); - if (c >= 0) { - BotEntityInfo(c, &entinfo); - VectorSubtract(entinfo.origin, bs->origin, dir); - if (VectorLengthSquared(dir) < Square(KAMIKAZE_DIST)) - return; - } - c = BotEnemyFlagCarrierVisible(bs); - if (c >= 0) { - BotEntityInfo(c, &entinfo); - VectorSubtract(entinfo.origin, bs->origin, dir); - if (VectorLengthSquared(dir) < Square(KAMIKAZE_DIST)) { - trap_EA_Use(bs->client); - return; - } - } - } - else if (gametype == GT_OBELISK) { - switch(BotTeam(bs)) { - case TEAM_RED: goal = &blueobelisk; break; - default: goal = &redobelisk; break; - } - //if the obelisk is visible - VectorCopy(goal->origin, target); - target[2] += 1; - VectorSubtract(bs->origin, target, dir); - if (VectorLengthSquared(dir) < Square(KAMIKAZE_DIST * 0.9)) { - BotAI_Trace(&trace, bs->eye, NULL, NULL, target, bs->client, CONTENTS_SOLID); - if (trace.fraction >= 1 || trace.ent == goal->entitynum) { - trap_EA_Use(bs->client); - return; - } - } - } - else if (gametype == GT_HARVESTER) { - // - if (BotHarvesterCarryingCubes(bs)) - return; - //never use kamikaze if a team mate carrying cubes is visible - c = BotTeamCubeCarrierVisible(bs); - if (c >= 0) { - BotEntityInfo(c, &entinfo); - VectorSubtract(entinfo.origin, bs->origin, dir); - if (VectorLengthSquared(dir) < Square(KAMIKAZE_DIST)) - return; - } - c = BotEnemyCubeCarrierVisible(bs); - if (c >= 0) { - BotEntityInfo(c, &entinfo); - VectorSubtract(entinfo.origin, bs->origin, dir); - if (VectorLengthSquared(dir) < Square(KAMIKAZE_DIST)) { - trap_EA_Use(bs->client); - return; - } - } - } - // - BotVisibleTeamMatesAndEnemies(bs, &teammates, &enemies, KAMIKAZE_DIST); - // - if (enemies > 2 && enemies > teammates+1) { - trap_EA_Use(bs->client); - return; - } -} - -/* -================== -BotUseInvulnerability -================== -*/ -void BotUseInvulnerability(bot_state_t *bs) { - int c; - vec3_t dir, target; - bot_goal_t *goal; - bsp_trace_t trace; - - //if the bot has no invulnerability - if (bs->inventory[INVENTORY_INVULNERABILITY] <= 0) - return; - if (bs->invulnerability_time > FloatTime()) - return; - bs->invulnerability_time = FloatTime() + 0.2; - if (gametype == GT_CTF) { - //never use kamikaze if the team flag carrier is visible - if (BotCTFCarryingFlag(bs)) - return; - c = BotEnemyFlagCarrierVisible(bs); - if (c >= 0) - return; - //if near enemy flag and the flag is visible - switch(BotTeam(bs)) { - case TEAM_RED: goal = &ctf_blueflag; break; - default: goal = &ctf_redflag; break; - } - //if the obelisk is visible - VectorCopy(goal->origin, target); - target[2] += 1; - VectorSubtract(bs->origin, target, dir); - if (VectorLengthSquared(dir) < Square(200)) { - BotAI_Trace(&trace, bs->eye, NULL, NULL, target, bs->client, CONTENTS_SOLID); - if (trace.fraction >= 1 || trace.ent == goal->entitynum) { - trap_EA_Use(bs->client); - return; - } - } - } - else if (gametype == GT_1FCTF) { - //never use kamikaze if the team flag carrier is visible - if (Bot1FCTFCarryingFlag(bs)) - return; - c = BotEnemyFlagCarrierVisible(bs); - if (c >= 0) - return; - //if near enemy flag and the flag is visible - switch(BotTeam(bs)) { - case TEAM_RED: goal = &ctf_blueflag; break; - default: goal = &ctf_redflag; break; - } - //if the obelisk is visible - VectorCopy(goal->origin, target); - target[2] += 1; - VectorSubtract(bs->origin, target, dir); - if (VectorLengthSquared(dir) < Square(200)) { - BotAI_Trace(&trace, bs->eye, NULL, NULL, target, bs->client, CONTENTS_SOLID); - if (trace.fraction >= 1 || trace.ent == goal->entitynum) { - trap_EA_Use(bs->client); - return; - } - } - } - else if (gametype == GT_OBELISK) { - switch(BotTeam(bs)) { - case TEAM_RED: goal = &blueobelisk; break; - default: goal = &redobelisk; break; - } - //if the obelisk is visible - VectorCopy(goal->origin, target); - target[2] += 1; - VectorSubtract(bs->origin, target, dir); - if (VectorLengthSquared(dir) < Square(300)) { - BotAI_Trace(&trace, bs->eye, NULL, NULL, target, bs->client, CONTENTS_SOLID); - if (trace.fraction >= 1 || trace.ent == goal->entitynum) { - trap_EA_Use(bs->client); - return; - } - } - } - else if (gametype == GT_HARVESTER) { - // - if (BotHarvesterCarryingCubes(bs)) - return; - c = BotEnemyCubeCarrierVisible(bs); - if (c >= 0) - return; - //if near enemy base and enemy base is visible - switch(BotTeam(bs)) { - case TEAM_RED: goal = &blueobelisk; break; - default: goal = &redobelisk; break; - } - //if the obelisk is visible - VectorCopy(goal->origin, target); - target[2] += 1; - VectorSubtract(bs->origin, target, dir); - if (VectorLengthSquared(dir) < Square(200)) { - BotAI_Trace(&trace, bs->eye, NULL, NULL, target, bs->client, CONTENTS_SOLID); - if (trace.fraction >= 1 || trace.ent == goal->entitynum) { - trap_EA_Use(bs->client); - return; - } - } - } -} -#endif - -/* -================== -BotBattleUseItems -================== -*/ -void BotBattleUseItems(bot_state_t *bs) { - if (bs->inventory[INVENTORY_HEALTH] < 40) { - if (bs->inventory[INVENTORY_TELEPORTER] > 0) { - if (!BotCTFCarryingFlag(bs) -#ifdef MISSIONPACK - && !Bot1FCTFCarryingFlag(bs) - && !BotHarvesterCarryingCubes(bs) -#endif - ) { - trap_EA_Use(bs->client); - } - } - } - if (bs->inventory[INVENTORY_HEALTH] < 60) { - if (bs->inventory[INVENTORY_MEDKIT] > 0) { - trap_EA_Use(bs->client); - } - } -#ifdef MISSIONPACK - BotUseKamikaze(bs); - BotUseInvulnerability(bs); -#endif -} - -/* -================== -BotSetTeleportTime -================== -*/ -void BotSetTeleportTime(bot_state_t *bs) { - if ((bs->cur_ps.eFlags ^ bs->last_eFlags) & EF_TELEPORT_BIT) { - bs->teleport_time = FloatTime(); - } - bs->last_eFlags = bs->cur_ps.eFlags; -} - -/* -================== -BotIsDead -================== -*/ -qboolean BotIsDead(bot_state_t *bs) { - return (bs->cur_ps.pm_type == PM_DEAD); -} - -/* -================== -BotIsObserver -================== -*/ -qboolean BotIsObserver(bot_state_t *bs) { - char buf[MAX_INFO_STRING]; - if (bs->cur_ps.pm_type == PM_SPECTATOR) return qtrue; - trap_GetConfigstring(CS_PLAYERS+bs->client, buf, sizeof(buf)); - if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) return qtrue; - return qfalse; -} - -/* -================== -BotIntermission -================== -*/ -qboolean BotIntermission(bot_state_t *bs) { - //NOTE: we shouldn't be looking at the game code... - if (level.intermissiontime) return qtrue; - return (bs->cur_ps.pm_type == PM_FREEZE || bs->cur_ps.pm_type == PM_INTERMISSION); -} - -/* -================== -BotInLavaOrSlime -================== -*/ -qboolean BotInLavaOrSlime(bot_state_t *bs) { - vec3_t feet; - - VectorCopy(bs->origin, feet); - feet[2] -= 23; - return (trap_AAS_PointContents(feet) & (CONTENTS_LAVA|CONTENTS_SLIME)); -} - -/* -================== -BotCreateWayPoint -================== -*/ -bot_waypoint_t *BotCreateWayPoint(char *name, vec3_t origin, int areanum) { - bot_waypoint_t *wp; - vec3_t waypointmins = {-8, -8, -8}, waypointmaxs = {8, 8, 8}; - - wp = botai_freewaypoints; - if ( !wp ) { - BotAI_Print( PRT_WARNING, "BotCreateWayPoint: Out of waypoints\n" ); - return NULL; - } - botai_freewaypoints = botai_freewaypoints->next; - - Q_strncpyz( wp->name, name, sizeof(wp->name) ); - VectorCopy(origin, wp->goal.origin); - VectorCopy(waypointmins, wp->goal.mins); - VectorCopy(waypointmaxs, wp->goal.maxs); - wp->goal.areanum = areanum; - wp->next = NULL; - wp->prev = NULL; - return wp; -} - -/* -================== -BotFindWayPoint -================== -*/ -bot_waypoint_t *BotFindWayPoint(bot_waypoint_t *waypoints, char *name) { - bot_waypoint_t *wp; - - for (wp = waypoints; wp; wp = wp->next) { - if (!Q_stricmp(wp->name, name)) return wp; - } - return NULL; -} - -/* -================== -BotFreeWaypoints -================== -*/ -void BotFreeWaypoints(bot_waypoint_t *wp) { - bot_waypoint_t *nextwp; - - for (; wp; wp = nextwp) { - nextwp = wp->next; - wp->next = botai_freewaypoints; - botai_freewaypoints = wp; - } -} - -/* -================== -BotInitWaypoints -================== -*/ -void BotInitWaypoints(void) { - int i; - - botai_freewaypoints = NULL; - for (i = 0; i < MAX_WAYPOINTS; i++) { - botai_waypoints[i].next = botai_freewaypoints; - botai_freewaypoints = &botai_waypoints[i]; - } -} - -/* -================== -TeamPlayIsOn -================== -*/ -int TeamPlayIsOn(void) { - return ( gametype >= GT_TEAM ); -} - -/* -================== -BotAggression -================== -*/ -float BotAggression(bot_state_t *bs) { - //if the bot has quad - if (bs->inventory[INVENTORY_QUAD]) { - //if the bot is not holding the gauntlet or the enemy is really nearby - if (bs->weaponnum != WP_GAUNTLET || - bs->inventory[ENEMY_HORIZONTAL_DIST] < 80) { - return 70; - } - } - //if the enemy is located way higher than the bot - if (bs->inventory[ENEMY_HEIGHT] > 200) return 0; - //if the bot is very low on health - if (bs->inventory[INVENTORY_HEALTH] < 60) return 0; - //if the bot is low on health - if (bs->inventory[INVENTORY_HEALTH] < 80) { - //if the bot has insufficient armor - if (bs->inventory[INVENTORY_ARMOR] < 40) return 0; - } - //if the bot can use the bfg - if (bs->inventory[INVENTORY_BFG10K] > 0 && - bs->inventory[INVENTORY_BFGAMMO] > 7) return 100; - //if the bot can use the railgun - if (bs->inventory[INVENTORY_RAILGUN] > 0 && - bs->inventory[INVENTORY_SLUGS] > 5) return 95; - //if the bot can use the lightning gun - if (bs->inventory[INVENTORY_LIGHTNING] > 0 && - bs->inventory[INVENTORY_LIGHTNINGAMMO] > 50) return 90; - //if the bot can use the rocketlauncher - if (bs->inventory[INVENTORY_ROCKETLAUNCHER] > 0 && - bs->inventory[INVENTORY_ROCKETS] > 5) return 90; - //if the bot can use the plasmagun - if (bs->inventory[INVENTORY_PLASMAGUN] > 0 && - bs->inventory[INVENTORY_CELLS] > 40) return 85; - //if the bot can use the grenade launcher - if (bs->inventory[INVENTORY_GRENADELAUNCHER] > 0 && - bs->inventory[INVENTORY_GRENADES] > 10) return 80; - //if the bot can use the shotgun - if (bs->inventory[INVENTORY_SHOTGUN] > 0 && - bs->inventory[INVENTORY_SHELLS] > 10) return 50; - //otherwise the bot is not feeling too good - return 0; -} - -/* -================== -BotFeelingBad -================== -*/ -float BotFeelingBad(bot_state_t *bs) { - if (bs->weaponnum == WP_GAUNTLET) { - return 100; - } - if (bs->inventory[INVENTORY_HEALTH] < 40) { - return 100; - } - if (bs->weaponnum == WP_MACHINEGUN) { - return 90; - } - if (bs->inventory[INVENTORY_HEALTH] < 60) { - return 80; - } - return 0; -} - -/* -================== -BotWantsToRetreat -================== -*/ -int BotWantsToRetreat(bot_state_t *bs) { - aas_entityinfo_t entinfo; - - if (gametype == GT_CTF) { - //always retreat when carrying a CTF flag - if (BotCTFCarryingFlag(bs)) - return qtrue; - } -#ifdef MISSIONPACK - else if (gametype == GT_1FCTF) { - //if carrying the flag then always retreat - if (Bot1FCTFCarryingFlag(bs)) - return qtrue; - } - else if (gametype == GT_OBELISK) { - //the bots should be dedicated to attacking the enemy obelisk - if (bs->ltgtype == LTG_ATTACKENEMYBASE) { - if (bs->enemy != redobelisk.entitynum || - bs->enemy != blueobelisk.entitynum) { - return qtrue; - } - } - if (BotFeelingBad(bs) > 50) { - return qtrue; - } - return qfalse; - } - else if (gametype == GT_HARVESTER) { - //if carrying cubes then always retreat - if (BotHarvesterCarryingCubes(bs)) return qtrue; - } -#endif - // - if (bs->enemy >= 0) { - //if the enemy is carrying a flag - BotEntityInfo(bs->enemy, &entinfo); - if (EntityCarriesFlag(&entinfo)) - return qfalse; - } - //if the bot is getting the flag - if (bs->ltgtype == LTG_GETFLAG) - return qtrue; - // - if (BotAggression(bs) < 50) - return qtrue; - return qfalse; -} - -/* -================== -BotWantsToChase -================== -*/ -int BotWantsToChase(bot_state_t *bs) { - aas_entityinfo_t entinfo; - - if (gametype == GT_CTF) { - //never chase when carrying a CTF flag - if (BotCTFCarryingFlag(bs)) - return qfalse; - //always chase if the enemy is carrying a flag - BotEntityInfo(bs->enemy, &entinfo); - if (EntityCarriesFlag(&entinfo)) - return qtrue; - } -#ifdef MISSIONPACK - else if (gametype == GT_1FCTF) { - //never chase if carrying the flag - if (Bot1FCTFCarryingFlag(bs)) - return qfalse; - //always chase if the enemy is carrying a flag - BotEntityInfo(bs->enemy, &entinfo); - if (EntityCarriesFlag(&entinfo)) - return qtrue; - } - else if (gametype == GT_OBELISK) { - //the bots should be dedicated to attacking the enemy obelisk - if (bs->ltgtype == LTG_ATTACKENEMYBASE) { - if (bs->enemy != redobelisk.entitynum || - bs->enemy != blueobelisk.entitynum) { - return qfalse; - } - } - } - else if (gametype == GT_HARVESTER) { - //never chase if carrying cubes - if (BotHarvesterCarryingCubes(bs)) - return qfalse; - } -#endif - //if the bot is getting the flag - if (bs->ltgtype == LTG_GETFLAG) - return qfalse; - // - if (BotAggression(bs) > 50) - return qtrue; - return qfalse; -} - -/* -================== -BotWantsToHelp -================== -*/ -int BotWantsToHelp(bot_state_t *bs) { - return qtrue; -} - -/* -================== -BotCanAndWantsToRocketJump -================== -*/ -int BotCanAndWantsToRocketJump(bot_state_t *bs) { - float rocketjumper; - - //if rocket jumping is disabled - if (!bot_rocketjump.integer) return qfalse; - //if no rocket launcher - if (bs->inventory[INVENTORY_ROCKETLAUNCHER] <= 0) return qfalse; - //if low on rockets - if (bs->inventory[INVENTORY_ROCKETS] < 3) return qfalse; - //never rocket jump with the Quad - if (bs->inventory[INVENTORY_QUAD]) return qfalse; - //if low on health - if (bs->inventory[INVENTORY_HEALTH] < 60) return qfalse; - //if not full health - if (bs->inventory[INVENTORY_HEALTH] < 90) { - //if the bot has insufficient armor - if (bs->inventory[INVENTORY_ARMOR] < 40) return qfalse; - } - rocketjumper = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_WEAPONJUMPING, 0, 1); - if (rocketjumper < 0.5) return qfalse; - return qtrue; -} - -/* -================== -BotHasPersistantPowerupAndWeapon -================== -*/ -int BotHasPersistantPowerupAndWeapon(bot_state_t *bs) { -#ifdef MISSIONPACK - // if the bot does not have a persistant powerup - if (!bs->inventory[INVENTORY_SCOUT] && - !bs->inventory[INVENTORY_GUARD] && - !bs->inventory[INVENTORY_DOUBLER] && - !bs->inventory[INVENTORY_AMMOREGEN] ) { - return qfalse; - } -#endif - //if the bot is very low on health - if (bs->inventory[INVENTORY_HEALTH] < 60) return qfalse; - //if the bot is low on health - if (bs->inventory[INVENTORY_HEALTH] < 80) { - //if the bot has insufficient armor - if (bs->inventory[INVENTORY_ARMOR] < 40) return qfalse; - } - //if the bot can use the bfg - if (bs->inventory[INVENTORY_BFG10K] > 0 && - bs->inventory[INVENTORY_BFGAMMO] > 7) return qtrue; - //if the bot can use the railgun - if (bs->inventory[INVENTORY_RAILGUN] > 0 && - bs->inventory[INVENTORY_SLUGS] > 5) return qtrue; - //if the bot can use the lightning gun - if (bs->inventory[INVENTORY_LIGHTNING] > 0 && - bs->inventory[INVENTORY_LIGHTNINGAMMO] > 50) return qtrue; - //if the bot can use the rocketlauncher - if (bs->inventory[INVENTORY_ROCKETLAUNCHER] > 0 && - bs->inventory[INVENTORY_ROCKETS] > 5) return qtrue; - // - if (bs->inventory[INVENTORY_NAILGUN] > 0 && - bs->inventory[INVENTORY_NAILS] > 5) return qtrue; - // - if (bs->inventory[INVENTORY_PROXLAUNCHER] > 0 && - bs->inventory[INVENTORY_MINES] > 5) return qtrue; - // - if (bs->inventory[INVENTORY_CHAINGUN] > 0 && - bs->inventory[INVENTORY_BELT] > 40) return qtrue; - //if the bot can use the plasmagun - if (bs->inventory[INVENTORY_PLASMAGUN] > 0 && - bs->inventory[INVENTORY_CELLS] > 20) return qtrue; - return qfalse; -} - -/* -================== -BotGoCamp -================== -*/ -void BotGoCamp(bot_state_t *bs, bot_goal_t *goal) { - float camper; - - bs->decisionmaker = bs->client; - //set message time to zero so bot will NOT show any message - bs->teammessage_time = 0; - //set the ltg type - bs->ltgtype = LTG_CAMP; - //set the team goal - memcpy(&bs->teamgoal, goal, sizeof(bot_goal_t)); - //get the team goal time - camper = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CAMPER, 0, 1); - if (camper > 0.99) bs->teamgoal_time = FloatTime() + 99999; - else bs->teamgoal_time = FloatTime() + 120 + 180 * camper + random() * 15; - //set the last time the bot started camping - bs->camp_time = FloatTime(); - //the teammate that requested the camping - bs->teammate = 0; - //do NOT type arrive message - bs->arrive_time = 1; -} - -/* -================== -BotWantsToCamp -================== -*/ -int BotWantsToCamp(bot_state_t *bs) { - float camper; - int cs, traveltime, besttraveltime; - bot_goal_t goal, bestgoal; - - camper = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CAMPER, 0, 1); - if (camper < 0.1) return qfalse; - //if the bot has a team goal - if (bs->ltgtype == LTG_TEAMHELP || - bs->ltgtype == LTG_TEAMACCOMPANY || - bs->ltgtype == LTG_DEFENDKEYAREA || - bs->ltgtype == LTG_GETFLAG || - bs->ltgtype == LTG_RUSHBASE || - bs->ltgtype == LTG_CAMP || - bs->ltgtype == LTG_CAMPORDER || - bs->ltgtype == LTG_PATROL) { - return qfalse; - } - //if camped recently - if (bs->camp_time > FloatTime() - 60 + 300 * (1-camper)) return qfalse; - // - if (random() > camper) { - bs->camp_time = FloatTime(); - return qfalse; - } - //if the bot isn't healthy anough - if (BotAggression(bs) < 50) return qfalse; - //the bot should have at least have the rocket launcher, the railgun or the bfg10k with some ammo - if ((bs->inventory[INVENTORY_ROCKETLAUNCHER] <= 0 || bs->inventory[INVENTORY_ROCKETS < 10]) && - (bs->inventory[INVENTORY_RAILGUN] <= 0 || bs->inventory[INVENTORY_SLUGS] < 10) && - (bs->inventory[INVENTORY_BFG10K] <= 0 || bs->inventory[INVENTORY_BFGAMMO] < 10)) { - return qfalse; - } - //find the closest camp spot - besttraveltime = 99999; - for (cs = trap_BotGetNextCampSpotGoal(0, &goal); cs; cs = trap_BotGetNextCampSpotGoal(cs, &goal)) { - traveltime = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, goal.areanum, TFL_DEFAULT); - if (traveltime && traveltime < besttraveltime) { - besttraveltime = traveltime; - memcpy(&bestgoal, &goal, sizeof(bot_goal_t)); - } - } - if (besttraveltime > 150) return qfalse; - //ok found a camp spot, go camp there - BotGoCamp(bs, &bestgoal); - bs->ordered = qfalse; - // - return qtrue; -} - -/* -================== -BotDontAvoid -================== -*/ -void BotDontAvoid(bot_state_t *bs, char *itemname) { - bot_goal_t goal; - int num; - - num = trap_BotGetLevelItemGoal(-1, itemname, &goal); - while(num >= 0) { - trap_BotRemoveFromAvoidGoals(bs->gs, goal.number); - num = trap_BotGetLevelItemGoal(num, itemname, &goal); - } -} - -/* -================== -BotGoForPowerups -================== -*/ -void BotGoForPowerups(bot_state_t *bs) { - - //don't avoid any of the powerups anymore - BotDontAvoid(bs, "Quad Damage"); - BotDontAvoid(bs, "Regeneration"); - BotDontAvoid(bs, "Battle Suit"); - BotDontAvoid(bs, "Speed"); - BotDontAvoid(bs, "Invisibility"); - //BotDontAvoid(bs, "Flight"); - //reset the long term goal time so the bot will go for the powerup - //NOTE: the long term goal type doesn't change - bs->ltg_time = 0; -} - -/* -================== -BotRoamGoal -================== -*/ -void BotRoamGoal(bot_state_t *bs, vec3_t goal) { - int pc, i; - float len, rnd; - vec3_t dir, bestorg, belowbestorg; - bsp_trace_t trace; - - for (i = 0; i < 10; i++) { - //start at the bot origin - VectorCopy(bs->origin, bestorg); - rnd = random(); - if (rnd > 0.25) { - //add a random value to the x-coordinate - if (random() < 0.5) bestorg[0] -= 800 * random() + 100; - else bestorg[0] += 800 * random() + 100; - } - if (rnd < 0.75) { - //add a random value to the y-coordinate - if (random() < 0.5) bestorg[1] -= 800 * random() + 100; - else bestorg[1] += 800 * random() + 100; - } - //add a random value to the z-coordinate (NOTE: 48 = maxjump?) - bestorg[2] += 2 * 48 * crandom(); - //trace a line from the origin to the roam target - BotAI_Trace(&trace, bs->origin, NULL, NULL, bestorg, bs->entitynum, MASK_SOLID); - //direction and length towards the roam target - VectorSubtract(trace.endpos, bs->origin, dir); - len = VectorNormalize(dir); - //if the roam target is far away anough - if (len > 200) { - //the roam target is in the given direction before walls - VectorScale(dir, len * trace.fraction - 40, dir); - VectorAdd(bs->origin, dir, bestorg); - //get the coordinates of the floor below the roam target - belowbestorg[0] = bestorg[0]; - belowbestorg[1] = bestorg[1]; - belowbestorg[2] = bestorg[2] - 800; - BotAI_Trace(&trace, bestorg, NULL, NULL, belowbestorg, bs->entitynum, MASK_SOLID); - // - if (!trace.startsolid) { - trace.endpos[2]++; - pc = trap_PointContents(trace.endpos, bs->entitynum); - if (!(pc & (CONTENTS_LAVA | CONTENTS_SLIME))) { - VectorCopy(bestorg, goal); - return; - } - } - } - } - VectorCopy(bestorg, goal); -} - -/* -================== -BotAttackMove -================== -*/ -bot_moveresult_t BotAttackMove(bot_state_t *bs, int tfl) { - int movetype, i, attackentity; - float attack_skill, jumper, croucher, dist, strafechange_time; - float attack_dist, attack_range; - vec3_t forward, backward, sideward, hordir, up = {0, 0, 1}; - aas_entityinfo_t entinfo; - bot_moveresult_t moveresult; - bot_goal_t goal; - - attackentity = bs->enemy; - // - if (bs->attackchase_time > FloatTime()) { - //create the chase goal - goal.entitynum = attackentity; - goal.areanum = bs->lastenemyareanum; - VectorCopy(bs->lastenemyorigin, goal.origin); - VectorSet(goal.mins, -8, -8, -8); - VectorSet(goal.maxs, 8, 8, 8); - //initialize the movement state - BotSetupForMovement(bs); - //move towards the goal - trap_BotMoveToGoal(&moveresult, bs->ms, &goal, tfl); - return moveresult; - } - // - memset(&moveresult, 0, sizeof(bot_moveresult_t)); - // - attack_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_ATTACK_SKILL, 0, 1); - jumper = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_JUMPER, 0, 1); - croucher = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CROUCHER, 0, 1); - //if the bot is really stupid - if (attack_skill < 0.2) return moveresult; - //initialize the movement state - BotSetupForMovement(bs); - //get the enemy entity info - BotEntityInfo(attackentity, &entinfo); - //direction towards the enemy - VectorSubtract(entinfo.origin, bs->origin, forward); - //the distance towards the enemy - dist = VectorNormalize(forward); - VectorNegate(forward, backward); - //walk, crouch or jump - movetype = MOVE_WALK; - // - if (bs->attackcrouch_time < FloatTime() - 1) { - if (random() < jumper) { - movetype = MOVE_JUMP; - } - //wait at least one second before crouching again - else if (bs->attackcrouch_time < FloatTime() - 1 && random() < croucher) { - bs->attackcrouch_time = FloatTime() + croucher * 5; - } - } - if (bs->attackcrouch_time > FloatTime()) movetype = MOVE_CROUCH; - //if the bot should jump - if (movetype == MOVE_JUMP) { - //if jumped last frame - if (bs->attackjump_time > FloatTime()) { - movetype = MOVE_WALK; - } - else { - bs->attackjump_time = FloatTime() + 1; - } - } - if (bs->cur_ps.weapon == WP_GAUNTLET) { - attack_dist = 0; - attack_range = 0; - } - else { - attack_dist = IDEAL_ATTACKDIST; - attack_range = 40; - } - //if the bot is stupid - if (attack_skill <= 0.4) { - //just walk to or away from the enemy - if (dist > attack_dist + attack_range) { - if (trap_BotMoveInDirection(bs->ms, forward, 400, movetype)) return moveresult; - } - if (dist < attack_dist - attack_range) { - if (trap_BotMoveInDirection(bs->ms, backward, 400, movetype)) return moveresult; - } - return moveresult; - } - //increase the strafe time - bs->attackstrafe_time += bs->thinktime; - //get the strafe change time - strafechange_time = 0.4 + (1 - attack_skill) * 0.2; - if (attack_skill > 0.7) strafechange_time += crandom() * 0.2; - //if the strafe direction should be changed - if (bs->attackstrafe_time > strafechange_time) { - //some magic number :) - if (random() > 0.935) { - //flip the strafe direction - bs->flags ^= BFL_STRAFERIGHT; - bs->attackstrafe_time = 0; - } - } - // - for (i = 0; i < 2; i++) { - hordir[0] = forward[0]; - hordir[1] = forward[1]; - hordir[2] = 0; - VectorNormalize(hordir); - //get the sideward vector - CrossProduct(hordir, up, sideward); - //reverse the vector depending on the strafe direction - if (bs->flags & BFL_STRAFERIGHT) VectorNegate(sideward, sideward); - //randomly go back a little - if (random() > 0.9) { - VectorAdd(sideward, backward, sideward); - } - else { - //walk forward or backward to get at the ideal attack distance - if (dist > attack_dist + attack_range) { - VectorAdd(sideward, forward, sideward); - } - else if (dist < attack_dist - attack_range) { - VectorAdd(sideward, backward, sideward); - } - } - //perform the movement - if (trap_BotMoveInDirection(bs->ms, sideward, 400, movetype)) - return moveresult; - //movement failed, flip the strafe direction - bs->flags ^= BFL_STRAFERIGHT; - bs->attackstrafe_time = 0; - } - //bot couldn't do any usefull movement -// bs->attackchase_time = AAS_Time() + 6; - return moveresult; -} - -/* -================== -BotSameTeam -================== -*/ -int BotSameTeam(bot_state_t *bs, int entnum) { - char info1[1024], info2[1024]; - - if (bs->client < 0 || bs->client >= MAX_CLIENTS) { - //BotAI_Print(PRT_ERROR, "BotSameTeam: client out of range\n"); - return qfalse; - } - if (entnum < 0 || entnum >= MAX_CLIENTS) { - //BotAI_Print(PRT_ERROR, "BotSameTeam: client out of range\n"); - return qfalse; - } - if ( gametype >= GT_TEAM ) { - trap_GetConfigstring(CS_PLAYERS+bs->client, info1, sizeof(info1)); - trap_GetConfigstring(CS_PLAYERS+entnum, info2, sizeof(info2)); - // - if (atoi(Info_ValueForKey(info1, "t")) == atoi(Info_ValueForKey(info2, "t"))) return qtrue; - } - return qfalse; -} - -/* -================== -InFieldOfVision -================== -*/ -qboolean InFieldOfVision(vec3_t viewangles, float fov, vec3_t angles) -{ - int i; - float diff, angle; - - for (i = 0; i < 2; i++) { - angle = AngleMod(viewangles[i]); - angles[i] = AngleMod(angles[i]); - diff = angles[i] - angle; - if (angles[i] > angle) { - if (diff > 180.0) diff -= 360.0; - } - else { - if (diff < -180.0) diff += 360.0; - } - if (diff > 0) { - if (diff > fov * 0.5) return qfalse; - } - else { - if (diff < -fov * 0.5) return qfalse; - } - } - return qtrue; -} - -/* -================== -BotEntityVisible - -returns visibility in the range [0, 1] taking fog and water surfaces into account -================== -*/ -float BotEntityVisible(int viewer, vec3_t eye, vec3_t viewangles, float fov, int ent) { - int i, contents_mask, passent, hitent, infog, inwater, otherinfog, pc; - float squaredfogdist, waterfactor, vis, bestvis; - bsp_trace_t trace; - aas_entityinfo_t entinfo; - vec3_t dir, entangles, start, end, middle; - - //calculate middle of bounding box - BotEntityInfo(ent, &entinfo); - VectorAdd(entinfo.mins, entinfo.maxs, middle); - VectorScale(middle, 0.5, middle); - VectorAdd(entinfo.origin, middle, middle); - //check if entity is within field of vision - VectorSubtract(middle, eye, dir); - vectoangles(dir, entangles); - if (!InFieldOfVision(viewangles, fov, entangles)) return 0; - // - pc = trap_AAS_PointContents(eye); - infog = (pc & CONTENTS_FOG); - inwater = (pc & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER)); - // - bestvis = 0; - for (i = 0; i < 3; i++) { - //if the point is not in potential visible sight - //if (!AAS_inPVS(eye, middle)) continue; - // - contents_mask = CONTENTS_SOLID|CONTENTS_PLAYERCLIP; - passent = viewer; - hitent = ent; - VectorCopy(eye, start); - VectorCopy(middle, end); - //if the entity is in water, lava or slime - if (trap_AAS_PointContents(middle) & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER)) { - contents_mask |= (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER); - } - //if eye is in water, lava or slime - if (inwater) { - if (!(contents_mask & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER))) { - passent = ent; - hitent = viewer; - VectorCopy(middle, start); - VectorCopy(eye, end); - } - contents_mask ^= (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER); - } - //trace from start to end - BotAI_Trace(&trace, start, NULL, NULL, end, passent, contents_mask); - //if water was hit - waterfactor = 1.0; - if (trace.contents & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER)) { - //if the water surface is translucent - if (1) { - //trace through the water - contents_mask &= ~(CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER); - BotAI_Trace(&trace, trace.endpos, NULL, NULL, end, passent, contents_mask); - waterfactor = 0.5; - } - } - //if a full trace or the hitent was hit - if (trace.fraction >= 1 || trace.ent == hitent) { - //check for fog, assuming there's only one fog brush where - //either the viewer or the entity is in or both are in - otherinfog = (trap_AAS_PointContents(middle) & CONTENTS_FOG); - if (infog && otherinfog) { - VectorSubtract(trace.endpos, eye, dir); - squaredfogdist = VectorLengthSquared(dir); - } - else if (infog) { - VectorCopy(trace.endpos, start); - BotAI_Trace(&trace, start, NULL, NULL, eye, viewer, CONTENTS_FOG); - VectorSubtract(eye, trace.endpos, dir); - squaredfogdist = VectorLengthSquared(dir); - } - else if (otherinfog) { - VectorCopy(trace.endpos, end); - BotAI_Trace(&trace, eye, NULL, NULL, end, viewer, CONTENTS_FOG); - VectorSubtract(end, trace.endpos, dir); - squaredfogdist = VectorLengthSquared(dir); - } - else { - //if the entity and the viewer are not in fog assume there's no fog in between - squaredfogdist = 0; - } - //decrease visibility with the view distance through fog - vis = 1 / ((squaredfogdist * 0.001) < 1 ? 1 : (squaredfogdist * 0.001)); - //if entering water visibility is reduced - vis *= waterfactor; - // - if (vis > bestvis) bestvis = vis; - //if pretty much no fog - if (bestvis >= 0.95) return bestvis; - } - //check bottom and top of bounding box as well - if (i == 0) middle[2] += entinfo.mins[2]; - else if (i == 1) middle[2] += entinfo.maxs[2] - entinfo.mins[2]; - } - return bestvis; -} - -/* -================== -BotFindEnemy -================== -*/ -int BotFindEnemy(bot_state_t *bs, int curenemy) { - int i, healthdecrease; - float f, alertness, easyfragger, vis; - float squaredist, cursquaredist; - aas_entityinfo_t entinfo, curenemyinfo; - vec3_t dir, angles; - - alertness = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_ALERTNESS, 0, 1); - easyfragger = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_EASY_FRAGGER, 0, 1); - //check if the health decreased - healthdecrease = bs->lasthealth > bs->inventory[INVENTORY_HEALTH]; - //remember the current health value - bs->lasthealth = bs->inventory[INVENTORY_HEALTH]; - // - if (curenemy >= 0) { - BotEntityInfo(curenemy, &curenemyinfo); - if (EntityCarriesFlag(&curenemyinfo)) return qfalse; - VectorSubtract(curenemyinfo.origin, bs->origin, dir); - cursquaredist = VectorLengthSquared(dir); - } - else { - cursquaredist = 0; - } -#ifdef MISSIONPACK - if (gametype == GT_OBELISK) { - vec3_t target; - bot_goal_t *goal; - bsp_trace_t trace; - - if (BotTeam(bs) == TEAM_RED) - goal = &blueobelisk; - else - goal = &redobelisk; - //if the obelisk is visible - VectorCopy(goal->origin, target); - target[2] += 1; - BotAI_Trace(&trace, bs->eye, NULL, NULL, target, bs->client, CONTENTS_SOLID); - if (trace.fraction >= 1 || trace.ent == goal->entitynum) { - if (goal->entitynum == bs->enemy) { - return qfalse; - } - bs->enemy = goal->entitynum; - bs->enemysight_time = FloatTime(); - bs->enemysuicide = qfalse; - bs->enemydeath_time = 0; - bs->enemyvisible_time = FloatTime(); - return qtrue; - } - } -#endif - // - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { - - if (i == bs->client) continue; - //if it's the current enemy - if (i == curenemy) continue; - // - BotEntityInfo(i, &entinfo); - // - if (!entinfo.valid) continue; - //if the enemy isn't dead and the enemy isn't the bot self - if (EntityIsDead(&entinfo) || entinfo.number == bs->entitynum) continue; - //if the enemy is invisible and not shooting - if (EntityIsInvisible(&entinfo) && !EntityIsShooting(&entinfo)) { - continue; - } - //if not an easy fragger don't shoot at chatting players - if (easyfragger < 0.5 && EntityIsChatting(&entinfo)) continue; - // - if (lastteleport_time > FloatTime() - 3) { - VectorSubtract(entinfo.origin, lastteleport_origin, dir); - if (VectorLengthSquared(dir) < Square(70)) continue; - } - //calculate the distance towards the enemy - VectorSubtract(entinfo.origin, bs->origin, dir); - squaredist = VectorLengthSquared(dir); - //if this entity is not carrying a flag - if (!EntityCarriesFlag(&entinfo)) - { - //if this enemy is further away than the current one - if (curenemy >= 0 && squaredist > cursquaredist) continue; - } //end if - //if the bot has no - if (squaredist > Square(900.0 + alertness * 4000.0)) continue; - //if on the same team - if (BotSameTeam(bs, i)) continue; - //if the bot's health decreased or the enemy is shooting - if (curenemy < 0 && (healthdecrease || EntityIsShooting(&entinfo))) - f = 360; - else - f = 90 + 90 - (90 - (squaredist > Square(810) ? Square(810) : squaredist) / (810 * 9)); - //check if the enemy is visible - vis = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, f, i); - if (vis <= 0) continue; - //if the enemy is quite far away, not shooting and the bot is not damaged - if (curenemy < 0 && squaredist > Square(100) && !healthdecrease && !EntityIsShooting(&entinfo)) - { - //check if we can avoid this enemy - VectorSubtract(bs->origin, entinfo.origin, dir); - vectoangles(dir, angles); - //if the bot isn't in the fov of the enemy - if (!InFieldOfVision(entinfo.angles, 90, angles)) { - //update some stuff for this enemy - BotUpdateBattleInventory(bs, i); - //if the bot doesn't really want to fight - if (BotWantsToRetreat(bs)) continue; - } - } - //found an enemy - bs->enemy = entinfo.number; - if (curenemy >= 0) bs->enemysight_time = FloatTime() - 2; - else bs->enemysight_time = FloatTime(); - bs->enemysuicide = qfalse; - bs->enemydeath_time = 0; - bs->enemyvisible_time = FloatTime(); - return qtrue; - } - return qfalse; -} - -/* -================== -BotTeamFlagCarrierVisible -================== -*/ -int BotTeamFlagCarrierVisible(bot_state_t *bs) { - int i; - float vis; - aas_entityinfo_t entinfo; - - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { - if (i == bs->client) - continue; - // - BotEntityInfo(i, &entinfo); - //if this player is active - if (!entinfo.valid) - continue; - //if this player is carrying a flag - if (!EntityCarriesFlag(&entinfo)) - continue; - //if the flag carrier is not on the same team - if (!BotSameTeam(bs, i)) - continue; - //if the flag carrier is not visible - vis = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, i); - if (vis <= 0) - continue; - // - return i; - } - return -1; -} - -/* -================== -BotTeamFlagCarrier -================== -*/ -int BotTeamFlagCarrier(bot_state_t *bs) { - int i; - aas_entityinfo_t entinfo; - - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { - if (i == bs->client) - continue; - // - BotEntityInfo(i, &entinfo); - //if this player is active - if (!entinfo.valid) - continue; - //if this player is carrying a flag - if (!EntityCarriesFlag(&entinfo)) - continue; - //if the flag carrier is not on the same team - if (!BotSameTeam(bs, i)) - continue; - // - return i; - } - return -1; -} - -/* -================== -BotEnemyFlagCarrierVisible -================== -*/ -int BotEnemyFlagCarrierVisible(bot_state_t *bs) { - int i; - float vis; - aas_entityinfo_t entinfo; - - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { - if (i == bs->client) - continue; - // - BotEntityInfo(i, &entinfo); - //if this player is active - if (!entinfo.valid) - continue; - //if this player is carrying a flag - if (!EntityCarriesFlag(&entinfo)) - continue; - //if the flag carrier is on the same team - if (BotSameTeam(bs, i)) - continue; - //if the flag carrier is not visible - vis = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, i); - if (vis <= 0) - continue; - // - return i; - } - return -1; -} - -/* -================== -BotVisibleTeamMatesAndEnemies -================== -*/ -void BotVisibleTeamMatesAndEnemies(bot_state_t *bs, int *teammates, int *enemies, float range) { - int i; - float vis; - aas_entityinfo_t entinfo; - vec3_t dir; - - if (teammates) - *teammates = 0; - if (enemies) - *enemies = 0; - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { - if (i == bs->client) - continue; - // - BotEntityInfo(i, &entinfo); - //if this player is active - if (!entinfo.valid) - continue; - //if this player is carrying a flag - if (!EntityCarriesFlag(&entinfo)) - continue; - //if not within range - VectorSubtract(entinfo.origin, bs->origin, dir); - if (VectorLengthSquared(dir) > Square(range)) - continue; - //if the flag carrier is not visible - vis = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, i); - if (vis <= 0) - continue; - //if the flag carrier is on the same team - if (BotSameTeam(bs, i)) { - if (teammates) - (*teammates)++; - } - else { - if (enemies) - (*enemies)++; - } - } -} - -#ifdef MISSIONPACK -/* -================== -BotTeamCubeCarrierVisible -================== -*/ -int BotTeamCubeCarrierVisible(bot_state_t *bs) { - int i; - float vis; - aas_entityinfo_t entinfo; - - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { - if (i == bs->client) continue; - // - BotEntityInfo(i, &entinfo); - //if this player is active - if (!entinfo.valid) continue; - //if this player is carrying a flag - if (!EntityCarriesCubes(&entinfo)) continue; - //if the flag carrier is not on the same team - if (!BotSameTeam(bs, i)) continue; - //if the flag carrier is not visible - vis = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, i); - if (vis <= 0) continue; - // - return i; - } - return -1; -} - -/* -================== -BotEnemyCubeCarrierVisible -================== -*/ -int BotEnemyCubeCarrierVisible(bot_state_t *bs) { - int i; - float vis; - aas_entityinfo_t entinfo; - - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { - if (i == bs->client) - continue; - // - BotEntityInfo(i, &entinfo); - //if this player is active - if (!entinfo.valid) - continue; - //if this player is carrying a flag - if (!EntityCarriesCubes(&entinfo)) continue; - //if the flag carrier is on the same team - if (BotSameTeam(bs, i)) - continue; - //if the flag carrier is not visible - vis = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, i); - if (vis <= 0) - continue; - // - return i; - } - return -1; -} -#endif - -/* -================== -BotAimAtEnemy -================== -*/ -void BotAimAtEnemy(bot_state_t *bs) { - int i, enemyvisible; - float dist, f, aim_skill, aim_accuracy, speed, reactiontime; - vec3_t dir, bestorigin, end, start, groundtarget, cmdmove, enemyvelocity; - vec3_t mins = {-4,-4,-4}, maxs = {4, 4, 4}; - weaponinfo_t wi; - aas_entityinfo_t entinfo; - bot_goal_t goal; - bsp_trace_t trace; - vec3_t target; - - //if the bot has no enemy - if (bs->enemy < 0) { - return; - } - //get the enemy entity information - BotEntityInfo(bs->enemy, &entinfo); - //if this is not a player (should be an obelisk) - if (bs->enemy >= MAX_CLIENTS) { - //if the obelisk is visible - VectorCopy(entinfo.origin, target); -#ifdef MISSIONPACK - // if attacking an obelisk - if ( bs->enemy == redobelisk.entitynum || - bs->enemy == blueobelisk.entitynum ) { - target[2] += 32; - } -#endif - //aim at the obelisk - VectorSubtract(target, bs->eye, dir); - vectoangles(dir, bs->ideal_viewangles); - //set the aim target before trying to attack - VectorCopy(target, bs->aimtarget); - return; - } - // - //BotAI_Print(PRT_MESSAGE, "client %d: aiming at client %d\n", bs->entitynum, bs->enemy); - // - aim_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_SKILL, 0, 1); - aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY, 0, 1); - // - if (aim_skill > 0.95) { - //don't aim too early - reactiontime = 0.5 * trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_REACTIONTIME, 0, 1); - if (bs->enemysight_time > FloatTime() - reactiontime) return; - if (bs->teleport_time > FloatTime() - reactiontime) return; - } - - //get the weapon information - trap_BotGetWeaponInfo(bs->ws, bs->weaponnum, &wi); - //get the weapon specific aim accuracy and or aim skill - if (wi.number == WP_MACHINEGUN) { - aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_MACHINEGUN, 0, 1); - } - else if (wi.number == WP_SHOTGUN) { - aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_SHOTGUN, 0, 1); - } - else if (wi.number == WP_GRENADE_LAUNCHER) { - aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_GRENADELAUNCHER, 0, 1); - aim_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_SKILL_GRENADELAUNCHER, 0, 1); - } - else if (wi.number == WP_ROCKET_LAUNCHER) { - aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_ROCKETLAUNCHER, 0, 1); - aim_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_SKILL_ROCKETLAUNCHER, 0, 1); - } - else if (wi.number == WP_LIGHTNING) { - aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_LIGHTNING, 0, 1); - } - else if (wi.number == WP_RAILGUN) { - aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_RAILGUN, 0, 1); - } - else if (wi.number == WP_PLASMAGUN) { - aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_PLASMAGUN, 0, 1); - aim_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_SKILL_PLASMAGUN, 0, 1); - } - else if (wi.number == WP_BFG) { - aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_BFG10K, 0, 1); - aim_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_SKILL_BFG10K, 0, 1); - } - // - if (aim_accuracy <= 0) aim_accuracy = 0.0001f; - //get the enemy entity information - BotEntityInfo(bs->enemy, &entinfo); - //if the enemy is invisible then shoot crappy most of the time - if (EntityIsInvisible(&entinfo)) { - if (random() > 0.1) aim_accuracy *= 0.4f; - } - // - VectorSubtract(entinfo.origin, entinfo.lastvisorigin, enemyvelocity); - VectorScale(enemyvelocity, 1 / entinfo.update_time, enemyvelocity); - //enemy origin and velocity is remembered every 0.5 seconds - if (bs->enemyposition_time < FloatTime()) { - // - bs->enemyposition_time = FloatTime() + 0.5; - VectorCopy(enemyvelocity, bs->enemyvelocity); - VectorCopy(entinfo.origin, bs->enemyorigin); - } - //if not extremely skilled - if (aim_skill < 0.9) { - VectorSubtract(entinfo.origin, bs->enemyorigin, dir); - //if the enemy moved a bit - if (VectorLengthSquared(dir) > Square(48)) { - //if the enemy changed direction - if (DotProduct(bs->enemyvelocity, enemyvelocity) < 0) { - //aim accuracy should be worse now - aim_accuracy *= 0.7f; - } - } - } - //check visibility of enemy - enemyvisible = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy); - //if the enemy is visible - if (enemyvisible) { - // - VectorCopy(entinfo.origin, bestorigin); - bestorigin[2] += 8; - //get the start point shooting from - //NOTE: the x and y projectile start offsets are ignored - VectorCopy(bs->origin, start); - start[2] += bs->cur_ps.viewheight; - start[2] += wi.offset[2]; - // - BotAI_Trace(&trace, start, mins, maxs, bestorigin, bs->entitynum, MASK_SHOT); - //if the enemy is NOT hit - if (trace.fraction <= 1 && trace.ent != entinfo.number) { - bestorigin[2] += 16; - } - //if it is not an instant hit weapon the bot might want to predict the enemy - if (wi.speed) { - // - VectorSubtract(bestorigin, bs->origin, dir); - dist = VectorLength(dir); - VectorSubtract(entinfo.origin, bs->enemyorigin, dir); - //if the enemy is NOT pretty far away and strafing just small steps left and right - if (!(dist > 100 && VectorLengthSquared(dir) < Square(32))) { - //if skilled anough do exact prediction - if (aim_skill > 0.8 && - //if the weapon is ready to fire - bs->cur_ps.weaponstate == WEAPON_READY) { - aas_clientmove_t move; - vec3_t origin; - - VectorSubtract(entinfo.origin, bs->origin, dir); - //distance towards the enemy - dist = VectorLength(dir); - //direction the enemy is moving in - VectorSubtract(entinfo.origin, entinfo.lastvisorigin, dir); - // - VectorScale(dir, 1 / entinfo.update_time, dir); - // - VectorCopy(entinfo.origin, origin); - origin[2] += 1; - // - VectorClear(cmdmove); - //AAS_ClearShownDebugLines(); - trap_AAS_PredictClientMovement(&move, bs->enemy, origin, - PRESENCE_CROUCH, qfalse, - dir, cmdmove, 0, - dist * 10 / wi.speed, 0.1f, 0, 0, qfalse); - VectorCopy(move.endpos, bestorigin); - //BotAI_Print(PRT_MESSAGE, "%1.1f predicted speed = %f, frames = %f\n", FloatTime(), VectorLength(dir), dist * 10 / wi.speed); - } - //if not that skilled do linear prediction - else if (aim_skill > 0.4) { - VectorSubtract(entinfo.origin, bs->origin, dir); - //distance towards the enemy - dist = VectorLength(dir); - //direction the enemy is moving in - VectorSubtract(entinfo.origin, entinfo.lastvisorigin, dir); - dir[2] = 0; - // - speed = VectorNormalize(dir) / entinfo.update_time; - //botimport.Print(PRT_MESSAGE, "speed = %f, wi->speed = %f\n", speed, wi->speed); - //best spot to aim at - VectorMA(entinfo.origin, (dist / wi.speed) * speed, dir, bestorigin); - } - } - } - //if the projectile does radial damage - if (aim_skill > 0.6 && wi.proj.damagetype & DAMAGETYPE_RADIAL) { - //if the enemy isn't standing significantly higher than the bot - if (entinfo.origin[2] < bs->origin[2] + 16) { - //try to aim at the ground in front of the enemy - VectorCopy(entinfo.origin, end); - end[2] -= 64; - BotAI_Trace(&trace, entinfo.origin, NULL, NULL, end, entinfo.number, MASK_SHOT); - // - VectorCopy(bestorigin, groundtarget); - if (trace.startsolid) groundtarget[2] = entinfo.origin[2] - 16; - else groundtarget[2] = trace.endpos[2] - 8; - //trace a line from projectile start to ground target - BotAI_Trace(&trace, start, NULL, NULL, groundtarget, bs->entitynum, MASK_SHOT); - //if hitpoint is not vertically too far from the ground target - if (fabs(trace.endpos[2] - groundtarget[2]) < 50) { - VectorSubtract(trace.endpos, groundtarget, dir); - //if the hitpoint is near anough the ground target - if (VectorLengthSquared(dir) < Square(60)) { - VectorSubtract(trace.endpos, start, dir); - //if the hitpoint is far anough from the bot - if (VectorLengthSquared(dir) > Square(100)) { - //check if the bot is visible from the ground target - trace.endpos[2] += 1; - BotAI_Trace(&trace, trace.endpos, NULL, NULL, entinfo.origin, entinfo.number, MASK_SHOT); - if (trace.fraction >= 1) { - //botimport.Print(PRT_MESSAGE, "%1.1f aiming at ground\n", AAS_Time()); - VectorCopy(groundtarget, bestorigin); - } - } - } - } - } - } - bestorigin[0] += 20 * crandom() * (1 - aim_accuracy); - bestorigin[1] += 20 * crandom() * (1 - aim_accuracy); - bestorigin[2] += 10 * crandom() * (1 - aim_accuracy); - } - else { - // - VectorCopy(bs->lastenemyorigin, bestorigin); - bestorigin[2] += 8; - //if the bot is skilled anough - if (aim_skill > 0.5) { - //do prediction shots around corners - if (wi.number == WP_BFG || - wi.number == WP_ROCKET_LAUNCHER || - wi.number == WP_GRENADE_LAUNCHER) { - //create the chase goal - goal.entitynum = bs->client; - goal.areanum = bs->areanum; - VectorCopy(bs->eye, goal.origin); - VectorSet(goal.mins, -8, -8, -8); - VectorSet(goal.maxs, 8, 8, 8); - // - if (trap_BotPredictVisiblePosition(bs->lastenemyorigin, bs->lastenemyareanum, &goal, TFL_DEFAULT, target)) { - VectorSubtract(target, bs->eye, dir); - if (VectorLengthSquared(dir) > Square(80)) { - VectorCopy(target, bestorigin); - bestorigin[2] -= 20; - } - } - aim_accuracy = 1; - } - } - } - // - if (enemyvisible) { - BotAI_Trace(&trace, bs->eye, NULL, NULL, bestorigin, bs->entitynum, MASK_SHOT); - VectorCopy(trace.endpos, bs->aimtarget); - } - else { - VectorCopy(bestorigin, bs->aimtarget); - } - //get aim direction - VectorSubtract(bestorigin, bs->eye, dir); - // - if (wi.number == WP_MACHINEGUN || - wi.number == WP_SHOTGUN || - wi.number == WP_LIGHTNING || - wi.number == WP_RAILGUN) { - //distance towards the enemy - dist = VectorLength(dir); - if (dist > 150) dist = 150; - f = 0.6 + dist / 150 * 0.4; - aim_accuracy *= f; - } - //add some random stuff to the aim direction depending on the aim accuracy - if (aim_accuracy < 0.8) { - VectorNormalize(dir); - for (i = 0; i < 3; i++) dir[i] += 0.3 * crandom() * (1 - aim_accuracy); - } - //set the ideal view angles - vectoangles(dir, bs->ideal_viewangles); - //take the weapon spread into account for lower skilled bots - bs->ideal_viewangles[PITCH] += 6 * wi.vspread * crandom() * (1 - aim_accuracy); - bs->ideal_viewangles[PITCH] = AngleMod(bs->ideal_viewangles[PITCH]); - bs->ideal_viewangles[YAW] += 6 * wi.hspread * crandom() * (1 - aim_accuracy); - bs->ideal_viewangles[YAW] = AngleMod(bs->ideal_viewangles[YAW]); - //if the bots should be really challenging - if (bot_challenge.integer) { - //if the bot is really accurate and has the enemy in view for some time - if (aim_accuracy > 0.9 && bs->enemysight_time < FloatTime() - 1) { - //set the view angles directly - if (bs->ideal_viewangles[PITCH] > 180) bs->ideal_viewangles[PITCH] -= 360; - VectorCopy(bs->ideal_viewangles, bs->viewangles); - trap_EA_View(bs->client, bs->viewangles); - } - } -} - -/* -================== -BotCheckAttack -================== -*/ -void BotCheckAttack(bot_state_t *bs) { - float points, reactiontime, fov, firethrottle; - int attackentity; - bsp_trace_t bsptrace; - //float selfpreservation; - vec3_t forward, right, start, end, dir, angles; - weaponinfo_t wi; - bsp_trace_t trace; - aas_entityinfo_t entinfo; - vec3_t mins = {-8, -8, -8}, maxs = {8, 8, 8}; - - attackentity = bs->enemy; - // - BotEntityInfo(attackentity, &entinfo); - // if not attacking a player - if (attackentity >= MAX_CLIENTS) { -#ifdef MISSIONPACK - // if attacking an obelisk - if ( entinfo.number == redobelisk.entitynum || - entinfo.number == blueobelisk.entitynum ) { - // if obelisk is respawning return - if ( g_entities[entinfo.number].activator && - g_entities[entinfo.number].activator->s.frame == 2 ) { - return; - } - } -#endif - } - // - reactiontime = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_REACTIONTIME, 0, 1); - if (bs->enemysight_time > FloatTime() - reactiontime) return; - if (bs->teleport_time > FloatTime() - reactiontime) return; - //if changing weapons - if (bs->weaponchange_time > FloatTime() - 0.1) return; - //check fire throttle characteristic - if (bs->firethrottlewait_time > FloatTime()) return; - firethrottle = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_FIRETHROTTLE, 0, 1); - if (bs->firethrottleshoot_time < FloatTime()) { - if (random() > firethrottle) { - bs->firethrottlewait_time = FloatTime() + firethrottle; - bs->firethrottleshoot_time = 0; - } - else { - bs->firethrottleshoot_time = FloatTime() + 1 - firethrottle; - bs->firethrottlewait_time = 0; - } - } - // - // - VectorSubtract(bs->aimtarget, bs->eye, dir); - // - if (bs->weaponnum == WP_GAUNTLET) { - if (VectorLengthSquared(dir) > Square(60)) { - return; - } - } - if (VectorLengthSquared(dir) < Square(100)) - fov = 120; - else - fov = 50; - // - vectoangles(dir, angles); - if (!InFieldOfVision(bs->viewangles, fov, angles)) - return; - BotAI_Trace(&bsptrace, bs->eye, NULL, NULL, bs->aimtarget, bs->client, CONTENTS_SOLID|CONTENTS_PLAYERCLIP); - if (bsptrace.fraction < 1 && bsptrace.ent != attackentity) - return; - - //get the weapon info - trap_BotGetWeaponInfo(bs->ws, bs->weaponnum, &wi); - //get the start point shooting from - VectorCopy(bs->origin, start); - start[2] += bs->cur_ps.viewheight; - AngleVectors(bs->viewangles, forward, right, NULL); - start[0] += forward[0] * wi.offset[0] + right[0] * wi.offset[1]; - start[1] += forward[1] * wi.offset[0] + right[1] * wi.offset[1]; - start[2] += forward[2] * wi.offset[0] + right[2] * wi.offset[1] + wi.offset[2]; - //end point aiming at - VectorMA(start, 1000, forward, end); - //a little back to make sure not inside a very close enemy - VectorMA(start, -12, forward, start); - BotAI_Trace(&trace, start, mins, maxs, end, bs->entitynum, MASK_SHOT); - //if the entity is a client - if (trace.ent > 0 && trace.ent <= MAX_CLIENTS) { - if (trace.ent != attackentity) { - //if a teammate is hit - if (BotSameTeam(bs, trace.ent)) - return; - } - } - //if won't hit the enemy or not attacking a player (obelisk) - if (trace.ent != attackentity || attackentity >= MAX_CLIENTS) { - //if the projectile does radial damage - if (wi.proj.damagetype & DAMAGETYPE_RADIAL) { - if (trace.fraction * 1000 < wi.proj.radius) { - points = (wi.proj.damage - 0.5 * trace.fraction * 1000) * 0.5; - if (points > 0) { - return; - } - } - //FIXME: check if a teammate gets radial damage - } - } - //if fire has to be release to activate weapon - if (wi.flags & WFL_FIRERELEASED) { - if (bs->flags & BFL_ATTACKED) { - trap_EA_Attack(bs->client); - } - } - else { - trap_EA_Attack(bs->client); - } - bs->flags ^= BFL_ATTACKED; -} - -/* -================== -BotMapScripts -================== -*/ -void BotMapScripts(bot_state_t *bs) { - char info[1024]; - char mapname[128]; - int i, shootbutton; - float aim_accuracy; - aas_entityinfo_t entinfo; - vec3_t dir; - - trap_GetServerinfo(info, sizeof(info)); - - strncpy(mapname, Info_ValueForKey( info, "mapname" ), sizeof(mapname)-1); - mapname[sizeof(mapname)-1] = '\0'; - - if (!Q_stricmp(mapname, "q3tourney6")) { - vec3_t mins = {700, 204, 672}, maxs = {964, 468, 680}; - vec3_t buttonorg = {304, 352, 920}; - //NOTE: NEVER use the func_bobbing in q3tourney6 - bs->tfl &= ~TFL_FUNCBOB; - //if the bot is below the bounding box - if (bs->origin[0] > mins[0] && bs->origin[0] < maxs[0]) { - if (bs->origin[1] > mins[1] && bs->origin[1] < maxs[1]) { - if (bs->origin[2] < mins[2]) { - return; - } - } - } - shootbutton = qfalse; - //if an enemy is below this bounding box then shoot the button - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { - - if (i == bs->client) continue; - // - BotEntityInfo(i, &entinfo); - // - if (!entinfo.valid) continue; - //if the enemy isn't dead and the enemy isn't the bot self - if (EntityIsDead(&entinfo) || entinfo.number == bs->entitynum) continue; - // - if (entinfo.origin[0] > mins[0] && entinfo.origin[0] < maxs[0]) { - if (entinfo.origin[1] > mins[1] && entinfo.origin[1] < maxs[1]) { - if (entinfo.origin[2] < mins[2]) { - //if there's a team mate below the crusher - if (BotSameTeam(bs, i)) { - shootbutton = qfalse; - break; - } - else { - shootbutton = qtrue; - } - } - } - } - } - if (shootbutton) { - bs->flags |= BFL_IDEALVIEWSET; - VectorSubtract(buttonorg, bs->eye, dir); - vectoangles(dir, bs->ideal_viewangles); - aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY, 0, 1); - bs->ideal_viewangles[PITCH] += 8 * crandom() * (1 - aim_accuracy); - bs->ideal_viewangles[PITCH] = AngleMod(bs->ideal_viewangles[PITCH]); - bs->ideal_viewangles[YAW] += 8 * crandom() * (1 - aim_accuracy); - bs->ideal_viewangles[YAW] = AngleMod(bs->ideal_viewangles[YAW]); - // - if (InFieldOfVision(bs->viewangles, 20, bs->ideal_viewangles)) { - trap_EA_Attack(bs->client); - } - } - } - else if (!Q_stricmp(mapname, "mpq3tourney6")) { - //NOTE: NEVER use the func_bobbing in mpq3tourney6 - bs->tfl &= ~TFL_FUNCBOB; - } -} - -/* -================== -BotSetMovedir -================== -*/ -// bk001205 - made these static -static vec3_t VEC_UP = {0, -1, 0}; -static vec3_t MOVEDIR_UP = {0, 0, 1}; -static vec3_t VEC_DOWN = {0, -2, 0}; -static vec3_t MOVEDIR_DOWN = {0, 0, -1}; - -void BotSetMovedir(vec3_t angles, vec3_t movedir) { - if (VectorCompare(angles, VEC_UP)) { - VectorCopy(MOVEDIR_UP, movedir); - } - else if (VectorCompare(angles, VEC_DOWN)) { - VectorCopy(MOVEDIR_DOWN, movedir); - } - else { - AngleVectors(angles, movedir, NULL, NULL); - } -} - -/* -================== -BotModelMinsMaxs - -this is ugly -================== -*/ -int BotModelMinsMaxs(int modelindex, int eType, int contents, vec3_t mins, vec3_t maxs) { - gentity_t *ent; - int i; - - ent = &g_entities[0]; - for (i = 0; i < level.num_entities; i++, ent++) { - if ( !ent->inuse ) { - continue; - } - if ( eType && ent->s.eType != eType) { - continue; - } - if ( contents && ent->r.contents != contents) { - continue; - } - if (ent->s.modelindex == modelindex) { - if (mins) - VectorAdd(ent->r.currentOrigin, ent->r.mins, mins); - if (maxs) - VectorAdd(ent->r.currentOrigin, ent->r.maxs, maxs); - return i; - } - } - if (mins) - VectorClear(mins); - if (maxs) - VectorClear(maxs); - return 0; -} - -/* -================== -BotFuncButtonGoal -================== -*/ -int BotFuncButtonActivateGoal(bot_state_t *bs, int bspent, bot_activategoal_t *activategoal) { - int i, areas[10], numareas, modelindex, entitynum; - char model[128]; - float lip, dist, health, angle; - vec3_t size, start, end, mins, maxs, angles, points[10]; - vec3_t movedir, origin, goalorigin, bboxmins, bboxmaxs; - vec3_t extramins = {1, 1, 1}, extramaxs = {-1, -1, -1}; - bsp_trace_t bsptrace; - - activategoal->shoot = qfalse; - VectorClear(activategoal->target); - //create a bot goal towards the button - trap_AAS_ValueForBSPEpairKey(bspent, "model", model, sizeof(model)); - if (!*model) - return qfalse; - modelindex = atoi(model+1); - if (!modelindex) - return qfalse; - VectorClear(angles); - entitynum = BotModelMinsMaxs(modelindex, ET_MOVER, 0, mins, maxs); - //get the lip of the button - trap_AAS_FloatForBSPEpairKey(bspent, "lip", &lip); - if (!lip) lip = 4; - //get the move direction from the angle - trap_AAS_FloatForBSPEpairKey(bspent, "angle", &angle); - VectorSet(angles, 0, angle, 0); - BotSetMovedir(angles, movedir); - //button size - VectorSubtract(maxs, mins, size); - //button origin - VectorAdd(mins, maxs, origin); - VectorScale(origin, 0.5, origin); - //touch distance of the button - dist = fabs(movedir[0]) * size[0] + fabs(movedir[1]) * size[1] + fabs(movedir[2]) * size[2]; - dist *= 0.5; - // - trap_AAS_FloatForBSPEpairKey(bspent, "health", &health); - //if the button is shootable - if (health) { - //calculate the shoot target - VectorMA(origin, -dist, movedir, goalorigin); - // - VectorCopy(goalorigin, activategoal->target); - activategoal->shoot = qtrue; - // - BotAI_Trace(&bsptrace, bs->eye, NULL, NULL, goalorigin, bs->entitynum, MASK_SHOT); - // if the button is visible from the current position - if (bsptrace.fraction >= 1.0 || bsptrace.ent == entitynum) { - // - activategoal->goal.entitynum = entitynum; //NOTE: this is the entity number of the shootable button - activategoal->goal.number = 0; - activategoal->goal.flags = 0; - VectorCopy(bs->origin, activategoal->goal.origin); - activategoal->goal.areanum = bs->areanum; - VectorSet(activategoal->goal.mins, -8, -8, -8); - VectorSet(activategoal->goal.maxs, 8, 8, 8); - // - return qtrue; - } - else { - //create a goal from where the button is visible and shoot at the button from there - //add bounding box size to the dist - trap_AAS_PresenceTypeBoundingBox(PRESENCE_CROUCH, bboxmins, bboxmaxs); - for (i = 0; i < 3; i++) { - if (movedir[i] < 0) dist += fabs(movedir[i]) * fabs(bboxmaxs[i]); - else dist += fabs(movedir[i]) * fabs(bboxmins[i]); - } - //calculate the goal origin - VectorMA(origin, -dist, movedir, goalorigin); - // - VectorCopy(goalorigin, start); - start[2] += 24; - VectorCopy(start, end); - end[2] -= 512; - numareas = trap_AAS_TraceAreas(start, end, areas, points, 10); - // - for (i = numareas-1; i >= 0; i--) { - if (trap_AAS_AreaReachability(areas[i])) { - break; - } - } - if (i < 0) { - // FIXME: trace forward and maybe in other directions to find a valid area - } - if (i >= 0) { - // - VectorCopy(points[i], activategoal->goal.origin); - activategoal->goal.areanum = areas[i]; - VectorSet(activategoal->goal.mins, 8, 8, 8); - VectorSet(activategoal->goal.maxs, -8, -8, -8); - // - for (i = 0; i < 3; i++) - { - if (movedir[i] < 0) activategoal->goal.maxs[i] += fabs(movedir[i]) * fabs(extramaxs[i]); - else activategoal->goal.mins[i] += fabs(movedir[i]) * fabs(extramins[i]); - } //end for - // - activategoal->goal.entitynum = entitynum; - activategoal->goal.number = 0; - activategoal->goal.flags = 0; - return qtrue; - } - } - return qfalse; - } - else { - //add bounding box size to the dist - trap_AAS_PresenceTypeBoundingBox(PRESENCE_CROUCH, bboxmins, bboxmaxs); - for (i = 0; i < 3; i++) { - if (movedir[i] < 0) dist += fabs(movedir[i]) * fabs(bboxmaxs[i]); - else dist += fabs(movedir[i]) * fabs(bboxmins[i]); - } - //calculate the goal origin - VectorMA(origin, -dist, movedir, goalorigin); - // - VectorCopy(goalorigin, start); - start[2] += 24; - VectorCopy(start, end); - end[2] -= 100; - numareas = trap_AAS_TraceAreas(start, end, areas, NULL, 10); - // - for (i = 0; i < numareas; i++) { - if (trap_AAS_AreaReachability(areas[i])) { - break; - } - } - if (i < numareas) { - // - VectorCopy(origin, activategoal->goal.origin); - activategoal->goal.areanum = areas[i]; - VectorSubtract(mins, origin, activategoal->goal.mins); - VectorSubtract(maxs, origin, activategoal->goal.maxs); - // - for (i = 0; i < 3; i++) - { - if (movedir[i] < 0) activategoal->goal.maxs[i] += fabs(movedir[i]) * fabs(extramaxs[i]); - else activategoal->goal.mins[i] += fabs(movedir[i]) * fabs(extramins[i]); - } //end for - // - activategoal->goal.entitynum = entitynum; - activategoal->goal.number = 0; - activategoal->goal.flags = 0; - return qtrue; - } - } - return qfalse; -} - -/* -================== -BotFuncDoorGoal -================== -*/ -int BotFuncDoorActivateGoal(bot_state_t *bs, int bspent, bot_activategoal_t *activategoal) { - int modelindex, entitynum; - char model[MAX_INFO_STRING]; - vec3_t mins, maxs, origin, angles; - - //shoot at the shootable door - trap_AAS_ValueForBSPEpairKey(bspent, "model", model, sizeof(model)); - if (!*model) - return qfalse; - modelindex = atoi(model+1); - if (!modelindex) - return qfalse; - VectorClear(angles); - entitynum = BotModelMinsMaxs(modelindex, ET_MOVER, 0, mins, maxs); - //door origin - VectorAdd(mins, maxs, origin); - VectorScale(origin, 0.5, origin); - VectorCopy(origin, activategoal->target); - activategoal->shoot = qtrue; - // - activategoal->goal.entitynum = entitynum; //NOTE: this is the entity number of the shootable door - activategoal->goal.number = 0; - activategoal->goal.flags = 0; - VectorCopy(bs->origin, activategoal->goal.origin); - activategoal->goal.areanum = bs->areanum; - VectorSet(activategoal->goal.mins, -8, -8, -8); - VectorSet(activategoal->goal.maxs, 8, 8, 8); - return qtrue; -} - -/* -================== -BotTriggerMultipleGoal -================== -*/ -int BotTriggerMultipleActivateGoal(bot_state_t *bs, int bspent, bot_activategoal_t *activategoal) { - int i, areas[10], numareas, modelindex, entitynum; - char model[128]; - vec3_t start, end, mins, maxs, angles; - vec3_t origin, goalorigin; - - activategoal->shoot = qfalse; - VectorClear(activategoal->target); - //create a bot goal towards the trigger - trap_AAS_ValueForBSPEpairKey(bspent, "model", model, sizeof(model)); - if (!*model) - return qfalse; - modelindex = atoi(model+1); - if (!modelindex) - return qfalse; - VectorClear(angles); - entitynum = BotModelMinsMaxs(modelindex, 0, CONTENTS_TRIGGER, mins, maxs); - //trigger origin - VectorAdd(mins, maxs, origin); - VectorScale(origin, 0.5, origin); - VectorCopy(origin, goalorigin); - // - VectorCopy(goalorigin, start); - start[2] += 24; - VectorCopy(start, end); - end[2] -= 100; - numareas = trap_AAS_TraceAreas(start, end, areas, NULL, 10); - // - for (i = 0; i < numareas; i++) { - if (trap_AAS_AreaReachability(areas[i])) { - break; - } - } - if (i < numareas) { - VectorCopy(origin, activategoal->goal.origin); - activategoal->goal.areanum = areas[i]; - VectorSubtract(mins, origin, activategoal->goal.mins); - VectorSubtract(maxs, origin, activategoal->goal.maxs); - // - activategoal->goal.entitynum = entitynum; - activategoal->goal.number = 0; - activategoal->goal.flags = 0; - return qtrue; - } - return qfalse; -} - -/* -================== -BotPopFromActivateGoalStack -================== -*/ -int BotPopFromActivateGoalStack(bot_state_t *bs) { - if (!bs->activatestack) - return qfalse; - BotEnableActivateGoalAreas(bs->activatestack, qtrue); - bs->activatestack->inuse = qfalse; - bs->activatestack->justused_time = FloatTime(); - bs->activatestack = bs->activatestack->next; - return qtrue; -} - -/* -================== -BotPushOntoActivateGoalStack -================== -*/ -int BotPushOntoActivateGoalStack(bot_state_t *bs, bot_activategoal_t *activategoal) { - int i, best; - float besttime; - - best = -1; - besttime = FloatTime() + 9999; - // - for (i = 0; i < MAX_ACTIVATESTACK; i++) { - if (!bs->activategoalheap[i].inuse) { - if (bs->activategoalheap[i].justused_time < besttime) { - besttime = bs->activategoalheap[i].justused_time; - best = i; - } - } - } - if (best != -1) { - memcpy(&bs->activategoalheap[best], activategoal, sizeof(bot_activategoal_t)); - bs->activategoalheap[best].inuse = qtrue; - bs->activategoalheap[best].next = bs->activatestack; - bs->activatestack = &bs->activategoalheap[best]; - return qtrue; - } - return qfalse; -} - -/* -================== -BotClearActivateGoalStack -================== -*/ -void BotClearActivateGoalStack(bot_state_t *bs) { - while(bs->activatestack) - BotPopFromActivateGoalStack(bs); -} - -/* -================== -BotEnableActivateGoalAreas -================== -*/ -void BotEnableActivateGoalAreas(bot_activategoal_t *activategoal, int enable) { - int i; - - if (activategoal->areasdisabled == !enable) - return; - for (i = 0; i < activategoal->numareas; i++) - trap_AAS_EnableRoutingArea( activategoal->areas[i], enable ); - activategoal->areasdisabled = !enable; -} - -/* -================== -BotIsGoingToActivateEntity -================== -*/ -int BotIsGoingToActivateEntity(bot_state_t *bs, int entitynum) { - bot_activategoal_t *a; - int i; - - for (a = bs->activatestack; a; a = a->next) { - if (a->time < FloatTime()) - continue; - if (a->goal.entitynum == entitynum) - return qtrue; - } - for (i = 0; i < MAX_ACTIVATESTACK; i++) { - if (bs->activategoalheap[i].inuse) - continue; - // - if (bs->activategoalheap[i].goal.entitynum == entitynum) { - // if the bot went for this goal less than 2 seconds ago - if (bs->activategoalheap[i].justused_time > FloatTime() - 2) - return qtrue; - } - } - return qfalse; -} - -/* -================== -BotGetActivateGoal - - returns the number of the bsp entity to activate - goal->entitynum will be set to the game entity to activate -================== -*/ -//#define OBSTACLEDEBUG - -int BotGetActivateGoal(bot_state_t *bs, int entitynum, bot_activategoal_t *activategoal) { - int i, ent, cur_entities[10], spawnflags, modelindex, areas[MAX_ACTIVATEAREAS*2], numareas, t; - char model[MAX_INFO_STRING], tmpmodel[128]; - char target[128], classname[128]; - float health; - char targetname[10][128]; - aas_entityinfo_t entinfo; - aas_areainfo_t areainfo; - vec3_t origin, angles, absmins, absmaxs; - - memset(activategoal, 0, sizeof(bot_activategoal_t)); - BotEntityInfo(entitynum, &entinfo); - Com_sprintf(model, sizeof( model ), "*%d", entinfo.modelindex); - for (ent = trap_AAS_NextBSPEntity(0); ent; ent = trap_AAS_NextBSPEntity(ent)) { - if (!trap_AAS_ValueForBSPEpairKey(ent, "model", tmpmodel, sizeof(tmpmodel))) continue; - if (!strcmp(model, tmpmodel)) break; - } - if (!ent) { - BotAI_Print(PRT_ERROR, "BotGetActivateGoal: no entity found with model %s\n", model); - return 0; - } - trap_AAS_ValueForBSPEpairKey(ent, "classname", classname, sizeof(classname)); - if (!classname) { - BotAI_Print(PRT_ERROR, "BotGetActivateGoal: entity with model %s has no classname\n", model); - return 0; - } - //if it is a door - if (!strcmp(classname, "func_door")) { - if (trap_AAS_FloatForBSPEpairKey(ent, "health", &health)) { - //if the door has health then the door must be shot to open - if (health) { - BotFuncDoorActivateGoal(bs, ent, activategoal); - return ent; - } - } - // - trap_AAS_IntForBSPEpairKey(ent, "spawnflags", &spawnflags); - // if the door starts open then just wait for the door to return - if ( spawnflags & 1 ) - return 0; - //get the door origin - if (!trap_AAS_VectorForBSPEpairKey(ent, "origin", origin)) { - VectorClear(origin); - } - //if the door is open or opening already - if (!VectorCompare(origin, entinfo.origin)) - return 0; - // store all the areas the door is in - trap_AAS_ValueForBSPEpairKey(ent, "model", model, sizeof(model)); - if (*model) { - modelindex = atoi(model+1); - if (modelindex) { - VectorClear(angles); - BotModelMinsMaxs(modelindex, ET_MOVER, 0, absmins, absmaxs); - // - numareas = trap_AAS_BBoxAreas(absmins, absmaxs, areas, MAX_ACTIVATEAREAS*2); - // store the areas with reachabilities first - for (i = 0; i < numareas; i++) { - if (activategoal->numareas >= MAX_ACTIVATEAREAS) - break; - if ( !trap_AAS_AreaReachability(areas[i]) ) { - continue; - } - trap_AAS_AreaInfo(areas[i], &areainfo); - if (areainfo.contents & AREACONTENTS_MOVER) { - activategoal->areas[activategoal->numareas++] = areas[i]; - } - } - // store any remaining areas - for (i = 0; i < numareas; i++) { - if (activategoal->numareas >= MAX_ACTIVATEAREAS) - break; - if ( trap_AAS_AreaReachability(areas[i]) ) { - continue; - } - trap_AAS_AreaInfo(areas[i], &areainfo); - if (areainfo.contents & AREACONTENTS_MOVER) { - activategoal->areas[activategoal->numareas++] = areas[i]; - } - } - } - } - } - // if the bot is blocked by or standing on top of a button - if (!strcmp(classname, "func_button")) { - return 0; - } - // get the targetname so we can find an entity with a matching target - if (!trap_AAS_ValueForBSPEpairKey(ent, "targetname", targetname[0], sizeof(targetname[0]))) { - if (bot_developer.integer) { - BotAI_Print(PRT_ERROR, "BotGetActivateGoal: entity with model \"%s\" has no targetname\n", model); - } - return 0; - } - // allow tree-like activation - cur_entities[0] = trap_AAS_NextBSPEntity(0); - for (i = 0; i >= 0 && i < 10;) { - for (ent = cur_entities[i]; ent; ent = trap_AAS_NextBSPEntity(ent)) { - if (!trap_AAS_ValueForBSPEpairKey(ent, "target", target, sizeof(target))) continue; - if (!strcmp(targetname[i], target)) { - cur_entities[i] = trap_AAS_NextBSPEntity(ent); - break; - } - } - if (!ent) { - if (bot_developer.integer) { - BotAI_Print(PRT_ERROR, "BotGetActivateGoal: no entity with target \"%s\"\n", targetname[i]); - } - i--; - continue; - } - if (!trap_AAS_ValueForBSPEpairKey(ent, "classname", classname, sizeof(classname))) { - if (bot_developer.integer) { - BotAI_Print(PRT_ERROR, "BotGetActivateGoal: entity with target \"%s\" has no classname\n", targetname[i]); - } - continue; - } - // BSP button model - if (!strcmp(classname, "func_button")) { - // - if (!BotFuncButtonActivateGoal(bs, ent, activategoal)) - continue; - // if the bot tries to activate this button already - if ( bs->activatestack && bs->activatestack->inuse && - bs->activatestack->goal.entitynum == activategoal->goal.entitynum && - bs->activatestack->time > FloatTime() && - bs->activatestack->start_time < FloatTime() - 2) - continue; - // if the bot is in a reachability area - if ( trap_AAS_AreaReachability(bs->areanum) ) { - // disable all areas the blocking entity is in - BotEnableActivateGoalAreas( activategoal, qfalse ); - // - t = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, activategoal->goal.areanum, bs->tfl); - // if the button is not reachable - if (!t) { - continue; - } - activategoal->time = FloatTime() + t * 0.01 + 5; - } - return ent; - } - // invisible trigger multiple box - else if (!strcmp(classname, "trigger_multiple")) { - // - if (!BotTriggerMultipleActivateGoal(bs, ent, activategoal)) - continue; - // if the bot tries to activate this trigger already - if ( bs->activatestack && bs->activatestack->inuse && - bs->activatestack->goal.entitynum == activategoal->goal.entitynum && - bs->activatestack->time > FloatTime() && - bs->activatestack->start_time < FloatTime() - 2) - continue; - // if the bot is in a reachability area - if ( trap_AAS_AreaReachability(bs->areanum) ) { - // disable all areas the blocking entity is in - BotEnableActivateGoalAreas( activategoal, qfalse ); - // - t = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, activategoal->goal.areanum, bs->tfl); - // if the trigger is not reachable - if (!t) { - continue; - } - activategoal->time = FloatTime() + t * 0.01 + 5; - } - return ent; - } - else if (!strcmp(classname, "func_timer")) { - // just skip the func_timer - continue; - } - // the actual button or trigger might be linked through a target_relay or target_delay - else if (!strcmp(classname, "target_relay") || !strcmp(classname, "target_delay")) { - if (trap_AAS_ValueForBSPEpairKey(ent, "targetname", targetname[i+1], sizeof(targetname[0]))) { - i++; - cur_entities[i] = trap_AAS_NextBSPEntity(0); - } - } - } -#ifdef OBSTACLEDEBUG - BotAI_Print(PRT_ERROR, "BotGetActivateGoal: no valid activator for entity with target \"%s\"\n", targetname[0]); -#endif - return 0; -} - -/* -================== -BotGoForActivateGoal -================== -*/ -int BotGoForActivateGoal(bot_state_t *bs, bot_activategoal_t *activategoal) { - aas_entityinfo_t activateinfo; - - activategoal->inuse = qtrue; - if (!activategoal->time) - activategoal->time = FloatTime() + 10; - activategoal->start_time = FloatTime(); - BotEntityInfo(activategoal->goal.entitynum, &activateinfo); - VectorCopy(activateinfo.origin, activategoal->origin); - // - if (BotPushOntoActivateGoalStack(bs, activategoal)) { - // enter the activate entity AI node - AIEnter_Seek_ActivateEntity(bs, "BotGoForActivateGoal"); - return qtrue; - } - else { - // enable any routing areas that were disabled - BotEnableActivateGoalAreas(activategoal, qtrue); - return qfalse; - } -} - -/* -================== -BotPrintActivateGoalInfo -================== -*/ -void BotPrintActivateGoalInfo(bot_state_t *bs, bot_activategoal_t *activategoal, int bspent) { - char netname[MAX_NETNAME]; - char classname[128]; - char buf[128]; - - ClientName(bs->client, netname, sizeof(netname)); - trap_AAS_ValueForBSPEpairKey(bspent, "classname", classname, sizeof(classname)); - if (activategoal->shoot) { - Com_sprintf(buf, sizeof(buf), "%s: I have to shoot at a %s from %1.1f %1.1f %1.1f in area %d\n", - netname, classname, - activategoal->goal.origin[0], - activategoal->goal.origin[1], - activategoal->goal.origin[2], - activategoal->goal.areanum); - } - else { - Com_sprintf(buf, sizeof(buf), "%s: I have to activate a %s at %1.1f %1.1f %1.1f in area %d\n", - netname, classname, - activategoal->goal.origin[0], - activategoal->goal.origin[1], - activategoal->goal.origin[2], - activategoal->goal.areanum); - } - trap_EA_Say(bs->client, buf); -} - -/* -================== -BotRandomMove -================== -*/ -void BotRandomMove(bot_state_t *bs, bot_moveresult_t *moveresult) { - vec3_t dir, angles; - - angles[0] = 0; - angles[1] = random() * 360; - angles[2] = 0; - AngleVectors(angles, dir, NULL, NULL); - - trap_BotMoveInDirection(bs->ms, dir, 400, MOVE_WALK); - - moveresult->failure = qfalse; - VectorCopy(dir, moveresult->movedir); -} - -/* -================== -BotAIBlocked - -Very basic handling of bots being blocked by other entities. -Check what kind of entity is blocking the bot and try to activate -it. If that's not an option then try to walk around or over the entity. -Before the bot ends in this part of the AI it should predict which doors to -open, which buttons to activate etc. -================== -*/ -void BotAIBlocked(bot_state_t *bs, bot_moveresult_t *moveresult, int activate) { - int movetype, bspent; - vec3_t hordir, start, end, mins, maxs, sideward, angles, up = {0, 0, 1}; - aas_entityinfo_t entinfo; - bot_activategoal_t activategoal; - - // if the bot is not blocked by anything - if (!moveresult->blocked) { - bs->notblocked_time = FloatTime(); - return; - } - // if stuck in a solid area - if ( moveresult->type == RESULTTYPE_INSOLIDAREA ) { - // move in a random direction in the hope to get out - BotRandomMove(bs, moveresult); - // - return; - } - // get info for the entity that is blocking the bot - BotEntityInfo(moveresult->blockentity, &entinfo); -#ifdef OBSTACLEDEBUG - ClientName(bs->client, netname, sizeof(netname)); - BotAI_Print(PRT_MESSAGE, "%s: I'm blocked by model %d\n", netname, entinfo.modelindex); -#endif // OBSTACLEDEBUG - // if blocked by a bsp model and the bot wants to activate it - if (activate && entinfo.modelindex > 0 && entinfo.modelindex <= max_bspmodelindex) { - // find the bsp entity which should be activated in order to get the blocking entity out of the way - bspent = BotGetActivateGoal(bs, entinfo.number, &activategoal); - if (bspent) { - // - if (bs->activatestack && !bs->activatestack->inuse) - bs->activatestack = NULL; - // if not already trying to activate this entity - if (!BotIsGoingToActivateEntity(bs, activategoal.goal.entitynum)) { - // - BotGoForActivateGoal(bs, &activategoal); - } - // if ontop of an obstacle or - // if the bot is not in a reachability area it'll still - // need some dynamic obstacle avoidance, otherwise return - if (!(moveresult->flags & MOVERESULT_ONTOPOFOBSTACLE) && - trap_AAS_AreaReachability(bs->areanum)) - return; - } - else { - // enable any routing areas that were disabled - BotEnableActivateGoalAreas(&activategoal, qtrue); - } - } - // just some basic dynamic obstacle avoidance code - hordir[0] = moveresult->movedir[0]; - hordir[1] = moveresult->movedir[1]; - hordir[2] = 0; - // if no direction just take a random direction - if (VectorNormalize(hordir) < 0.1) { - VectorSet(angles, 0, 360 * random(), 0); - AngleVectors(angles, hordir, NULL, NULL); - } - // - //if (moveresult->flags & MOVERESULT_ONTOPOFOBSTACLE) movetype = MOVE_JUMP; - //else - movetype = MOVE_WALK; - // if there's an obstacle at the bot's feet and head then - // the bot might be able to crouch through - VectorCopy(bs->origin, start); - start[2] += 18; - VectorMA(start, 5, hordir, end); - VectorSet(mins, -16, -16, -24); - VectorSet(maxs, 16, 16, 4); - // - //bsptrace = AAS_Trace(start, mins, maxs, end, bs->entitynum, MASK_PLAYERSOLID); - //if (bsptrace.fraction >= 1) movetype = MOVE_CROUCH; - // get the sideward vector - CrossProduct(hordir, up, sideward); - // - if (bs->flags & BFL_AVOIDRIGHT) VectorNegate(sideward, sideward); - // try to crouch straight forward? - if (movetype != MOVE_CROUCH || !trap_BotMoveInDirection(bs->ms, hordir, 400, movetype)) { - // perform the movement - if (!trap_BotMoveInDirection(bs->ms, sideward, 400, movetype)) { - // flip the avoid direction flag - bs->flags ^= BFL_AVOIDRIGHT; - // flip the direction - // VectorNegate(sideward, sideward); - VectorMA(sideward, -1, hordir, sideward); - // move in the other direction - trap_BotMoveInDirection(bs->ms, sideward, 400, movetype); - } - } - // - if (bs->notblocked_time < FloatTime() - 0.4) { - // just reset goals and hope the bot will go into another direction? - // is this still needed?? - if (bs->ainode == AINode_Seek_NBG) bs->nbg_time = 0; - else if (bs->ainode == AINode_Seek_LTG) bs->ltg_time = 0; - } -} - -/* -================== -BotAIPredictObstacles - -Predict the route towards the goal and check if the bot -will be blocked by certain obstacles. When the bot has obstacles -on it's path the bot should figure out if they can be removed -by activating certain entities. -================== -*/ -int BotAIPredictObstacles(bot_state_t *bs, bot_goal_t *goal) { - int modelnum, entitynum, bspent; - bot_activategoal_t activategoal; - aas_predictroute_t route; - - if (!bot_predictobstacles.integer) - return qfalse; - - // always predict when the goal change or at regular intervals - if (bs->predictobstacles_goalareanum == goal->areanum && - bs->predictobstacles_time > FloatTime() - 6) { - return qfalse; - } - bs->predictobstacles_goalareanum = goal->areanum; - bs->predictobstacles_time = FloatTime(); - - // predict at most 100 areas or 10 seconds ahead - trap_AAS_PredictRoute(&route, bs->areanum, bs->origin, - goal->areanum, bs->tfl, 100, 1000, - RSE_USETRAVELTYPE|RSE_ENTERCONTENTS, - AREACONTENTS_MOVER, TFL_BRIDGE, 0); - // if bot has to travel through an area with a mover - if (route.stopevent & RSE_ENTERCONTENTS) { - // if the bot will run into a mover - if (route.endcontents & AREACONTENTS_MOVER) { - //NOTE: this only works with bspc 2.1 or higher - modelnum = (route.endcontents & AREACONTENTS_MODELNUM) >> AREACONTENTS_MODELNUMSHIFT; - if (modelnum) { - // - entitynum = BotModelMinsMaxs(modelnum, ET_MOVER, 0, NULL, NULL); - if (entitynum) { - //NOTE: BotGetActivateGoal already checks if the door is open or not - bspent = BotGetActivateGoal(bs, entitynum, &activategoal); - if (bspent) { - // - if (bs->activatestack && !bs->activatestack->inuse) - bs->activatestack = NULL; - // if not already trying to activate this entity - if (!BotIsGoingToActivateEntity(bs, activategoal.goal.entitynum)) { - // - //BotAI_Print(PRT_MESSAGE, "blocked by mover model %d, entity %d ?\n", modelnum, entitynum); - // - BotGoForActivateGoal(bs, &activategoal); - return qtrue; - } - else { - // enable any routing areas that were disabled - BotEnableActivateGoalAreas(&activategoal, qtrue); - } - } - } - } - } - } - else if (route.stopevent & RSE_USETRAVELTYPE) { - if (route.endtravelflags & TFL_BRIDGE) { - //FIXME: check if the bridge is available to travel over - } - } - return qfalse; -} - -/* -================== -BotCheckConsoleMessages -================== -*/ -void BotCheckConsoleMessages(bot_state_t *bs) { - char botname[MAX_NETNAME], message[MAX_MESSAGE_SIZE], netname[MAX_NETNAME], *ptr; - float chat_reply; - int context, handle; - bot_consolemessage_t m; - bot_match_t match; - - //the name of this bot - ClientName(bs->client, botname, sizeof(botname)); - // - while((handle = trap_BotNextConsoleMessage(bs->cs, &m)) != 0) { - //if the chat state is flooded with messages the bot will read them quickly - if (trap_BotNumConsoleMessages(bs->cs) < 10) { - //if it is a chat message the bot needs some time to read it - if (m.type == CMS_CHAT && m.time > FloatTime() - (1 + random())) break; - } - // - ptr = m.message; - //if it is a chat message then don't unify white spaces and don't - //replace synonyms in the netname - if (m.type == CMS_CHAT) { - // - if (trap_BotFindMatch(m.message, &match, MTCONTEXT_REPLYCHAT)) { - ptr = m.message + match.variables[MESSAGE].offset; - } - } - //unify the white spaces in the message - trap_UnifyWhiteSpaces(ptr); - //replace synonyms in the right context - context = BotSynonymContext(bs); - trap_BotReplaceSynonyms(ptr, context); - //if there's no match - if (!BotMatchMessage(bs, m.message)) { - //if it is a chat message - if (m.type == CMS_CHAT && !bot_nochat.integer) { - // - if (!trap_BotFindMatch(m.message, &match, MTCONTEXT_REPLYCHAT)) { - trap_BotRemoveConsoleMessage(bs->cs, handle); - continue; - } - //don't use eliza chats with team messages - if (match.subtype & ST_TEAM) { - trap_BotRemoveConsoleMessage(bs->cs, handle); - continue; - } - // - trap_BotMatchVariable(&match, NETNAME, netname, sizeof(netname)); - trap_BotMatchVariable(&match, MESSAGE, message, sizeof(message)); - //if this is a message from the bot self - if (bs->client == ClientFromName(netname)) { - trap_BotRemoveConsoleMessage(bs->cs, handle); - continue; - } - //unify the message - trap_UnifyWhiteSpaces(message); - // - trap_Cvar_Update(&bot_testrchat); - if (bot_testrchat.integer) { - // - trap_BotLibVarSet("bot_testrchat", "1"); - //if bot replies with a chat message - if (trap_BotReplyChat(bs->cs, message, context, CONTEXT_REPLY, - NULL, NULL, - NULL, NULL, - NULL, NULL, - botname, netname)) { - BotAI_Print(PRT_MESSAGE, "------------------------\n"); - } - else { - BotAI_Print(PRT_MESSAGE, "**** no valid reply ****\n"); - } - } - //if at a valid chat position and not chatting already and not in teamplay - else if (bs->ainode != AINode_Stand && BotValidChatPosition(bs) && !TeamPlayIsOn()) { - chat_reply = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_REPLY, 0, 1); - if (random() < 1.5 / (NumBots()+1) && random() < chat_reply) { - //if bot replies with a chat message - if (trap_BotReplyChat(bs->cs, message, context, CONTEXT_REPLY, - NULL, NULL, - NULL, NULL, - NULL, NULL, - botname, netname)) { - //remove the console message - trap_BotRemoveConsoleMessage(bs->cs, handle); - bs->stand_time = FloatTime() + BotChatTime(bs); - AIEnter_Stand(bs, "BotCheckConsoleMessages: reply chat"); - //EA_Say(bs->client, bs->cs.chatmessage); - break; - } - } - } - } - } - //remove the console message - trap_BotRemoveConsoleMessage(bs->cs, handle); - } -} - -/* -================== -BotCheckEvents -================== -*/ -void BotCheckForGrenades(bot_state_t *bs, entityState_t *state) { - // if this is not a grenade - if (state->eType != ET_MISSILE || state->weapon != WP_GRENADE_LAUNCHER) - return; - // try to avoid the grenade - trap_BotAddAvoidSpot(bs->ms, state->pos.trBase, 160, AVOID_ALWAYS); -} - -#ifdef MISSIONPACK -/* -================== -BotCheckForProxMines -================== -*/ -void BotCheckForProxMines(bot_state_t *bs, entityState_t *state) { - // if this is not a prox mine - if (state->eType != ET_MISSILE || state->weapon != WP_PROX_LAUNCHER) - return; - // if this prox mine is from someone on our own team - if (state->generic1 == BotTeam(bs)) - return; - // if the bot doesn't have a weapon to deactivate the mine - if (!(bs->inventory[INVENTORY_PLASMAGUN] > 0 && bs->inventory[INVENTORY_CELLS] > 0) && - !(bs->inventory[INVENTORY_ROCKETLAUNCHER] > 0 && bs->inventory[INVENTORY_ROCKETS] > 0) && - !(bs->inventory[INVENTORY_BFG10K] > 0 && bs->inventory[INVENTORY_BFGAMMO] > 0) ) { - return; - } - // try to avoid the prox mine - trap_BotAddAvoidSpot(bs->ms, state->pos.trBase, 160, AVOID_ALWAYS); - // - if (bs->numproxmines >= MAX_PROXMINES) - return; - bs->proxmines[bs->numproxmines] = state->number; - bs->numproxmines++; -} - -/* -================== -BotCheckForKamikazeBody -================== -*/ -void BotCheckForKamikazeBody(bot_state_t *bs, entityState_t *state) { - // if this entity is not wearing the kamikaze - if (!(state->eFlags & EF_KAMIKAZE)) - return; - // if this entity isn't dead - if (!(state->eFlags & EF_DEAD)) - return; - //remember this kamikaze body - bs->kamikazebody = state->number; -} -#endif - -/* -================== -BotCheckEvents -================== -*/ -void BotCheckEvents(bot_state_t *bs, entityState_t *state) { - int event; - char buf[128]; -#ifdef MISSIONPACK - aas_entityinfo_t entinfo; -#endif - - //NOTE: this sucks, we're accessing the gentity_t directly - //but there's no other fast way to do it right now - if (bs->entityeventTime[state->number] == g_entities[state->number].eventTime) { - return; - } - bs->entityeventTime[state->number] = g_entities[state->number].eventTime; - //if it's an event only entity - if (state->eType > ET_EVENTS) { - event = (state->eType - ET_EVENTS) & ~EV_EVENT_BITS; - } - else { - event = state->event & ~EV_EVENT_BITS; - } - // - switch(event) { - //client obituary event - case EV_OBITUARY: - { - int target, attacker, mod; - - target = state->otherEntityNum; - attacker = state->otherEntityNum2; - mod = state->eventParm; - // - if (target == bs->client) { - bs->botdeathtype = mod; - bs->lastkilledby = attacker; - // - if (target == attacker || - target == ENTITYNUM_NONE || - target == ENTITYNUM_WORLD) bs->botsuicide = qtrue; - else bs->botsuicide = qfalse; - // - bs->num_deaths++; - } - //else if this client was killed by the bot - else if (attacker == bs->client) { - bs->enemydeathtype = mod; - bs->lastkilledplayer = target; - bs->killedenemy_time = FloatTime(); - // - bs->num_kills++; - } - else if (attacker == bs->enemy && target == attacker) { - bs->enemysuicide = qtrue; - } - // -#ifdef MISSIONPACK - if (gametype == GT_1FCTF) { - // - BotEntityInfo(target, &entinfo); - if ( entinfo.powerups & ( 1 << PW_NEUTRALFLAG ) ) { - if (!BotSameTeam(bs, target)) { - bs->neutralflagstatus = 3; //enemy dropped the flag - bs->flagstatuschanged = qtrue; - } - } - } -#endif - break; - } - case EV_GLOBAL_SOUND: - { - if (state->eventParm < 0 || state->eventParm > MAX_SOUNDS) { - BotAI_Print(PRT_ERROR, "EV_GLOBAL_SOUND: eventParm (%d) out of range\n", state->eventParm); - break; - } - trap_GetConfigstring(CS_SOUNDS + state->eventParm, buf, sizeof(buf)); - /* - if (!strcmp(buf, "sound/teamplay/flagret_red.wav")) { - //red flag is returned - bs->redflagstatus = 0; - bs->flagstatuschanged = qtrue; - } - else if (!strcmp(buf, "sound/teamplay/flagret_blu.wav")) { - //blue flag is returned - bs->blueflagstatus = 0; - bs->flagstatuschanged = qtrue; - } - else*/ -#ifdef MISSIONPACK - if (!strcmp(buf, "sound/items/kamikazerespawn.wav" )) { - //the kamikaze respawned so dont avoid it - BotDontAvoid(bs, "Kamikaze"); - } - else -#endif - if (!strcmp(buf, "sound/items/poweruprespawn.wav")) { - //powerup respawned... go get it - BotGoForPowerups(bs); - } - break; - } - case EV_GLOBAL_TEAM_SOUND: - { - if (gametype == GT_CTF) { - switch(state->eventParm) { - case GTS_RED_CAPTURE: - bs->blueflagstatus = 0; - bs->redflagstatus = 0; - bs->flagstatuschanged = qtrue; - break; //see BotMatch_CTF - case GTS_BLUE_CAPTURE: - bs->blueflagstatus = 0; - bs->redflagstatus = 0; - bs->flagstatuschanged = qtrue; - break; //see BotMatch_CTF - case GTS_RED_RETURN: - //blue flag is returned - bs->blueflagstatus = 0; - bs->flagstatuschanged = qtrue; - break; - case GTS_BLUE_RETURN: - //red flag is returned - bs->redflagstatus = 0; - bs->flagstatuschanged = qtrue; - break; - case GTS_RED_TAKEN: - //blue flag is taken - bs->blueflagstatus = 1; - bs->flagstatuschanged = qtrue; - break; //see BotMatch_CTF - case GTS_BLUE_TAKEN: - //red flag is taken - bs->redflagstatus = 1; - bs->flagstatuschanged = qtrue; - break; //see BotMatch_CTF - } - } -#ifdef MISSIONPACK - else if (gametype == GT_1FCTF) { - switch(state->eventParm) { - case GTS_RED_CAPTURE: - bs->neutralflagstatus = 0; - bs->flagstatuschanged = qtrue; - break; - case GTS_BLUE_CAPTURE: - bs->neutralflagstatus = 0; - bs->flagstatuschanged = qtrue; - break; - case GTS_RED_RETURN: - //flag has returned - bs->neutralflagstatus = 0; - bs->flagstatuschanged = qtrue; - break; - case GTS_BLUE_RETURN: - //flag has returned - bs->neutralflagstatus = 0; - bs->flagstatuschanged = qtrue; - break; - case GTS_RED_TAKEN: - bs->neutralflagstatus = BotTeam(bs) == TEAM_RED ? 2 : 1; //FIXME: check Team_TakeFlagSound in g_team.c - bs->flagstatuschanged = qtrue; - break; - case GTS_BLUE_TAKEN: - bs->neutralflagstatus = BotTeam(bs) == TEAM_BLUE ? 2 : 1; //FIXME: check Team_TakeFlagSound in g_team.c - bs->flagstatuschanged = qtrue; - break; - } - } -#endif - break; - } - case EV_PLAYER_TELEPORT_IN: - { - VectorCopy(state->origin, lastteleport_origin); - lastteleport_time = FloatTime(); - break; - } - case EV_GENERAL_SOUND: - { - //if this sound is played on the bot - if (state->number == bs->client) { - if (state->eventParm < 0 || state->eventParm > MAX_SOUNDS) { - BotAI_Print(PRT_ERROR, "EV_GENERAL_SOUND: eventParm (%d) out of range\n", state->eventParm); - break; - } - //check out the sound - trap_GetConfigstring(CS_SOUNDS + state->eventParm, buf, sizeof(buf)); - //if falling into a death pit - if (!strcmp(buf, "*falling1.wav")) { - //if the bot has a personal teleporter - if (bs->inventory[INVENTORY_TELEPORTER] > 0) { - //use the holdable item - trap_EA_Use(bs->client); - } - } - } - break; - } - case EV_FOOTSTEP: - case EV_FOOTSTEP_METAL: - case EV_FOOTSPLASH: - case EV_FOOTWADE: - case EV_SWIM: - case EV_FALL_SHORT: - case EV_FALL_MEDIUM: - case EV_FALL_FAR: - case EV_STEP_4: - case EV_STEP_8: - case EV_STEP_12: - case EV_STEP_16: - case EV_JUMP_PAD: - case EV_JUMP: - case EV_TAUNT: - case EV_WATER_TOUCH: - case EV_WATER_LEAVE: - case EV_WATER_UNDER: - case EV_WATER_CLEAR: - case EV_ITEM_PICKUP: - case EV_GLOBAL_ITEM_PICKUP: - case EV_NOAMMO: - case EV_CHANGE_WEAPON: - case EV_FIRE_WEAPON: - //FIXME: either add to sound queue or mark player as someone making noise - break; - case EV_USE_ITEM0: - case EV_USE_ITEM1: - case EV_USE_ITEM2: - case EV_USE_ITEM3: - case EV_USE_ITEM4: - case EV_USE_ITEM5: - case EV_USE_ITEM6: - case EV_USE_ITEM7: - case EV_USE_ITEM8: - case EV_USE_ITEM9: - case EV_USE_ITEM10: - case EV_USE_ITEM11: - case EV_USE_ITEM12: - case EV_USE_ITEM13: - case EV_USE_ITEM14: - break; - } -} - -/* -================== -BotCheckSnapshot -================== -*/ -void BotCheckSnapshot(bot_state_t *bs) { - int ent; - entityState_t state; - - //remove all avoid spots - trap_BotAddAvoidSpot(bs->ms, vec3_origin, 0, AVOID_CLEAR); - //reset kamikaze body - bs->kamikazebody = 0; - //reset number of proxmines - bs->numproxmines = 0; - // - ent = 0; - while( ( ent = BotAI_GetSnapshotEntity( bs->client, ent, &state ) ) != -1 ) { - //check the entity state for events - BotCheckEvents(bs, &state); - //check for grenades the bot should avoid - BotCheckForGrenades(bs, &state); - // -#ifdef MISSIONPACK - //check for proximity mines which the bot should deactivate - BotCheckForProxMines(bs, &state); - //check for dead bodies with the kamikaze effect which should be gibbed - BotCheckForKamikazeBody(bs, &state); -#endif - } - //check the player state for events - BotAI_GetEntityState(bs->client, &state); - //copy the player state events to the entity state - state.event = bs->cur_ps.externalEvent; - state.eventParm = bs->cur_ps.externalEventParm; - // - BotCheckEvents(bs, &state); -} - -/* -================== -BotCheckAir -================== -*/ -void BotCheckAir(bot_state_t *bs) { - if (bs->inventory[INVENTORY_ENVIRONMENTSUIT] <= 0) { - if (trap_AAS_PointContents(bs->eye) & (CONTENTS_WATER|CONTENTS_SLIME|CONTENTS_LAVA)) { - return; - } - } - bs->lastair_time = FloatTime(); -} - -/* -================== -BotAlternateRoute -================== -*/ -bot_goal_t *BotAlternateRoute(bot_state_t *bs, bot_goal_t *goal) { - int t; - - // if the bot has an alternative route goal - if (bs->altroutegoal.areanum) { - // - if (bs->reachedaltroutegoal_time) - return goal; - // travel time towards alternative route goal - t = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, bs->altroutegoal.areanum, bs->tfl); - if (t && t < 20) { - //BotAI_Print(PRT_MESSAGE, "reached alternate route goal\n"); - bs->reachedaltroutegoal_time = FloatTime(); - } - memcpy(goal, &bs->altroutegoal, sizeof(bot_goal_t)); - return &bs->altroutegoal; - } - return goal; -} - -/* -================== -BotGetAlternateRouteGoal -================== -*/ -int BotGetAlternateRouteGoal(bot_state_t *bs, int base) { - aas_altroutegoal_t *altroutegoals; - bot_goal_t *goal; - int numaltroutegoals, rnd; - - if (base == TEAM_RED) { - altroutegoals = red_altroutegoals; - numaltroutegoals = red_numaltroutegoals; - } - else { - altroutegoals = blue_altroutegoals; - numaltroutegoals = blue_numaltroutegoals; - } - if (!numaltroutegoals) - return qfalse; - rnd = (float) random() * numaltroutegoals; - if (rnd >= numaltroutegoals) - rnd = numaltroutegoals-1; - goal = &bs->altroutegoal; - goal->areanum = altroutegoals[rnd].areanum; - VectorCopy(altroutegoals[rnd].origin, goal->origin); - VectorSet(goal->mins, -8, -8, -8); - VectorSet(goal->maxs, 8, 8, 8); - goal->entitynum = 0; - goal->iteminfo = 0; - goal->number = 0; - goal->flags = 0; - // - bs->reachedaltroutegoal_time = 0; - return qtrue; -} - -/* -================== -BotSetupAlternateRouteGoals -================== -*/ -void BotSetupAlternativeRouteGoals(void) { - - if (altroutegoals_setup) - return; -#ifdef MISSIONPACK - if (gametype == GT_CTF) { - if (trap_BotGetLevelItemGoal(-1, "Neutral Flag", &ctf_neutralflag) < 0) - BotAI_Print(PRT_WARNING, "no alt routes without Neutral Flag\n"); - if (ctf_neutralflag.areanum) { - // - red_numaltroutegoals = trap_AAS_AlternativeRouteGoals( - ctf_neutralflag.origin, ctf_neutralflag.areanum, - ctf_redflag.origin, ctf_redflag.areanum, TFL_DEFAULT, - red_altroutegoals, MAX_ALTROUTEGOALS, - ALTROUTEGOAL_CLUSTERPORTALS| - ALTROUTEGOAL_VIEWPORTALS); - blue_numaltroutegoals = trap_AAS_AlternativeRouteGoals( - ctf_neutralflag.origin, ctf_neutralflag.areanum, - ctf_blueflag.origin, ctf_blueflag.areanum, TFL_DEFAULT, - blue_altroutegoals, MAX_ALTROUTEGOALS, - ALTROUTEGOAL_CLUSTERPORTALS| - ALTROUTEGOAL_VIEWPORTALS); - } - } - else if (gametype == GT_1FCTF) { - // - red_numaltroutegoals = trap_AAS_AlternativeRouteGoals( - ctf_neutralflag.origin, ctf_neutralflag.areanum, - ctf_redflag.origin, ctf_redflag.areanum, TFL_DEFAULT, - red_altroutegoals, MAX_ALTROUTEGOALS, - ALTROUTEGOAL_CLUSTERPORTALS| - ALTROUTEGOAL_VIEWPORTALS); - blue_numaltroutegoals = trap_AAS_AlternativeRouteGoals( - ctf_neutralflag.origin, ctf_neutralflag.areanum, - ctf_blueflag.origin, ctf_blueflag.areanum, TFL_DEFAULT, - blue_altroutegoals, MAX_ALTROUTEGOALS, - ALTROUTEGOAL_CLUSTERPORTALS| - ALTROUTEGOAL_VIEWPORTALS); - } - else if (gametype == GT_OBELISK) { - if (trap_BotGetLevelItemGoal(-1, "Neutral Obelisk", &neutralobelisk) < 0) - BotAI_Print(PRT_WARNING, "Harvester without neutral obelisk\n"); - // - red_numaltroutegoals = trap_AAS_AlternativeRouteGoals( - neutralobelisk.origin, neutralobelisk.areanum, - redobelisk.origin, redobelisk.areanum, TFL_DEFAULT, - red_altroutegoals, MAX_ALTROUTEGOALS, - ALTROUTEGOAL_CLUSTERPORTALS| - ALTROUTEGOAL_VIEWPORTALS); - blue_numaltroutegoals = trap_AAS_AlternativeRouteGoals( - neutralobelisk.origin, neutralobelisk.areanum, - blueobelisk.origin, blueobelisk.areanum, TFL_DEFAULT, - blue_altroutegoals, MAX_ALTROUTEGOALS, - ALTROUTEGOAL_CLUSTERPORTALS| - ALTROUTEGOAL_VIEWPORTALS); - } - else if (gametype == GT_HARVESTER) { - // - red_numaltroutegoals = trap_AAS_AlternativeRouteGoals( - neutralobelisk.origin, neutralobelisk.areanum, - redobelisk.origin, redobelisk.areanum, TFL_DEFAULT, - red_altroutegoals, MAX_ALTROUTEGOALS, - ALTROUTEGOAL_CLUSTERPORTALS| - ALTROUTEGOAL_VIEWPORTALS); - blue_numaltroutegoals = trap_AAS_AlternativeRouteGoals( - neutralobelisk.origin, neutralobelisk.areanum, - blueobelisk.origin, blueobelisk.areanum, TFL_DEFAULT, - blue_altroutegoals, MAX_ALTROUTEGOALS, - ALTROUTEGOAL_CLUSTERPORTALS| - ALTROUTEGOAL_VIEWPORTALS); - } -#endif - altroutegoals_setup = qtrue; -} - -/* -================== -BotDeathmatchAI -================== -*/ -void BotDeathmatchAI(bot_state_t *bs, float thinktime) { - char gender[144], name[144], buf[144]; - char userinfo[MAX_INFO_STRING]; - int i; - - //if the bot has just been setup - if (bs->setupcount > 0) { - bs->setupcount--; - if (bs->setupcount > 0) return; - //get the gender characteristic - trap_Characteristic_String(bs->character, CHARACTERISTIC_GENDER, gender, sizeof(gender)); - //set the bot gender - trap_GetUserinfo(bs->client, userinfo, sizeof(userinfo)); - Info_SetValueForKey(userinfo, "sex", gender); - trap_SetUserinfo(bs->client, userinfo); - //set the team - if ( !bs->map_restart && g_gametype.integer != GT_TOURNAMENT ) { - Com_sprintf(buf, sizeof(buf), "team %s", bs->settings.team); - trap_EA_Command(bs->client, buf); - } - //set the chat gender - if (gender[0] == 'm') trap_BotSetChatGender(bs->cs, CHAT_GENDERMALE); - else if (gender[0] == 'f') trap_BotSetChatGender(bs->cs, CHAT_GENDERFEMALE); - else trap_BotSetChatGender(bs->cs, CHAT_GENDERLESS); - //set the chat name - ClientName(bs->client, name, sizeof(name)); - trap_BotSetChatName(bs->cs, name, bs->client); - // - bs->lastframe_health = bs->inventory[INVENTORY_HEALTH]; - bs->lasthitcount = bs->cur_ps.persistant[PERS_HITS]; - // - bs->setupcount = 0; - // - BotSetupAlternativeRouteGoals(); - } - //no ideal view set - bs->flags &= ~BFL_IDEALVIEWSET; - // - if (!BotIntermission(bs)) { - //set the teleport time - BotSetTeleportTime(bs); - //update some inventory values - BotUpdateInventory(bs); - //check out the snapshot - BotCheckSnapshot(bs); - //check for air - BotCheckAir(bs); - } - //check the console messages - BotCheckConsoleMessages(bs); - //if not in the intermission and not in observer mode - if (!BotIntermission(bs) && !BotIsObserver(bs)) { - //do team AI - BotTeamAI(bs); - } - //if the bot has no ai node - if (!bs->ainode) { - AIEnter_Seek_LTG(bs, "BotDeathmatchAI: no ai node"); - } - //if the bot entered the game less than 8 seconds ago - if (!bs->entergamechat && bs->entergame_time > FloatTime() - 8) { - if (BotChat_EnterGame(bs)) { - bs->stand_time = FloatTime() + BotChatTime(bs); - AIEnter_Stand(bs, "BotDeathmatchAI: chat enter game"); - } - bs->entergamechat = qtrue; - } - //reset the node switches from the previous frame - BotResetNodeSwitches(); - //execute AI nodes - for (i = 0; i < MAX_NODESWITCHES; i++) { - if (bs->ainode(bs)) break; - } - //if the bot removed itself :) - if (!bs->inuse) return; - //if the bot executed too many AI nodes - if (i >= MAX_NODESWITCHES) { - trap_BotDumpGoalStack(bs->gs); - trap_BotDumpAvoidGoals(bs->gs); - BotDumpNodeSwitches(bs); - ClientName(bs->client, name, sizeof(name)); - BotAI_Print(PRT_ERROR, "%s at %1.1f switched more than %d AI nodes\n", name, FloatTime(), MAX_NODESWITCHES); - } - // - bs->lastframe_health = bs->inventory[INVENTORY_HEALTH]; - bs->lasthitcount = bs->cur_ps.persistant[PERS_HITS]; -} - -/* -================== -BotSetEntityNumForGoalWithModel -================== -*/ -void BotSetEntityNumForGoalWithModel(bot_goal_t *goal, int eType, char *modelname) { - gentity_t *ent; - int i, modelindex; - vec3_t dir; - - modelindex = G_ModelIndex( modelname ); - ent = &g_entities[0]; - for (i = 0; i < level.num_entities; i++, ent++) { - if ( !ent->inuse ) { - continue; - } - if ( eType && ent->s.eType != eType) { - continue; - } - if (ent->s.modelindex != modelindex) { - continue; - } - VectorSubtract(goal->origin, ent->s.origin, dir); - if (VectorLengthSquared(dir) < Square(10)) { - goal->entitynum = i; - return; - } - } -} - -/* -================== -BotSetEntityNumForGoal -================== -*/ -void BotSetEntityNumForGoal(bot_goal_t *goal, char *classname) { - gentity_t *ent; - int i; - vec3_t dir; - - ent = &g_entities[0]; - for (i = 0; i < level.num_entities; i++, ent++) { - if ( !ent->inuse ) { - continue; - } - if ( !Q_stricmp(ent->classname, classname) ) { - continue; - } - VectorSubtract(goal->origin, ent->s.origin, dir); - if (VectorLengthSquared(dir) < Square(10)) { - goal->entitynum = i; - return; - } - } -} - -/* -================== -BotGoalForBSPEntity -================== -*/ -int BotGoalForBSPEntity( char *classname, bot_goal_t *goal ) { - char value[MAX_INFO_STRING]; - vec3_t origin, start, end; - int ent, numareas, areas[10]; - - memset(goal, 0, sizeof(bot_goal_t)); - for (ent = trap_AAS_NextBSPEntity(0); ent; ent = trap_AAS_NextBSPEntity(ent)) { - if (!trap_AAS_ValueForBSPEpairKey(ent, "classname", value, sizeof(value))) - continue; - if (!strcmp(value, classname)) { - if (!trap_AAS_VectorForBSPEpairKey(ent, "origin", origin)) - return qfalse; - VectorCopy(origin, goal->origin); - VectorCopy(origin, start); - start[2] -= 32; - VectorCopy(origin, end); - end[2] += 32; - numareas = trap_AAS_TraceAreas(start, end, areas, NULL, 10); - if (!numareas) - return qfalse; - goal->areanum = areas[0]; - return qtrue; - } - } - return qfalse; -} - -/* -================== -BotSetupDeathmatchAI -================== -*/ -void BotSetupDeathmatchAI(void) { - int ent, modelnum; - char model[128]; - - gametype = trap_Cvar_VariableIntegerValue("g_gametype"); - maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); - - trap_Cvar_Register(&bot_rocketjump, "bot_rocketjump", "1", 0); - trap_Cvar_Register(&bot_grapple, "bot_grapple", "0", 0); - trap_Cvar_Register(&bot_fastchat, "bot_fastchat", "0", 0); - trap_Cvar_Register(&bot_nochat, "bot_nochat", "0", 0); - trap_Cvar_Register(&bot_testrchat, "bot_testrchat", "0", 0); - trap_Cvar_Register(&bot_challenge, "bot_challenge", "0", 0); - trap_Cvar_Register(&bot_predictobstacles, "bot_predictobstacles", "1", 0); - trap_Cvar_Register(&g_spSkill, "g_spSkill", "2", 0); - // - if (gametype == GT_CTF) { - if (trap_BotGetLevelItemGoal(-1, "Red Flag", &ctf_redflag) < 0) - BotAI_Print(PRT_WARNING, "CTF without Red Flag\n"); - if (trap_BotGetLevelItemGoal(-1, "Blue Flag", &ctf_blueflag) < 0) - BotAI_Print(PRT_WARNING, "CTF without Blue Flag\n"); - } -#ifdef MISSIONPACK - else if (gametype == GT_1FCTF) { - if (trap_BotGetLevelItemGoal(-1, "Neutral Flag", &ctf_neutralflag) < 0) - BotAI_Print(PRT_WARNING, "One Flag CTF without Neutral Flag\n"); - if (trap_BotGetLevelItemGoal(-1, "Red Flag", &ctf_redflag) < 0) - BotAI_Print(PRT_WARNING, "CTF without Red Flag\n"); - if (trap_BotGetLevelItemGoal(-1, "Blue Flag", &ctf_blueflag) < 0) - BotAI_Print(PRT_WARNING, "CTF without Blue Flag\n"); - } - else if (gametype == GT_OBELISK) { - if (trap_BotGetLevelItemGoal(-1, "Red Obelisk", &redobelisk) < 0) - BotAI_Print(PRT_WARNING, "Obelisk without red obelisk\n"); - BotSetEntityNumForGoal(&redobelisk, "team_redobelisk"); - if (trap_BotGetLevelItemGoal(-1, "Blue Obelisk", &blueobelisk) < 0) - BotAI_Print(PRT_WARNING, "Obelisk without blue obelisk\n"); - BotSetEntityNumForGoal(&blueobelisk, "team_blueobelisk"); - } - else if (gametype == GT_HARVESTER) { - if (trap_BotGetLevelItemGoal(-1, "Red Obelisk", &redobelisk) < 0) - BotAI_Print(PRT_WARNING, "Harvester without red obelisk\n"); - BotSetEntityNumForGoal(&redobelisk, "team_redobelisk"); - if (trap_BotGetLevelItemGoal(-1, "Blue Obelisk", &blueobelisk) < 0) - BotAI_Print(PRT_WARNING, "Harvester without blue obelisk\n"); - BotSetEntityNumForGoal(&blueobelisk, "team_blueobelisk"); - if (trap_BotGetLevelItemGoal(-1, "Neutral Obelisk", &neutralobelisk) < 0) - BotAI_Print(PRT_WARNING, "Harvester without neutral obelisk\n"); - BotSetEntityNumForGoal(&neutralobelisk, "team_neutralobelisk"); - } -#endif - - max_bspmodelindex = 0; - for (ent = trap_AAS_NextBSPEntity(0); ent; ent = trap_AAS_NextBSPEntity(ent)) { - if (!trap_AAS_ValueForBSPEpairKey(ent, "model", model, sizeof(model))) continue; - if (model[0] == '*') { - modelnum = atoi(model+1); - if (modelnum > max_bspmodelindex) - max_bspmodelindex = modelnum; - } - } - //initialize the waypoint heap - BotInitWaypoints(); -} - -/* -================== -BotShutdownDeathmatchAI -================== -*/ -void BotShutdownDeathmatchAI(void) { - altroutegoals_setup = qfalse; -} +/* +=========================================================================== +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 Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// + +/***************************************************************************** + * name: ai_dmq3.c + * + * desc: Quake3 bot AI + * + * $Archive: /MissionPack/code/game/ai_dmq3.c $ + * + *****************************************************************************/ + + +#include "g_local.h" +#include "botlib.h" +#include "be_aas.h" +#include "be_ea.h" +#include "be_ai_char.h" +#include "be_ai_chat.h" +#include "be_ai_gen.h" +#include "be_ai_goal.h" +#include "be_ai_move.h" +#include "be_ai_weap.h" +// +#include "ai_main.h" +#include "ai_dmq3.h" +#include "ai_chat.h" +#include "ai_cmd.h" +#include "ai_dmnet.h" +#include "ai_team.h" +// +#include "chars.h" //characteristics +#include "inv.h" //indexes into the inventory +#include "syn.h" //synonyms +#include "match.h" //string matching types and vars + +// for the voice chats +#include "../../ui/menudef.h" // sos001205 - for q3_ui also + +// from aasfile.h +#define AREACONTENTS_MOVER 1024 +#define AREACONTENTS_MODELNUMSHIFT 24 +#define AREACONTENTS_MAXMODELNUM 0xFF +#define AREACONTENTS_MODELNUM (AREACONTENTS_MAXMODELNUM << AREACONTENTS_MODELNUMSHIFT) + +#define IDEAL_ATTACKDIST 140 + +#define MAX_WAYPOINTS 128 +// +bot_waypoint_t botai_waypoints[MAX_WAYPOINTS]; +bot_waypoint_t *botai_freewaypoints; + +//NOTE: not using a cvars which can be updated because the game should be reloaded anyway +int gametype; //game type +int maxclients; //maximum number of clients + +vmCvar_t bot_grapple; +vmCvar_t bot_rocketjump; +vmCvar_t bot_fastchat; +vmCvar_t bot_nochat; +vmCvar_t bot_testrchat; +vmCvar_t bot_challenge; +vmCvar_t bot_predictobstacles; +vmCvar_t g_spSkill; + +extern vmCvar_t bot_developer; + +vec3_t lastteleport_origin; //last teleport event origin +float lastteleport_time; //last teleport event time +int max_bspmodelindex; //maximum BSP model index + +//CTF flag goals +bot_goal_t ctf_redflag; +bot_goal_t ctf_blueflag; +#ifdef MISSIONPACK +bot_goal_t ctf_neutralflag; +bot_goal_t redobelisk; +bot_goal_t blueobelisk; +bot_goal_t neutralobelisk; +#endif + +#define MAX_ALTROUTEGOALS 32 + +int altroutegoals_setup; +aas_altroutegoal_t red_altroutegoals[MAX_ALTROUTEGOALS]; +int red_numaltroutegoals; +aas_altroutegoal_t blue_altroutegoals[MAX_ALTROUTEGOALS]; +int blue_numaltroutegoals; + + +/* +================== +BotSetUserInfo +================== +*/ +void BotSetUserInfo(bot_state_t *bs, char *key, char *value) { + char userinfo[MAX_INFO_STRING]; + + trap_GetUserinfo(bs->client, userinfo, sizeof(userinfo)); + Info_SetValueForKey(userinfo, key, value); + trap_SetUserinfo(bs->client, userinfo); + ClientUserinfoChanged( bs->client ); +} + +/* +================== +BotCTFCarryingFlag +================== +*/ +int BotCTFCarryingFlag(bot_state_t *bs) { + if (gametype != GT_CTF) return CTF_FLAG_NONE; + + if (bs->inventory[INVENTORY_REDFLAG] > 0) return CTF_FLAG_RED; + else if (bs->inventory[INVENTORY_BLUEFLAG] > 0) return CTF_FLAG_BLUE; + return CTF_FLAG_NONE; +} + +/* +================== +BotTeam +================== +*/ +int BotTeam(bot_state_t *bs) { + char info[1024]; + + if (bs->client < 0 || bs->client >= MAX_CLIENTS) { + //BotAI_Print(PRT_ERROR, "BotCTFTeam: client out of range\n"); + return qfalse; + } + trap_GetConfigstring(CS_PLAYERS+bs->client, info, sizeof(info)); + // + if (atoi(Info_ValueForKey(info, "t")) == TEAM_RED) return TEAM_RED; + else if (atoi(Info_ValueForKey(info, "t")) == TEAM_BLUE) return TEAM_BLUE; + return TEAM_FREE; +} + +/* +================== +BotOppositeTeam +================== +*/ +int BotOppositeTeam(bot_state_t *bs) { + switch(BotTeam(bs)) { + case TEAM_RED: return TEAM_BLUE; + case TEAM_BLUE: return TEAM_RED; + default: return TEAM_FREE; + } +} + +/* +================== +BotEnemyFlag +================== +*/ +bot_goal_t *BotEnemyFlag(bot_state_t *bs) { + if (BotTeam(bs) == TEAM_RED) { + return &ctf_blueflag; + } + else { + return &ctf_redflag; + } +} + +/* +================== +BotTeamFlag +================== +*/ +bot_goal_t *BotTeamFlag(bot_state_t *bs) { + if (BotTeam(bs) == TEAM_RED) { + return &ctf_redflag; + } + else { + return &ctf_blueflag; + } +} + + +/* +================== +EntityIsDead +================== +*/ +qboolean EntityIsDead(aas_entityinfo_t *entinfo) { + playerState_t ps; + + if (entinfo->number >= 0 && entinfo->number < MAX_CLIENTS) { + //retrieve the current client state + BotAI_GetClientState( entinfo->number, &ps ); + if (ps.pm_type != PM_NORMAL) return qtrue; + } + return qfalse; +} + +/* +================== +EntityCarriesFlag +================== +*/ +qboolean EntityCarriesFlag(aas_entityinfo_t *entinfo) { + if ( entinfo->powerups & ( 1 << PW_REDFLAG ) ) + return qtrue; + if ( entinfo->powerups & ( 1 << PW_BLUEFLAG ) ) + return qtrue; +#ifdef MISSIONPACK + if ( entinfo->powerups & ( 1 << PW_NEUTRALFLAG ) ) + return qtrue; +#endif + return qfalse; +} + +/* +================== +EntityIsInvisible +================== +*/ +qboolean EntityIsInvisible(aas_entityinfo_t *entinfo) { + // the flag is always visible + if (EntityCarriesFlag(entinfo)) { + return qfalse; + } + if (entinfo->powerups & (1 << PW_INVIS)) { + return qtrue; + } + return qfalse; +} + +/* +================== +EntityIsShooting +================== +*/ +qboolean EntityIsShooting(aas_entityinfo_t *entinfo) { + if (entinfo->flags & EF_FIRING) { + return qtrue; + } + return qfalse; +} + +/* +================== +EntityIsChatting +================== +*/ +qboolean EntityIsChatting(aas_entityinfo_t *entinfo) { + if (entinfo->flags & EF_TALK) { + return qtrue; + } + return qfalse; +} + +/* +================== +EntityHasQuad +================== +*/ +qboolean EntityHasQuad(aas_entityinfo_t *entinfo) { + if (entinfo->powerups & (1 << PW_QUAD)) { + return qtrue; + } + return qfalse; +} + +#ifdef MISSIONPACK +/* +================== +EntityHasKamikze +================== +*/ +qboolean EntityHasKamikaze(aas_entityinfo_t *entinfo) { + if (entinfo->flags & EF_KAMIKAZE) { + return qtrue; + } + return qfalse; +} + +/* +================== +EntityCarriesCubes +================== +*/ +qboolean EntityCarriesCubes(aas_entityinfo_t *entinfo) { + entityState_t state; + + if (gametype != GT_HARVESTER) + return qfalse; + //FIXME: get this info from the aas_entityinfo_t ? + BotAI_GetEntityState(entinfo->number, &state); + if (state.generic1 > 0) + return qtrue; + return qfalse; +} + +/* +================== +Bot1FCTFCarryingFlag +================== +*/ +int Bot1FCTFCarryingFlag(bot_state_t *bs) { + if (gametype != GT_1FCTF) return qfalse; + + if (bs->inventory[INVENTORY_NEUTRALFLAG] > 0) return qtrue; + return qfalse; +} + +/* +================== +BotHarvesterCarryingCubes +================== +*/ +int BotHarvesterCarryingCubes(bot_state_t *bs) { + if (gametype != GT_HARVESTER) return qfalse; + + if (bs->inventory[INVENTORY_REDCUBE] > 0) return qtrue; + if (bs->inventory[INVENTORY_BLUECUBE] > 0) return qtrue; + return qfalse; +} +#endif + +/* +================== +BotRememberLastOrderedTask +================== +*/ +void BotRememberLastOrderedTask(bot_state_t *bs) { + if (!bs->ordered) { + return; + } + bs->lastgoal_decisionmaker = bs->decisionmaker; + bs->lastgoal_ltgtype = bs->ltgtype; + memcpy(&bs->lastgoal_teamgoal, &bs->teamgoal, sizeof(bot_goal_t)); + bs->lastgoal_teammate = bs->teammate; +} + +/* +================== +BotSetTeamStatus +================== +*/ +void BotSetTeamStatus(bot_state_t *bs) { +#ifdef MISSIONPACK + int teamtask; + aas_entityinfo_t entinfo; + + teamtask = TEAMTASK_PATROL; + + switch(bs->ltgtype) { + case LTG_TEAMHELP: + break; + case LTG_TEAMACCOMPANY: + BotEntityInfo(bs->teammate, &entinfo); + if ( ( (gametype == GT_CTF || gametype == GT_1FCTF) && EntityCarriesFlag(&entinfo)) + || ( gametype == GT_HARVESTER && EntityCarriesCubes(&entinfo)) ) { + teamtask = TEAMTASK_ESCORT; + } + else { + teamtask = TEAMTASK_FOLLOW; + } + break; + case LTG_DEFENDKEYAREA: + teamtask = TEAMTASK_DEFENSE; + break; + case LTG_GETFLAG: + teamtask = TEAMTASK_OFFENSE; + break; + case LTG_RUSHBASE: + teamtask = TEAMTASK_DEFENSE; + break; + case LTG_RETURNFLAG: + teamtask = TEAMTASK_RETRIEVE; + break; + case LTG_CAMP: + case LTG_CAMPORDER: + teamtask = TEAMTASK_CAMP; + break; + case LTG_PATROL: + teamtask = TEAMTASK_PATROL; + break; + case LTG_GETITEM: + teamtask = TEAMTASK_PATROL; + break; + case LTG_KILL: + teamtask = TEAMTASK_PATROL; + break; + case LTG_HARVEST: + teamtask = TEAMTASK_OFFENSE; + break; + case LTG_ATTACKENEMYBASE: + teamtask = TEAMTASK_OFFENSE; + break; + default: + teamtask = TEAMTASK_PATROL; + break; + } + BotSetUserInfo(bs, "teamtask", va("%d", teamtask)); +#endif +} + +/* +================== +BotSetLastOrderedTask +================== +*/ +int BotSetLastOrderedTask(bot_state_t *bs) { + + if (gametype == GT_CTF) { + // don't go back to returning the flag if it's at the base + if ( bs->lastgoal_ltgtype == LTG_RETURNFLAG ) { + if ( BotTeam(bs) == TEAM_RED ) { + if ( bs->redflagstatus == 0 ) { + bs->lastgoal_ltgtype = 0; + } + } + else { + if ( bs->blueflagstatus == 0 ) { + bs->lastgoal_ltgtype = 0; + } + } + } + } + + if ( bs->lastgoal_ltgtype ) { + bs->decisionmaker = bs->lastgoal_decisionmaker; + bs->ordered = qtrue; + bs->ltgtype = bs->lastgoal_ltgtype; + memcpy(&bs->teamgoal, &bs->lastgoal_teamgoal, sizeof(bot_goal_t)); + bs->teammate = bs->lastgoal_teammate; + bs->teamgoal_time = FloatTime() + 300; + BotSetTeamStatus(bs); + // + if ( gametype == GT_CTF ) { + if ( bs->ltgtype == LTG_GETFLAG ) { + bot_goal_t *tb, *eb; + int tt, et; + + tb = BotTeamFlag(bs); + eb = BotEnemyFlag(bs); + tt = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, tb->areanum, TFL_DEFAULT); + et = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, eb->areanum, TFL_DEFAULT); + // if the travel time towards the enemy base is larger than towards our base + if (et > tt) { + //get an alternative route goal towards the enemy base + BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs)); + } + } + } + return qtrue; + } + return qfalse; +} + +/* +================== +BotRefuseOrder +================== +*/ +void BotRefuseOrder(bot_state_t *bs) { + if (!bs->ordered) + return; + // if the bot was ordered to do something + if ( bs->order_time && bs->order_time > FloatTime() - 10 ) { + trap_EA_Action(bs->client, ACTION_NEGATIVE); + BotVoiceChat(bs, bs->decisionmaker, VOICECHAT_NO); + bs->order_time = 0; + } +} + +/* +================== +BotCTFSeekGoals +================== +*/ +void BotCTFSeekGoals(bot_state_t *bs) { + float rnd, l1, l2; + int flagstatus, c; + vec3_t dir; + aas_entityinfo_t entinfo; + + //when carrying a flag in ctf the bot should rush to the base + if (BotCTFCarryingFlag(bs)) { + //if not already rushing to the base + if (bs->ltgtype != LTG_RUSHBASE) { + BotRefuseOrder(bs); + bs->ltgtype = LTG_RUSHBASE; + bs->teamgoal_time = FloatTime() + CTF_RUSHBASE_TIME; + bs->rushbaseaway_time = 0; + bs->decisionmaker = bs->client; + bs->ordered = qfalse; + // + switch(BotTeam(bs)) { + case TEAM_RED: VectorSubtract(bs->origin, ctf_blueflag.origin, dir); break; + case TEAM_BLUE: VectorSubtract(bs->origin, ctf_redflag.origin, dir); break; + default: VectorSet(dir, 999, 999, 999); break; + } + // if the bot picked up the flag very close to the enemy base + if ( VectorLength(dir) < 128 ) { + // get an alternative route goal through the enemy base + BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs)); + } else { + // don't use any alt route goal, just get the hell out of the base + bs->altroutegoal.areanum = 0; + } + BotSetUserInfo(bs, "teamtask", va("%d", TEAMTASK_OFFENSE)); + BotVoiceChat(bs, -1, VOICECHAT_IHAVEFLAG); + } + else if (bs->rushbaseaway_time > FloatTime()) { + if (BotTeam(bs) == TEAM_RED) flagstatus = bs->redflagstatus; + else flagstatus = bs->blueflagstatus; + //if the flag is back + if (flagstatus == 0) { + bs->rushbaseaway_time = 0; + } + } + return; + } + // if the bot decided to follow someone + if ( bs->ltgtype == LTG_TEAMACCOMPANY && !bs->ordered ) { + // if the team mate being accompanied no longer carries the flag + BotEntityInfo(bs->teammate, &entinfo); + if (!EntityCarriesFlag(&entinfo)) { + bs->ltgtype = 0; + } + } + // + if (BotTeam(bs) == TEAM_RED) flagstatus = bs->redflagstatus * 2 + bs->blueflagstatus; + else flagstatus = bs->blueflagstatus * 2 + bs->redflagstatus; + //if our team has the enemy flag and our flag is at the base + if (flagstatus == 1) { + // + if (bs->owndecision_time < FloatTime()) { + //if Not defending the base already + if (!(bs->ltgtype == LTG_DEFENDKEYAREA && + (bs->teamgoal.number == ctf_redflag.number || + bs->teamgoal.number == ctf_blueflag.number))) { + //if there is a visible team mate flag carrier + c = BotTeamFlagCarrierVisible(bs); + if (c >= 0 && + // and not already following the team mate flag carrier + (bs->ltgtype != LTG_TEAMACCOMPANY || bs->teammate != c)) { + // + BotRefuseOrder(bs); + //follow the flag carrier + bs->decisionmaker = bs->client; + bs->ordered = qfalse; + //the team mate + bs->teammate = c; + //last time the team mate was visible + bs->teammatevisible_time = FloatTime(); + //no message + bs->teammessage_time = 0; + //no arrive message + bs->arrive_time = 1; + // + BotVoiceChat(bs, bs->teammate, VOICECHAT_ONFOLLOW); + //get the team goal time + bs->teamgoal_time = FloatTime() + TEAM_ACCOMPANY_TIME; + bs->ltgtype = LTG_TEAMACCOMPANY; + bs->formation_dist = 3.5 * 32; //3.5 meter + BotSetTeamStatus(bs); + bs->owndecision_time = FloatTime() + 5; + } + } + } + return; + } + //if the enemy has our flag + else if (flagstatus == 2) { + // + if (bs->owndecision_time < FloatTime()) { + //if enemy flag carrier is visible + c = BotEnemyFlagCarrierVisible(bs); + if (c >= 0) { + //FIXME: fight enemy flag carrier + } + //if not already doing something important + if (bs->ltgtype != LTG_GETFLAG && + bs->ltgtype != LTG_RETURNFLAG && + bs->ltgtype != LTG_TEAMHELP && + bs->ltgtype != LTG_TEAMACCOMPANY && + bs->ltgtype != LTG_CAMPORDER && + bs->ltgtype != LTG_PATROL && + bs->ltgtype != LTG_GETITEM) { + + BotRefuseOrder(bs); + bs->decisionmaker = bs->client; + bs->ordered = qfalse; + // + if (random() < 0.5) { + //go for the enemy flag + bs->ltgtype = LTG_GETFLAG; + } + else { + bs->ltgtype = LTG_RETURNFLAG; + } + //no team message + bs->teammessage_time = 0; + //set the time the bot will stop getting the flag + bs->teamgoal_time = FloatTime() + CTF_GETFLAG_TIME; + //get an alternative route goal towards the enemy base + BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs)); + // + BotSetTeamStatus(bs); + bs->owndecision_time = FloatTime() + 5; + } + } + return; + } + //if both flags Not at their bases + else if (flagstatus == 3) { + // + if (bs->owndecision_time < FloatTime()) { + // if not trying to return the flag and not following the team flag carrier + if ( bs->ltgtype != LTG_RETURNFLAG && bs->ltgtype != LTG_TEAMACCOMPANY ) { + // + c = BotTeamFlagCarrierVisible(bs); + // if there is a visible team mate flag carrier + if (c >= 0) { + BotRefuseOrder(bs); + //follow the flag carrier + bs->decisionmaker = bs->client; + bs->ordered = qfalse; + //the team mate + bs->teammate = c; + //last time the team mate was visible + bs->teammatevisible_time = FloatTime(); + //no message + bs->teammessage_time = 0; + //no arrive message + bs->arrive_time = 1; + // + BotVoiceChat(bs, bs->teammate, VOICECHAT_ONFOLLOW); + //get the team goal time + bs->teamgoal_time = FloatTime() + TEAM_ACCOMPANY_TIME; + bs->ltgtype = LTG_TEAMACCOMPANY; + bs->formation_dist = 3.5 * 32; //3.5 meter + // + BotSetTeamStatus(bs); + bs->owndecision_time = FloatTime() + 5; + } + else { + BotRefuseOrder(bs); + bs->decisionmaker = bs->client; + bs->ordered = qfalse; + //get the enemy flag + bs->teammessage_time = FloatTime() + 2 * random(); + //get the flag + bs->ltgtype = LTG_RETURNFLAG; + //set the time the bot will stop getting the flag + bs->teamgoal_time = FloatTime() + CTF_RETURNFLAG_TIME; + //get an alternative route goal towards the enemy base + BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs)); + // + BotSetTeamStatus(bs); + bs->owndecision_time = FloatTime() + 5; + } + } + } + return; + } + // don't just do something wait for the bot team leader to give orders + if (BotTeamLeader(bs)) { + return; + } + // if the bot is ordered to do something + if ( bs->lastgoal_ltgtype ) { + bs->teamgoal_time += 60; + } + // if the bot decided to do something on it's own and has a last ordered goal + if ( !bs->ordered && bs->lastgoal_ltgtype ) { + bs->ltgtype = 0; + } + //if already a CTF or team goal + if (bs->ltgtype == LTG_TEAMHELP || + bs->ltgtype == LTG_TEAMACCOMPANY || + bs->ltgtype == LTG_DEFENDKEYAREA || + bs->ltgtype == LTG_GETFLAG || + bs->ltgtype == LTG_RUSHBASE || + bs->ltgtype == LTG_RETURNFLAG || + bs->ltgtype == LTG_CAMPORDER || + bs->ltgtype == LTG_PATROL || + bs->ltgtype == LTG_GETITEM || + bs->ltgtype == LTG_MAKELOVE_UNDER || + bs->ltgtype == LTG_MAKELOVE_ONTOP) { + return; + } + // + if (BotSetLastOrderedTask(bs)) + return; + // + if (bs->owndecision_time > FloatTime()) + return;; + //if the bot is roaming + if (bs->ctfroam_time > FloatTime()) + return; + //if the bot has anough aggression to decide what to do + if (BotAggression(bs) < 50) + return; + //set the time to send a message to the team mates + bs->teammessage_time = FloatTime() + 2 * random(); + // + if (bs->teamtaskpreference & (TEAMTP_ATTACKER|TEAMTP_DEFENDER)) { + if (bs->teamtaskpreference & TEAMTP_ATTACKER) { + l1 = 0.7f; + } + else { + l1 = 0.2f; + } + l2 = 0.9f; + } + else { + l1 = 0.4f; + l2 = 0.7f; + } + //get the flag or defend the base + rnd = random(); + if (rnd < l1 && ctf_redflag.areanum && ctf_blueflag.areanum) { + bs->decisionmaker = bs->client; + bs->ordered = qfalse; + bs->ltgtype = LTG_GETFLAG; + //set the time the bot will stop getting the flag + bs->teamgoal_time = FloatTime() + CTF_GETFLAG_TIME; + //get an alternative route goal towards the enemy base + BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs)); + BotSetTeamStatus(bs); + } + else if (rnd < l2 && ctf_redflag.areanum && ctf_blueflag.areanum) { + bs->decisionmaker = bs->client; + bs->ordered = qfalse; + // + if (BotTeam(bs) == TEAM_RED) memcpy(&bs->teamgoal, &ctf_redflag, sizeof(bot_goal_t)); + else memcpy(&bs->teamgoal, &ctf_blueflag, sizeof(bot_goal_t)); + //set the ltg type + bs->ltgtype = LTG_DEFENDKEYAREA; + //set the time the bot stops defending the base + bs->teamgoal_time = FloatTime() + TEAM_DEFENDKEYAREA_TIME; + bs->defendaway_time = 0; + BotSetTeamStatus(bs); + } + else { + bs->ltgtype = 0; + //set the time the bot will stop roaming + bs->ctfroam_time = FloatTime() + CTF_ROAM_TIME; + BotSetTeamStatus(bs); + } + bs->owndecision_time = FloatTime() + 5; +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} + +/* +================== +BotCTFRetreatGoals +================== +*/ +void BotCTFRetreatGoals(bot_state_t *bs) { + //when carrying a flag in ctf the bot should rush to the base + if (BotCTFCarryingFlag(bs)) { + //if not already rushing to the base + if (bs->ltgtype != LTG_RUSHBASE) { + BotRefuseOrder(bs); + bs->ltgtype = LTG_RUSHBASE; + bs->teamgoal_time = FloatTime() + CTF_RUSHBASE_TIME; + bs->rushbaseaway_time = 0; + bs->decisionmaker = bs->client; + bs->ordered = qfalse; + BotSetTeamStatus(bs); + } + } +} + +#ifdef MISSIONPACK +/* +================== +Bot1FCTFSeekGoals +================== +*/ +void Bot1FCTFSeekGoals(bot_state_t *bs) { + aas_entityinfo_t entinfo; + float rnd, l1, l2; + int c; + + //when carrying a flag in ctf the bot should rush to the base + if (Bot1FCTFCarryingFlag(bs)) { + //if not already rushing to the base + if (bs->ltgtype != LTG_RUSHBASE) { + BotRefuseOrder(bs); + bs->ltgtype = LTG_RUSHBASE; + bs->teamgoal_time = FloatTime() + CTF_RUSHBASE_TIME; + bs->rushbaseaway_time = 0; + bs->decisionmaker = bs->client; + bs->ordered = qfalse; + //get an alternative route goal towards the enemy base + BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs)); + // + BotSetTeamStatus(bs); + BotVoiceChat(bs, -1, VOICECHAT_IHAVEFLAG); + } + return; + } + // if the bot decided to follow someone + if ( bs->ltgtype == LTG_TEAMACCOMPANY && !bs->ordered ) { + // if the team mate being accompanied no longer carries the flag + BotEntityInfo(bs->teammate, &entinfo); + if (!EntityCarriesFlag(&entinfo)) { + bs->ltgtype = 0; + } + } + //our team has the flag + if (bs->neutralflagstatus == 1) { + if (bs->owndecision_time < FloatTime()) { + // if not already following someone + if (bs->ltgtype != LTG_TEAMACCOMPANY) { + //if there is a visible team mate flag carrier + c = BotTeamFlagCarrierVisible(bs); + if (c >= 0) { + BotRefuseOrder(bs); + //follow the flag carrier + bs->decisionmaker = bs->client; + bs->ordered = qfalse; + //the team mate + bs->teammate = c; + //last time the team mate was visible + bs->teammatevisible_time = FloatTime(); + //no message + bs->teammessage_time = 0; + //no arrive message + bs->arrive_time = 1; + // + BotVoiceChat(bs, bs->teammate, VOICECHAT_ONFOLLOW); + //get the team goal time + bs->teamgoal_time = FloatTime() + TEAM_ACCOMPANY_TIME; + bs->ltgtype = LTG_TEAMACCOMPANY; + bs->formation_dist = 3.5 * 32; //3.5 meter + BotSetTeamStatus(bs); + bs->owndecision_time = FloatTime() + 5; + return; + } + } + //if already a CTF or team goal + if (bs->ltgtype == LTG_TEAMHELP || + bs->ltgtype == LTG_TEAMACCOMPANY || + bs->ltgtype == LTG_DEFENDKEYAREA || + bs->ltgtype == LTG_GETFLAG || + bs->ltgtype == LTG_RUSHBASE || + bs->ltgtype == LTG_CAMPORDER || + bs->ltgtype == LTG_PATROL || + bs->ltgtype == LTG_ATTACKENEMYBASE || + bs->ltgtype == LTG_GETITEM || + bs->ltgtype == LTG_MAKELOVE_UNDER || + bs->ltgtype == LTG_MAKELOVE_ONTOP) { + return; + } + //if not already attacking the enemy base + if (bs->ltgtype != LTG_ATTACKENEMYBASE) { + BotRefuseOrder(bs); + bs->decisionmaker = bs->client; + bs->ordered = qfalse; + // + if (BotTeam(bs) == TEAM_RED) memcpy(&bs->teamgoal, &ctf_blueflag, sizeof(bot_goal_t)); + else memcpy(&bs->teamgoal, &ctf_redflag, sizeof(bot_goal_t)); + //set the ltg type + bs->ltgtype = LTG_ATTACKENEMYBASE; + //set the time the bot will stop getting the flag + bs->teamgoal_time = FloatTime() + TEAM_ATTACKENEMYBASE_TIME; + BotSetTeamStatus(bs); + bs->owndecision_time = FloatTime() + 5; + } + } + return; + } + //enemy team has the flag + else if (bs->neutralflagstatus == 2) { + if (bs->owndecision_time < FloatTime()) { + c = BotEnemyFlagCarrierVisible(bs); + if (c >= 0) { + //FIXME: attack enemy flag carrier + } + //if already a CTF or team goal + if (bs->ltgtype == LTG_TEAMHELP || + bs->ltgtype == LTG_TEAMACCOMPANY || + bs->ltgtype == LTG_CAMPORDER || + bs->ltgtype == LTG_PATROL || + bs->ltgtype == LTG_GETITEM) { + return; + } + // if not already defending the base + if (bs->ltgtype != LTG_DEFENDKEYAREA) { + BotRefuseOrder(bs); + bs->decisionmaker = bs->client; + bs->ordered = qfalse; + // + if (BotTeam(bs) == TEAM_RED) memcpy(&bs->teamgoal, &ctf_redflag, sizeof(bot_goal_t)); + else memcpy(&bs->teamgoal, &ctf_blueflag, sizeof(bot_goal_t)); + //set the ltg type + bs->ltgtype = LTG_DEFENDKEYAREA; + //set the time the bot stops defending the base + bs->teamgoal_time = FloatTime() + TEAM_DEFENDKEYAREA_TIME; + bs->defendaway_time = 0; + BotSetTeamStatus(bs); + bs->owndecision_time = FloatTime() + 5; + } + } + return; + } + // don't just do something wait for the bot team leader to give orders + if (BotTeamLeader(bs)) { + return; + } + // if the bot is ordered to do something + if ( bs->lastgoal_ltgtype ) { + bs->teamgoal_time += 60; + } + // if the bot decided to do something on it's own and has a last ordered goal + if ( !bs->ordered && bs->lastgoal_ltgtype ) { + bs->ltgtype = 0; + } + //if already a CTF or team goal + if (bs->ltgtype == LTG_TEAMHELP || + bs->ltgtype == LTG_TEAMACCOMPANY || + bs->ltgtype == LTG_DEFENDKEYAREA || + bs->ltgtype == LTG_GETFLAG || + bs->ltgtype == LTG_RUSHBASE || + bs->ltgtype == LTG_RETURNFLAG || + bs->ltgtype == LTG_CAMPORDER || + bs->ltgtype == LTG_PATROL || + bs->ltgtype == LTG_ATTACKENEMYBASE || + bs->ltgtype == LTG_GETITEM || + bs->ltgtype == LTG_MAKELOVE_UNDER || + bs->ltgtype == LTG_MAKELOVE_ONTOP) { + return; + } + // + if (BotSetLastOrderedTask(bs)) + return; + // + if (bs->owndecision_time > FloatTime()) + return;; + //if the bot is roaming + if (bs->ctfroam_time > FloatTime()) + return; + //if the bot has anough aggression to decide what to do + if (BotAggression(bs) < 50) + return; + //set the time to send a message to the team mates + bs->teammessage_time = FloatTime() + 2 * random(); + // + if (bs->teamtaskpreference & (TEAMTP_ATTACKER|TEAMTP_DEFENDER)) { + if (bs->teamtaskpreference & TEAMTP_ATTACKER) { + l1 = 0.7f; + } + else { + l1 = 0.2f; + } + l2 = 0.9f; + } + else { + l1 = 0.4f; + l2 = 0.7f; + } + //get the flag or defend the base + rnd = random(); + if (rnd < l1 && ctf_neutralflag.areanum) { + bs->decisionmaker = bs->client; + bs->ordered = qfalse; + bs->ltgtype = LTG_GETFLAG; + //set the time the bot will stop getting the flag + bs->teamgoal_time = FloatTime() + CTF_GETFLAG_TIME; + BotSetTeamStatus(bs); + } + else if (rnd < l2 && ctf_redflag.areanum && ctf_blueflag.areanum) { + bs->decisionmaker = bs->client; + bs->ordered = qfalse; + // + if (BotTeam(bs) == TEAM_RED) memcpy(&bs->teamgoal, &ctf_redflag, sizeof(bot_goal_t)); + else memcpy(&bs->teamgoal, &ctf_blueflag, sizeof(bot_goal_t)); + //set the ltg type + bs->ltgtype = LTG_DEFENDKEYAREA; + //set the time the bot stops defending the base + bs->teamgoal_time = FloatTime() + TEAM_DEFENDKEYAREA_TIME; + bs->defendaway_time = 0; + BotSetTeamStatus(bs); + } + else { + bs->ltgtype = 0; + //set the time the bot will stop roaming + bs->ctfroam_time = FloatTime() + CTF_ROAM_TIME; + BotSetTeamStatus(bs); + } + bs->owndecision_time = FloatTime() + 5; +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} + +/* +================== +Bot1FCTFRetreatGoals +================== +*/ +void Bot1FCTFRetreatGoals(bot_state_t *bs) { + //when carrying a flag in ctf the bot should rush to the enemy base + if (Bot1FCTFCarryingFlag(bs)) { + //if not already rushing to the base + if (bs->ltgtype != LTG_RUSHBASE) { + BotRefuseOrder(bs); + bs->ltgtype = LTG_RUSHBASE; + bs->teamgoal_time = FloatTime() + CTF_RUSHBASE_TIME; + bs->rushbaseaway_time = 0; + bs->decisionmaker = bs->client; + bs->ordered = qfalse; + //get an alternative route goal towards the enemy base + BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs)); + BotSetTeamStatus(bs); + } + } +} + +/* +================== +BotObeliskSeekGoals +================== +*/ +void BotObeliskSeekGoals(bot_state_t *bs) { + float rnd, l1, l2; + + // don't just do something wait for the bot team leader to give orders + if (BotTeamLeader(bs)) { + return; + } + // if the bot is ordered to do something + if ( bs->lastgoal_ltgtype ) { + bs->teamgoal_time += 60; + } + //if already a team goal + if (bs->ltgtype == LTG_TEAMHELP || + bs->ltgtype == LTG_TEAMACCOMPANY || + bs->ltgtype == LTG_DEFENDKEYAREA || + bs->ltgtype == LTG_GETFLAG || + bs->ltgtype == LTG_RUSHBASE || + bs->ltgtype == LTG_RETURNFLAG || + bs->ltgtype == LTG_CAMPORDER || + bs->ltgtype == LTG_PATROL || + bs->ltgtype == LTG_ATTACKENEMYBASE || + bs->ltgtype == LTG_GETITEM || + bs->ltgtype == LTG_MAKELOVE_UNDER || + bs->ltgtype == LTG_MAKELOVE_ONTOP) { + return; + } + // + if (BotSetLastOrderedTask(bs)) + return; + //if the bot is roaming + if (bs->ctfroam_time > FloatTime()) + return; + //if the bot has anough aggression to decide what to do + if (BotAggression(bs) < 50) + return; + //set the time to send a message to the team mates + bs->teammessage_time = FloatTime() + 2 * random(); + // + if (bs->teamtaskpreference & (TEAMTP_ATTACKER|TEAMTP_DEFENDER)) { + if (bs->teamtaskpreference & TEAMTP_ATTACKER) { + l1 = 0.7f; + } + else { + l1 = 0.2f; + } + l2 = 0.9f; + } + else { + l1 = 0.4f; + l2 = 0.7f; + } + //get the flag or defend the base + rnd = random(); + if (rnd < l1 && redobelisk.areanum && blueobelisk.areanum) { + bs->decisionmaker = bs->client; + bs->ordered = qfalse; + // + if (BotTeam(bs) == TEAM_RED) memcpy(&bs->teamgoal, &blueobelisk, sizeof(bot_goal_t)); + else memcpy(&bs->teamgoal, &redobelisk, sizeof(bot_goal_t)); + //set the ltg type + bs->ltgtype = LTG_ATTACKENEMYBASE; + //set the time the bot will stop attacking the enemy base + bs->teamgoal_time = FloatTime() + TEAM_ATTACKENEMYBASE_TIME; + //get an alternate route goal towards the enemy base + BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs)); + BotSetTeamStatus(bs); + } + else if (rnd < l2 && redobelisk.areanum && blueobelisk.areanum) { + bs->decisionmaker = bs->client; + bs->ordered = qfalse; + // + if (BotTeam(bs) == TEAM_RED) memcpy(&bs->teamgoal, &redobelisk, sizeof(bot_goal_t)); + else memcpy(&bs->teamgoal, &blueobelisk, sizeof(bot_goal_t)); + //set the ltg type + bs->ltgtype = LTG_DEFENDKEYAREA; + //set the time the bot stops defending the base + bs->teamgoal_time = FloatTime() + TEAM_DEFENDKEYAREA_TIME; + bs->defendaway_time = 0; + BotSetTeamStatus(bs); + } + else { + bs->ltgtype = 0; + //set the time the bot will stop roaming + bs->ctfroam_time = FloatTime() + CTF_ROAM_TIME; + BotSetTeamStatus(bs); + } +} + +/* +================== +BotGoHarvest +================== +*/ +void BotGoHarvest(bot_state_t *bs) { + // + if (BotTeam(bs) == TEAM_RED) memcpy(&bs->teamgoal, &blueobelisk, sizeof(bot_goal_t)); + else memcpy(&bs->teamgoal, &redobelisk, sizeof(bot_goal_t)); + //set the ltg type + bs->ltgtype = LTG_HARVEST; + //set the time the bot will stop harvesting + bs->teamgoal_time = FloatTime() + TEAM_HARVEST_TIME; + bs->harvestaway_time = 0; + BotSetTeamStatus(bs); +} + +/* +================== +BotObeliskRetreatGoals +================== +*/ +void BotObeliskRetreatGoals(bot_state_t *bs) { + //nothing special +} + +/* +================== +BotHarvesterSeekGoals +================== +*/ +void BotHarvesterSeekGoals(bot_state_t *bs) { + aas_entityinfo_t entinfo; + float rnd, l1, l2; + int c; + + //when carrying cubes in harvester the bot should rush to the base + if (BotHarvesterCarryingCubes(bs)) { + //if not already rushing to the base + if (bs->ltgtype != LTG_RUSHBASE) { + BotRefuseOrder(bs); + bs->ltgtype = LTG_RUSHBASE; + bs->teamgoal_time = FloatTime() + CTF_RUSHBASE_TIME; + bs->rushbaseaway_time = 0; + bs->decisionmaker = bs->client; + bs->ordered = qfalse; + //get an alternative route goal towards the enemy base + BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs)); + // + BotSetTeamStatus(bs); + } + return; + } + // don't just do something wait for the bot team leader to give orders + if (BotTeamLeader(bs)) { + return; + } + // if the bot decided to follow someone + if ( bs->ltgtype == LTG_TEAMACCOMPANY && !bs->ordered ) { + // if the team mate being accompanied no longer carries the flag + BotEntityInfo(bs->teammate, &entinfo); + if (!EntityCarriesCubes(&entinfo)) { + bs->ltgtype = 0; + } + } + // if the bot is ordered to do something + if ( bs->lastgoal_ltgtype ) { + bs->teamgoal_time += 60; + } + //if not yet doing something + if (bs->ltgtype == LTG_TEAMHELP || + bs->ltgtype == LTG_TEAMACCOMPANY || + bs->ltgtype == LTG_DEFENDKEYAREA || + bs->ltgtype == LTG_GETFLAG || + bs->ltgtype == LTG_CAMPORDER || + bs->ltgtype == LTG_PATROL || + bs->ltgtype == LTG_ATTACKENEMYBASE || + bs->ltgtype == LTG_HARVEST || + bs->ltgtype == LTG_GETITEM || + bs->ltgtype == LTG_MAKELOVE_UNDER || + bs->ltgtype == LTG_MAKELOVE_ONTOP) { + return; + } + // + if (BotSetLastOrderedTask(bs)) + return; + //if the bot is roaming + if (bs->ctfroam_time > FloatTime()) + return; + //if the bot has anough aggression to decide what to do + if (BotAggression(bs) < 50) + return; + //set the time to send a message to the team mates + bs->teammessage_time = FloatTime() + 2 * random(); + // + c = BotEnemyCubeCarrierVisible(bs); + if (c >= 0) { + //FIXME: attack enemy cube carrier + } + if (bs->ltgtype != LTG_TEAMACCOMPANY) { + //if there is a visible team mate carrying cubes + c = BotTeamCubeCarrierVisible(bs); + if (c >= 0) { + //follow the team mate carrying cubes + bs->decisionmaker = bs->client; + bs->ordered = qfalse; + //the team mate + bs->teammate = c; + //last time the team mate was visible + bs->teammatevisible_time = FloatTime(); + //no message + bs->teammessage_time = 0; + //no arrive message + bs->arrive_time = 1; + // + BotVoiceChat(bs, bs->teammate, VOICECHAT_ONFOLLOW); + //get the team goal time + bs->teamgoal_time = FloatTime() + TEAM_ACCOMPANY_TIME; + bs->ltgtype = LTG_TEAMACCOMPANY; + bs->formation_dist = 3.5 * 32; //3.5 meter + BotSetTeamStatus(bs); + return; + } + } + // + if (bs->teamtaskpreference & (TEAMTP_ATTACKER|TEAMTP_DEFENDER)) { + if (bs->teamtaskpreference & TEAMTP_ATTACKER) { + l1 = 0.7f; + } + else { + l1 = 0.2f; + } + l2 = 0.9f; + } + else { + l1 = 0.4f; + l2 = 0.7f; + } + // + rnd = random(); + if (rnd < l1 && redobelisk.areanum && blueobelisk.areanum) { + bs->decisionmaker = bs->client; + bs->ordered = qfalse; + BotGoHarvest(bs); + } + else if (rnd < l2 && redobelisk.areanum && blueobelisk.areanum) { + bs->decisionmaker = bs->client; + bs->ordered = qfalse; + // + if (BotTeam(bs) == TEAM_RED) memcpy(&bs->teamgoal, &redobelisk, sizeof(bot_goal_t)); + else memcpy(&bs->teamgoal, &blueobelisk, sizeof(bot_goal_t)); + //set the ltg type + bs->ltgtype = LTG_DEFENDKEYAREA; + //set the time the bot stops defending the base + bs->teamgoal_time = FloatTime() + TEAM_DEFENDKEYAREA_TIME; + bs->defendaway_time = 0; + BotSetTeamStatus(bs); + } + else { + bs->ltgtype = 0; + //set the time the bot will stop roaming + bs->ctfroam_time = FloatTime() + CTF_ROAM_TIME; + BotSetTeamStatus(bs); + } +} + +/* +================== +BotHarvesterRetreatGoals +================== +*/ +void BotHarvesterRetreatGoals(bot_state_t *bs) { + //when carrying cubes in harvester the bot should rush to the base + if (BotHarvesterCarryingCubes(bs)) { + //if not already rushing to the base + if (bs->ltgtype != LTG_RUSHBASE) { + BotRefuseOrder(bs); + bs->ltgtype = LTG_RUSHBASE; + bs->teamgoal_time = FloatTime() + CTF_RUSHBASE_TIME; + bs->rushbaseaway_time = 0; + bs->decisionmaker = bs->client; + bs->ordered = qfalse; + BotSetTeamStatus(bs); + } + return; + } +} +#endif + +/* +================== +BotTeamGoals +================== +*/ +void BotTeamGoals(bot_state_t *bs, int retreat) { + + if ( retreat ) { + if (gametype == GT_CTF) { + BotCTFRetreatGoals(bs); + } +#ifdef MISSIONPACK + else if (gametype == GT_1FCTF) { + Bot1FCTFRetreatGoals(bs); + } + else if (gametype == GT_OBELISK) { + BotObeliskRetreatGoals(bs); + } + else if (gametype == GT_HARVESTER) { + BotHarvesterRetreatGoals(bs); + } +#endif + } + else { + if (gametype == GT_CTF) { + //decide what to do in CTF mode + BotCTFSeekGoals(bs); + } +#ifdef MISSIONPACK + else if (gametype == GT_1FCTF) { + Bot1FCTFSeekGoals(bs); + } + else if (gametype == GT_OBELISK) { + BotObeliskSeekGoals(bs); + } + else if (gametype == GT_HARVESTER) { + BotHarvesterSeekGoals(bs); + } +#endif + } + // reset the order time which is used to see if + // we decided to refuse an order + bs->order_time = 0; +} + +/* +================== +BotPointAreaNum +================== +*/ +int BotPointAreaNum(vec3_t origin) { + int areanum, numareas, areas[10]; + vec3_t end; + + areanum = trap_AAS_PointAreaNum(origin); + if (areanum) return areanum; + VectorCopy(origin, end); + end[2] += 10; + numareas = trap_AAS_TraceAreas(origin, end, areas, NULL, 10); + if (numareas > 0) return areas[0]; + return 0; +} + +/* +================== +ClientName +================== +*/ +char *ClientName(int client, char *name, int size) { + char buf[MAX_INFO_STRING]; + + if (client < 0 || client >= MAX_CLIENTS) { + BotAI_Print(PRT_ERROR, "ClientName: client out of range\n"); + return "[client out of range]"; + } + trap_GetConfigstring(CS_PLAYERS+client, buf, sizeof(buf)); + strncpy(name, Info_ValueForKey(buf, "n"), size-1); + name[size-1] = '\0'; + Q_CleanStr( name ); + return name; +} + +/* +================== +ClientSkin +================== +*/ +char *ClientSkin(int client, char *skin, int size) { + char buf[MAX_INFO_STRING]; + + if (client < 0 || client >= MAX_CLIENTS) { + BotAI_Print(PRT_ERROR, "ClientSkin: client out of range\n"); + return "[client out of range]"; + } + trap_GetConfigstring(CS_PLAYERS+client, buf, sizeof(buf)); + strncpy(skin, Info_ValueForKey(buf, "model"), size-1); + skin[size-1] = '\0'; + return skin; +} + +/* +================== +ClientFromName +================== +*/ +int ClientFromName(char *name) { + int i; + char buf[MAX_INFO_STRING]; + static int maxclients; + + if (!maxclients) + maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); + Q_CleanStr( buf ); + if (!Q_stricmp(Info_ValueForKey(buf, "n"), name)) return i; + } + return -1; +} + +/* +================== +ClientOnSameTeamFromName +================== +*/ +int ClientOnSameTeamFromName(bot_state_t *bs, char *name) { + int i; + char buf[MAX_INFO_STRING]; + static int maxclients; + + if (!maxclients) + maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + if (!BotSameTeam(bs, i)) + continue; + trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); + Q_CleanStr( buf ); + if (!Q_stricmp(Info_ValueForKey(buf, "n"), name)) return i; + } + return -1; +} + +/* +================== +stristr +================== +*/ +char *stristr(char *str, char *charset) { + int i; + + while(*str) { + for (i = 0; charset[i] && str[i]; i++) { + if (toupper(charset[i]) != toupper(str[i])) break; + } + if (!charset[i]) return str; + str++; + } + return NULL; +} + +/* +================== +EasyClientName +================== +*/ +char *EasyClientName(int client, char *buf, int size) { + int i; + char *str1, *str2, *ptr, c; + char name[128]; + + strcpy(name, ClientName(client, name, sizeof(name))); + for (i = 0; name[i]; i++) name[i] &= 127; + //remove all spaces + for (ptr = strstr(name, " "); ptr; ptr = strstr(name, " ")) { + memmove(ptr, ptr+1, strlen(ptr+1)+1); + } + //check for [x] and ]x[ clan names + str1 = strstr(name, "["); + str2 = strstr(name, "]"); + if (str1 && str2) { + if (str2 > str1) memmove(str1, str2+1, strlen(str2+1)+1); + else memmove(str2, str1+1, strlen(str1+1)+1); + } + //remove Mr prefix + if ((name[0] == 'm' || name[0] == 'M') && + (name[1] == 'r' || name[1] == 'R')) { + memmove(name, name+2, strlen(name+2)+1); + } + //only allow lower case alphabet characters + ptr = name; + while(*ptr) { + c = *ptr; + if ((c >= 'a' && c <= 'z') || + (c >= '0' && c <= '9') || c == '_') { + ptr++; + } + else if (c >= 'A' && c <= 'Z') { + *ptr += 'a' - 'A'; + ptr++; + } + else { + memmove(ptr, ptr+1, strlen(ptr + 1)+1); + } + } + strncpy(buf, name, size-1); + buf[size-1] = '\0'; + return buf; +} + +/* +================== +BotSynonymContext +================== +*/ +int BotSynonymContext(bot_state_t *bs) { + int context; + + context = CONTEXT_NORMAL|CONTEXT_NEARBYITEM|CONTEXT_NAMES; + // + if (gametype == GT_CTF +#ifdef MISSIONPACK + || gametype == GT_1FCTF +#endif + ) { + if (BotTeam(bs) == TEAM_RED) context |= CONTEXT_CTFREDTEAM; + else context |= CONTEXT_CTFBLUETEAM; + } +#ifdef MISSIONPACK + else if (gametype == GT_OBELISK) { + if (BotTeam(bs) == TEAM_RED) context |= CONTEXT_OBELISKREDTEAM; + else context |= CONTEXT_OBELISKBLUETEAM; + } + else if (gametype == GT_HARVESTER) { + if (BotTeam(bs) == TEAM_RED) context |= CONTEXT_HARVESTERREDTEAM; + else context |= CONTEXT_HARVESTERBLUETEAM; + } +#endif + return context; +} + +/* +================== +BotChooseWeapon +================== +*/ +void BotChooseWeapon(bot_state_t *bs) { + int newweaponnum; + + if (bs->cur_ps.weaponstate == WEAPON_RAISING || + bs->cur_ps.weaponstate == WEAPON_DROPPING) { + trap_EA_SelectWeapon(bs->client, bs->weaponnum); + } + else { + newweaponnum = trap_BotChooseBestFightWeapon(bs->ws, bs->inventory); + if (bs->weaponnum != newweaponnum) bs->weaponchange_time = FloatTime(); + bs->weaponnum = newweaponnum; + //BotAI_Print(PRT_MESSAGE, "bs->weaponnum = %d\n", bs->weaponnum); + trap_EA_SelectWeapon(bs->client, bs->weaponnum); + } +} + +/* +================== +BotSetupForMovement +================== +*/ +void BotSetupForMovement(bot_state_t *bs) { + bot_initmove_t initmove; + + memset(&initmove, 0, sizeof(bot_initmove_t)); + VectorCopy(bs->cur_ps.origin, initmove.origin); + VectorCopy(bs->cur_ps.velocity, initmove.velocity); + VectorClear(initmove.viewoffset); + initmove.viewoffset[2] += bs->cur_ps.viewheight; + initmove.entitynum = bs->entitynum; + initmove.client = bs->client; + initmove.thinktime = bs->thinktime; + //set the onground flag + if (bs->cur_ps.groundEntityNum != ENTITYNUM_NONE) initmove.or_moveflags |= MFL_ONGROUND; + //set the teleported flag + if ((bs->cur_ps.pm_flags & PMF_TIME_KNOCKBACK) && (bs->cur_ps.pm_time > 0)) { + initmove.or_moveflags |= MFL_TELEPORTED; + } + //set the waterjump flag + if ((bs->cur_ps.pm_flags & PMF_TIME_WATERJUMP) && (bs->cur_ps.pm_time > 0)) { + initmove.or_moveflags |= MFL_WATERJUMP; + } + //set presence type + if (bs->cur_ps.pm_flags & PMF_DUCKED) initmove.presencetype = PRESENCE_CROUCH; + else initmove.presencetype = PRESENCE_NORMAL; + // + if (bs->walker > 0.5) initmove.or_moveflags |= MFL_WALK; + // + VectorCopy(bs->viewangles, initmove.viewangles); + // + trap_BotInitMoveState(bs->ms, &initmove); +} + +/* +================== +BotCheckItemPickup +================== +*/ +void BotCheckItemPickup(bot_state_t *bs, int *oldinventory) { +#ifdef MISSIONPACK + int offence, leader; + + if (gametype <= GT_TEAM) + return; + + offence = -1; + // go into offence if picked up the kamikaze or invulnerability + if (!oldinventory[INVENTORY_KAMIKAZE] && bs->inventory[INVENTORY_KAMIKAZE] >= 1) { + offence = qtrue; + } + if (!oldinventory[INVENTORY_INVULNERABILITY] && bs->inventory[INVENTORY_INVULNERABILITY] >= 1) { + offence = qtrue; + } + // if not already wearing the kamikaze or invulnerability + if (!bs->inventory[INVENTORY_KAMIKAZE] && !bs->inventory[INVENTORY_INVULNERABILITY]) { + if (!oldinventory[INVENTORY_SCOUT] && bs->inventory[INVENTORY_SCOUT] >= 1) { + offence = qtrue; + } + if (!oldinventory[INVENTORY_GUARD] && bs->inventory[INVENTORY_GUARD] >= 1) { + offence = qtrue; + } + if (!oldinventory[INVENTORY_DOUBLER] && bs->inventory[INVENTORY_DOUBLER] >= 1) { + offence = qfalse; + } + if (!oldinventory[INVENTORY_AMMOREGEN] && bs->inventory[INVENTORY_AMMOREGEN] >= 1) { + offence = qfalse; + } + } + + if (offence >= 0) { + leader = ClientFromName(bs->teamleader); + if (offence) { + if (!(bs->teamtaskpreference & TEAMTP_ATTACKER)) { + // if we have a bot team leader + if (BotTeamLeader(bs)) { + // tell the leader we want to be on offence + BotVoiceChat(bs, leader, VOICECHAT_WANTONOFFENSE); + //BotAI_BotInitialChat(bs, "wantoffence", NULL); + //trap_BotEnterChat(bs->cs, leader, CHAT_TELL); + } + else if (g_spSkill.integer <= 3) { + if ( bs->ltgtype != LTG_GETFLAG && + bs->ltgtype != LTG_ATTACKENEMYBASE && + bs->ltgtype != LTG_HARVEST ) { + // + if ((gametype != GT_CTF || (bs->redflagstatus == 0 && bs->blueflagstatus == 0)) && + (gametype != GT_1FCTF || bs->neutralflagstatus == 0) ) { + // tell the leader we want to be on offence + BotVoiceChat(bs, leader, VOICECHAT_WANTONOFFENSE); + //BotAI_BotInitialChat(bs, "wantoffence", NULL); + //trap_BotEnterChat(bs->cs, leader, CHAT_TELL); + } + } + bs->teamtaskpreference |= TEAMTP_ATTACKER; + } + } + bs->teamtaskpreference &= ~TEAMTP_DEFENDER; + } + else { + if (!(bs->teamtaskpreference & TEAMTP_DEFENDER)) { + // if we have a bot team leader + if (BotTeamLeader(bs)) { + // tell the leader we want to be on defense + BotVoiceChat(bs, -1, VOICECHAT_WANTONDEFENSE); + //BotAI_BotInitialChat(bs, "wantdefence", NULL); + //trap_BotEnterChat(bs->cs, leader, CHAT_TELL); + } + else if (g_spSkill.integer <= 3) { + if ( bs->ltgtype != LTG_DEFENDKEYAREA ) { + // + if ((gametype != GT_CTF || (bs->redflagstatus == 0 && bs->blueflagstatus == 0)) && + (gametype != GT_1FCTF || bs->neutralflagstatus == 0) ) { + // tell the leader we want to be on defense + BotVoiceChat(bs, -1, VOICECHAT_WANTONDEFENSE); + //BotAI_BotInitialChat(bs, "wantdefence", NULL); + //trap_BotEnterChat(bs->cs, leader, CHAT_TELL); + } + } + } + bs->teamtaskpreference |= TEAMTP_DEFENDER; + } + bs->teamtaskpreference &= ~TEAMTP_ATTACKER; + } + } +#endif +} + +/* +================== +BotUpdateInventory +================== +*/ +void BotUpdateInventory(bot_state_t *bs) { + int oldinventory[MAX_ITEMS]; + + memcpy(oldinventory, bs->inventory, sizeof(oldinventory)); + //armor + bs->inventory[INVENTORY_ARMOR] = bs->cur_ps.stats[STAT_ARMOR]; + //weapons + bs->inventory[INVENTORY_GAUNTLET] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_GAUNTLET)) != 0; + bs->inventory[INVENTORY_SHOTGUN] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_SHOTGUN)) != 0; + bs->inventory[INVENTORY_MACHINEGUN] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_MACHINEGUN)) != 0; + bs->inventory[INVENTORY_GRENADELAUNCHER] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_GRENADE_LAUNCHER)) != 0; + bs->inventory[INVENTORY_ROCKETLAUNCHER] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_ROCKET_LAUNCHER)) != 0; + bs->inventory[INVENTORY_LIGHTNING] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_LIGHTNING)) != 0; + bs->inventory[INVENTORY_RAILGUN] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_RAILGUN)) != 0; + bs->inventory[INVENTORY_PLASMAGUN] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_PLASMAGUN)) != 0; + bs->inventory[INVENTORY_BFG10K] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_BFG)) != 0; + bs->inventory[INVENTORY_GRAPPLINGHOOK] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_GRAPPLING_HOOK)) != 0; +#ifdef MISSIONPACK + bs->inventory[INVENTORY_NAILGUN] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_NAILGUN)) != 0;; + bs->inventory[INVENTORY_PROXLAUNCHER] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_PROX_LAUNCHER)) != 0;; + bs->inventory[INVENTORY_CHAINGUN] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_CHAINGUN)) != 0;; +#endif + //ammo + bs->inventory[INVENTORY_SHELLS] = bs->cur_ps.ammo[WP_SHOTGUN]; + bs->inventory[INVENTORY_BULLETS] = bs->cur_ps.ammo[WP_MACHINEGUN]; + bs->inventory[INVENTORY_GRENADES] = bs->cur_ps.ammo[WP_GRENADE_LAUNCHER]; + bs->inventory[INVENTORY_CELLS] = bs->cur_ps.ammo[WP_PLASMAGUN]; + bs->inventory[INVENTORY_LIGHTNINGAMMO] = bs->cur_ps.ammo[WP_LIGHTNING]; + bs->inventory[INVENTORY_ROCKETS] = bs->cur_ps.ammo[WP_ROCKET_LAUNCHER]; + bs->inventory[INVENTORY_SLUGS] = bs->cur_ps.ammo[WP_RAILGUN]; + bs->inventory[INVENTORY_BFGAMMO] = bs->cur_ps.ammo[WP_BFG]; +#ifdef MISSIONPACK + bs->inventory[INVENTORY_NAILS] = bs->cur_ps.ammo[WP_NAILGUN]; + bs->inventory[INVENTORY_MINES] = bs->cur_ps.ammo[WP_PROX_LAUNCHER]; + bs->inventory[INVENTORY_BELT] = bs->cur_ps.ammo[WP_CHAINGUN]; +#endif + //powerups + bs->inventory[INVENTORY_HEALTH] = bs->cur_ps.stats[STAT_HEALTH]; + bs->inventory[INVENTORY_TELEPORTER] = bs->cur_ps.stats[STAT_HOLDABLE_ITEM] == MODELINDEX_TELEPORTER; + bs->inventory[INVENTORY_MEDKIT] = bs->cur_ps.stats[STAT_HOLDABLE_ITEM] == MODELINDEX_MEDKIT; +#ifdef MISSIONPACK + bs->inventory[INVENTORY_KAMIKAZE] = bs->cur_ps.stats[STAT_HOLDABLE_ITEM] == MODELINDEX_KAMIKAZE; + bs->inventory[INVENTORY_PORTAL] = bs->cur_ps.stats[STAT_HOLDABLE_ITEM] == MODELINDEX_PORTAL; + bs->inventory[INVENTORY_INVULNERABILITY] = bs->cur_ps.stats[STAT_HOLDABLE_ITEM] == MODELINDEX_INVULNERABILITY; +#endif + bs->inventory[INVENTORY_QUAD] = bs->cur_ps.powerups[PW_QUAD] != 0; + bs->inventory[INVENTORY_ENVIRONMENTSUIT] = bs->cur_ps.powerups[PW_BATTLESUIT] != 0; + bs->inventory[INVENTORY_HASTE] = bs->cur_ps.powerups[PW_HASTE] != 0; + bs->inventory[INVENTORY_INVISIBILITY] = bs->cur_ps.powerups[PW_INVIS] != 0; + bs->inventory[INVENTORY_REGEN] = bs->cur_ps.powerups[PW_REGEN] != 0; + bs->inventory[INVENTORY_FLIGHT] = bs->cur_ps.powerups[PW_FLIGHT] != 0; +#ifdef MISSIONPACK + bs->inventory[INVENTORY_SCOUT] = bs->cur_ps.stats[STAT_PERSISTANT_POWERUP] == MODELINDEX_SCOUT; + bs->inventory[INVENTORY_GUARD] = bs->cur_ps.stats[STAT_PERSISTANT_POWERUP] == MODELINDEX_GUARD; + bs->inventory[INVENTORY_DOUBLER] = bs->cur_ps.stats[STAT_PERSISTANT_POWERUP] == MODELINDEX_DOUBLER; + bs->inventory[INVENTORY_AMMOREGEN] = bs->cur_ps.stats[STAT_PERSISTANT_POWERUP] == MODELINDEX_AMMOREGEN; +#endif + bs->inventory[INVENTORY_REDFLAG] = bs->cur_ps.powerups[PW_REDFLAG] != 0; + bs->inventory[INVENTORY_BLUEFLAG] = bs->cur_ps.powerups[PW_BLUEFLAG] != 0; +#ifdef MISSIONPACK + bs->inventory[INVENTORY_NEUTRALFLAG] = bs->cur_ps.powerups[PW_NEUTRALFLAG] != 0; + if (BotTeam(bs) == TEAM_RED) { + bs->inventory[INVENTORY_REDCUBE] = bs->cur_ps.generic1; + bs->inventory[INVENTORY_BLUECUBE] = 0; + } + else { + bs->inventory[INVENTORY_REDCUBE] = 0; + bs->inventory[INVENTORY_BLUECUBE] = bs->cur_ps.generic1; + } +#endif + BotCheckItemPickup(bs, oldinventory); +} + +/* +================== +BotUpdateBattleInventory +================== +*/ +void BotUpdateBattleInventory(bot_state_t *bs, int enemy) { + vec3_t dir; + aas_entityinfo_t entinfo; + + BotEntityInfo(enemy, &entinfo); + VectorSubtract(entinfo.origin, bs->origin, dir); + bs->inventory[ENEMY_HEIGHT] = (int) dir[2]; + dir[2] = 0; + bs->inventory[ENEMY_HORIZONTAL_DIST] = (int) VectorLength(dir); + //FIXME: add num visible enemies and num visible team mates to the inventory +} + +#ifdef MISSIONPACK +/* +================== +BotUseKamikaze +================== +*/ +#define KAMIKAZE_DIST 1024 + +void BotUseKamikaze(bot_state_t *bs) { + int c, teammates, enemies; + aas_entityinfo_t entinfo; + vec3_t dir, target; + bot_goal_t *goal; + bsp_trace_t trace; + + //if the bot has no kamikaze + if (bs->inventory[INVENTORY_KAMIKAZE] <= 0) + return; + if (bs->kamikaze_time > FloatTime()) + return; + bs->kamikaze_time = FloatTime() + 0.2; + if (gametype == GT_CTF) { + //never use kamikaze if the team flag carrier is visible + if (BotCTFCarryingFlag(bs)) + return; + c = BotTeamFlagCarrierVisible(bs); + if (c >= 0) { + BotEntityInfo(c, &entinfo); + VectorSubtract(entinfo.origin, bs->origin, dir); + if (VectorLengthSquared(dir) < Square(KAMIKAZE_DIST)) + return; + } + c = BotEnemyFlagCarrierVisible(bs); + if (c >= 0) { + BotEntityInfo(c, &entinfo); + VectorSubtract(entinfo.origin, bs->origin, dir); + if (VectorLengthSquared(dir) < Square(KAMIKAZE_DIST)) { + trap_EA_Use(bs->client); + return; + } + } + } + else if (gametype == GT_1FCTF) { + //never use kamikaze if the team flag carrier is visible + if (Bot1FCTFCarryingFlag(bs)) + return; + c = BotTeamFlagCarrierVisible(bs); + if (c >= 0) { + BotEntityInfo(c, &entinfo); + VectorSubtract(entinfo.origin, bs->origin, dir); + if (VectorLengthSquared(dir) < Square(KAMIKAZE_DIST)) + return; + } + c = BotEnemyFlagCarrierVisible(bs); + if (c >= 0) { + BotEntityInfo(c, &entinfo); + VectorSubtract(entinfo.origin, bs->origin, dir); + if (VectorLengthSquared(dir) < Square(KAMIKAZE_DIST)) { + trap_EA_Use(bs->client); + return; + } + } + } + else if (gametype == GT_OBELISK) { + switch(BotTeam(bs)) { + case TEAM_RED: goal = &blueobelisk; break; + default: goal = &redobelisk; break; + } + //if the obelisk is visible + VectorCopy(goal->origin, target); + target[2] += 1; + VectorSubtract(bs->origin, target, dir); + if (VectorLengthSquared(dir) < Square(KAMIKAZE_DIST * 0.9)) { + BotAI_Trace(&trace, bs->eye, NULL, NULL, target, bs->client, CONTENTS_SOLID); + if (trace.fraction >= 1 || trace.ent == goal->entitynum) { + trap_EA_Use(bs->client); + return; + } + } + } + else if (gametype == GT_HARVESTER) { + // + if (BotHarvesterCarryingCubes(bs)) + return; + //never use kamikaze if a team mate carrying cubes is visible + c = BotTeamCubeCarrierVisible(bs); + if (c >= 0) { + BotEntityInfo(c, &entinfo); + VectorSubtract(entinfo.origin, bs->origin, dir); + if (VectorLengthSquared(dir) < Square(KAMIKAZE_DIST)) + return; + } + c = BotEnemyCubeCarrierVisible(bs); + if (c >= 0) { + BotEntityInfo(c, &entinfo); + VectorSubtract(entinfo.origin, bs->origin, dir); + if (VectorLengthSquared(dir) < Square(KAMIKAZE_DIST)) { + trap_EA_Use(bs->client); + return; + } + } + } + // + BotVisibleTeamMatesAndEnemies(bs, &teammates, &enemies, KAMIKAZE_DIST); + // + if (enemies > 2 && enemies > teammates+1) { + trap_EA_Use(bs->client); + return; + } +} + +/* +================== +BotUseInvulnerability +================== +*/ +void BotUseInvulnerability(bot_state_t *bs) { + int c; + vec3_t dir, target; + bot_goal_t *goal; + bsp_trace_t trace; + + //if the bot has no invulnerability + if (bs->inventory[INVENTORY_INVULNERABILITY] <= 0) + return; + if (bs->invulnerability_time > FloatTime()) + return; + bs->invulnerability_time = FloatTime() + 0.2; + if (gametype == GT_CTF) { + //never use kamikaze if the team flag carrier is visible + if (BotCTFCarryingFlag(bs)) + return; + c = BotEnemyFlagCarrierVisible(bs); + if (c >= 0) + return; + //if near enemy flag and the flag is visible + switch(BotTeam(bs)) { + case TEAM_RED: goal = &ctf_blueflag; break; + default: goal = &ctf_redflag; break; + } + //if the obelisk is visible + VectorCopy(goal->origin, target); + target[2] += 1; + VectorSubtract(bs->origin, target, dir); + if (VectorLengthSquared(dir) < Square(200)) { + BotAI_Trace(&trace, bs->eye, NULL, NULL, target, bs->client, CONTENTS_SOLID); + if (trace.fraction >= 1 || trace.ent == goal->entitynum) { + trap_EA_Use(bs->client); + return; + } + } + } + else if (gametype == GT_1FCTF) { + //never use kamikaze if the team flag carrier is visible + if (Bot1FCTFCarryingFlag(bs)) + return; + c = BotEnemyFlagCarrierVisible(bs); + if (c >= 0) + return; + //if near enemy flag and the flag is visible + switch(BotTeam(bs)) { + case TEAM_RED: goal = &ctf_blueflag; break; + default: goal = &ctf_redflag; break; + } + //if the obelisk is visible + VectorCopy(goal->origin, target); + target[2] += 1; + VectorSubtract(bs->origin, target, dir); + if (VectorLengthSquared(dir) < Square(200)) { + BotAI_Trace(&trace, bs->eye, NULL, NULL, target, bs->client, CONTENTS_SOLID); + if (trace.fraction >= 1 || trace.ent == goal->entitynum) { + trap_EA_Use(bs->client); + return; + } + } + } + else if (gametype == GT_OBELISK) { + switch(BotTeam(bs)) { + case TEAM_RED: goal = &blueobelisk; break; + default: goal = &redobelisk; break; + } + //if the obelisk is visible + VectorCopy(goal->origin, target); + target[2] += 1; + VectorSubtract(bs->origin, target, dir); + if (VectorLengthSquared(dir) < Square(300)) { + BotAI_Trace(&trace, bs->eye, NULL, NULL, target, bs->client, CONTENTS_SOLID); + if (trace.fraction >= 1 || trace.ent == goal->entitynum) { + trap_EA_Use(bs->client); + return; + } + } + } + else if (gametype == GT_HARVESTER) { + // + if (BotHarvesterCarryingCubes(bs)) + return; + c = BotEnemyCubeCarrierVisible(bs); + if (c >= 0) + return; + //if near enemy base and enemy base is visible + switch(BotTeam(bs)) { + case TEAM_RED: goal = &blueobelisk; break; + default: goal = &redobelisk; break; + } + //if the obelisk is visible + VectorCopy(goal->origin, target); + target[2] += 1; + VectorSubtract(bs->origin, target, dir); + if (VectorLengthSquared(dir) < Square(200)) { + BotAI_Trace(&trace, bs->eye, NULL, NULL, target, bs->client, CONTENTS_SOLID); + if (trace.fraction >= 1 || trace.ent == goal->entitynum) { + trap_EA_Use(bs->client); + return; + } + } + } +} +#endif + +/* +================== +BotBattleUseItems +================== +*/ +void BotBattleUseItems(bot_state_t *bs) { + if (bs->inventory[INVENTORY_HEALTH] < 40) { + if (bs->inventory[INVENTORY_TELEPORTER] > 0) { + if (!BotCTFCarryingFlag(bs) +#ifdef MISSIONPACK + && !Bot1FCTFCarryingFlag(bs) + && !BotHarvesterCarryingCubes(bs) +#endif + ) { + trap_EA_Use(bs->client); + } + } + } + if (bs->inventory[INVENTORY_HEALTH] < 60) { + if (bs->inventory[INVENTORY_MEDKIT] > 0) { + trap_EA_Use(bs->client); + } + } +#ifdef MISSIONPACK + BotUseKamikaze(bs); + BotUseInvulnerability(bs); +#endif +} + +/* +================== +BotSetTeleportTime +================== +*/ +void BotSetTeleportTime(bot_state_t *bs) { + if ((bs->cur_ps.eFlags ^ bs->last_eFlags) & EF_TELEPORT_BIT) { + bs->teleport_time = FloatTime(); + } + bs->last_eFlags = bs->cur_ps.eFlags; +} + +/* +================== +BotIsDead +================== +*/ +qboolean BotIsDead(bot_state_t *bs) { + return (bs->cur_ps.pm_type == PM_DEAD); +} + +/* +================== +BotIsObserver +================== +*/ +qboolean BotIsObserver(bot_state_t *bs) { + char buf[MAX_INFO_STRING]; + if (bs->cur_ps.pm_type == PM_SPECTATOR) return qtrue; + trap_GetConfigstring(CS_PLAYERS+bs->client, buf, sizeof(buf)); + if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) return qtrue; + return qfalse; +} + +/* +================== +BotIntermission +================== +*/ +qboolean BotIntermission(bot_state_t *bs) { + //NOTE: we shouldn't be looking at the game code... + if (level.intermissiontime) return qtrue; + return (bs->cur_ps.pm_type == PM_FREEZE || bs->cur_ps.pm_type == PM_INTERMISSION); +} + +/* +================== +BotInLavaOrSlime +================== +*/ +qboolean BotInLavaOrSlime(bot_state_t *bs) { + vec3_t feet; + + VectorCopy(bs->origin, feet); + feet[2] -= 23; + return (trap_AAS_PointContents(feet) & (CONTENTS_LAVA|CONTENTS_SLIME)); +} + +/* +================== +BotCreateWayPoint +================== +*/ +bot_waypoint_t *BotCreateWayPoint(char *name, vec3_t origin, int areanum) { + bot_waypoint_t *wp; + vec3_t waypointmins = {-8, -8, -8}, waypointmaxs = {8, 8, 8}; + + wp = botai_freewaypoints; + if ( !wp ) { + BotAI_Print( PRT_WARNING, "BotCreateWayPoint: Out of waypoints\n" ); + return NULL; + } + botai_freewaypoints = botai_freewaypoints->next; + + Q_strncpyz( wp->name, name, sizeof(wp->name) ); + VectorCopy(origin, wp->goal.origin); + VectorCopy(waypointmins, wp->goal.mins); + VectorCopy(waypointmaxs, wp->goal.maxs); + wp->goal.areanum = areanum; + wp->next = NULL; + wp->prev = NULL; + return wp; +} + +/* +================== +BotFindWayPoint +================== +*/ +bot_waypoint_t *BotFindWayPoint(bot_waypoint_t *waypoints, char *name) { + bot_waypoint_t *wp; + + for (wp = waypoints; wp; wp = wp->next) { + if (!Q_stricmp(wp->name, name)) return wp; + } + return NULL; +} + +/* +================== +BotFreeWaypoints +================== +*/ +void BotFreeWaypoints(bot_waypoint_t *wp) { + bot_waypoint_t *nextwp; + + for (; wp; wp = nextwp) { + nextwp = wp->next; + wp->next = botai_freewaypoints; + botai_freewaypoints = wp; + } +} + +/* +================== +BotInitWaypoints +================== +*/ +void BotInitWaypoints(void) { + int i; + + botai_freewaypoints = NULL; + for (i = 0; i < MAX_WAYPOINTS; i++) { + botai_waypoints[i].next = botai_freewaypoints; + botai_freewaypoints = &botai_waypoints[i]; + } +} + +/* +================== +TeamPlayIsOn +================== +*/ +int TeamPlayIsOn(void) { + return ( gametype >= GT_TEAM ); +} + +/* +================== +BotAggression +================== +*/ +float BotAggression(bot_state_t *bs) { + //if the bot has quad + if (bs->inventory[INVENTORY_QUAD]) { + //if the bot is not holding the gauntlet or the enemy is really nearby + if (bs->weaponnum != WP_GAUNTLET || + bs->inventory[ENEMY_HORIZONTAL_DIST] < 80) { + return 70; + } + } + //if the enemy is located way higher than the bot + if (bs->inventory[ENEMY_HEIGHT] > 200) return 0; + //if the bot is very low on health + if (bs->inventory[INVENTORY_HEALTH] < 60) return 0; + //if the bot is low on health + if (bs->inventory[INVENTORY_HEALTH] < 80) { + //if the bot has insufficient armor + if (bs->inventory[INVENTORY_ARMOR] < 40) return 0; + } + //if the bot can use the bfg + if (bs->inventory[INVENTORY_BFG10K] > 0 && + bs->inventory[INVENTORY_BFGAMMO] > 7) return 100; + //if the bot can use the railgun + if (bs->inventory[INVENTORY_RAILGUN] > 0 && + bs->inventory[INVENTORY_SLUGS] > 5) return 95; + //if the bot can use the lightning gun + if (bs->inventory[INVENTORY_LIGHTNING] > 0 && + bs->inventory[INVENTORY_LIGHTNINGAMMO] > 50) return 90; + //if the bot can use the rocketlauncher + if (bs->inventory[INVENTORY_ROCKETLAUNCHER] > 0 && + bs->inventory[INVENTORY_ROCKETS] > 5) return 90; + //if the bot can use the plasmagun + if (bs->inventory[INVENTORY_PLASMAGUN] > 0 && + bs->inventory[INVENTORY_CELLS] > 40) return 85; + //if the bot can use the grenade launcher + if (bs->inventory[INVENTORY_GRENADELAUNCHER] > 0 && + bs->inventory[INVENTORY_GRENADES] > 10) return 80; + //if the bot can use the shotgun + if (bs->inventory[INVENTORY_SHOTGUN] > 0 && + bs->inventory[INVENTORY_SHELLS] > 10) return 50; + //otherwise the bot is not feeling too good + return 0; +} + +/* +================== +BotFeelingBad +================== +*/ +float BotFeelingBad(bot_state_t *bs) { + if (bs->weaponnum == WP_GAUNTLET) { + return 100; + } + if (bs->inventory[INVENTORY_HEALTH] < 40) { + return 100; + } + if (bs->weaponnum == WP_MACHINEGUN) { + return 90; + } + if (bs->inventory[INVENTORY_HEALTH] < 60) { + return 80; + } + return 0; +} + +/* +================== +BotWantsToRetreat +================== +*/ +int BotWantsToRetreat(bot_state_t *bs) { + aas_entityinfo_t entinfo; + + if (gametype == GT_CTF) { + //always retreat when carrying a CTF flag + if (BotCTFCarryingFlag(bs)) + return qtrue; + } +#ifdef MISSIONPACK + else if (gametype == GT_1FCTF) { + //if carrying the flag then always retreat + if (Bot1FCTFCarryingFlag(bs)) + return qtrue; + } + else if (gametype == GT_OBELISK) { + //the bots should be dedicated to attacking the enemy obelisk + if (bs->ltgtype == LTG_ATTACKENEMYBASE) { + if (bs->enemy != redobelisk.entitynum || + bs->enemy != blueobelisk.entitynum) { + return qtrue; + } + } + if (BotFeelingBad(bs) > 50) { + return qtrue; + } + return qfalse; + } + else if (gametype == GT_HARVESTER) { + //if carrying cubes then always retreat + if (BotHarvesterCarryingCubes(bs)) return qtrue; + } +#endif + // + if (bs->enemy >= 0) { + //if the enemy is carrying a flag + BotEntityInfo(bs->enemy, &entinfo); + if (EntityCarriesFlag(&entinfo)) + return qfalse; + } + //if the bot is getting the flag + if (bs->ltgtype == LTG_GETFLAG) + return qtrue; + // + if (BotAggression(bs) < 50) + return qtrue; + return qfalse; +} + +/* +================== +BotWantsToChase +================== +*/ +int BotWantsToChase(bot_state_t *bs) { + aas_entityinfo_t entinfo; + + if (gametype == GT_CTF) { + //never chase when carrying a CTF flag + if (BotCTFCarryingFlag(bs)) + return qfalse; + //always chase if the enemy is carrying a flag + BotEntityInfo(bs->enemy, &entinfo); + if (EntityCarriesFlag(&entinfo)) + return qtrue; + } +#ifdef MISSIONPACK + else if (gametype == GT_1FCTF) { + //never chase if carrying the flag + if (Bot1FCTFCarryingFlag(bs)) + return qfalse; + //always chase if the enemy is carrying a flag + BotEntityInfo(bs->enemy, &entinfo); + if (EntityCarriesFlag(&entinfo)) + return qtrue; + } + else if (gametype == GT_OBELISK) { + //the bots should be dedicated to attacking the enemy obelisk + if (bs->ltgtype == LTG_ATTACKENEMYBASE) { + if (bs->enemy != redobelisk.entitynum || + bs->enemy != blueobelisk.entitynum) { + return qfalse; + } + } + } + else if (gametype == GT_HARVESTER) { + //never chase if carrying cubes + if (BotHarvesterCarryingCubes(bs)) + return qfalse; + } +#endif + //if the bot is getting the flag + if (bs->ltgtype == LTG_GETFLAG) + return qfalse; + // + if (BotAggression(bs) > 50) + return qtrue; + return qfalse; +} + +/* +================== +BotWantsToHelp +================== +*/ +int BotWantsToHelp(bot_state_t *bs) { + return qtrue; +} + +/* +================== +BotCanAndWantsToRocketJump +================== +*/ +int BotCanAndWantsToRocketJump(bot_state_t *bs) { + float rocketjumper; + + //if rocket jumping is disabled + if (!bot_rocketjump.integer) return qfalse; + //if no rocket launcher + if (bs->inventory[INVENTORY_ROCKETLAUNCHER] <= 0) return qfalse; + //if low on rockets + if (bs->inventory[INVENTORY_ROCKETS] < 3) return qfalse; + //never rocket jump with the Quad + if (bs->inventory[INVENTORY_QUAD]) return qfalse; + //if low on health + if (bs->inventory[INVENTORY_HEALTH] < 60) return qfalse; + //if not full health + if (bs->inventory[INVENTORY_HEALTH] < 90) { + //if the bot has insufficient armor + if (bs->inventory[INVENTORY_ARMOR] < 40) return qfalse; + } + rocketjumper = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_WEAPONJUMPING, 0, 1); + if (rocketjumper < 0.5) return qfalse; + return qtrue; +} + +/* +================== +BotHasPersistantPowerupAndWeapon +================== +*/ +int BotHasPersistantPowerupAndWeapon(bot_state_t *bs) { +#ifdef MISSIONPACK + // if the bot does not have a persistant powerup + if (!bs->inventory[INVENTORY_SCOUT] && + !bs->inventory[INVENTORY_GUARD] && + !bs->inventory[INVENTORY_DOUBLER] && + !bs->inventory[INVENTORY_AMMOREGEN] ) { + return qfalse; + } +#endif + //if the bot is very low on health + if (bs->inventory[INVENTORY_HEALTH] < 60) return qfalse; + //if the bot is low on health + if (bs->inventory[INVENTORY_HEALTH] < 80) { + //if the bot has insufficient armor + if (bs->inventory[INVENTORY_ARMOR] < 40) return qfalse; + } + //if the bot can use the bfg + if (bs->inventory[INVENTORY_BFG10K] > 0 && + bs->inventory[INVENTORY_BFGAMMO] > 7) return qtrue; + //if the bot can use the railgun + if (bs->inventory[INVENTORY_RAILGUN] > 0 && + bs->inventory[INVENTORY_SLUGS] > 5) return qtrue; + //if the bot can use the lightning gun + if (bs->inventory[INVENTORY_LIGHTNING] > 0 && + bs->inventory[INVENTORY_LIGHTNINGAMMO] > 50) return qtrue; + //if the bot can use the rocketlauncher + if (bs->inventory[INVENTORY_ROCKETLAUNCHER] > 0 && + bs->inventory[INVENTORY_ROCKETS] > 5) return qtrue; + // + if (bs->inventory[INVENTORY_NAILGUN] > 0 && + bs->inventory[INVENTORY_NAILS] > 5) return qtrue; + // + if (bs->inventory[INVENTORY_PROXLAUNCHER] > 0 && + bs->inventory[INVENTORY_MINES] > 5) return qtrue; + // + if (bs->inventory[INVENTORY_CHAINGUN] > 0 && + bs->inventory[INVENTORY_BELT] > 40) return qtrue; + //if the bot can use the plasmagun + if (bs->inventory[INVENTORY_PLASMAGUN] > 0 && + bs->inventory[INVENTORY_CELLS] > 20) return qtrue; + return qfalse; +} + +/* +================== +BotGoCamp +================== +*/ +void BotGoCamp(bot_state_t *bs, bot_goal_t *goal) { + float camper; + + bs->decisionmaker = bs->client; + //set message time to zero so bot will NOT show any message + bs->teammessage_time = 0; + //set the ltg type + bs->ltgtype = LTG_CAMP; + //set the team goal + memcpy(&bs->teamgoal, goal, sizeof(bot_goal_t)); + //get the team goal time + camper = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CAMPER, 0, 1); + if (camper > 0.99) bs->teamgoal_time = FloatTime() + 99999; + else bs->teamgoal_time = FloatTime() + 120 + 180 * camper + random() * 15; + //set the last time the bot started camping + bs->camp_time = FloatTime(); + //the teammate that requested the camping + bs->teammate = 0; + //do NOT type arrive message + bs->arrive_time = 1; +} + +/* +================== +BotWantsToCamp +================== +*/ +int BotWantsToCamp(bot_state_t *bs) { + float camper; + int cs, traveltime, besttraveltime; + bot_goal_t goal, bestgoal; + + camper = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CAMPER, 0, 1); + if (camper < 0.1) return qfalse; + //if the bot has a team goal + if (bs->ltgtype == LTG_TEAMHELP || + bs->ltgtype == LTG_TEAMACCOMPANY || + bs->ltgtype == LTG_DEFENDKEYAREA || + bs->ltgtype == LTG_GETFLAG || + bs->ltgtype == LTG_RUSHBASE || + bs->ltgtype == LTG_CAMP || + bs->ltgtype == LTG_CAMPORDER || + bs->ltgtype == LTG_PATROL) { + return qfalse; + } + //if camped recently + if (bs->camp_time > FloatTime() - 60 + 300 * (1-camper)) return qfalse; + // + if (random() > camper) { + bs->camp_time = FloatTime(); + return qfalse; + } + //if the bot isn't healthy anough + if (BotAggression(bs) < 50) return qfalse; + //the bot should have at least have the rocket launcher, the railgun or the bfg10k with some ammo + if ((bs->inventory[INVENTORY_ROCKETLAUNCHER] <= 0 || bs->inventory[INVENTORY_ROCKETS < 10]) && + (bs->inventory[INVENTORY_RAILGUN] <= 0 || bs->inventory[INVENTORY_SLUGS] < 10) && + (bs->inventory[INVENTORY_BFG10K] <= 0 || bs->inventory[INVENTORY_BFGAMMO] < 10)) { + return qfalse; + } + //find the closest camp spot + besttraveltime = 99999; + for (cs = trap_BotGetNextCampSpotGoal(0, &goal); cs; cs = trap_BotGetNextCampSpotGoal(cs, &goal)) { + traveltime = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, goal.areanum, TFL_DEFAULT); + if (traveltime && traveltime < besttraveltime) { + besttraveltime = traveltime; + memcpy(&bestgoal, &goal, sizeof(bot_goal_t)); + } + } + if (besttraveltime > 150) return qfalse; + //ok found a camp spot, go camp there + BotGoCamp(bs, &bestgoal); + bs->ordered = qfalse; + // + return qtrue; +} + +/* +================== +BotDontAvoid +================== +*/ +void BotDontAvoid(bot_state_t *bs, char *itemname) { + bot_goal_t goal; + int num; + + num = trap_BotGetLevelItemGoal(-1, itemname, &goal); + while(num >= 0) { + trap_BotRemoveFromAvoidGoals(bs->gs, goal.number); + num = trap_BotGetLevelItemGoal(num, itemname, &goal); + } +} + +/* +================== +BotGoForPowerups +================== +*/ +void BotGoForPowerups(bot_state_t *bs) { + + //don't avoid any of the powerups anymore + BotDontAvoid(bs, "Quad Damage"); + BotDontAvoid(bs, "Regeneration"); + BotDontAvoid(bs, "Battle Suit"); + BotDontAvoid(bs, "Speed"); + BotDontAvoid(bs, "Invisibility"); + //BotDontAvoid(bs, "Flight"); + //reset the long term goal time so the bot will go for the powerup + //NOTE: the long term goal type doesn't change + bs->ltg_time = 0; +} + +/* +================== +BotRoamGoal +================== +*/ +void BotRoamGoal(bot_state_t *bs, vec3_t goal) { + int pc, i; + float len, rnd; + vec3_t dir, bestorg, belowbestorg; + bsp_trace_t trace; + + for (i = 0; i < 10; i++) { + //start at the bot origin + VectorCopy(bs->origin, bestorg); + rnd = random(); + if (rnd > 0.25) { + //add a random value to the x-coordinate + if (random() < 0.5) bestorg[0] -= 800 * random() + 100; + else bestorg[0] += 800 * random() + 100; + } + if (rnd < 0.75) { + //add a random value to the y-coordinate + if (random() < 0.5) bestorg[1] -= 800 * random() + 100; + else bestorg[1] += 800 * random() + 100; + } + //add a random value to the z-coordinate (NOTE: 48 = maxjump?) + bestorg[2] += 2 * 48 * crandom(); + //trace a line from the origin to the roam target + BotAI_Trace(&trace, bs->origin, NULL, NULL, bestorg, bs->entitynum, MASK_SOLID); + //direction and length towards the roam target + VectorSubtract(trace.endpos, bs->origin, dir); + len = VectorNormalize(dir); + //if the roam target is far away anough + if (len > 200) { + //the roam target is in the given direction before walls + VectorScale(dir, len * trace.fraction - 40, dir); + VectorAdd(bs->origin, dir, bestorg); + //get the coordinates of the floor below the roam target + belowbestorg[0] = bestorg[0]; + belowbestorg[1] = bestorg[1]; + belowbestorg[2] = bestorg[2] - 800; + BotAI_Trace(&trace, bestorg, NULL, NULL, belowbestorg, bs->entitynum, MASK_SOLID); + // + if (!trace.startsolid) { + trace.endpos[2]++; + pc = trap_PointContents(trace.endpos, bs->entitynum); + if (!(pc & (CONTENTS_LAVA | CONTENTS_SLIME))) { + VectorCopy(bestorg, goal); + return; + } + } + } + } + VectorCopy(bestorg, goal); +} + +/* +================== +BotAttackMove +================== +*/ +bot_moveresult_t BotAttackMove(bot_state_t *bs, int tfl) { + int movetype, i, attackentity; + float attack_skill, jumper, croucher, dist, strafechange_time; + float attack_dist, attack_range; + vec3_t forward, backward, sideward, hordir, up = {0, 0, 1}; + aas_entityinfo_t entinfo; + bot_moveresult_t moveresult; + bot_goal_t goal; + + attackentity = bs->enemy; + // + if (bs->attackchase_time > FloatTime()) { + //create the chase goal + goal.entitynum = attackentity; + goal.areanum = bs->lastenemyareanum; + VectorCopy(bs->lastenemyorigin, goal.origin); + VectorSet(goal.mins, -8, -8, -8); + VectorSet(goal.maxs, 8, 8, 8); + //initialize the movement state + BotSetupForMovement(bs); + //move towards the goal + trap_BotMoveToGoal(&moveresult, bs->ms, &goal, tfl); + return moveresult; + } + // + memset(&moveresult, 0, sizeof(bot_moveresult_t)); + // + attack_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_ATTACK_SKILL, 0, 1); + jumper = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_JUMPER, 0, 1); + croucher = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CROUCHER, 0, 1); + //if the bot is really stupid + if (attack_skill < 0.2) return moveresult; + //initialize the movement state + BotSetupForMovement(bs); + //get the enemy entity info + BotEntityInfo(attackentity, &entinfo); + //direction towards the enemy + VectorSubtract(entinfo.origin, bs->origin, forward); + //the distance towards the enemy + dist = VectorNormalize(forward); + VectorNegate(forward, backward); + //walk, crouch or jump + movetype = MOVE_WALK; + // + if (bs->attackcrouch_time < FloatTime() - 1) { + if (random() < jumper) { + movetype = MOVE_JUMP; + } + //wait at least one second before crouching again + else if (bs->attackcrouch_time < FloatTime() - 1 && random() < croucher) { + bs->attackcrouch_time = FloatTime() + croucher * 5; + } + } + if (bs->attackcrouch_time > FloatTime()) movetype = MOVE_CROUCH; + //if the bot should jump + if (movetype == MOVE_JUMP) { + //if jumped last frame + if (bs->attackjump_time > FloatTime()) { + movetype = MOVE_WALK; + } + else { + bs->attackjump_time = FloatTime() + 1; + } + } + if (bs->cur_ps.weapon == WP_GAUNTLET) { + attack_dist = 0; + attack_range = 0; + } + else { + attack_dist = IDEAL_ATTACKDIST; + attack_range = 40; + } + //if the bot is stupid + if (attack_skill <= 0.4) { + //just walk to or away from the enemy + if (dist > attack_dist + attack_range) { + if (trap_BotMoveInDirection(bs->ms, forward, 400, movetype)) return moveresult; + } + if (dist < attack_dist - attack_range) { + if (trap_BotMoveInDirection(bs->ms, backward, 400, movetype)) return moveresult; + } + return moveresult; + } + //increase the strafe time + bs->attackstrafe_time += bs->thinktime; + //get the strafe change time + strafechange_time = 0.4 + (1 - attack_skill) * 0.2; + if (attack_skill > 0.7) strafechange_time += crandom() * 0.2; + //if the strafe direction should be changed + if (bs->attackstrafe_time > strafechange_time) { + //some magic number :) + if (random() > 0.935) { + //flip the strafe direction + bs->flags ^= BFL_STRAFERIGHT; + bs->attackstrafe_time = 0; + } + } + // + for (i = 0; i < 2; i++) { + hordir[0] = forward[0]; + hordir[1] = forward[1]; + hordir[2] = 0; + VectorNormalize(hordir); + //get the sideward vector + CrossProduct(hordir, up, sideward); + //reverse the vector depending on the strafe direction + if (bs->flags & BFL_STRAFERIGHT) VectorNegate(sideward, sideward); + //randomly go back a little + if (random() > 0.9) { + VectorAdd(sideward, backward, sideward); + } + else { + //walk forward or backward to get at the ideal attack distance + if (dist > attack_dist + attack_range) { + VectorAdd(sideward, forward, sideward); + } + else if (dist < attack_dist - attack_range) { + VectorAdd(sideward, backward, sideward); + } + } + //perform the movement + if (trap_BotMoveInDirection(bs->ms, sideward, 400, movetype)) + return moveresult; + //movement failed, flip the strafe direction + bs->flags ^= BFL_STRAFERIGHT; + bs->attackstrafe_time = 0; + } + //bot couldn't do any usefull movement +// bs->attackchase_time = AAS_Time() + 6; + return moveresult; +} + +/* +================== +BotSameTeam +================== +*/ +int BotSameTeam(bot_state_t *bs, int entnum) { + char info1[1024], info2[1024]; + + if (bs->client < 0 || bs->client >= MAX_CLIENTS) { + //BotAI_Print(PRT_ERROR, "BotSameTeam: client out of range\n"); + return qfalse; + } + if (entnum < 0 || entnum >= MAX_CLIENTS) { + //BotAI_Print(PRT_ERROR, "BotSameTeam: client out of range\n"); + return qfalse; + } + if ( gametype >= GT_TEAM ) { + trap_GetConfigstring(CS_PLAYERS+bs->client, info1, sizeof(info1)); + trap_GetConfigstring(CS_PLAYERS+entnum, info2, sizeof(info2)); + // + if (atoi(Info_ValueForKey(info1, "t")) == atoi(Info_ValueForKey(info2, "t"))) return qtrue; + } + return qfalse; +} + +/* +================== +InFieldOfVision +================== +*/ +qboolean InFieldOfVision(vec3_t viewangles, float fov, vec3_t angles) +{ + int i; + float diff, angle; + + for (i = 0; i < 2; i++) { + angle = AngleMod(viewangles[i]); + angles[i] = AngleMod(angles[i]); + diff = angles[i] - angle; + if (angles[i] > angle) { + if (diff > 180.0) diff -= 360.0; + } + else { + if (diff < -180.0) diff += 360.0; + } + if (diff > 0) { + if (diff > fov * 0.5) return qfalse; + } + else { + if (diff < -fov * 0.5) return qfalse; + } + } + return qtrue; +} + +/* +================== +BotEntityVisible + +returns visibility in the range [0, 1] taking fog and water surfaces into account +================== +*/ +float BotEntityVisible(int viewer, vec3_t eye, vec3_t viewangles, float fov, int ent) { + int i, contents_mask, passent, hitent, infog, inwater, otherinfog, pc; + float squaredfogdist, waterfactor, vis, bestvis; + bsp_trace_t trace; + aas_entityinfo_t entinfo; + vec3_t dir, entangles, start, end, middle; + + //calculate middle of bounding box + BotEntityInfo(ent, &entinfo); + VectorAdd(entinfo.mins, entinfo.maxs, middle); + VectorScale(middle, 0.5, middle); + VectorAdd(entinfo.origin, middle, middle); + //check if entity is within field of vision + VectorSubtract(middle, eye, dir); + vectoangles(dir, entangles); + if (!InFieldOfVision(viewangles, fov, entangles)) return 0; + // + pc = trap_AAS_PointContents(eye); + infog = (pc & CONTENTS_FOG); + inwater = (pc & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER)); + // + bestvis = 0; + for (i = 0; i < 3; i++) { + //if the point is not in potential visible sight + //if (!AAS_inPVS(eye, middle)) continue; + // + contents_mask = CONTENTS_SOLID|CONTENTS_PLAYERCLIP; + passent = viewer; + hitent = ent; + VectorCopy(eye, start); + VectorCopy(middle, end); + //if the entity is in water, lava or slime + if (trap_AAS_PointContents(middle) & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER)) { + contents_mask |= (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER); + } + //if eye is in water, lava or slime + if (inwater) { + if (!(contents_mask & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER))) { + passent = ent; + hitent = viewer; + VectorCopy(middle, start); + VectorCopy(eye, end); + } + contents_mask ^= (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER); + } + //trace from start to end + BotAI_Trace(&trace, start, NULL, NULL, end, passent, contents_mask); + //if water was hit + waterfactor = 1.0; + if (trace.contents & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER)) { + //if the water surface is translucent + if (1) { + //trace through the water + contents_mask &= ~(CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER); + BotAI_Trace(&trace, trace.endpos, NULL, NULL, end, passent, contents_mask); + waterfactor = 0.5; + } + } + //if a full trace or the hitent was hit + if (trace.fraction >= 1 || trace.ent == hitent) { + //check for fog, assuming there's only one fog brush where + //either the viewer or the entity is in or both are in + otherinfog = (trap_AAS_PointContents(middle) & CONTENTS_FOG); + if (infog && otherinfog) { + VectorSubtract(trace.endpos, eye, dir); + squaredfogdist = VectorLengthSquared(dir); + } + else if (infog) { + VectorCopy(trace.endpos, start); + BotAI_Trace(&trace, start, NULL, NULL, eye, viewer, CONTENTS_FOG); + VectorSubtract(eye, trace.endpos, dir); + squaredfogdist = VectorLengthSquared(dir); + } + else if (otherinfog) { + VectorCopy(trace.endpos, end); + BotAI_Trace(&trace, eye, NULL, NULL, end, viewer, CONTENTS_FOG); + VectorSubtract(end, trace.endpos, dir); + squaredfogdist = VectorLengthSquared(dir); + } + else { + //if the entity and the viewer are not in fog assume there's no fog in between + squaredfogdist = 0; + } + //decrease visibility with the view distance through fog + vis = 1 / ((squaredfogdist * 0.001) < 1 ? 1 : (squaredfogdist * 0.001)); + //if entering water visibility is reduced + vis *= waterfactor; + // + if (vis > bestvis) bestvis = vis; + //if pretty much no fog + if (bestvis >= 0.95) return bestvis; + } + //check bottom and top of bounding box as well + if (i == 0) middle[2] += entinfo.mins[2]; + else if (i == 1) middle[2] += entinfo.maxs[2] - entinfo.mins[2]; + } + return bestvis; +} + +/* +================== +BotFindEnemy +================== +*/ +int BotFindEnemy(bot_state_t *bs, int curenemy) { + int i, healthdecrease; + float f, alertness, easyfragger, vis; + float squaredist, cursquaredist; + aas_entityinfo_t entinfo, curenemyinfo; + vec3_t dir, angles; + + alertness = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_ALERTNESS, 0, 1); + easyfragger = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_EASY_FRAGGER, 0, 1); + //check if the health decreased + healthdecrease = bs->lasthealth > bs->inventory[INVENTORY_HEALTH]; + //remember the current health value + bs->lasthealth = bs->inventory[INVENTORY_HEALTH]; + // + if (curenemy >= 0) { + BotEntityInfo(curenemy, &curenemyinfo); + if (EntityCarriesFlag(&curenemyinfo)) return qfalse; + VectorSubtract(curenemyinfo.origin, bs->origin, dir); + cursquaredist = VectorLengthSquared(dir); + } + else { + cursquaredist = 0; + } +#ifdef MISSIONPACK + if (gametype == GT_OBELISK) { + vec3_t target; + bot_goal_t *goal; + bsp_trace_t trace; + + if (BotTeam(bs) == TEAM_RED) + goal = &blueobelisk; + else + goal = &redobelisk; + //if the obelisk is visible + VectorCopy(goal->origin, target); + target[2] += 1; + BotAI_Trace(&trace, bs->eye, NULL, NULL, target, bs->client, CONTENTS_SOLID); + if (trace.fraction >= 1 || trace.ent == goal->entitynum) { + if (goal->entitynum == bs->enemy) { + return qfalse; + } + bs->enemy = goal->entitynum; + bs->enemysight_time = FloatTime(); + bs->enemysuicide = qfalse; + bs->enemydeath_time = 0; + bs->enemyvisible_time = FloatTime(); + return qtrue; + } + } +#endif + // + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + + if (i == bs->client) continue; + //if it's the current enemy + if (i == curenemy) continue; + // + BotEntityInfo(i, &entinfo); + // + if (!entinfo.valid) continue; + //if the enemy isn't dead and the enemy isn't the bot self + if (EntityIsDead(&entinfo) || entinfo.number == bs->entitynum) continue; + //if the enemy is invisible and not shooting + if (EntityIsInvisible(&entinfo) && !EntityIsShooting(&entinfo)) { + continue; + } + //if not an easy fragger don't shoot at chatting players + if (easyfragger < 0.5 && EntityIsChatting(&entinfo)) continue; + // + if (lastteleport_time > FloatTime() - 3) { + VectorSubtract(entinfo.origin, lastteleport_origin, dir); + if (VectorLengthSquared(dir) < Square(70)) continue; + } + //calculate the distance towards the enemy + VectorSubtract(entinfo.origin, bs->origin, dir); + squaredist = VectorLengthSquared(dir); + //if this entity is not carrying a flag + if (!EntityCarriesFlag(&entinfo)) + { + //if this enemy is further away than the current one + if (curenemy >= 0 && squaredist > cursquaredist) continue; + } //end if + //if the bot has no + if (squaredist > Square(900.0 + alertness * 4000.0)) continue; + //if on the same team + if (BotSameTeam(bs, i)) continue; + //if the bot's health decreased or the enemy is shooting + if (curenemy < 0 && (healthdecrease || EntityIsShooting(&entinfo))) + f = 360; + else + f = 90 + 90 - (90 - (squaredist > Square(810) ? Square(810) : squaredist) / (810 * 9)); + //check if the enemy is visible + vis = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, f, i); + if (vis <= 0) continue; + //if the enemy is quite far away, not shooting and the bot is not damaged + if (curenemy < 0 && squaredist > Square(100) && !healthdecrease && !EntityIsShooting(&entinfo)) + { + //check if we can avoid this enemy + VectorSubtract(bs->origin, entinfo.origin, dir); + vectoangles(dir, angles); + //if the bot isn't in the fov of the enemy + if (!InFieldOfVision(entinfo.angles, 90, angles)) { + //update some stuff for this enemy + BotUpdateBattleInventory(bs, i); + //if the bot doesn't really want to fight + if (BotWantsToRetreat(bs)) continue; + } + } + //found an enemy + bs->enemy = entinfo.number; + if (curenemy >= 0) bs->enemysight_time = FloatTime() - 2; + else bs->enemysight_time = FloatTime(); + bs->enemysuicide = qfalse; + bs->enemydeath_time = 0; + bs->enemyvisible_time = FloatTime(); + return qtrue; + } + return qfalse; +} + +/* +================== +BotTeamFlagCarrierVisible +================== +*/ +int BotTeamFlagCarrierVisible(bot_state_t *bs) { + int i; + float vis; + aas_entityinfo_t entinfo; + + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + if (i == bs->client) + continue; + // + BotEntityInfo(i, &entinfo); + //if this player is active + if (!entinfo.valid) + continue; + //if this player is carrying a flag + if (!EntityCarriesFlag(&entinfo)) + continue; + //if the flag carrier is not on the same team + if (!BotSameTeam(bs, i)) + continue; + //if the flag carrier is not visible + vis = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, i); + if (vis <= 0) + continue; + // + return i; + } + return -1; +} + +/* +================== +BotTeamFlagCarrier +================== +*/ +int BotTeamFlagCarrier(bot_state_t *bs) { + int i; + aas_entityinfo_t entinfo; + + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + if (i == bs->client) + continue; + // + BotEntityInfo(i, &entinfo); + //if this player is active + if (!entinfo.valid) + continue; + //if this player is carrying a flag + if (!EntityCarriesFlag(&entinfo)) + continue; + //if the flag carrier is not on the same team + if (!BotSameTeam(bs, i)) + continue; + // + return i; + } + return -1; +} + +/* +================== +BotEnemyFlagCarrierVisible +================== +*/ +int BotEnemyFlagCarrierVisible(bot_state_t *bs) { + int i; + float vis; + aas_entityinfo_t entinfo; + + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + if (i == bs->client) + continue; + // + BotEntityInfo(i, &entinfo); + //if this player is active + if (!entinfo.valid) + continue; + //if this player is carrying a flag + if (!EntityCarriesFlag(&entinfo)) + continue; + //if the flag carrier is on the same team + if (BotSameTeam(bs, i)) + continue; + //if the flag carrier is not visible + vis = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, i); + if (vis <= 0) + continue; + // + return i; + } + return -1; +} + +/* +================== +BotVisibleTeamMatesAndEnemies +================== +*/ +void BotVisibleTeamMatesAndEnemies(bot_state_t *bs, int *teammates, int *enemies, float range) { + int i; + float vis; + aas_entityinfo_t entinfo; + vec3_t dir; + + if (teammates) + *teammates = 0; + if (enemies) + *enemies = 0; + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + if (i == bs->client) + continue; + // + BotEntityInfo(i, &entinfo); + //if this player is active + if (!entinfo.valid) + continue; + //if this player is carrying a flag + if (!EntityCarriesFlag(&entinfo)) + continue; + //if not within range + VectorSubtract(entinfo.origin, bs->origin, dir); + if (VectorLengthSquared(dir) > Square(range)) + continue; + //if the flag carrier is not visible + vis = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, i); + if (vis <= 0) + continue; + //if the flag carrier is on the same team + if (BotSameTeam(bs, i)) { + if (teammates) + (*teammates)++; + } + else { + if (enemies) + (*enemies)++; + } + } +} + +#ifdef MISSIONPACK +/* +================== +BotTeamCubeCarrierVisible +================== +*/ +int BotTeamCubeCarrierVisible(bot_state_t *bs) { + int i; + float vis; + aas_entityinfo_t entinfo; + + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + if (i == bs->client) continue; + // + BotEntityInfo(i, &entinfo); + //if this player is active + if (!entinfo.valid) continue; + //if this player is carrying a flag + if (!EntityCarriesCubes(&entinfo)) continue; + //if the flag carrier is not on the same team + if (!BotSameTeam(bs, i)) continue; + //if the flag carrier is not visible + vis = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, i); + if (vis <= 0) continue; + // + return i; + } + return -1; +} + +/* +================== +BotEnemyCubeCarrierVisible +================== +*/ +int BotEnemyCubeCarrierVisible(bot_state_t *bs) { + int i; + float vis; + aas_entityinfo_t entinfo; + + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + if (i == bs->client) + continue; + // + BotEntityInfo(i, &entinfo); + //if this player is active + if (!entinfo.valid) + continue; + //if this player is carrying a flag + if (!EntityCarriesCubes(&entinfo)) continue; + //if the flag carrier is on the same team + if (BotSameTeam(bs, i)) + continue; + //if the flag carrier is not visible + vis = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, i); + if (vis <= 0) + continue; + // + return i; + } + return -1; +} +#endif + +/* +================== +BotAimAtEnemy +================== +*/ +void BotAimAtEnemy(bot_state_t *bs) { + int i, enemyvisible; + float dist, f, aim_skill, aim_accuracy, speed, reactiontime; + vec3_t dir, bestorigin, end, start, groundtarget, cmdmove, enemyvelocity; + vec3_t mins = {-4,-4,-4}, maxs = {4, 4, 4}; + weaponinfo_t wi; + aas_entityinfo_t entinfo; + bot_goal_t goal; + bsp_trace_t trace; + vec3_t target; + + //if the bot has no enemy + if (bs->enemy < 0) { + return; + } + //get the enemy entity information + BotEntityInfo(bs->enemy, &entinfo); + //if this is not a player (should be an obelisk) + if (bs->enemy >= MAX_CLIENTS) { + //if the obelisk is visible + VectorCopy(entinfo.origin, target); +#ifdef MISSIONPACK + // if attacking an obelisk + if ( bs->enemy == redobelisk.entitynum || + bs->enemy == blueobelisk.entitynum ) { + target[2] += 32; + } +#endif + //aim at the obelisk + VectorSubtract(target, bs->eye, dir); + vectoangles(dir, bs->ideal_viewangles); + //set the aim target before trying to attack + VectorCopy(target, bs->aimtarget); + return; + } + // + //BotAI_Print(PRT_MESSAGE, "client %d: aiming at client %d\n", bs->entitynum, bs->enemy); + // + aim_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_SKILL, 0, 1); + aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY, 0, 1); + // + if (aim_skill > 0.95) { + //don't aim too early + reactiontime = 0.5 * trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_REACTIONTIME, 0, 1); + if (bs->enemysight_time > FloatTime() - reactiontime) return; + if (bs->teleport_time > FloatTime() - reactiontime) return; + } + + //get the weapon information + trap_BotGetWeaponInfo(bs->ws, bs->weaponnum, &wi); + //get the weapon specific aim accuracy and or aim skill + if (wi.number == WP_MACHINEGUN) { + aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_MACHINEGUN, 0, 1); + } + else if (wi.number == WP_SHOTGUN) { + aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_SHOTGUN, 0, 1); + } + else if (wi.number == WP_GRENADE_LAUNCHER) { + aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_GRENADELAUNCHER, 0, 1); + aim_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_SKILL_GRENADELAUNCHER, 0, 1); + } + else if (wi.number == WP_ROCKET_LAUNCHER) { + aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_ROCKETLAUNCHER, 0, 1); + aim_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_SKILL_ROCKETLAUNCHER, 0, 1); + } + else if (wi.number == WP_LIGHTNING) { + aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_LIGHTNING, 0, 1); + } + else if (wi.number == WP_RAILGUN) { + aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_RAILGUN, 0, 1); + } + else if (wi.number == WP_PLASMAGUN) { + aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_PLASMAGUN, 0, 1); + aim_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_SKILL_PLASMAGUN, 0, 1); + } + else if (wi.number == WP_BFG) { + aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_BFG10K, 0, 1); + aim_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_SKILL_BFG10K, 0, 1); + } + // + if (aim_accuracy <= 0) aim_accuracy = 0.0001f; + //get the enemy entity information + BotEntityInfo(bs->enemy, &entinfo); + //if the enemy is invisible then shoot crappy most of the time + if (EntityIsInvisible(&entinfo)) { + if (random() > 0.1) aim_accuracy *= 0.4f; + } + // + VectorSubtract(entinfo.origin, entinfo.lastvisorigin, enemyvelocity); + VectorScale(enemyvelocity, 1 / entinfo.update_time, enemyvelocity); + //enemy origin and velocity is remembered every 0.5 seconds + if (bs->enemyposition_time < FloatTime()) { + // + bs->enemyposition_time = FloatTime() + 0.5; + VectorCopy(enemyvelocity, bs->enemyvelocity); + VectorCopy(entinfo.origin, bs->enemyorigin); + } + //if not extremely skilled + if (aim_skill < 0.9) { + VectorSubtract(entinfo.origin, bs->enemyorigin, dir); + //if the enemy moved a bit + if (VectorLengthSquared(dir) > Square(48)) { + //if the enemy changed direction + if (DotProduct(bs->enemyvelocity, enemyvelocity) < 0) { + //aim accuracy should be worse now + aim_accuracy *= 0.7f; + } + } + } + //check visibility of enemy + enemyvisible = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy); + //if the enemy is visible + if (enemyvisible) { + // + VectorCopy(entinfo.origin, bestorigin); + bestorigin[2] += 8; + //get the start point shooting from + //NOTE: the x and y projectile start offsets are ignored + VectorCopy(bs->origin, start); + start[2] += bs->cur_ps.viewheight; + start[2] += wi.offset[2]; + // + BotAI_Trace(&trace, start, mins, maxs, bestorigin, bs->entitynum, MASK_SHOT); + //if the enemy is NOT hit + if (trace.fraction <= 1 && trace.ent != entinfo.number) { + bestorigin[2] += 16; + } + //if it is not an instant hit weapon the bot might want to predict the enemy + if (wi.speed) { + // + VectorSubtract(bestorigin, bs->origin, dir); + dist = VectorLength(dir); + VectorSubtract(entinfo.origin, bs->enemyorigin, dir); + //if the enemy is NOT pretty far away and strafing just small steps left and right + if (!(dist > 100 && VectorLengthSquared(dir) < Square(32))) { + //if skilled anough do exact prediction + if (aim_skill > 0.8 && + //if the weapon is ready to fire + bs->cur_ps.weaponstate == WEAPON_READY) { + aas_clientmove_t move; + vec3_t origin; + + VectorSubtract(entinfo.origin, bs->origin, dir); + //distance towards the enemy + dist = VectorLength(dir); + //direction the enemy is moving in + VectorSubtract(entinfo.origin, entinfo.lastvisorigin, dir); + // + VectorScale(dir, 1 / entinfo.update_time, dir); + // + VectorCopy(entinfo.origin, origin); + origin[2] += 1; + // + VectorClear(cmdmove); + //AAS_ClearShownDebugLines(); + trap_AAS_PredictClientMovement(&move, bs->enemy, origin, + PRESENCE_CROUCH, qfalse, + dir, cmdmove, 0, + dist * 10 / wi.speed, 0.1f, 0, 0, qfalse); + VectorCopy(move.endpos, bestorigin); + //BotAI_Print(PRT_MESSAGE, "%1.1f predicted speed = %f, frames = %f\n", FloatTime(), VectorLength(dir), dist * 10 / wi.speed); + } + //if not that skilled do linear prediction + else if (aim_skill > 0.4) { + VectorSubtract(entinfo.origin, bs->origin, dir); + //distance towards the enemy + dist = VectorLength(dir); + //direction the enemy is moving in + VectorSubtract(entinfo.origin, entinfo.lastvisorigin, dir); + dir[2] = 0; + // + speed = VectorNormalize(dir) / entinfo.update_time; + //botimport.Print(PRT_MESSAGE, "speed = %f, wi->speed = %f\n", speed, wi->speed); + //best spot to aim at + VectorMA(entinfo.origin, (dist / wi.speed) * speed, dir, bestorigin); + } + } + } + //if the projectile does radial damage + if (aim_skill > 0.6 && wi.proj.damagetype & DAMAGETYPE_RADIAL) { + //if the enemy isn't standing significantly higher than the bot + if (entinfo.origin[2] < bs->origin[2] + 16) { + //try to aim at the ground in front of the enemy + VectorCopy(entinfo.origin, end); + end[2] -= 64; + BotAI_Trace(&trace, entinfo.origin, NULL, NULL, end, entinfo.number, MASK_SHOT); + // + VectorCopy(bestorigin, groundtarget); + if (trace.startsolid) groundtarget[2] = entinfo.origin[2] - 16; + else groundtarget[2] = trace.endpos[2] - 8; + //trace a line from projectile start to ground target + BotAI_Trace(&trace, start, NULL, NULL, groundtarget, bs->entitynum, MASK_SHOT); + //if hitpoint is not vertically too far from the ground target + if (fabs(trace.endpos[2] - groundtarget[2]) < 50) { + VectorSubtract(trace.endpos, groundtarget, dir); + //if the hitpoint is near anough the ground target + if (VectorLengthSquared(dir) < Square(60)) { + VectorSubtract(trace.endpos, start, dir); + //if the hitpoint is far anough from the bot + if (VectorLengthSquared(dir) > Square(100)) { + //check if the bot is visible from the ground target + trace.endpos[2] += 1; + BotAI_Trace(&trace, trace.endpos, NULL, NULL, entinfo.origin, entinfo.number, MASK_SHOT); + if (trace.fraction >= 1) { + //botimport.Print(PRT_MESSAGE, "%1.1f aiming at ground\n", AAS_Time()); + VectorCopy(groundtarget, bestorigin); + } + } + } + } + } + } + bestorigin[0] += 20 * crandom() * (1 - aim_accuracy); + bestorigin[1] += 20 * crandom() * (1 - aim_accuracy); + bestorigin[2] += 10 * crandom() * (1 - aim_accuracy); + } + else { + // + VectorCopy(bs->lastenemyorigin, bestorigin); + bestorigin[2] += 8; + //if the bot is skilled anough + if (aim_skill > 0.5) { + //do prediction shots around corners + if (wi.number == WP_BFG || + wi.number == WP_ROCKET_LAUNCHER || + wi.number == WP_GRENADE_LAUNCHER) { + //create the chase goal + goal.entitynum = bs->client; + goal.areanum = bs->areanum; + VectorCopy(bs->eye, goal.origin); + VectorSet(goal.mins, -8, -8, -8); + VectorSet(goal.maxs, 8, 8, 8); + // + if (trap_BotPredictVisiblePosition(bs->lastenemyorigin, bs->lastenemyareanum, &goal, TFL_DEFAULT, target)) { + VectorSubtract(target, bs->eye, dir); + if (VectorLengthSquared(dir) > Square(80)) { + VectorCopy(target, bestorigin); + bestorigin[2] -= 20; + } + } + aim_accuracy = 1; + } + } + } + // + if (enemyvisible) { + BotAI_Trace(&trace, bs->eye, NULL, NULL, bestorigin, bs->entitynum, MASK_SHOT); + VectorCopy(trace.endpos, bs->aimtarget); + } + else { + VectorCopy(bestorigin, bs->aimtarget); + } + //get aim direction + VectorSubtract(bestorigin, bs->eye, dir); + // + if (wi.number == WP_MACHINEGUN || + wi.number == WP_SHOTGUN || + wi.number == WP_LIGHTNING || + wi.number == WP_RAILGUN) { + //distance towards the enemy + dist = VectorLength(dir); + if (dist > 150) dist = 150; + f = 0.6 + dist / 150 * 0.4; + aim_accuracy *= f; + } + //add some random stuff to the aim direction depending on the aim accuracy + if (aim_accuracy < 0.8) { + VectorNormalize(dir); + for (i = 0; i < 3; i++) dir[i] += 0.3 * crandom() * (1 - aim_accuracy); + } + //set the ideal view angles + vectoangles(dir, bs->ideal_viewangles); + //take the weapon spread into account for lower skilled bots + bs->ideal_viewangles[PITCH] += 6 * wi.vspread * crandom() * (1 - aim_accuracy); + bs->ideal_viewangles[PITCH] = AngleMod(bs->ideal_viewangles[PITCH]); + bs->ideal_viewangles[YAW] += 6 * wi.hspread * crandom() * (1 - aim_accuracy); + bs->ideal_viewangles[YAW] = AngleMod(bs->ideal_viewangles[YAW]); + //if the bots should be really challenging + if (bot_challenge.integer) { + //if the bot is really accurate and has the enemy in view for some time + if (aim_accuracy > 0.9 && bs->enemysight_time < FloatTime() - 1) { + //set the view angles directly + if (bs->ideal_viewangles[PITCH] > 180) bs->ideal_viewangles[PITCH] -= 360; + VectorCopy(bs->ideal_viewangles, bs->viewangles); + trap_EA_View(bs->client, bs->viewangles); + } + } +} + +/* +================== +BotCheckAttack +================== +*/ +void BotCheckAttack(bot_state_t *bs) { + float points, reactiontime, fov, firethrottle; + int attackentity; + bsp_trace_t bsptrace; + //float selfpreservation; + vec3_t forward, right, start, end, dir, angles; + weaponinfo_t wi; + bsp_trace_t trace; + aas_entityinfo_t entinfo; + vec3_t mins = {-8, -8, -8}, maxs = {8, 8, 8}; + + attackentity = bs->enemy; + // + BotEntityInfo(attackentity, &entinfo); + // if not attacking a player + if (attackentity >= MAX_CLIENTS) { +#ifdef MISSIONPACK + // if attacking an obelisk + if ( entinfo.number == redobelisk.entitynum || + entinfo.number == blueobelisk.entitynum ) { + // if obelisk is respawning return + if ( g_entities[entinfo.number].activator && + g_entities[entinfo.number].activator->s.frame == 2 ) { + return; + } + } +#endif + } + // + reactiontime = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_REACTIONTIME, 0, 1); + if (bs->enemysight_time > FloatTime() - reactiontime) return; + if (bs->teleport_time > FloatTime() - reactiontime) return; + //if changing weapons + if (bs->weaponchange_time > FloatTime() - 0.1) return; + //check fire throttle characteristic + if (bs->firethrottlewait_time > FloatTime()) return; + firethrottle = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_FIRETHROTTLE, 0, 1); + if (bs->firethrottleshoot_time < FloatTime()) { + if (random() > firethrottle) { + bs->firethrottlewait_time = FloatTime() + firethrottle; + bs->firethrottleshoot_time = 0; + } + else { + bs->firethrottleshoot_time = FloatTime() + 1 - firethrottle; + bs->firethrottlewait_time = 0; + } + } + // + // + VectorSubtract(bs->aimtarget, bs->eye, dir); + // + if (bs->weaponnum == WP_GAUNTLET) { + if (VectorLengthSquared(dir) > Square(60)) { + return; + } + } + if (VectorLengthSquared(dir) < Square(100)) + fov = 120; + else + fov = 50; + // + vectoangles(dir, angles); + if (!InFieldOfVision(bs->viewangles, fov, angles)) + return; + BotAI_Trace(&bsptrace, bs->eye, NULL, NULL, bs->aimtarget, bs->client, CONTENTS_SOLID|CONTENTS_PLAYERCLIP); + if (bsptrace.fraction < 1 && bsptrace.ent != attackentity) + return; + + //get the weapon info + trap_BotGetWeaponInfo(bs->ws, bs->weaponnum, &wi); + //get the start point shooting from + VectorCopy(bs->origin, start); + start[2] += bs->cur_ps.viewheight; + AngleVectors(bs->viewangles, forward, right, NULL); + start[0] += forward[0] * wi.offset[0] + right[0] * wi.offset[1]; + start[1] += forward[1] * wi.offset[0] + right[1] * wi.offset[1]; + start[2] += forward[2] * wi.offset[0] + right[2] * wi.offset[1] + wi.offset[2]; + //end point aiming at + VectorMA(start, 1000, forward, end); + //a little back to make sure not inside a very close enemy + VectorMA(start, -12, forward, start); + BotAI_Trace(&trace, start, mins, maxs, end, bs->entitynum, MASK_SHOT); + //if the entity is a client + if (trace.ent > 0 && trace.ent <= MAX_CLIENTS) { + if (trace.ent != attackentity) { + //if a teammate is hit + if (BotSameTeam(bs, trace.ent)) + return; + } + } + //if won't hit the enemy or not attacking a player (obelisk) + if (trace.ent != attackentity || attackentity >= MAX_CLIENTS) { + //if the projectile does radial damage + if (wi.proj.damagetype & DAMAGETYPE_RADIAL) { + if (trace.fraction * 1000 < wi.proj.radius) { + points = (wi.proj.damage - 0.5 * trace.fraction * 1000) * 0.5; + if (points > 0) { + return; + } + } + //FIXME: check if a teammate gets radial damage + } + } + //if fire has to be release to activate weapon + if (wi.flags & WFL_FIRERELEASED) { + if (bs->flags & BFL_ATTACKED) { + trap_EA_Attack(bs->client); + } + } + else { + trap_EA_Attack(bs->client); + } + bs->flags ^= BFL_ATTACKED; +} + +/* +================== +BotMapScripts +================== +*/ +void BotMapScripts(bot_state_t *bs) { + char info[1024]; + char mapname[128]; + int i, shootbutton; + float aim_accuracy; + aas_entityinfo_t entinfo; + vec3_t dir; + + trap_GetServerinfo(info, sizeof(info)); + + strncpy(mapname, Info_ValueForKey( info, "mapname" ), sizeof(mapname)-1); + mapname[sizeof(mapname)-1] = '\0'; + + if (!Q_stricmp(mapname, "q3tourney6")) { + vec3_t mins = {700, 204, 672}, maxs = {964, 468, 680}; + vec3_t buttonorg = {304, 352, 920}; + //NOTE: NEVER use the func_bobbing in q3tourney6 + bs->tfl &= ~TFL_FUNCBOB; + //if the bot is below the bounding box + if (bs->origin[0] > mins[0] && bs->origin[0] < maxs[0]) { + if (bs->origin[1] > mins[1] && bs->origin[1] < maxs[1]) { + if (bs->origin[2] < mins[2]) { + return; + } + } + } + shootbutton = qfalse; + //if an enemy is below this bounding box then shoot the button + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + + if (i == bs->client) continue; + // + BotEntityInfo(i, &entinfo); + // + if (!entinfo.valid) continue; + //if the enemy isn't dead and the enemy isn't the bot self + if (EntityIsDead(&entinfo) || entinfo.number == bs->entitynum) continue; + // + if (entinfo.origin[0] > mins[0] && entinfo.origin[0] < maxs[0]) { + if (entinfo.origin[1] > mins[1] && entinfo.origin[1] < maxs[1]) { + if (entinfo.origin[2] < mins[2]) { + //if there's a team mate below the crusher + if (BotSameTeam(bs, i)) { + shootbutton = qfalse; + break; + } + else { + shootbutton = qtrue; + } + } + } + } + } + if (shootbutton) { + bs->flags |= BFL_IDEALVIEWSET; + VectorSubtract(buttonorg, bs->eye, dir); + vectoangles(dir, bs->ideal_viewangles); + aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY, 0, 1); + bs->ideal_viewangles[PITCH] += 8 * crandom() * (1 - aim_accuracy); + bs->ideal_viewangles[PITCH] = AngleMod(bs->ideal_viewangles[PITCH]); + bs->ideal_viewangles[YAW] += 8 * crandom() * (1 - aim_accuracy); + bs->ideal_viewangles[YAW] = AngleMod(bs->ideal_viewangles[YAW]); + // + if (InFieldOfVision(bs->viewangles, 20, bs->ideal_viewangles)) { + trap_EA_Attack(bs->client); + } + } + } + else if (!Q_stricmp(mapname, "mpq3tourney6")) { + //NOTE: NEVER use the func_bobbing in mpq3tourney6 + bs->tfl &= ~TFL_FUNCBOB; + } +} + +/* +================== +BotSetMovedir +================== +*/ +// bk001205 - made these static +static vec3_t VEC_UP = {0, -1, 0}; +static vec3_t MOVEDIR_UP = {0, 0, 1}; +static vec3_t VEC_DOWN = {0, -2, 0}; +static vec3_t MOVEDIR_DOWN = {0, 0, -1}; + +void BotSetMovedir(vec3_t angles, vec3_t movedir) { + if (VectorCompare(angles, VEC_UP)) { + VectorCopy(MOVEDIR_UP, movedir); + } + else if (VectorCompare(angles, VEC_DOWN)) { + VectorCopy(MOVEDIR_DOWN, movedir); + } + else { + AngleVectors(angles, movedir, NULL, NULL); + } +} + +/* +================== +BotModelMinsMaxs + +this is ugly +================== +*/ +int BotModelMinsMaxs(int modelindex, int eType, int contents, vec3_t mins, vec3_t maxs) { + gentity_t *ent; + int i; + + ent = &g_entities[0]; + for (i = 0; i < level.num_entities; i++, ent++) { + if ( !ent->inuse ) { + continue; + } + if ( eType && ent->s.eType != eType) { + continue; + } + if ( contents && ent->r.contents != contents) { + continue; + } + if (ent->s.modelindex == modelindex) { + if (mins) + VectorAdd(ent->r.currentOrigin, ent->r.mins, mins); + if (maxs) + VectorAdd(ent->r.currentOrigin, ent->r.maxs, maxs); + return i; + } + } + if (mins) + VectorClear(mins); + if (maxs) + VectorClear(maxs); + return 0; +} + +/* +================== +BotFuncButtonGoal +================== +*/ +int BotFuncButtonActivateGoal(bot_state_t *bs, int bspent, bot_activategoal_t *activategoal) { + int i, areas[10], numareas, modelindex, entitynum; + char model[128]; + float lip, dist, health, angle; + vec3_t size, start, end, mins, maxs, angles, points[10]; + vec3_t movedir, origin, goalorigin, bboxmins, bboxmaxs; + vec3_t extramins = {1, 1, 1}, extramaxs = {-1, -1, -1}; + bsp_trace_t bsptrace; + + activategoal->shoot = qfalse; + VectorClear(activategoal->target); + //create a bot goal towards the button + trap_AAS_ValueForBSPEpairKey(bspent, "model", model, sizeof(model)); + if (!*model) + return qfalse; + modelindex = atoi(model+1); + if (!modelindex) + return qfalse; + VectorClear(angles); + entitynum = BotModelMinsMaxs(modelindex, ET_MOVER, 0, mins, maxs); + //get the lip of the button + trap_AAS_FloatForBSPEpairKey(bspent, "lip", &lip); + if (!lip) lip = 4; + //get the move direction from the angle + trap_AAS_FloatForBSPEpairKey(bspent, "angle", &angle); + VectorSet(angles, 0, angle, 0); + BotSetMovedir(angles, movedir); + //button size + VectorSubtract(maxs, mins, size); + //button origin + VectorAdd(mins, maxs, origin); + VectorScale(origin, 0.5, origin); + //touch distance of the button + dist = fabs(movedir[0]) * size[0] + fabs(movedir[1]) * size[1] + fabs(movedir[2]) * size[2]; + dist *= 0.5; + // + trap_AAS_FloatForBSPEpairKey(bspent, "health", &health); + //if the button is shootable + if (health) { + //calculate the shoot target + VectorMA(origin, -dist, movedir, goalorigin); + // + VectorCopy(goalorigin, activategoal->target); + activategoal->shoot = qtrue; + // + BotAI_Trace(&bsptrace, bs->eye, NULL, NULL, goalorigin, bs->entitynum, MASK_SHOT); + // if the button is visible from the current position + if (bsptrace.fraction >= 1.0 || bsptrace.ent == entitynum) { + // + activategoal->goal.entitynum = entitynum; //NOTE: this is the entity number of the shootable button + activategoal->goal.number = 0; + activategoal->goal.flags = 0; + VectorCopy(bs->origin, activategoal->goal.origin); + activategoal->goal.areanum = bs->areanum; + VectorSet(activategoal->goal.mins, -8, -8, -8); + VectorSet(activategoal->goal.maxs, 8, 8, 8); + // + return qtrue; + } + else { + //create a goal from where the button is visible and shoot at the button from there + //add bounding box size to the dist + trap_AAS_PresenceTypeBoundingBox(PRESENCE_CROUCH, bboxmins, bboxmaxs); + for (i = 0; i < 3; i++) { + if (movedir[i] < 0) dist += fabs(movedir[i]) * fabs(bboxmaxs[i]); + else dist += fabs(movedir[i]) * fabs(bboxmins[i]); + } + //calculate the goal origin + VectorMA(origin, -dist, movedir, goalorigin); + // + VectorCopy(goalorigin, start); + start[2] += 24; + VectorCopy(start, end); + end[2] -= 512; + numareas = trap_AAS_TraceAreas(start, end, areas, points, 10); + // + for (i = numareas-1; i >= 0; i--) { + if (trap_AAS_AreaReachability(areas[i])) { + break; + } + } + if (i < 0) { + // FIXME: trace forward and maybe in other directions to find a valid area + } + if (i >= 0) { + // + VectorCopy(points[i], activategoal->goal.origin); + activategoal->goal.areanum = areas[i]; + VectorSet(activategoal->goal.mins, 8, 8, 8); + VectorSet(activategoal->goal.maxs, -8, -8, -8); + // + for (i = 0; i < 3; i++) + { + if (movedir[i] < 0) activategoal->goal.maxs[i] += fabs(movedir[i]) * fabs(extramaxs[i]); + else activategoal->goal.mins[i] += fabs(movedir[i]) * fabs(extramins[i]); + } //end for + // + activategoal->goal.entitynum = entitynum; + activategoal->goal.number = 0; + activategoal->goal.flags = 0; + return qtrue; + } + } + return qfalse; + } + else { + //add bounding box size to the dist + trap_AAS_PresenceTypeBoundingBox(PRESENCE_CROUCH, bboxmins, bboxmaxs); + for (i = 0; i < 3; i++) { + if (movedir[i] < 0) dist += fabs(movedir[i]) * fabs(bboxmaxs[i]); + else dist += fabs(movedir[i]) * fabs(bboxmins[i]); + } + //calculate the goal origin + VectorMA(origin, -dist, movedir, goalorigin); + // + VectorCopy(goalorigin, start); + start[2] += 24; + VectorCopy(start, end); + end[2] -= 100; + numareas = trap_AAS_TraceAreas(start, end, areas, NULL, 10); + // + for (i = 0; i < numareas; i++) { + if (trap_AAS_AreaReachability(areas[i])) { + break; + } + } + if (i < numareas) { + // + VectorCopy(origin, activategoal->goal.origin); + activategoal->goal.areanum = areas[i]; + VectorSubtract(mins, origin, activategoal->goal.mins); + VectorSubtract(maxs, origin, activategoal->goal.maxs); + // + for (i = 0; i < 3; i++) + { + if (movedir[i] < 0) activategoal->goal.maxs[i] += fabs(movedir[i]) * fabs(extramaxs[i]); + else activategoal->goal.mins[i] += fabs(movedir[i]) * fabs(extramins[i]); + } //end for + // + activategoal->goal.entitynum = entitynum; + activategoal->goal.number = 0; + activategoal->goal.flags = 0; + return qtrue; + } + } + return qfalse; +} + +/* +================== +BotFuncDoorGoal +================== +*/ +int BotFuncDoorActivateGoal(bot_state_t *bs, int bspent, bot_activategoal_t *activategoal) { + int modelindex, entitynum; + char model[MAX_INFO_STRING]; + vec3_t mins, maxs, origin, angles; + + //shoot at the shootable door + trap_AAS_ValueForBSPEpairKey(bspent, "model", model, sizeof(model)); + if (!*model) + return qfalse; + modelindex = atoi(model+1); + if (!modelindex) + return qfalse; + VectorClear(angles); + entitynum = BotModelMinsMaxs(modelindex, ET_MOVER, 0, mins, maxs); + //door origin + VectorAdd(mins, maxs, origin); + VectorScale(origin, 0.5, origin); + VectorCopy(origin, activategoal->target); + activategoal->shoot = qtrue; + // + activategoal->goal.entitynum = entitynum; //NOTE: this is the entity number of the shootable door + activategoal->goal.number = 0; + activategoal->goal.flags = 0; + VectorCopy(bs->origin, activategoal->goal.origin); + activategoal->goal.areanum = bs->areanum; + VectorSet(activategoal->goal.mins, -8, -8, -8); + VectorSet(activategoal->goal.maxs, 8, 8, 8); + return qtrue; +} + +/* +================== +BotTriggerMultipleGoal +================== +*/ +int BotTriggerMultipleActivateGoal(bot_state_t *bs, int bspent, bot_activategoal_t *activategoal) { + int i, areas[10], numareas, modelindex, entitynum; + char model[128]; + vec3_t start, end, mins, maxs, angles; + vec3_t origin, goalorigin; + + activategoal->shoot = qfalse; + VectorClear(activategoal->target); + //create a bot goal towards the trigger + trap_AAS_ValueForBSPEpairKey(bspent, "model", model, sizeof(model)); + if (!*model) + return qfalse; + modelindex = atoi(model+1); + if (!modelindex) + return qfalse; + VectorClear(angles); + entitynum = BotModelMinsMaxs(modelindex, 0, CONTENTS_TRIGGER, mins, maxs); + //trigger origin + VectorAdd(mins, maxs, origin); + VectorScale(origin, 0.5, origin); + VectorCopy(origin, goalorigin); + // + VectorCopy(goalorigin, start); + start[2] += 24; + VectorCopy(start, end); + end[2] -= 100; + numareas = trap_AAS_TraceAreas(start, end, areas, NULL, 10); + // + for (i = 0; i < numareas; i++) { + if (trap_AAS_AreaReachability(areas[i])) { + break; + } + } + if (i < numareas) { + VectorCopy(origin, activategoal->goal.origin); + activategoal->goal.areanum = areas[i]; + VectorSubtract(mins, origin, activategoal->goal.mins); + VectorSubtract(maxs, origin, activategoal->goal.maxs); + // + activategoal->goal.entitynum = entitynum; + activategoal->goal.number = 0; + activategoal->goal.flags = 0; + return qtrue; + } + return qfalse; +} + +/* +================== +BotPopFromActivateGoalStack +================== +*/ +int BotPopFromActivateGoalStack(bot_state_t *bs) { + if (!bs->activatestack) + return qfalse; + BotEnableActivateGoalAreas(bs->activatestack, qtrue); + bs->activatestack->inuse = qfalse; + bs->activatestack->justused_time = FloatTime(); + bs->activatestack = bs->activatestack->next; + return qtrue; +} + +/* +================== +BotPushOntoActivateGoalStack +================== +*/ +int BotPushOntoActivateGoalStack(bot_state_t *bs, bot_activategoal_t *activategoal) { + int i, best; + float besttime; + + best = -1; + besttime = FloatTime() + 9999; + // + for (i = 0; i < MAX_ACTIVATESTACK; i++) { + if (!bs->activategoalheap[i].inuse) { + if (bs->activategoalheap[i].justused_time < besttime) { + besttime = bs->activategoalheap[i].justused_time; + best = i; + } + } + } + if (best != -1) { + memcpy(&bs->activategoalheap[best], activategoal, sizeof(bot_activategoal_t)); + bs->activategoalheap[best].inuse = qtrue; + bs->activategoalheap[best].next = bs->activatestack; + bs->activatestack = &bs->activategoalheap[best]; + return qtrue; + } + return qfalse; +} + +/* +================== +BotClearActivateGoalStack +================== +*/ +void BotClearActivateGoalStack(bot_state_t *bs) { + while(bs->activatestack) + BotPopFromActivateGoalStack(bs); +} + +/* +================== +BotEnableActivateGoalAreas +================== +*/ +void BotEnableActivateGoalAreas(bot_activategoal_t *activategoal, int enable) { + int i; + + if (activategoal->areasdisabled == !enable) + return; + for (i = 0; i < activategoal->numareas; i++) + trap_AAS_EnableRoutingArea( activategoal->areas[i], enable ); + activategoal->areasdisabled = !enable; +} + +/* +================== +BotIsGoingToActivateEntity +================== +*/ +int BotIsGoingToActivateEntity(bot_state_t *bs, int entitynum) { + bot_activategoal_t *a; + int i; + + for (a = bs->activatestack; a; a = a->next) { + if (a->time < FloatTime()) + continue; + if (a->goal.entitynum == entitynum) + return qtrue; + } + for (i = 0; i < MAX_ACTIVATESTACK; i++) { + if (bs->activategoalheap[i].inuse) + continue; + // + if (bs->activategoalheap[i].goal.entitynum == entitynum) { + // if the bot went for this goal less than 2 seconds ago + if (bs->activategoalheap[i].justused_time > FloatTime() - 2) + return qtrue; + } + } + return qfalse; +} + +/* +================== +BotGetActivateGoal + + returns the number of the bsp entity to activate + goal->entitynum will be set to the game entity to activate +================== +*/ +//#define OBSTACLEDEBUG + +int BotGetActivateGoal(bot_state_t *bs, int entitynum, bot_activategoal_t *activategoal) { + int i, ent, cur_entities[10], spawnflags, modelindex, areas[MAX_ACTIVATEAREAS*2], numareas, t; + char model[MAX_INFO_STRING], tmpmodel[128]; + char target[128], classname[128]; + float health; + char targetname[10][128]; + aas_entityinfo_t entinfo; + aas_areainfo_t areainfo; + vec3_t origin, angles, absmins, absmaxs; + + memset(activategoal, 0, sizeof(bot_activategoal_t)); + BotEntityInfo(entitynum, &entinfo); + Com_sprintf(model, sizeof( model ), "*%d", entinfo.modelindex); + for (ent = trap_AAS_NextBSPEntity(0); ent; ent = trap_AAS_NextBSPEntity(ent)) { + if (!trap_AAS_ValueForBSPEpairKey(ent, "model", tmpmodel, sizeof(tmpmodel))) continue; + if (!strcmp(model, tmpmodel)) break; + } + if (!ent) { + BotAI_Print(PRT_ERROR, "BotGetActivateGoal: no entity found with model %s\n", model); + return 0; + } + trap_AAS_ValueForBSPEpairKey(ent, "classname", classname, sizeof(classname)); + if (!classname) { + BotAI_Print(PRT_ERROR, "BotGetActivateGoal: entity with model %s has no classname\n", model); + return 0; + } + //if it is a door + if (!strcmp(classname, "func_door")) { + if (trap_AAS_FloatForBSPEpairKey(ent, "health", &health)) { + //if the door has health then the door must be shot to open + if (health) { + BotFuncDoorActivateGoal(bs, ent, activategoal); + return ent; + } + } + // + trap_AAS_IntForBSPEpairKey(ent, "spawnflags", &spawnflags); + // if the door starts open then just wait for the door to return + if ( spawnflags & 1 ) + return 0; + //get the door origin + if (!trap_AAS_VectorForBSPEpairKey(ent, "origin", origin)) { + VectorClear(origin); + } + //if the door is open or opening already + if (!VectorCompare(origin, entinfo.origin)) + return 0; + // store all the areas the door is in + trap_AAS_ValueForBSPEpairKey(ent, "model", model, sizeof(model)); + if (*model) { + modelindex = atoi(model+1); + if (modelindex) { + VectorClear(angles); + BotModelMinsMaxs(modelindex, ET_MOVER, 0, absmins, absmaxs); + // + numareas = trap_AAS_BBoxAreas(absmins, absmaxs, areas, MAX_ACTIVATEAREAS*2); + // store the areas with reachabilities first + for (i = 0; i < numareas; i++) { + if (activategoal->numareas >= MAX_ACTIVATEAREAS) + break; + if ( !trap_AAS_AreaReachability(areas[i]) ) { + continue; + } + trap_AAS_AreaInfo(areas[i], &areainfo); + if (areainfo.contents & AREACONTENTS_MOVER) { + activategoal->areas[activategoal->numareas++] = areas[i]; + } + } + // store any remaining areas + for (i = 0; i < numareas; i++) { + if (activategoal->numareas >= MAX_ACTIVATEAREAS) + break; + if ( trap_AAS_AreaReachability(areas[i]) ) { + continue; + } + trap_AAS_AreaInfo(areas[i], &areainfo); + if (areainfo.contents & AREACONTENTS_MOVER) { + activategoal->areas[activategoal->numareas++] = areas[i]; + } + } + } + } + } + // if the bot is blocked by or standing on top of a button + if (!strcmp(classname, "func_button")) { + return 0; + } + // get the targetname so we can find an entity with a matching target + if (!trap_AAS_ValueForBSPEpairKey(ent, "targetname", targetname[0], sizeof(targetname[0]))) { + if (bot_developer.integer) { + BotAI_Print(PRT_ERROR, "BotGetActivateGoal: entity with model \"%s\" has no targetname\n", model); + } + return 0; + } + // allow tree-like activation + cur_entities[0] = trap_AAS_NextBSPEntity(0); + for (i = 0; i >= 0 && i < 10;) { + for (ent = cur_entities[i]; ent; ent = trap_AAS_NextBSPEntity(ent)) { + if (!trap_AAS_ValueForBSPEpairKey(ent, "target", target, sizeof(target))) continue; + if (!strcmp(targetname[i], target)) { + cur_entities[i] = trap_AAS_NextBSPEntity(ent); + break; + } + } + if (!ent) { + if (bot_developer.integer) { + BotAI_Print(PRT_ERROR, "BotGetActivateGoal: no entity with target \"%s\"\n", targetname[i]); + } + i--; + continue; + } + if (!trap_AAS_ValueForBSPEpairKey(ent, "classname", classname, sizeof(classname))) { + if (bot_developer.integer) { + BotAI_Print(PRT_ERROR, "BotGetActivateGoal: entity with target \"%s\" has no classname\n", targetname[i]); + } + continue; + } + // BSP button model + if (!strcmp(classname, "func_button")) { + // + if (!BotFuncButtonActivateGoal(bs, ent, activategoal)) + continue; + // if the bot tries to activate this button already + if ( bs->activatestack && bs->activatestack->inuse && + bs->activatestack->goal.entitynum == activategoal->goal.entitynum && + bs->activatestack->time > FloatTime() && + bs->activatestack->start_time < FloatTime() - 2) + continue; + // if the bot is in a reachability area + if ( trap_AAS_AreaReachability(bs->areanum) ) { + // disable all areas the blocking entity is in + BotEnableActivateGoalAreas( activategoal, qfalse ); + // + t = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, activategoal->goal.areanum, bs->tfl); + // if the button is not reachable + if (!t) { + continue; + } + activategoal->time = FloatTime() + t * 0.01 + 5; + } + return ent; + } + // invisible trigger multiple box + else if (!strcmp(classname, "trigger_multiple")) { + // + if (!BotTriggerMultipleActivateGoal(bs, ent, activategoal)) + continue; + // if the bot tries to activate this trigger already + if ( bs->activatestack && bs->activatestack->inuse && + bs->activatestack->goal.entitynum == activategoal->goal.entitynum && + bs->activatestack->time > FloatTime() && + bs->activatestack->start_time < FloatTime() - 2) + continue; + // if the bot is in a reachability area + if ( trap_AAS_AreaReachability(bs->areanum) ) { + // disable all areas the blocking entity is in + BotEnableActivateGoalAreas( activategoal, qfalse ); + // + t = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, activategoal->goal.areanum, bs->tfl); + // if the trigger is not reachable + if (!t) { + continue; + } + activategoal->time = FloatTime() + t * 0.01 + 5; + } + return ent; + } + else if (!strcmp(classname, "func_timer")) { + // just skip the func_timer + continue; + } + // the actual button or trigger might be linked through a target_relay or target_delay + else if (!strcmp(classname, "target_relay") || !strcmp(classname, "target_delay")) { + if (trap_AAS_ValueForBSPEpairKey(ent, "targetname", targetname[i+1], sizeof(targetname[0]))) { + i++; + cur_entities[i] = trap_AAS_NextBSPEntity(0); + } + } + } +#ifdef OBSTACLEDEBUG + BotAI_Print(PRT_ERROR, "BotGetActivateGoal: no valid activator for entity with target \"%s\"\n", targetname[0]); +#endif + return 0; +} + +/* +================== +BotGoForActivateGoal +================== +*/ +int BotGoForActivateGoal(bot_state_t *bs, bot_activategoal_t *activategoal) { + aas_entityinfo_t activateinfo; + + activategoal->inuse = qtrue; + if (!activategoal->time) + activategoal->time = FloatTime() + 10; + activategoal->start_time = FloatTime(); + BotEntityInfo(activategoal->goal.entitynum, &activateinfo); + VectorCopy(activateinfo.origin, activategoal->origin); + // + if (BotPushOntoActivateGoalStack(bs, activategoal)) { + // enter the activate entity AI node + AIEnter_Seek_ActivateEntity(bs, "BotGoForActivateGoal"); + return qtrue; + } + else { + // enable any routing areas that were disabled + BotEnableActivateGoalAreas(activategoal, qtrue); + return qfalse; + } +} + +/* +================== +BotPrintActivateGoalInfo +================== +*/ +void BotPrintActivateGoalInfo(bot_state_t *bs, bot_activategoal_t *activategoal, int bspent) { + char netname[MAX_NETNAME]; + char classname[128]; + char buf[128]; + + ClientName(bs->client, netname, sizeof(netname)); + trap_AAS_ValueForBSPEpairKey(bspent, "classname", classname, sizeof(classname)); + if (activategoal->shoot) { + Com_sprintf(buf, sizeof(buf), "%s: I have to shoot at a %s from %1.1f %1.1f %1.1f in area %d\n", + netname, classname, + activategoal->goal.origin[0], + activategoal->goal.origin[1], + activategoal->goal.origin[2], + activategoal->goal.areanum); + } + else { + Com_sprintf(buf, sizeof(buf), "%s: I have to activate a %s at %1.1f %1.1f %1.1f in area %d\n", + netname, classname, + activategoal->goal.origin[0], + activategoal->goal.origin[1], + activategoal->goal.origin[2], + activategoal->goal.areanum); + } + trap_EA_Say(bs->client, buf); +} + +/* +================== +BotRandomMove +================== +*/ +void BotRandomMove(bot_state_t *bs, bot_moveresult_t *moveresult) { + vec3_t dir, angles; + + angles[0] = 0; + angles[1] = random() * 360; + angles[2] = 0; + AngleVectors(angles, dir, NULL, NULL); + + trap_BotMoveInDirection(bs->ms, dir, 400, MOVE_WALK); + + moveresult->failure = qfalse; + VectorCopy(dir, moveresult->movedir); +} + +/* +================== +BotAIBlocked + +Very basic handling of bots being blocked by other entities. +Check what kind of entity is blocking the bot and try to activate +it. If that's not an option then try to walk around or over the entity. +Before the bot ends in this part of the AI it should predict which doors to +open, which buttons to activate etc. +================== +*/ +void BotAIBlocked(bot_state_t *bs, bot_moveresult_t *moveresult, int activate) { + int movetype, bspent; + vec3_t hordir, start, end, mins, maxs, sideward, angles, up = {0, 0, 1}; + aas_entityinfo_t entinfo; + bot_activategoal_t activategoal; + + // if the bot is not blocked by anything + if (!moveresult->blocked) { + bs->notblocked_time = FloatTime(); + return; + } + // if stuck in a solid area + if ( moveresult->type == RESULTTYPE_INSOLIDAREA ) { + // move in a random direction in the hope to get out + BotRandomMove(bs, moveresult); + // + return; + } + // get info for the entity that is blocking the bot + BotEntityInfo(moveresult->blockentity, &entinfo); +#ifdef OBSTACLEDEBUG + ClientName(bs->client, netname, sizeof(netname)); + BotAI_Print(PRT_MESSAGE, "%s: I'm blocked by model %d\n", netname, entinfo.modelindex); +#endif // OBSTACLEDEBUG + // if blocked by a bsp model and the bot wants to activate it + if (activate && entinfo.modelindex > 0 && entinfo.modelindex <= max_bspmodelindex) { + // find the bsp entity which should be activated in order to get the blocking entity out of the way + bspent = BotGetActivateGoal(bs, entinfo.number, &activategoal); + if (bspent) { + // + if (bs->activatestack && !bs->activatestack->inuse) + bs->activatestack = NULL; + // if not already trying to activate this entity + if (!BotIsGoingToActivateEntity(bs, activategoal.goal.entitynum)) { + // + BotGoForActivateGoal(bs, &activategoal); + } + // if ontop of an obstacle or + // if the bot is not in a reachability area it'll still + // need some dynamic obstacle avoidance, otherwise return + if (!(moveresult->flags & MOVERESULT_ONTOPOFOBSTACLE) && + trap_AAS_AreaReachability(bs->areanum)) + return; + } + else { + // enable any routing areas that were disabled + BotEnableActivateGoalAreas(&activategoal, qtrue); + } + } + // just some basic dynamic obstacle avoidance code + hordir[0] = moveresult->movedir[0]; + hordir[1] = moveresult->movedir[1]; + hordir[2] = 0; + // if no direction just take a random direction + if (VectorNormalize(hordir) < 0.1) { + VectorSet(angles, 0, 360 * random(), 0); + AngleVectors(angles, hordir, NULL, NULL); + } + // + //if (moveresult->flags & MOVERESULT_ONTOPOFOBSTACLE) movetype = MOVE_JUMP; + //else + movetype = MOVE_WALK; + // if there's an obstacle at the bot's feet and head then + // the bot might be able to crouch through + VectorCopy(bs->origin, start); + start[2] += 18; + VectorMA(start, 5, hordir, end); + VectorSet(mins, -16, -16, -24); + VectorSet(maxs, 16, 16, 4); + // + //bsptrace = AAS_Trace(start, mins, maxs, end, bs->entitynum, MASK_PLAYERSOLID); + //if (bsptrace.fraction >= 1) movetype = MOVE_CROUCH; + // get the sideward vector + CrossProduct(hordir, up, sideward); + // + if (bs->flags & BFL_AVOIDRIGHT) VectorNegate(sideward, sideward); + // try to crouch straight forward? + if (movetype != MOVE_CROUCH || !trap_BotMoveInDirection(bs->ms, hordir, 400, movetype)) { + // perform the movement + if (!trap_BotMoveInDirection(bs->ms, sideward, 400, movetype)) { + // flip the avoid direction flag + bs->flags ^= BFL_AVOIDRIGHT; + // flip the direction + // VectorNegate(sideward, sideward); + VectorMA(sideward, -1, hordir, sideward); + // move in the other direction + trap_BotMoveInDirection(bs->ms, sideward, 400, movetype); + } + } + // + if (bs->notblocked_time < FloatTime() - 0.4) { + // just reset goals and hope the bot will go into another direction? + // is this still needed?? + if (bs->ainode == AINode_Seek_NBG) bs->nbg_time = 0; + else if (bs->ainode == AINode_Seek_LTG) bs->ltg_time = 0; + } +} + +/* +================== +BotAIPredictObstacles + +Predict the route towards the goal and check if the bot +will be blocked by certain obstacles. When the bot has obstacles +on it's path the bot should figure out if they can be removed +by activating certain entities. +================== +*/ +int BotAIPredictObstacles(bot_state_t *bs, bot_goal_t *goal) { + int modelnum, entitynum, bspent; + bot_activategoal_t activategoal; + aas_predictroute_t route; + + if (!bot_predictobstacles.integer) + return qfalse; + + // always predict when the goal change or at regular intervals + if (bs->predictobstacles_goalareanum == goal->areanum && + bs->predictobstacles_time > FloatTime() - 6) { + return qfalse; + } + bs->predictobstacles_goalareanum = goal->areanum; + bs->predictobstacles_time = FloatTime(); + + // predict at most 100 areas or 10 seconds ahead + trap_AAS_PredictRoute(&route, bs->areanum, bs->origin, + goal->areanum, bs->tfl, 100, 1000, + RSE_USETRAVELTYPE|RSE_ENTERCONTENTS, + AREACONTENTS_MOVER, TFL_BRIDGE, 0); + // if bot has to travel through an area with a mover + if (route.stopevent & RSE_ENTERCONTENTS) { + // if the bot will run into a mover + if (route.endcontents & AREACONTENTS_MOVER) { + //NOTE: this only works with bspc 2.1 or higher + modelnum = (route.endcontents & AREACONTENTS_MODELNUM) >> AREACONTENTS_MODELNUMSHIFT; + if (modelnum) { + // + entitynum = BotModelMinsMaxs(modelnum, ET_MOVER, 0, NULL, NULL); + if (entitynum) { + //NOTE: BotGetActivateGoal already checks if the door is open or not + bspent = BotGetActivateGoal(bs, entitynum, &activategoal); + if (bspent) { + // + if (bs->activatestack && !bs->activatestack->inuse) + bs->activatestack = NULL; + // if not already trying to activate this entity + if (!BotIsGoingToActivateEntity(bs, activategoal.goal.entitynum)) { + // + //BotAI_Print(PRT_MESSAGE, "blocked by mover model %d, entity %d ?\n", modelnum, entitynum); + // + BotGoForActivateGoal(bs, &activategoal); + return qtrue; + } + else { + // enable any routing areas that were disabled + BotEnableActivateGoalAreas(&activategoal, qtrue); + } + } + } + } + } + } + else if (route.stopevent & RSE_USETRAVELTYPE) { + if (route.endtravelflags & TFL_BRIDGE) { + //FIXME: check if the bridge is available to travel over + } + } + return qfalse; +} + +/* +================== +BotCheckConsoleMessages +================== +*/ +void BotCheckConsoleMessages(bot_state_t *bs) { + char botname[MAX_NETNAME], message[MAX_MESSAGE_SIZE], netname[MAX_NETNAME], *ptr; + float chat_reply; + int context, handle; + bot_consolemessage_t m; + bot_match_t match; + + //the name of this bot + ClientName(bs->client, botname, sizeof(botname)); + // + while((handle = trap_BotNextConsoleMessage(bs->cs, &m)) != 0) { + //if the chat state is flooded with messages the bot will read them quickly + if (trap_BotNumConsoleMessages(bs->cs) < 10) { + //if it is a chat message the bot needs some time to read it + if (m.type == CMS_CHAT && m.time > FloatTime() - (1 + random())) break; + } + // + ptr = m.message; + //if it is a chat message then don't unify white spaces and don't + //replace synonyms in the netname + if (m.type == CMS_CHAT) { + // + if (trap_BotFindMatch(m.message, &match, MTCONTEXT_REPLYCHAT)) { + ptr = m.message + match.variables[MESSAGE].offset; + } + } + //unify the white spaces in the message + trap_UnifyWhiteSpaces(ptr); + //replace synonyms in the right context + context = BotSynonymContext(bs); + trap_BotReplaceSynonyms(ptr, context); + //if there's no match + if (!BotMatchMessage(bs, m.message)) { + //if it is a chat message + if (m.type == CMS_CHAT && !bot_nochat.integer) { + // + if (!trap_BotFindMatch(m.message, &match, MTCONTEXT_REPLYCHAT)) { + trap_BotRemoveConsoleMessage(bs->cs, handle); + continue; + } + //don't use eliza chats with team messages + if (match.subtype & ST_TEAM) { + trap_BotRemoveConsoleMessage(bs->cs, handle); + continue; + } + // + trap_BotMatchVariable(&match, NETNAME, netname, sizeof(netname)); + trap_BotMatchVariable(&match, MESSAGE, message, sizeof(message)); + //if this is a message from the bot self + if (bs->client == ClientFromName(netname)) { + trap_BotRemoveConsoleMessage(bs->cs, handle); + continue; + } + //unify the message + trap_UnifyWhiteSpaces(message); + // + trap_Cvar_Update(&bot_testrchat); + if (bot_testrchat.integer) { + // + trap_BotLibVarSet("bot_testrchat", "1"); + //if bot replies with a chat message + if (trap_BotReplyChat(bs->cs, message, context, CONTEXT_REPLY, + NULL, NULL, + NULL, NULL, + NULL, NULL, + botname, netname)) { + BotAI_Print(PRT_MESSAGE, "------------------------\n"); + } + else { + BotAI_Print(PRT_MESSAGE, "**** no valid reply ****\n"); + } + } + //if at a valid chat position and not chatting already and not in teamplay + else if (bs->ainode != AINode_Stand && BotValidChatPosition(bs) && !TeamPlayIsOn()) { + chat_reply = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_REPLY, 0, 1); + if (random() < 1.5 / (NumBots()+1) && random() < chat_reply) { + //if bot replies with a chat message + if (trap_BotReplyChat(bs->cs, message, context, CONTEXT_REPLY, + NULL, NULL, + NULL, NULL, + NULL, NULL, + botname, netname)) { + //remove the console message + trap_BotRemoveConsoleMessage(bs->cs, handle); + bs->stand_time = FloatTime() + BotChatTime(bs); + AIEnter_Stand(bs, "BotCheckConsoleMessages: reply chat"); + //EA_Say(bs->client, bs->cs.chatmessage); + break; + } + } + } + } + } + //remove the console message + trap_BotRemoveConsoleMessage(bs->cs, handle); + } +} + +/* +================== +BotCheckEvents +================== +*/ +void BotCheckForGrenades(bot_state_t *bs, entityState_t *state) { + // if this is not a grenade + if (state->eType != ET_MISSILE || state->weapon != WP_GRENADE_LAUNCHER) + return; + // try to avoid the grenade + trap_BotAddAvoidSpot(bs->ms, state->pos.trBase, 160, AVOID_ALWAYS); +} + +#ifdef MISSIONPACK +/* +================== +BotCheckForProxMines +================== +*/ +void BotCheckForProxMines(bot_state_t *bs, entityState_t *state) { + // if this is not a prox mine + if (state->eType != ET_MISSILE || state->weapon != WP_PROX_LAUNCHER) + return; + // if this prox mine is from someone on our own team + if (state->generic1 == BotTeam(bs)) + return; + // if the bot doesn't have a weapon to deactivate the mine + if (!(bs->inventory[INVENTORY_PLASMAGUN] > 0 && bs->inventory[INVENTORY_CELLS] > 0) && + !(bs->inventory[INVENTORY_ROCKETLAUNCHER] > 0 && bs->inventory[INVENTORY_ROCKETS] > 0) && + !(bs->inventory[INVENTORY_BFG10K] > 0 && bs->inventory[INVENTORY_BFGAMMO] > 0) ) { + return; + } + // try to avoid the prox mine + trap_BotAddAvoidSpot(bs->ms, state->pos.trBase, 160, AVOID_ALWAYS); + // + if (bs->numproxmines >= MAX_PROXMINES) + return; + bs->proxmines[bs->numproxmines] = state->number; + bs->numproxmines++; +} + +/* +================== +BotCheckForKamikazeBody +================== +*/ +void BotCheckForKamikazeBody(bot_state_t *bs, entityState_t *state) { + // if this entity is not wearing the kamikaze + if (!(state->eFlags & EF_KAMIKAZE)) + return; + // if this entity isn't dead + if (!(state->eFlags & EF_DEAD)) + return; + //remember this kamikaze body + bs->kamikazebody = state->number; +} +#endif + +/* +================== +BotCheckEvents +================== +*/ +void BotCheckEvents(bot_state_t *bs, entityState_t *state) { + int event; + char buf[128]; +#ifdef MISSIONPACK + aas_entityinfo_t entinfo; +#endif + + //NOTE: this sucks, we're accessing the gentity_t directly + //but there's no other fast way to do it right now + if (bs->entityeventTime[state->number] == g_entities[state->number].eventTime) { + return; + } + bs->entityeventTime[state->number] = g_entities[state->number].eventTime; + //if it's an event only entity + if (state->eType > ET_EVENTS) { + event = (state->eType - ET_EVENTS) & ~EV_EVENT_BITS; + } + else { + event = state->event & ~EV_EVENT_BITS; + } + // + switch(event) { + //client obituary event + case EV_OBITUARY: + { + int target, attacker, mod; + + target = state->otherEntityNum; + attacker = state->otherEntityNum2; + mod = state->eventParm; + // + if (target == bs->client) { + bs->botdeathtype = mod; + bs->lastkilledby = attacker; + // + if (target == attacker || + target == ENTITYNUM_NONE || + target == ENTITYNUM_WORLD) bs->botsuicide = qtrue; + else bs->botsuicide = qfalse; + // + bs->num_deaths++; + } + //else if this client was killed by the bot + else if (attacker == bs->client) { + bs->enemydeathtype = mod; + bs->lastkilledplayer = target; + bs->killedenemy_time = FloatTime(); + // + bs->num_kills++; + } + else if (attacker == bs->enemy && target == attacker) { + bs->enemysuicide = qtrue; + } + // +#ifdef MISSIONPACK + if (gametype == GT_1FCTF) { + // + BotEntityInfo(target, &entinfo); + if ( entinfo.powerups & ( 1 << PW_NEUTRALFLAG ) ) { + if (!BotSameTeam(bs, target)) { + bs->neutralflagstatus = 3; //enemy dropped the flag + bs->flagstatuschanged = qtrue; + } + } + } +#endif + break; + } + case EV_GLOBAL_SOUND: + { + if (state->eventParm < 0 || state->eventParm > MAX_SOUNDS) { + BotAI_Print(PRT_ERROR, "EV_GLOBAL_SOUND: eventParm (%d) out of range\n", state->eventParm); + break; + } + trap_GetConfigstring(CS_SOUNDS + state->eventParm, buf, sizeof(buf)); + /* + if (!strcmp(buf, "sound/teamplay/flagret_red.wav")) { + //red flag is returned + bs->redflagstatus = 0; + bs->flagstatuschanged = qtrue; + } + else if (!strcmp(buf, "sound/teamplay/flagret_blu.wav")) { + //blue flag is returned + bs->blueflagstatus = 0; + bs->flagstatuschanged = qtrue; + } + else*/ +#ifdef MISSIONPACK + if (!strcmp(buf, "sound/items/kamikazerespawn.wav" )) { + //the kamikaze respawned so dont avoid it + BotDontAvoid(bs, "Kamikaze"); + } + else +#endif + if (!strcmp(buf, "sound/items/poweruprespawn.wav")) { + //powerup respawned... go get it + BotGoForPowerups(bs); + } + break; + } + case EV_GLOBAL_TEAM_SOUND: + { + if (gametype == GT_CTF) { + switch(state->eventParm) { + case GTS_RED_CAPTURE: + bs->blueflagstatus = 0; + bs->redflagstatus = 0; + bs->flagstatuschanged = qtrue; + break; //see BotMatch_CTF + case GTS_BLUE_CAPTURE: + bs->blueflagstatus = 0; + bs->redflagstatus = 0; + bs->flagstatuschanged = qtrue; + break; //see BotMatch_CTF + case GTS_RED_RETURN: + //blue flag is returned + bs->blueflagstatus = 0; + bs->flagstatuschanged = qtrue; + break; + case GTS_BLUE_RETURN: + //red flag is returned + bs->redflagstatus = 0; + bs->flagstatuschanged = qtrue; + break; + case GTS_RED_TAKEN: + //blue flag is taken + bs->blueflagstatus = 1; + bs->flagstatuschanged = qtrue; + break; //see BotMatch_CTF + case GTS_BLUE_TAKEN: + //red flag is taken + bs->redflagstatus = 1; + bs->flagstatuschanged = qtrue; + break; //see BotMatch_CTF + } + } +#ifdef MISSIONPACK + else if (gametype == GT_1FCTF) { + switch(state->eventParm) { + case GTS_RED_CAPTURE: + bs->neutralflagstatus = 0; + bs->flagstatuschanged = qtrue; + break; + case GTS_BLUE_CAPTURE: + bs->neutralflagstatus = 0; + bs->flagstatuschanged = qtrue; + break; + case GTS_RED_RETURN: + //flag has returned + bs->neutralflagstatus = 0; + bs->flagstatuschanged = qtrue; + break; + case GTS_BLUE_RETURN: + //flag has returned + bs->neutralflagstatus = 0; + bs->flagstatuschanged = qtrue; + break; + case GTS_RED_TAKEN: + bs->neutralflagstatus = BotTeam(bs) == TEAM_RED ? 2 : 1; //FIXME: check Team_TakeFlagSound in g_team.c + bs->flagstatuschanged = qtrue; + break; + case GTS_BLUE_TAKEN: + bs->neutralflagstatus = BotTeam(bs) == TEAM_BLUE ? 2 : 1; //FIXME: check Team_TakeFlagSound in g_team.c + bs->flagstatuschanged = qtrue; + break; + } + } +#endif + break; + } + case EV_PLAYER_TELEPORT_IN: + { + VectorCopy(state->origin, lastteleport_origin); + lastteleport_time = FloatTime(); + break; + } + case EV_GENERAL_SOUND: + { + //if this sound is played on the bot + if (state->number == bs->client) { + if (state->eventParm < 0 || state->eventParm > MAX_SOUNDS) { + BotAI_Print(PRT_ERROR, "EV_GENERAL_SOUND: eventParm (%d) out of range\n", state->eventParm); + break; + } + //check out the sound + trap_GetConfigstring(CS_SOUNDS + state->eventParm, buf, sizeof(buf)); + //if falling into a death pit + if (!strcmp(buf, "*falling1.wav")) { + //if the bot has a personal teleporter + if (bs->inventory[INVENTORY_TELEPORTER] > 0) { + //use the holdable item + trap_EA_Use(bs->client); + } + } + } + break; + } + case EV_FOOTSTEP: + case EV_FOOTSTEP_METAL: + case EV_FOOTSPLASH: + case EV_FOOTWADE: + case EV_SWIM: + case EV_FALL_SHORT: + case EV_FALL_MEDIUM: + case EV_FALL_FAR: + case EV_STEP_4: + case EV_STEP_8: + case EV_STEP_12: + case EV_STEP_16: + case EV_JUMP_PAD: + case EV_JUMP: + case EV_TAUNT: + case EV_WATER_TOUCH: + case EV_WATER_LEAVE: + case EV_WATER_UNDER: + case EV_WATER_CLEAR: + case EV_ITEM_PICKUP: + case EV_GLOBAL_ITEM_PICKUP: + case EV_NOAMMO: + case EV_CHANGE_WEAPON: + case EV_FIRE_WEAPON: + //FIXME: either add to sound queue or mark player as someone making noise + break; + case EV_USE_ITEM0: + case EV_USE_ITEM1: + case EV_USE_ITEM2: + case EV_USE_ITEM3: + case EV_USE_ITEM4: + case EV_USE_ITEM5: + case EV_USE_ITEM6: + case EV_USE_ITEM7: + case EV_USE_ITEM8: + case EV_USE_ITEM9: + case EV_USE_ITEM10: + case EV_USE_ITEM11: + case EV_USE_ITEM12: + case EV_USE_ITEM13: + case EV_USE_ITEM14: + break; + } +} + +/* +================== +BotCheckSnapshot +================== +*/ +void BotCheckSnapshot(bot_state_t *bs) { + int ent; + entityState_t state; + + //remove all avoid spots + trap_BotAddAvoidSpot(bs->ms, vec3_origin, 0, AVOID_CLEAR); + //reset kamikaze body + bs->kamikazebody = 0; + //reset number of proxmines + bs->numproxmines = 0; + // + ent = 0; + while( ( ent = BotAI_GetSnapshotEntity( bs->client, ent, &state ) ) != -1 ) { + //check the entity state for events + BotCheckEvents(bs, &state); + //check for grenades the bot should avoid + BotCheckForGrenades(bs, &state); + // +#ifdef MISSIONPACK + //check for proximity mines which the bot should deactivate + BotCheckForProxMines(bs, &state); + //check for dead bodies with the kamikaze effect which should be gibbed + BotCheckForKamikazeBody(bs, &state); +#endif + } + //check the player state for events + BotAI_GetEntityState(bs->client, &state); + //copy the player state events to the entity state + state.event = bs->cur_ps.externalEvent; + state.eventParm = bs->cur_ps.externalEventParm; + // + BotCheckEvents(bs, &state); +} + +/* +================== +BotCheckAir +================== +*/ +void BotCheckAir(bot_state_t *bs) { + if (bs->inventory[INVENTORY_ENVIRONMENTSUIT] <= 0) { + if (trap_AAS_PointContents(bs->eye) & (CONTENTS_WATER|CONTENTS_SLIME|CONTENTS_LAVA)) { + return; + } + } + bs->lastair_time = FloatTime(); +} + +/* +================== +BotAlternateRoute +================== +*/ +bot_goal_t *BotAlternateRoute(bot_state_t *bs, bot_goal_t *goal) { + int t; + + // if the bot has an alternative route goal + if (bs->altroutegoal.areanum) { + // + if (bs->reachedaltroutegoal_time) + return goal; + // travel time towards alternative route goal + t = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, bs->altroutegoal.areanum, bs->tfl); + if (t && t < 20) { + //BotAI_Print(PRT_MESSAGE, "reached alternate route goal\n"); + bs->reachedaltroutegoal_time = FloatTime(); + } + memcpy(goal, &bs->altroutegoal, sizeof(bot_goal_t)); + return &bs->altroutegoal; + } + return goal; +} + +/* +================== +BotGetAlternateRouteGoal +================== +*/ +int BotGetAlternateRouteGoal(bot_state_t *bs, int base) { + aas_altroutegoal_t *altroutegoals; + bot_goal_t *goal; + int numaltroutegoals, rnd; + + if (base == TEAM_RED) { + altroutegoals = red_altroutegoals; + numaltroutegoals = red_numaltroutegoals; + } + else { + altroutegoals = blue_altroutegoals; + numaltroutegoals = blue_numaltroutegoals; + } + if (!numaltroutegoals) + return qfalse; + rnd = (float) random() * numaltroutegoals; + if (rnd >= numaltroutegoals) + rnd = numaltroutegoals-1; + goal = &bs->altroutegoal; + goal->areanum = altroutegoals[rnd].areanum; + VectorCopy(altroutegoals[rnd].origin, goal->origin); + VectorSet(goal->mins, -8, -8, -8); + VectorSet(goal->maxs, 8, 8, 8); + goal->entitynum = 0; + goal->iteminfo = 0; + goal->number = 0; + goal->flags = 0; + // + bs->reachedaltroutegoal_time = 0; + return qtrue; +} + +/* +================== +BotSetupAlternateRouteGoals +================== +*/ +void BotSetupAlternativeRouteGoals(void) { + + if (altroutegoals_setup) + return; +#ifdef MISSIONPACK + if (gametype == GT_CTF) { + if (trap_BotGetLevelItemGoal(-1, "Neutral Flag", &ctf_neutralflag) < 0) + BotAI_Print(PRT_WARNING, "no alt routes without Neutral Flag\n"); + if (ctf_neutralflag.areanum) { + // + red_numaltroutegoals = trap_AAS_AlternativeRouteGoals( + ctf_neutralflag.origin, ctf_neutralflag.areanum, + ctf_redflag.origin, ctf_redflag.areanum, TFL_DEFAULT, + red_altroutegoals, MAX_ALTROUTEGOALS, + ALTROUTEGOAL_CLUSTERPORTALS| + ALTROUTEGOAL_VIEWPORTALS); + blue_numaltroutegoals = trap_AAS_AlternativeRouteGoals( + ctf_neutralflag.origin, ctf_neutralflag.areanum, + ctf_blueflag.origin, ctf_blueflag.areanum, TFL_DEFAULT, + blue_altroutegoals, MAX_ALTROUTEGOALS, + ALTROUTEGOAL_CLUSTERPORTALS| + ALTROUTEGOAL_VIEWPORTALS); + } + } + else if (gametype == GT_1FCTF) { + // + red_numaltroutegoals = trap_AAS_AlternativeRouteGoals( + ctf_neutralflag.origin, ctf_neutralflag.areanum, + ctf_redflag.origin, ctf_redflag.areanum, TFL_DEFAULT, + red_altroutegoals, MAX_ALTROUTEGOALS, + ALTROUTEGOAL_CLUSTERPORTALS| + ALTROUTEGOAL_VIEWPORTALS); + blue_numaltroutegoals = trap_AAS_AlternativeRouteGoals( + ctf_neutralflag.origin, ctf_neutralflag.areanum, + ctf_blueflag.origin, ctf_blueflag.areanum, TFL_DEFAULT, + blue_altroutegoals, MAX_ALTROUTEGOALS, + ALTROUTEGOAL_CLUSTERPORTALS| + ALTROUTEGOAL_VIEWPORTALS); + } + else if (gametype == GT_OBELISK) { + if (trap_BotGetLevelItemGoal(-1, "Neutral Obelisk", &neutralobelisk) < 0) + BotAI_Print(PRT_WARNING, "Harvester without neutral obelisk\n"); + // + red_numaltroutegoals = trap_AAS_AlternativeRouteGoals( + neutralobelisk.origin, neutralobelisk.areanum, + redobelisk.origin, redobelisk.areanum, TFL_DEFAULT, + red_altroutegoals, MAX_ALTROUTEGOALS, + ALTROUTEGOAL_CLUSTERPORTALS| + ALTROUTEGOAL_VIEWPORTALS); + blue_numaltroutegoals = trap_AAS_AlternativeRouteGoals( + neutralobelisk.origin, neutralobelisk.areanum, + blueobelisk.origin, blueobelisk.areanum, TFL_DEFAULT, + blue_altroutegoals, MAX_ALTROUTEGOALS, + ALTROUTEGOAL_CLUSTERPORTALS| + ALTROUTEGOAL_VIEWPORTALS); + } + else if (gametype == GT_HARVESTER) { + // + red_numaltroutegoals = trap_AAS_AlternativeRouteGoals( + neutralobelisk.origin, neutralobelisk.areanum, + redobelisk.origin, redobelisk.areanum, TFL_DEFAULT, + red_altroutegoals, MAX_ALTROUTEGOALS, + ALTROUTEGOAL_CLUSTERPORTALS| + ALTROUTEGOAL_VIEWPORTALS); + blue_numaltroutegoals = trap_AAS_AlternativeRouteGoals( + neutralobelisk.origin, neutralobelisk.areanum, + blueobelisk.origin, blueobelisk.areanum, TFL_DEFAULT, + blue_altroutegoals, MAX_ALTROUTEGOALS, + ALTROUTEGOAL_CLUSTERPORTALS| + ALTROUTEGOAL_VIEWPORTALS); + } +#endif + altroutegoals_setup = qtrue; +} + +/* +================== +BotDeathmatchAI +================== +*/ +void BotDeathmatchAI(bot_state_t *bs, float thinktime) { + char gender[144], name[144], buf[144]; + char userinfo[MAX_INFO_STRING]; + int i; + + //if the bot has just been setup + if (bs->setupcount > 0) { + bs->setupcount--; + if (bs->setupcount > 0) return; + //get the gender characteristic + trap_Characteristic_String(bs->character, CHARACTERISTIC_GENDER, gender, sizeof(gender)); + //set the bot gender + trap_GetUserinfo(bs->client, userinfo, sizeof(userinfo)); + Info_SetValueForKey(userinfo, "sex", gender); + trap_SetUserinfo(bs->client, userinfo); + //set the team + if ( !bs->map_restart && g_gametype.integer != GT_TOURNAMENT ) { + Com_sprintf(buf, sizeof(buf), "team %s", bs->settings.team); + trap_EA_Command(bs->client, buf); + } + //set the chat gender + if (gender[0] == 'm') trap_BotSetChatGender(bs->cs, CHAT_GENDERMALE); + else if (gender[0] == 'f') trap_BotSetChatGender(bs->cs, CHAT_GENDERFEMALE); + else trap_BotSetChatGender(bs->cs, CHAT_GENDERLESS); + //set the chat name + ClientName(bs->client, name, sizeof(name)); + trap_BotSetChatName(bs->cs, name, bs->client); + // + bs->lastframe_health = bs->inventory[INVENTORY_HEALTH]; + bs->lasthitcount = bs->cur_ps.persistant[PERS_HITS]; + // + bs->setupcount = 0; + // + BotSetupAlternativeRouteGoals(); + } + //no ideal view set + bs->flags &= ~BFL_IDEALVIEWSET; + // + if (!BotIntermission(bs)) { + //set the teleport time + BotSetTeleportTime(bs); + //update some inventory values + BotUpdateInventory(bs); + //check out the snapshot + BotCheckSnapshot(bs); + //check for air + BotCheckAir(bs); + } + //check the console messages + BotCheckConsoleMessages(bs); + //if not in the intermission and not in observer mode + if (!BotIntermission(bs) && !BotIsObserver(bs)) { + //do team AI + BotTeamAI(bs); + } + //if the bot has no ai node + if (!bs->ainode) { + AIEnter_Seek_LTG(bs, "BotDeathmatchAI: no ai node"); + } + //if the bot entered the game less than 8 seconds ago + if (!bs->entergamechat && bs->entergame_time > FloatTime() - 8) { + if (BotChat_EnterGame(bs)) { + bs->stand_time = FloatTime() + BotChatTime(bs); + AIEnter_Stand(bs, "BotDeathmatchAI: chat enter game"); + } + bs->entergamechat = qtrue; + } + //reset the node switches from the previous frame + BotResetNodeSwitches(); + //execute AI nodes + for (i = 0; i < MAX_NODESWITCHES; i++) { + if (bs->ainode(bs)) break; + } + //if the bot removed itself :) + if (!bs->inuse) return; + //if the bot executed too many AI nodes + if (i >= MAX_NODESWITCHES) { + trap_BotDumpGoalStack(bs->gs); + trap_BotDumpAvoidGoals(bs->gs); + BotDumpNodeSwitches(bs); + ClientName(bs->client, name, sizeof(name)); + BotAI_Print(PRT_ERROR, "%s at %1.1f switched more than %d AI nodes\n", name, FloatTime(), MAX_NODESWITCHES); + } + // + bs->lastframe_health = bs->inventory[INVENTORY_HEALTH]; + bs->lasthitcount = bs->cur_ps.persistant[PERS_HITS]; +} + +/* +================== +BotSetEntityNumForGoalWithModel +================== +*/ +void BotSetEntityNumForGoalWithModel(bot_goal_t *goal, int eType, char *modelname) { + gentity_t *ent; + int i, modelindex; + vec3_t dir; + + modelindex = G_ModelIndex( modelname ); + ent = &g_entities[0]; + for (i = 0; i < level.num_entities; i++, ent++) { + if ( !ent->inuse ) { + continue; + } + if ( eType && ent->s.eType != eType) { + continue; + } + if (ent->s.modelindex != modelindex) { + continue; + } + VectorSubtract(goal->origin, ent->s.origin, dir); + if (VectorLengthSquared(dir) < Square(10)) { + goal->entitynum = i; + return; + } + } +} + +/* +================== +BotSetEntityNumForGoal +================== +*/ +void BotSetEntityNumForGoal(bot_goal_t *goal, char *classname) { + gentity_t *ent; + int i; + vec3_t dir; + + ent = &g_entities[0]; + for (i = 0; i < level.num_entities; i++, ent++) { + if ( !ent->inuse ) { + continue; + } + if ( !Q_stricmp(ent->classname, classname) ) { + continue; + } + VectorSubtract(goal->origin, ent->s.origin, dir); + if (VectorLengthSquared(dir) < Square(10)) { + goal->entitynum = i; + return; + } + } +} + +/* +================== +BotGoalForBSPEntity +================== +*/ +int BotGoalForBSPEntity( char *classname, bot_goal_t *goal ) { + char value[MAX_INFO_STRING]; + vec3_t origin, start, end; + int ent, numareas, areas[10]; + + memset(goal, 0, sizeof(bot_goal_t)); + for (ent = trap_AAS_NextBSPEntity(0); ent; ent = trap_AAS_NextBSPEntity(ent)) { + if (!trap_AAS_ValueForBSPEpairKey(ent, "classname", value, sizeof(value))) + continue; + if (!strcmp(value, classname)) { + if (!trap_AAS_VectorForBSPEpairKey(ent, "origin", origin)) + return qfalse; + VectorCopy(origin, goal->origin); + VectorCopy(origin, start); + start[2] -= 32; + VectorCopy(origin, end); + end[2] += 32; + numareas = trap_AAS_TraceAreas(start, end, areas, NULL, 10); + if (!numareas) + return qfalse; + goal->areanum = areas[0]; + return qtrue; + } + } + return qfalse; +} + +/* +================== +BotSetupDeathmatchAI +================== +*/ +void BotSetupDeathmatchAI(void) { + int ent, modelnum; + char model[128]; + + gametype = trap_Cvar_VariableIntegerValue("g_gametype"); + maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); + + trap_Cvar_Register(&bot_rocketjump, "bot_rocketjump", "1", 0); + trap_Cvar_Register(&bot_grapple, "bot_grapple", "0", 0); + trap_Cvar_Register(&bot_fastchat, "bot_fastchat", "0", 0); + trap_Cvar_Register(&bot_nochat, "bot_nochat", "0", 0); + trap_Cvar_Register(&bot_testrchat, "bot_testrchat", "0", 0); + trap_Cvar_Register(&bot_challenge, "bot_challenge", "0", 0); + trap_Cvar_Register(&bot_predictobstacles, "bot_predictobstacles", "1", 0); + trap_Cvar_Register(&g_spSkill, "g_spSkill", "2", 0); + // + if (gametype == GT_CTF) { + if (trap_BotGetLevelItemGoal(-1, "Red Flag", &ctf_redflag) < 0) + BotAI_Print(PRT_WARNING, "CTF without Red Flag\n"); + if (trap_BotGetLevelItemGoal(-1, "Blue Flag", &ctf_blueflag) < 0) + BotAI_Print(PRT_WARNING, "CTF without Blue Flag\n"); + } +#ifdef MISSIONPACK + else if (gametype == GT_1FCTF) { + if (trap_BotGetLevelItemGoal(-1, "Neutral Flag", &ctf_neutralflag) < 0) + BotAI_Print(PRT_WARNING, "One Flag CTF without Neutral Flag\n"); + if (trap_BotGetLevelItemGoal(-1, "Red Flag", &ctf_redflag) < 0) + BotAI_Print(PRT_WARNING, "CTF without Red Flag\n"); + if (trap_BotGetLevelItemGoal(-1, "Blue Flag", &ctf_blueflag) < 0) + BotAI_Print(PRT_WARNING, "CTF without Blue Flag\n"); + } + else if (gametype == GT_OBELISK) { + if (trap_BotGetLevelItemGoal(-1, "Red Obelisk", &redobelisk) < 0) + BotAI_Print(PRT_WARNING, "Obelisk without red obelisk\n"); + BotSetEntityNumForGoal(&redobelisk, "team_redobelisk"); + if (trap_BotGetLevelItemGoal(-1, "Blue Obelisk", &blueobelisk) < 0) + BotAI_Print(PRT_WARNING, "Obelisk without blue obelisk\n"); + BotSetEntityNumForGoal(&blueobelisk, "team_blueobelisk"); + } + else if (gametype == GT_HARVESTER) { + if (trap_BotGetLevelItemGoal(-1, "Red Obelisk", &redobelisk) < 0) + BotAI_Print(PRT_WARNING, "Harvester without red obelisk\n"); + BotSetEntityNumForGoal(&redobelisk, "team_redobelisk"); + if (trap_BotGetLevelItemGoal(-1, "Blue Obelisk", &blueobelisk) < 0) + BotAI_Print(PRT_WARNING, "Harvester without blue obelisk\n"); + BotSetEntityNumForGoal(&blueobelisk, "team_blueobelisk"); + if (trap_BotGetLevelItemGoal(-1, "Neutral Obelisk", &neutralobelisk) < 0) + BotAI_Print(PRT_WARNING, "Harvester without neutral obelisk\n"); + BotSetEntityNumForGoal(&neutralobelisk, "team_neutralobelisk"); + } +#endif + + max_bspmodelindex = 0; + for (ent = trap_AAS_NextBSPEntity(0); ent; ent = trap_AAS_NextBSPEntity(ent)) { + if (!trap_AAS_ValueForBSPEpairKey(ent, "model", model, sizeof(model))) continue; + if (model[0] == '*') { + modelnum = atoi(model+1); + if (modelnum > max_bspmodelindex) + max_bspmodelindex = modelnum; + } + } + //initialize the waypoint heap + BotInitWaypoints(); +} + +/* +================== +BotShutdownDeathmatchAI +================== +*/ +void BotShutdownDeathmatchAI(void) { + altroutegoals_setup = qfalse; +} diff --git a/code/game/ai_dmq3.h b/code/game/ai_dmq3.h index b7ab19d..a39a350 100755 --- a/code/game/ai_dmq3.h +++ b/code/game/ai_dmq3.h @@ -1,206 +1,206 @@ -/* -=========================================================================== -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 Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// - -/***************************************************************************** - * name: ai_dmq3.h - * - * desc: Quake3 bot AI - * - * $Archive: /source/code/botai/ai_chat.c $ - * - *****************************************************************************/ - -//setup the deathmatch AI -void BotSetupDeathmatchAI(void); -//shutdown the deathmatch AI -void BotShutdownDeathmatchAI(void); -//let the bot live within it's deathmatch AI net -void BotDeathmatchAI(bot_state_t *bs, float thinktime); -//free waypoints -void BotFreeWaypoints(bot_waypoint_t *wp); -//choose a weapon -void BotChooseWeapon(bot_state_t *bs); -//setup movement stuff -void BotSetupForMovement(bot_state_t *bs); -//update the inventory -void BotUpdateInventory(bot_state_t *bs); -//update the inventory during battle -void BotUpdateBattleInventory(bot_state_t *bs, int enemy); -//use holdable items during battle -void BotBattleUseItems(bot_state_t *bs); -//return true if the bot is dead -qboolean BotIsDead(bot_state_t *bs); -//returns true if the bot is in observer mode -qboolean BotIsObserver(bot_state_t *bs); -//returns true if the bot is in the intermission -qboolean BotIntermission(bot_state_t *bs); -//returns true if the bot is in lava or slime -qboolean BotInLavaOrSlime(bot_state_t *bs); -//returns true if the entity is dead -qboolean EntityIsDead(aas_entityinfo_t *entinfo); -//returns true if the entity is invisible -qboolean EntityIsInvisible(aas_entityinfo_t *entinfo); -//returns true if the entity is shooting -qboolean EntityIsShooting(aas_entityinfo_t *entinfo); -#ifdef MISSIONPACK -//returns true if this entity has the kamikaze -qboolean EntityHasKamikaze(aas_entityinfo_t *entinfo); -#endif -// set a user info key/value pair -void BotSetUserInfo(bot_state_t *bs, char *key, char *value); -// set the team status (offense, defense etc.) -void BotSetTeamStatus(bot_state_t *bs); -//returns the name of the client -char *ClientName(int client, char *name, int size); -//returns an simplyfied client name -char *EasyClientName(int client, char *name, int size); -//returns the skin used by the client -char *ClientSkin(int client, char *skin, int size); -// returns the appropriate synonym context for the current game type and situation -int BotSynonymContext(bot_state_t *bs); -// set last ordered task -int BotSetLastOrderedTask(bot_state_t *bs); -// selection of goals for teamplay -void BotTeamGoals(bot_state_t *bs, int retreat); -//returns the aggression of the bot in the range [0, 100] -float BotAggression(bot_state_t *bs); -//returns how bad the bot feels -float BotFeelingBad(bot_state_t *bs); -//returns true if the bot wants to retreat -int BotWantsToRetreat(bot_state_t *bs); -//returns true if the bot wants to chase -int BotWantsToChase(bot_state_t *bs); -//returns true if the bot wants to help -int BotWantsToHelp(bot_state_t *bs); -//returns true if the bot can and wants to rocketjump -int BotCanAndWantsToRocketJump(bot_state_t *bs); -// returns true if the bot has a persistant powerup and a weapon -int BotHasPersistantPowerupAndWeapon(bot_state_t *bs); -//returns true if the bot wants to and goes camping -int BotWantsToCamp(bot_state_t *bs); -//the bot will perform attack movements -bot_moveresult_t BotAttackMove(bot_state_t *bs, int tfl); -//returns true if the bot and the entity are in the same team -int BotSameTeam(bot_state_t *bs, int entnum); -//returns true if teamplay is on -int TeamPlayIsOn(void); -// returns the client number of the team mate flag carrier (-1 if none) -int BotTeamFlagCarrier(bot_state_t *bs); -//returns visible team mate flag carrier if available -int BotTeamFlagCarrierVisible(bot_state_t *bs); -//returns visible enemy flag carrier if available -int BotEnemyFlagCarrierVisible(bot_state_t *bs); -//get the number of visible teammates and enemies -void BotVisibleTeamMatesAndEnemies(bot_state_t *bs, int *teammates, int *enemies, float range); -//returns true if within the field of vision for the given angles -qboolean InFieldOfVision(vec3_t viewangles, float fov, vec3_t angles); -//returns true and sets the .enemy field when an enemy is found -int BotFindEnemy(bot_state_t *bs, int curenemy); -//returns a roam goal -void BotRoamGoal(bot_state_t *bs, vec3_t goal); -//returns entity visibility in the range [0, 1] -float BotEntityVisible(int viewer, vec3_t eye, vec3_t viewangles, float fov, int ent); -//the bot will aim at the current enemy -void BotAimAtEnemy(bot_state_t *bs); -//check if the bot should attack -void BotCheckAttack(bot_state_t *bs); -//AI when the bot is blocked -void BotAIBlocked(bot_state_t *bs, bot_moveresult_t *moveresult, int activate); -//AI to predict obstacles -int BotAIPredictObstacles(bot_state_t *bs, bot_goal_t *goal); -//enable or disable the areas the blocking entity is in -void BotEnableActivateGoalAreas(bot_activategoal_t *activategoal, int enable); -//pop an activate goal from the stack -int BotPopFromActivateGoalStack(bot_state_t *bs); -//clear the activate goal stack -void BotClearActivateGoalStack(bot_state_t *bs); -//returns the team the bot is in -int BotTeam(bot_state_t *bs); -//retuns the opposite team of the bot -int BotOppositeTeam(bot_state_t *bs); -//returns the flag the bot is carrying (CTFFLAG_?) -int BotCTFCarryingFlag(bot_state_t *bs); -//remember the last ordered task -void BotRememberLastOrderedTask(bot_state_t *bs); -//set ctf goals (defend base, get enemy flag) during seek -void BotCTFSeekGoals(bot_state_t *bs); -//set ctf goals (defend base, get enemy flag) during retreat -void BotCTFRetreatGoals(bot_state_t *bs); -// -#ifdef MISSIONPACK -int Bot1FCTFCarryingFlag(bot_state_t *bs); -int BotHarvesterCarryingCubes(bot_state_t *bs); -void Bot1FCTFSeekGoals(bot_state_t *bs); -void Bot1FCTFRetreatGoals(bot_state_t *bs); -void BotObeliskSeekGoals(bot_state_t *bs); -void BotObeliskRetreatGoals(bot_state_t *bs); -void BotGoHarvest(bot_state_t *bs); -void BotHarvesterSeekGoals(bot_state_t *bs); -void BotHarvesterRetreatGoals(bot_state_t *bs); -int BotTeamCubeCarrierVisible(bot_state_t *bs); -int BotEnemyCubeCarrierVisible(bot_state_t *bs); -#endif -//get a random alternate route goal towards the given base -int BotGetAlternateRouteGoal(bot_state_t *bs, int base); -//returns either the alternate route goal or the given goal -bot_goal_t *BotAlternateRoute(bot_state_t *bs, bot_goal_t *goal); -//create a new waypoint -bot_waypoint_t *BotCreateWayPoint(char *name, vec3_t origin, int areanum); -//find a waypoint with the given name -bot_waypoint_t *BotFindWayPoint(bot_waypoint_t *waypoints, char *name); -//strstr but case insensitive -char *stristr(char *str, char *charset); -//returns the number of the client with the given name -int ClientFromName(char *name); -int ClientOnSameTeamFromName(bot_state_t *bs, char *name); -// -int BotPointAreaNum(vec3_t origin); -// -void BotMapScripts(bot_state_t *bs); - -//ctf flags -#define CTF_FLAG_NONE 0 -#define CTF_FLAG_RED 1 -#define CTF_FLAG_BLUE 2 -//CTF skins -#define CTF_SKIN_REDTEAM "red" -#define CTF_SKIN_BLUETEAM "blue" - -extern int gametype; //game type -extern int maxclients; //maximum number of clients - -extern vmCvar_t bot_grapple; -extern vmCvar_t bot_rocketjump; -extern vmCvar_t bot_fastchat; -extern vmCvar_t bot_nochat; -extern vmCvar_t bot_testrchat; -extern vmCvar_t bot_challenge; - -extern bot_goal_t ctf_redflag; -extern bot_goal_t ctf_blueflag; -#ifdef MISSIONPACK -extern bot_goal_t ctf_neutralflag; -extern bot_goal_t redobelisk; -extern bot_goal_t blueobelisk; -extern bot_goal_t neutralobelisk; -#endif +/* +=========================================================================== +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 Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// + +/***************************************************************************** + * name: ai_dmq3.h + * + * desc: Quake3 bot AI + * + * $Archive: /source/code/botai/ai_chat.c $ + * + *****************************************************************************/ + +//setup the deathmatch AI +void BotSetupDeathmatchAI(void); +//shutdown the deathmatch AI +void BotShutdownDeathmatchAI(void); +//let the bot live within it's deathmatch AI net +void BotDeathmatchAI(bot_state_t *bs, float thinktime); +//free waypoints +void BotFreeWaypoints(bot_waypoint_t *wp); +//choose a weapon +void BotChooseWeapon(bot_state_t *bs); +//setup movement stuff +void BotSetupForMovement(bot_state_t *bs); +//update the inventory +void BotUpdateInventory(bot_state_t *bs); +//update the inventory during battle +void BotUpdateBattleInventory(bot_state_t *bs, int enemy); +//use holdable items during battle +void BotBattleUseItems(bot_state_t *bs); +//return true if the bot is dead +qboolean BotIsDead(bot_state_t *bs); +//returns true if the bot is in observer mode +qboolean BotIsObserver(bot_state_t *bs); +//returns true if the bot is in the intermission +qboolean BotIntermission(bot_state_t *bs); +//returns true if the bot is in lava or slime +qboolean BotInLavaOrSlime(bot_state_t *bs); +//returns true if the entity is dead +qboolean EntityIsDead(aas_entityinfo_t *entinfo); +//returns true if the entity is invisible +qboolean EntityIsInvisible(aas_entityinfo_t *entinfo); +//returns true if the entity is shooting +qboolean EntityIsShooting(aas_entityinfo_t *entinfo); +#ifdef MISSIONPACK +//returns true if this entity has the kamikaze +qboolean EntityHasKamikaze(aas_entityinfo_t *entinfo); +#endif +// set a user info key/value pair +void BotSetUserInfo(bot_state_t *bs, char *key, char *value); +// set the team status (offense, defense etc.) +void BotSetTeamStatus(bot_state_t *bs); +//returns the name of the client +char *ClientName(int client, char *name, int size); +//returns an simplyfied client name +char *EasyClientName(int client, char *name, int size); +//returns the skin used by the client +char *ClientSkin(int client, char *skin, int size); +// returns the appropriate synonym context for the current game type and situation +int BotSynonymContext(bot_state_t *bs); +// set last ordered task +int BotSetLastOrderedTask(bot_state_t *bs); +// selection of goals for teamplay +void BotTeamGoals(bot_state_t *bs, int retreat); +//returns the aggression of the bot in the range [0, 100] +float BotAggression(bot_state_t *bs); +//returns how bad the bot feels +float BotFeelingBad(bot_state_t *bs); +//returns true if the bot wants to retreat +int BotWantsToRetreat(bot_state_t *bs); +//returns true if the bot wants to chase +int BotWantsToChase(bot_state_t *bs); +//returns true if the bot wants to help +int BotWantsToHelp(bot_state_t *bs); +//returns true if the bot can and wants to rocketjump +int BotCanAndWantsToRocketJump(bot_state_t *bs); +// returns true if the bot has a persistant powerup and a weapon +int BotHasPersistantPowerupAndWeapon(bot_state_t *bs); +//returns true if the bot wants to and goes camping +int BotWantsToCamp(bot_state_t *bs); +//the bot will perform attack movements +bot_moveresult_t BotAttackMove(bot_state_t *bs, int tfl); +//returns true if the bot and the entity are in the same team +int BotSameTeam(bot_state_t *bs, int entnum); +//returns true if teamplay is on +int TeamPlayIsOn(void); +// returns the client number of the team mate flag carrier (-1 if none) +int BotTeamFlagCarrier(bot_state_t *bs); +//returns visible team mate flag carrier if available +int BotTeamFlagCarrierVisible(bot_state_t *bs); +//returns visible enemy flag carrier if available +int BotEnemyFlagCarrierVisible(bot_state_t *bs); +//get the number of visible teammates and enemies +void BotVisibleTeamMatesAndEnemies(bot_state_t *bs, int *teammates, int *enemies, float range); +//returns true if within the field of vision for the given angles +qboolean InFieldOfVision(vec3_t viewangles, float fov, vec3_t angles); +//returns true and sets the .enemy field when an enemy is found +int BotFindEnemy(bot_state_t *bs, int curenemy); +//returns a roam goal +void BotRoamGoal(bot_state_t *bs, vec3_t goal); +//returns entity visibility in the range [0, 1] +float BotEntityVisible(int viewer, vec3_t eye, vec3_t viewangles, float fov, int ent); +//the bot will aim at the current enemy +void BotAimAtEnemy(bot_state_t *bs); +//check if the bot should attack +void BotCheckAttack(bot_state_t *bs); +//AI when the bot is blocked +void BotAIBlocked(bot_state_t *bs, bot_moveresult_t *moveresult, int activate); +//AI to predict obstacles +int BotAIPredictObstacles(bot_state_t *bs, bot_goal_t *goal); +//enable or disable the areas the blocking entity is in +void BotEnableActivateGoalAreas(bot_activategoal_t *activategoal, int enable); +//pop an activate goal from the stack +int BotPopFromActivateGoalStack(bot_state_t *bs); +//clear the activate goal stack +void BotClearActivateGoalStack(bot_state_t *bs); +//returns the team the bot is in +int BotTeam(bot_state_t *bs); +//retuns the opposite team of the bot +int BotOppositeTeam(bot_state_t *bs); +//returns the flag the bot is carrying (CTFFLAG_?) +int BotCTFCarryingFlag(bot_state_t *bs); +//remember the last ordered task +void BotRememberLastOrderedTask(bot_state_t *bs); +//set ctf goals (defend base, get enemy flag) during seek +void BotCTFSeekGoals(bot_state_t *bs); +//set ctf goals (defend base, get enemy flag) during retreat +void BotCTFRetreatGoals(bot_state_t *bs); +// +#ifdef MISSIONPACK +int Bot1FCTFCarryingFlag(bot_state_t *bs); +int BotHarvesterCarryingCubes(bot_state_t *bs); +void Bot1FCTFSeekGoals(bot_state_t *bs); +void Bot1FCTFRetreatGoals(bot_state_t *bs); +void BotObeliskSeekGoals(bot_state_t *bs); +void BotObeliskRetreatGoals(bot_state_t *bs); +void BotGoHarvest(bot_state_t *bs); +void BotHarvesterSeekGoals(bot_state_t *bs); +void BotHarvesterRetreatGoals(bot_state_t *bs); +int BotTeamCubeCarrierVisible(bot_state_t *bs); +int BotEnemyCubeCarrierVisible(bot_state_t *bs); +#endif +//get a random alternate route goal towards the given base +int BotGetAlternateRouteGoal(bot_state_t *bs, int base); +//returns either the alternate route goal or the given goal +bot_goal_t *BotAlternateRoute(bot_state_t *bs, bot_goal_t *goal); +//create a new waypoint +bot_waypoint_t *BotCreateWayPoint(char *name, vec3_t origin, int areanum); +//find a waypoint with the given name +bot_waypoint_t *BotFindWayPoint(bot_waypoint_t *waypoints, char *name); +//strstr but case insensitive +char *stristr(char *str, char *charset); +//returns the number of the client with the given name +int ClientFromName(char *name); +int ClientOnSameTeamFromName(bot_state_t *bs, char *name); +// +int BotPointAreaNum(vec3_t origin); +// +void BotMapScripts(bot_state_t *bs); + +//ctf flags +#define CTF_FLAG_NONE 0 +#define CTF_FLAG_RED 1 +#define CTF_FLAG_BLUE 2 +//CTF skins +#define CTF_SKIN_REDTEAM "red" +#define CTF_SKIN_BLUETEAM "blue" + +extern int gametype; //game type +extern int maxclients; //maximum number of clients + +extern vmCvar_t bot_grapple; +extern vmCvar_t bot_rocketjump; +extern vmCvar_t bot_fastchat; +extern vmCvar_t bot_nochat; +extern vmCvar_t bot_testrchat; +extern vmCvar_t bot_challenge; + +extern bot_goal_t ctf_redflag; +extern bot_goal_t ctf_blueflag; +#ifdef MISSIONPACK +extern bot_goal_t ctf_neutralflag; +extern bot_goal_t redobelisk; +extern bot_goal_t blueobelisk; +extern bot_goal_t neutralobelisk; +#endif diff --git a/code/game/ai_main.c b/code/game/ai_main.c index d13696a..cd6dbc9 100755 --- a/code/game/ai_main.c +++ b/code/game/ai_main.c @@ -1,1695 +1,1695 @@ -/* -=========================================================================== -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 Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// - -/***************************************************************************** - * name: ai_main.c - * - * desc: Quake3 bot AI - * - * $Archive: /MissionPack/code/game/ai_main.c $ - * - *****************************************************************************/ - - -#include "g_local.h" -#include "q_shared.h" -#include "botlib.h" //bot lib interface -#include "be_aas.h" -#include "be_ea.h" -#include "be_ai_char.h" -#include "be_ai_chat.h" -#include "be_ai_gen.h" -#include "be_ai_goal.h" -#include "be_ai_move.h" -#include "be_ai_weap.h" -// -#include "ai_main.h" -#include "ai_dmq3.h" -#include "ai_chat.h" -#include "ai_cmd.h" -#include "ai_dmnet.h" -#include "ai_vcmd.h" - -// -#include "chars.h" -#include "inv.h" -#include "syn.h" - -#define MAX_PATH 144 - - -//bot states -bot_state_t *botstates[MAX_CLIENTS]; -//number of bots -int numbots; -//floating point time -float floattime; -//time to do a regular update -float regularupdate_time; -// -int bot_interbreed; -int bot_interbreedmatchcount; -// -vmCvar_t bot_thinktime; -vmCvar_t bot_memorydump; -vmCvar_t bot_saveroutingcache; -vmCvar_t bot_pause; -vmCvar_t bot_report; -vmCvar_t bot_testsolid; -vmCvar_t bot_testclusters; -vmCvar_t bot_developer; -vmCvar_t bot_interbreedchar; -vmCvar_t bot_interbreedbots; -vmCvar_t bot_interbreedcycle; -vmCvar_t bot_interbreedwrite; - - -void ExitLevel( void ); - - -/* -================== -BotAI_Print -================== -*/ -void QDECL BotAI_Print(int type, char *fmt, ...) { - char str[2048]; - va_list ap; - - va_start(ap, fmt); - vsprintf(str, fmt, ap); - va_end(ap); - - switch(type) { - case PRT_MESSAGE: { - G_Printf("%s", str); - break; - } - case PRT_WARNING: { - G_Printf( S_COLOR_YELLOW "Warning: %s", str ); - break; - } - case PRT_ERROR: { - G_Printf( S_COLOR_RED "Error: %s", str ); - break; - } - case PRT_FATAL: { - G_Printf( S_COLOR_RED "Fatal: %s", str ); - break; - } - case PRT_EXIT: { - G_Error( S_COLOR_RED "Exit: %s", str ); - break; - } - default: { - G_Printf( "unknown print type\n" ); - break; - } - } -} - - -/* -================== -BotAI_Trace -================== -*/ -void BotAI_Trace(bsp_trace_t *bsptrace, vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, int passent, int contentmask) { - trace_t trace; - - trap_Trace(&trace, start, mins, maxs, end, passent, contentmask); - //copy the trace information - bsptrace->allsolid = trace.allsolid; - bsptrace->startsolid = trace.startsolid; - bsptrace->fraction = trace.fraction; - VectorCopy(trace.endpos, bsptrace->endpos); - bsptrace->plane.dist = trace.plane.dist; - VectorCopy(trace.plane.normal, bsptrace->plane.normal); - bsptrace->plane.signbits = trace.plane.signbits; - bsptrace->plane.type = trace.plane.type; - bsptrace->surface.value = trace.surfaceFlags; - bsptrace->ent = trace.entityNum; - bsptrace->exp_dist = 0; - bsptrace->sidenum = 0; - bsptrace->contents = 0; -} - -/* -================== -BotAI_GetClientState -================== -*/ -int BotAI_GetClientState( int clientNum, playerState_t *state ) { - gentity_t *ent; - - ent = &g_entities[clientNum]; - if ( !ent->inuse ) { - return qfalse; - } - if ( !ent->client ) { - return qfalse; - } - - memcpy( state, &ent->client->ps, sizeof(playerState_t) ); - return qtrue; -} - -/* -================== -BotAI_GetEntityState -================== -*/ -int BotAI_GetEntityState( int entityNum, entityState_t *state ) { - gentity_t *ent; - - ent = &g_entities[entityNum]; - memset( state, 0, sizeof(entityState_t) ); - if (!ent->inuse) return qfalse; - if (!ent->r.linked) return qfalse; - if (ent->r.svFlags & SVF_NOCLIENT) return qfalse; - memcpy( state, &ent->s, sizeof(entityState_t) ); - return qtrue; -} - -/* -================== -BotAI_GetSnapshotEntity -================== -*/ -int BotAI_GetSnapshotEntity( int clientNum, int sequence, entityState_t *state ) { - int entNum; - - entNum = trap_BotGetSnapshotEntity( clientNum, sequence ); - if ( entNum == -1 ) { - memset(state, 0, sizeof(entityState_t)); - return -1; - } - - BotAI_GetEntityState( entNum, state ); - - return sequence + 1; -} - -/* -================== -BotAI_BotInitialChat -================== -*/ -void QDECL BotAI_BotInitialChat( bot_state_t *bs, char *type, ... ) { - int i, mcontext; - va_list ap; - char *p; - char *vars[MAX_MATCHVARIABLES]; - - memset(vars, 0, sizeof(vars)); - va_start(ap, type); - p = va_arg(ap, char *); - for (i = 0; i < MAX_MATCHVARIABLES; i++) { - if( !p ) { - break; - } - vars[i] = p; - p = va_arg(ap, char *); - } - va_end(ap); - - mcontext = BotSynonymContext(bs); - - trap_BotInitialChat( bs->cs, type, mcontext, vars[0], vars[1], vars[2], vars[3], vars[4], vars[5], vars[6], vars[7] ); -} - - -/* -================== -BotTestAAS -================== -*/ -void BotTestAAS(vec3_t origin) { - int areanum; - aas_areainfo_t info; - - trap_Cvar_Update(&bot_testsolid); - trap_Cvar_Update(&bot_testclusters); - if (bot_testsolid.integer) { - if (!trap_AAS_Initialized()) return; - areanum = BotPointAreaNum(origin); - if (areanum) BotAI_Print(PRT_MESSAGE, "\remtpy area"); - else BotAI_Print(PRT_MESSAGE, "\r^1SOLID area"); - } - else if (bot_testclusters.integer) { - if (!trap_AAS_Initialized()) return; - areanum = BotPointAreaNum(origin); - if (!areanum) - BotAI_Print(PRT_MESSAGE, "\r^1Solid! "); - else { - trap_AAS_AreaInfo(areanum, &info); - BotAI_Print(PRT_MESSAGE, "\rarea %d, cluster %d ", areanum, info.cluster); - } - } -} - -/* -================== -BotReportStatus -================== -*/ -void BotReportStatus(bot_state_t *bs) { - char goalname[MAX_MESSAGE_SIZE]; - char netname[MAX_MESSAGE_SIZE]; - char *leader, flagstatus[32]; - // - ClientName(bs->client, netname, sizeof(netname)); - if (Q_stricmp(netname, bs->teamleader) == 0) leader = "L"; - else leader = " "; - - strcpy(flagstatus, " "); - if (gametype == GT_CTF) { - if (BotCTFCarryingFlag(bs)) { - if (BotTeam(bs) == TEAM_RED) strcpy(flagstatus, S_COLOR_RED"F "); - else strcpy(flagstatus, S_COLOR_BLUE"F "); - } - } -#ifdef MISSIONPACK - else if (gametype == GT_1FCTF) { - if (Bot1FCTFCarryingFlag(bs)) { - if (BotTeam(bs) == TEAM_RED) strcpy(flagstatus, S_COLOR_RED"F "); - else strcpy(flagstatus, S_COLOR_BLUE"F "); - } - } - else if (gametype == GT_HARVESTER) { - if (BotHarvesterCarryingCubes(bs)) { - if (BotTeam(bs) == TEAM_RED) Com_sprintf(flagstatus, sizeof(flagstatus), S_COLOR_RED"%2d", bs->inventory[INVENTORY_REDCUBE]); - else Com_sprintf(flagstatus, sizeof(flagstatus), S_COLOR_BLUE"%2d", bs->inventory[INVENTORY_BLUECUBE]); - } - } -#endif - - switch(bs->ltgtype) { - case LTG_TEAMHELP: - { - EasyClientName(bs->teammate, goalname, sizeof(goalname)); - BotAI_Print(PRT_MESSAGE, "%-20s%s%s: helping %s\n", netname, leader, flagstatus, goalname); - break; - } - case LTG_TEAMACCOMPANY: - { - EasyClientName(bs->teammate, goalname, sizeof(goalname)); - BotAI_Print(PRT_MESSAGE, "%-20s%s%s: accompanying %s\n", netname, leader, flagstatus, goalname); - break; - } - case LTG_DEFENDKEYAREA: - { - trap_BotGoalName(bs->teamgoal.number, goalname, sizeof(goalname)); - BotAI_Print(PRT_MESSAGE, "%-20s%s%s: defending %s\n", netname, leader, flagstatus, goalname); - break; - } - case LTG_GETITEM: - { - trap_BotGoalName(bs->teamgoal.number, goalname, sizeof(goalname)); - BotAI_Print(PRT_MESSAGE, "%-20s%s%s: getting item %s\n", netname, leader, flagstatus, goalname); - break; - } - case LTG_KILL: - { - ClientName(bs->teamgoal.entitynum, goalname, sizeof(goalname)); - BotAI_Print(PRT_MESSAGE, "%-20s%s%s: killing %s\n", netname, leader, flagstatus, goalname); - break; - } - case LTG_CAMP: - case LTG_CAMPORDER: - { - BotAI_Print(PRT_MESSAGE, "%-20s%s%s: camping\n", netname, leader, flagstatus); - break; - } - case LTG_PATROL: - { - BotAI_Print(PRT_MESSAGE, "%-20s%s%s: patrolling\n", netname, leader, flagstatus); - break; - } - case LTG_GETFLAG: - { - BotAI_Print(PRT_MESSAGE, "%-20s%s%s: capturing flag\n", netname, leader, flagstatus); - break; - } - case LTG_RUSHBASE: - { - BotAI_Print(PRT_MESSAGE, "%-20s%s%s: rushing base\n", netname, leader, flagstatus); - break; - } - case LTG_RETURNFLAG: - { - BotAI_Print(PRT_MESSAGE, "%-20s%s%s: returning flag\n", netname, leader, flagstatus); - break; - } - case LTG_ATTACKENEMYBASE: - { - BotAI_Print(PRT_MESSAGE, "%-20s%s%s: attacking the enemy base\n", netname, leader, flagstatus); - break; - } - case LTG_HARVEST: - { - BotAI_Print(PRT_MESSAGE, "%-20s%s%s: harvesting\n", netname, leader, flagstatus); - break; - } - default: - { - BotAI_Print(PRT_MESSAGE, "%-20s%s%s: roaming\n", netname, leader, flagstatus); - break; - } - } -} - -/* -================== -BotTeamplayReport -================== -*/ -void BotTeamplayReport(void) { - int i; - char buf[MAX_INFO_STRING]; - - BotAI_Print(PRT_MESSAGE, S_COLOR_RED"RED\n"); - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { - // - if ( !botstates[i] || !botstates[i]->inuse ) continue; - // - trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); - //if no config string or no name - if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue; - //skip spectators - if (atoi(Info_ValueForKey(buf, "t")) == TEAM_RED) { - BotReportStatus(botstates[i]); - } - } - BotAI_Print(PRT_MESSAGE, S_COLOR_BLUE"BLUE\n"); - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { - // - if ( !botstates[i] || !botstates[i]->inuse ) continue; - // - trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); - //if no config string or no name - if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue; - //skip spectators - if (atoi(Info_ValueForKey(buf, "t")) == TEAM_BLUE) { - BotReportStatus(botstates[i]); - } - } -} - -/* -================== -BotSetInfoConfigString -================== -*/ -void BotSetInfoConfigString(bot_state_t *bs) { - char goalname[MAX_MESSAGE_SIZE]; - char netname[MAX_MESSAGE_SIZE]; - char action[MAX_MESSAGE_SIZE]; - char *leader, carrying[32], *cs; - bot_goal_t goal; - // - ClientName(bs->client, netname, sizeof(netname)); - if (Q_stricmp(netname, bs->teamleader) == 0) leader = "L"; - else leader = " "; - - strcpy(carrying, " "); - if (gametype == GT_CTF) { - if (BotCTFCarryingFlag(bs)) { - strcpy(carrying, "F "); - } - } -#ifdef MISSIONPACK - else if (gametype == GT_1FCTF) { - if (Bot1FCTFCarryingFlag(bs)) { - strcpy(carrying, "F "); - } - } - else if (gametype == GT_HARVESTER) { - if (BotHarvesterCarryingCubes(bs)) { - if (BotTeam(bs) == TEAM_RED) Com_sprintf(carrying, sizeof(carrying), "%2d", bs->inventory[INVENTORY_REDCUBE]); - else Com_sprintf(carrying, sizeof(carrying), "%2d", bs->inventory[INVENTORY_BLUECUBE]); - } - } -#endif - - switch(bs->ltgtype) { - case LTG_TEAMHELP: - { - EasyClientName(bs->teammate, goalname, sizeof(goalname)); - Com_sprintf(action, sizeof(action), "helping %s", goalname); - break; - } - case LTG_TEAMACCOMPANY: - { - EasyClientName(bs->teammate, goalname, sizeof(goalname)); - Com_sprintf(action, sizeof(action), "accompanying %s", goalname); - break; - } - case LTG_DEFENDKEYAREA: - { - trap_BotGoalName(bs->teamgoal.number, goalname, sizeof(goalname)); - Com_sprintf(action, sizeof(action), "defending %s", goalname); - break; - } - case LTG_GETITEM: - { - trap_BotGoalName(bs->teamgoal.number, goalname, sizeof(goalname)); - Com_sprintf(action, sizeof(action), "getting item %s", goalname); - break; - } - case LTG_KILL: - { - ClientName(bs->teamgoal.entitynum, goalname, sizeof(goalname)); - Com_sprintf(action, sizeof(action), "killing %s", goalname); - break; - } - case LTG_CAMP: - case LTG_CAMPORDER: - { - Com_sprintf(action, sizeof(action), "camping"); - break; - } - case LTG_PATROL: - { - Com_sprintf(action, sizeof(action), "patrolling"); - break; - } - case LTG_GETFLAG: - { - Com_sprintf(action, sizeof(action), "capturing flag"); - break; - } - case LTG_RUSHBASE: - { - Com_sprintf(action, sizeof(action), "rushing base"); - break; - } - case LTG_RETURNFLAG: - { - Com_sprintf(action, sizeof(action), "returning flag"); - break; - } - case LTG_ATTACKENEMYBASE: - { - Com_sprintf(action, sizeof(action), "attacking the enemy base"); - break; - } - case LTG_HARVEST: - { - Com_sprintf(action, sizeof(action), "harvesting"); - break; - } - default: - { - trap_BotGetTopGoal(bs->gs, &goal); - trap_BotGoalName(goal.number, goalname, sizeof(goalname)); - Com_sprintf(action, sizeof(action), "roaming %s", goalname); - break; - } - } - cs = va("l\\%s\\c\\%s\\a\\%s", - leader, - carrying, - action); - trap_SetConfigstring (CS_BOTINFO + bs->client, cs); -} - -/* -============== -BotUpdateInfoConfigStrings -============== -*/ -void BotUpdateInfoConfigStrings(void) { - int i; - char buf[MAX_INFO_STRING]; - - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { - // - if ( !botstates[i] || !botstates[i]->inuse ) - continue; - // - trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); - //if no config string or no name - if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) - continue; - BotSetInfoConfigString(botstates[i]); - } -} - -/* -============== -BotInterbreedBots -============== -*/ -void BotInterbreedBots(void) { - float ranks[MAX_CLIENTS]; - int parent1, parent2, child; - int i; - - // get rankings for all the bots - for (i = 0; i < MAX_CLIENTS; i++) { - if ( botstates[i] && botstates[i]->inuse ) { - ranks[i] = botstates[i]->num_kills * 2 - botstates[i]->num_deaths; - } - else { - ranks[i] = -1; - } - } - - if (trap_GeneticParentsAndChildSelection(MAX_CLIENTS, ranks, &parent1, &parent2, &child)) { - trap_BotInterbreedGoalFuzzyLogic(botstates[parent1]->gs, botstates[parent2]->gs, botstates[child]->gs); - trap_BotMutateGoalFuzzyLogic(botstates[child]->gs, 1); - } - // reset the kills and deaths - for (i = 0; i < MAX_CLIENTS; i++) { - if (botstates[i] && botstates[i]->inuse) { - botstates[i]->num_kills = 0; - botstates[i]->num_deaths = 0; - } - } -} - -/* -============== -BotWriteInterbreeded -============== -*/ -void BotWriteInterbreeded(char *filename) { - float rank, bestrank; - int i, bestbot; - - bestrank = 0; - bestbot = -1; - // get the best bot - for (i = 0; i < MAX_CLIENTS; i++) { - if ( botstates[i] && botstates[i]->inuse ) { - rank = botstates[i]->num_kills * 2 - botstates[i]->num_deaths; - } - else { - rank = -1; - } - if (rank > bestrank) { - bestrank = rank; - bestbot = i; - } - } - if (bestbot >= 0) { - //write out the new goal fuzzy logic - trap_BotSaveGoalFuzzyLogic(botstates[bestbot]->gs, filename); - } -} - -/* -============== -BotInterbreedEndMatch - -add link back into ExitLevel? -============== -*/ -void BotInterbreedEndMatch(void) { - - if (!bot_interbreed) return; - bot_interbreedmatchcount++; - if (bot_interbreedmatchcount >= bot_interbreedcycle.integer) { - bot_interbreedmatchcount = 0; - // - trap_Cvar_Update(&bot_interbreedwrite); - if (strlen(bot_interbreedwrite.string)) { - BotWriteInterbreeded(bot_interbreedwrite.string); - trap_Cvar_Set("bot_interbreedwrite", ""); - } - BotInterbreedBots(); - } -} - -/* -============== -BotInterbreeding -============== -*/ -void BotInterbreeding(void) { - int i; - - trap_Cvar_Update(&bot_interbreedchar); - if (!strlen(bot_interbreedchar.string)) return; - //make sure we are in tournament mode - if (gametype != GT_TOURNAMENT) { - trap_Cvar_Set("g_gametype", va("%d", GT_TOURNAMENT)); - ExitLevel(); - return; - } - //shutdown all the bots - for (i = 0; i < MAX_CLIENTS; i++) { - if (botstates[i] && botstates[i]->inuse) { - BotAIShutdownClient(botstates[i]->client, qfalse); - } - } - //make sure all item weight configs are reloaded and Not shared - trap_BotLibVarSet("bot_reloadcharacters", "1"); - //add a number of bots using the desired bot character - for (i = 0; i < bot_interbreedbots.integer; i++) { - trap_SendConsoleCommand( EXEC_INSERT, va("addbot %s 4 free %i %s%d\n", - bot_interbreedchar.string, i * 50, bot_interbreedchar.string, i) ); - } - // - trap_Cvar_Set("bot_interbreedchar", ""); - bot_interbreed = qtrue; -} - -/* -============== -BotEntityInfo -============== -*/ -void BotEntityInfo(int entnum, aas_entityinfo_t *info) { - trap_AAS_EntityInfo(entnum, info); -} - -/* -============== -NumBots -============== -*/ -int NumBots(void) { - return numbots; -} - -/* -============== -BotTeamLeader -============== -*/ -int BotTeamLeader(bot_state_t *bs) { - int leader; - - leader = ClientFromName(bs->teamleader); - if (leader < 0) return qfalse; - if (!botstates[leader] || !botstates[leader]->inuse) return qfalse; - return qtrue; -} - -/* -============== -AngleDifference -============== -*/ -float AngleDifference(float ang1, float ang2) { - float diff; - - diff = ang1 - ang2; - if (ang1 > ang2) { - if (diff > 180.0) diff -= 360.0; - } - else { - if (diff < -180.0) diff += 360.0; - } - return diff; -} - -/* -============== -BotChangeViewAngle -============== -*/ -float BotChangeViewAngle(float angle, float ideal_angle, float speed) { - float move; - - angle = AngleMod(angle); - ideal_angle = AngleMod(ideal_angle); - if (angle == ideal_angle) return angle; - move = ideal_angle - angle; - if (ideal_angle > angle) { - if (move > 180.0) move -= 360.0; - } - else { - if (move < -180.0) move += 360.0; - } - if (move > 0) { - if (move > speed) move = speed; - } - else { - if (move < -speed) move = -speed; - } - return AngleMod(angle + move); -} - -/* -============== -BotChangeViewAngles -============== -*/ -void BotChangeViewAngles(bot_state_t *bs, float thinktime) { - float diff, factor, maxchange, anglespeed, disired_speed; - int i; - - if (bs->ideal_viewangles[PITCH] > 180) bs->ideal_viewangles[PITCH] -= 360; - // - if (bs->enemy >= 0) { - factor = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_VIEW_FACTOR, 0.01f, 1); - maxchange = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_VIEW_MAXCHANGE, 1, 1800); - } - else { - factor = 0.05f; - maxchange = 360; - } - if (maxchange < 240) maxchange = 240; - maxchange *= thinktime; - for (i = 0; i < 2; i++) { - // - if (bot_challenge.integer) { - //smooth slowdown view model - diff = abs(AngleDifference(bs->viewangles[i], bs->ideal_viewangles[i])); - anglespeed = diff * factor; - if (anglespeed > maxchange) anglespeed = maxchange; - bs->viewangles[i] = BotChangeViewAngle(bs->viewangles[i], - bs->ideal_viewangles[i], anglespeed); - } - else { - //over reaction view model - bs->viewangles[i] = AngleMod(bs->viewangles[i]); - bs->ideal_viewangles[i] = AngleMod(bs->ideal_viewangles[i]); - diff = AngleDifference(bs->viewangles[i], bs->ideal_viewangles[i]); - disired_speed = diff * factor; - bs->viewanglespeed[i] += (bs->viewanglespeed[i] - disired_speed); - if (bs->viewanglespeed[i] > 180) bs->viewanglespeed[i] = maxchange; - if (bs->viewanglespeed[i] < -180) bs->viewanglespeed[i] = -maxchange; - anglespeed = bs->viewanglespeed[i]; - if (anglespeed > maxchange) anglespeed = maxchange; - if (anglespeed < -maxchange) anglespeed = -maxchange; - bs->viewangles[i] += anglespeed; - bs->viewangles[i] = AngleMod(bs->viewangles[i]); - //demping - bs->viewanglespeed[i] *= 0.45 * (1 - factor); - } - //BotAI_Print(PRT_MESSAGE, "ideal_angles %f %f\n", bs->ideal_viewangles[0], bs->ideal_viewangles[1], bs->ideal_viewangles[2]);` - //bs->viewangles[i] = bs->ideal_viewangles[i]; - } - //bs->viewangles[PITCH] = 0; - if (bs->viewangles[PITCH] > 180) bs->viewangles[PITCH] -= 360; - //elementary action: view - trap_EA_View(bs->client, bs->viewangles); -} - -/* -============== -BotInputToUserCommand -============== -*/ -void BotInputToUserCommand(bot_input_t *bi, usercmd_t *ucmd, int delta_angles[3], int time) { - vec3_t angles, forward, right; - short temp; - int j; - - //clear the whole structure - memset(ucmd, 0, sizeof(usercmd_t)); - // - //Com_Printf("dir = %f %f %f speed = %f\n", bi->dir[0], bi->dir[1], bi->dir[2], bi->speed); - //the duration for the user command in milli seconds - ucmd->serverTime = time; - // - if (bi->actionflags & ACTION_DELAYEDJUMP) { - bi->actionflags |= ACTION_JUMP; - bi->actionflags &= ~ACTION_DELAYEDJUMP; - } - //set the buttons - if (bi->actionflags & ACTION_RESPAWN) ucmd->buttons = BUTTON_ATTACK; - if (bi->actionflags & ACTION_ATTACK) ucmd->buttons |= BUTTON_ATTACK; - if (bi->actionflags & ACTION_TALK) ucmd->buttons |= BUTTON_TALK; - if (bi->actionflags & ACTION_GESTURE) ucmd->buttons |= BUTTON_GESTURE; - if (bi->actionflags & ACTION_USE) ucmd->buttons |= BUTTON_USE_HOLDABLE; - if (bi->actionflags & ACTION_WALK) ucmd->buttons |= BUTTON_WALKING; - if (bi->actionflags & ACTION_AFFIRMATIVE) ucmd->buttons |= BUTTON_AFFIRMATIVE; - if (bi->actionflags & ACTION_NEGATIVE) ucmd->buttons |= BUTTON_NEGATIVE; - if (bi->actionflags & ACTION_GETFLAG) ucmd->buttons |= BUTTON_GETFLAG; - if (bi->actionflags & ACTION_GUARDBASE) ucmd->buttons |= BUTTON_GUARDBASE; - if (bi->actionflags & ACTION_PATROL) ucmd->buttons |= BUTTON_PATROL; - if (bi->actionflags & ACTION_FOLLOWME) ucmd->buttons |= BUTTON_FOLLOWME; - // - ucmd->weapon = bi->weapon; - //set the view angles - //NOTE: the ucmd->angles are the angles WITHOUT the delta angles - ucmd->angles[PITCH] = ANGLE2SHORT(bi->viewangles[PITCH]); - ucmd->angles[YAW] = ANGLE2SHORT(bi->viewangles[YAW]); - ucmd->angles[ROLL] = ANGLE2SHORT(bi->viewangles[ROLL]); - //subtract the delta angles - for (j = 0; j < 3; j++) { - temp = ucmd->angles[j] - delta_angles[j]; - /*NOTE: disabled because temp should be mod first - if ( j == PITCH ) { - // don't let the player look up or down more than 90 degrees - if ( temp > 16000 ) temp = 16000; - else if ( temp < -16000 ) temp = -16000; - } - */ - ucmd->angles[j] = temp; - } - //NOTE: movement is relative to the REAL view angles - //get the horizontal forward and right vector - //get the pitch in the range [-180, 180] - if (bi->dir[2]) angles[PITCH] = bi->viewangles[PITCH]; - else angles[PITCH] = 0; - angles[YAW] = bi->viewangles[YAW]; - angles[ROLL] = 0; - AngleVectors(angles, forward, right, NULL); - //bot input speed is in the range [0, 400] - bi->speed = bi->speed * 127 / 400; - //set the view independent movement - ucmd->forwardmove = DotProduct(forward, bi->dir) * bi->speed; - ucmd->rightmove = DotProduct(right, bi->dir) * bi->speed; - ucmd->upmove = abs(forward[2]) * bi->dir[2] * bi->speed; - //normal keyboard movement - if (bi->actionflags & ACTION_MOVEFORWARD) ucmd->forwardmove += 127; - if (bi->actionflags & ACTION_MOVEBACK) ucmd->forwardmove -= 127; - if (bi->actionflags & ACTION_MOVELEFT) ucmd->rightmove -= 127; - if (bi->actionflags & ACTION_MOVERIGHT) ucmd->rightmove += 127; - //jump/moveup - if (bi->actionflags & ACTION_JUMP) ucmd->upmove += 127; - //crouch/movedown - if (bi->actionflags & ACTION_CROUCH) ucmd->upmove -= 127; - // - //Com_Printf("forward = %d right = %d up = %d\n", ucmd.forwardmove, ucmd.rightmove, ucmd.upmove); - //Com_Printf("ucmd->serverTime = %d\n", ucmd->serverTime); -} - -/* -============== -BotUpdateInput -============== -*/ -void BotUpdateInput(bot_state_t *bs, int time, int elapsed_time) { - bot_input_t bi; - int j; - - //add the delta angles to the bot's current view angles - for (j = 0; j < 3; j++) { - bs->viewangles[j] = AngleMod(bs->viewangles[j] + SHORT2ANGLE(bs->cur_ps.delta_angles[j])); - } - //change the bot view angles - BotChangeViewAngles(bs, (float) elapsed_time / 1000); - //retrieve the bot input - trap_EA_GetInput(bs->client, (float) time / 1000, &bi); - //respawn hack - if (bi.actionflags & ACTION_RESPAWN) { - if (bs->lastucmd.buttons & BUTTON_ATTACK) bi.actionflags &= ~(ACTION_RESPAWN|ACTION_ATTACK); - } - //convert the bot input to a usercmd - BotInputToUserCommand(&bi, &bs->lastucmd, bs->cur_ps.delta_angles, time); - //subtract the delta angles - for (j = 0; j < 3; j++) { - bs->viewangles[j] = AngleMod(bs->viewangles[j] - SHORT2ANGLE(bs->cur_ps.delta_angles[j])); - } -} - -/* -============== -BotAIRegularUpdate -============== -*/ -void BotAIRegularUpdate(void) { - if (regularupdate_time < FloatTime()) { - trap_BotUpdateEntityItems(); - regularupdate_time = FloatTime() + 0.3; - } -} - -/* -============== -RemoveColorEscapeSequences -============== -*/ -void RemoveColorEscapeSequences( char *text ) { - int i, l; - - l = 0; - for ( i = 0; text[i]; i++ ) { - if (Q_IsColorString(&text[i])) { - i++; - continue; - } - if (text[i] > 0x7E) - continue; - text[l++] = text[i]; - } - text[l] = '\0'; -} - -/* -============== -BotAI -============== -*/ -int BotAI(int client, float thinktime) { - bot_state_t *bs; - char buf[1024], *args; - int j; - - trap_EA_ResetInput(client); - // - bs = botstates[client]; - if (!bs || !bs->inuse) { - BotAI_Print(PRT_FATAL, "BotAI: client %d is not setup\n", client); - return qfalse; - } - - //retrieve the current client state - BotAI_GetClientState( client, &bs->cur_ps ); - - //retrieve any waiting server commands - while( trap_BotGetServerCommand(client, buf, sizeof(buf)) ) { - //have buf point to the command and args to the command arguments - args = strchr( buf, ' '); - if (!args) continue; - *args++ = '\0'; - - //remove color espace sequences from the arguments - RemoveColorEscapeSequences( args ); - - if (!Q_stricmp(buf, "cp ")) - { /*CenterPrintf*/ } - else if (!Q_stricmp(buf, "cs")) - { /*ConfigStringModified*/ } - else if (!Q_stricmp(buf, "print")) { - //remove first and last quote from the chat message - memmove(args, args+1, strlen(args)); - args[strlen(args)-1] = '\0'; - trap_BotQueueConsoleMessage(bs->cs, CMS_NORMAL, args); - } - else if (!Q_stricmp(buf, "chat")) { - //remove first and last quote from the chat message - memmove(args, args+1, strlen(args)); - args[strlen(args)-1] = '\0'; - trap_BotQueueConsoleMessage(bs->cs, CMS_CHAT, args); - } - else if (!Q_stricmp(buf, "tchat")) { - //remove first and last quote from the chat message - memmove(args, args+1, strlen(args)); - args[strlen(args)-1] = '\0'; - trap_BotQueueConsoleMessage(bs->cs, CMS_CHAT, args); - } -#ifdef MISSIONPACK - else if (!Q_stricmp(buf, "vchat")) { - BotVoiceChatCommand(bs, SAY_ALL, args); - } - else if (!Q_stricmp(buf, "vtchat")) { - BotVoiceChatCommand(bs, SAY_TEAM, args); - } - else if (!Q_stricmp(buf, "vtell")) { - BotVoiceChatCommand(bs, SAY_TELL, args); - } -#endif - else if (!Q_stricmp(buf, "scores")) - { /*FIXME: parse scores?*/ } - else if (!Q_stricmp(buf, "clientLevelShot")) - { /*ignore*/ } - } - //add the delta angles to the bot's current view angles - for (j = 0; j < 3; j++) { - bs->viewangles[j] = AngleMod(bs->viewangles[j] + SHORT2ANGLE(bs->cur_ps.delta_angles[j])); - } - //increase the local time of the bot - bs->ltime += thinktime; - // - bs->thinktime = thinktime; - //origin of the bot - VectorCopy(bs->cur_ps.origin, bs->origin); - //eye coordinates of the bot - VectorCopy(bs->cur_ps.origin, bs->eye); - bs->eye[2] += bs->cur_ps.viewheight; - //get the area the bot is in - bs->areanum = BotPointAreaNum(bs->origin); - //the real AI - BotDeathmatchAI(bs, thinktime); - //set the weapon selection every AI frame - trap_EA_SelectWeapon(bs->client, bs->weaponnum); - //subtract the delta angles - for (j = 0; j < 3; j++) { - bs->viewangles[j] = AngleMod(bs->viewangles[j] - SHORT2ANGLE(bs->cur_ps.delta_angles[j])); - } - //everything was ok - return qtrue; -} - -/* -================== -BotScheduleBotThink -================== -*/ -void BotScheduleBotThink(void) { - int i, botnum; - - botnum = 0; - - for( i = 0; i < MAX_CLIENTS; i++ ) { - if( !botstates[i] || !botstates[i]->inuse ) { - continue; - } - //initialize the bot think residual time - botstates[i]->botthink_residual = bot_thinktime.integer * botnum / numbots; - botnum++; - } -} - -/* -============== -BotWriteSessionData -============== -*/ -void BotWriteSessionData(bot_state_t *bs) { - const char *s; - const char *var; - - s = va( - "%i %i %i %i %i %i %i %i" - " %f %f %f" - " %f %f %f" - " %f %f %f", - bs->lastgoal_decisionmaker, - bs->lastgoal_ltgtype, - bs->lastgoal_teammate, - bs->lastgoal_teamgoal.areanum, - bs->lastgoal_teamgoal.entitynum, - bs->lastgoal_teamgoal.flags, - bs->lastgoal_teamgoal.iteminfo, - bs->lastgoal_teamgoal.number, - bs->lastgoal_teamgoal.origin[0], - bs->lastgoal_teamgoal.origin[1], - bs->lastgoal_teamgoal.origin[2], - bs->lastgoal_teamgoal.mins[0], - bs->lastgoal_teamgoal.mins[1], - bs->lastgoal_teamgoal.mins[2], - bs->lastgoal_teamgoal.maxs[0], - bs->lastgoal_teamgoal.maxs[1], - bs->lastgoal_teamgoal.maxs[2] - ); - - var = va( "botsession%i", bs->client ); - - trap_Cvar_Set( var, s ); -} - -/* -============== -BotReadSessionData -============== -*/ -void BotReadSessionData(bot_state_t *bs) { - char s[MAX_STRING_CHARS]; - const char *var; - - var = va( "botsession%i", bs->client ); - trap_Cvar_VariableStringBuffer( var, s, sizeof(s) ); - - sscanf(s, - "%i %i %i %i %i %i %i %i" - " %f %f %f" - " %f %f %f" - " %f %f %f", - &bs->lastgoal_decisionmaker, - &bs->lastgoal_ltgtype, - &bs->lastgoal_teammate, - &bs->lastgoal_teamgoal.areanum, - &bs->lastgoal_teamgoal.entitynum, - &bs->lastgoal_teamgoal.flags, - &bs->lastgoal_teamgoal.iteminfo, - &bs->lastgoal_teamgoal.number, - &bs->lastgoal_teamgoal.origin[0], - &bs->lastgoal_teamgoal.origin[1], - &bs->lastgoal_teamgoal.origin[2], - &bs->lastgoal_teamgoal.mins[0], - &bs->lastgoal_teamgoal.mins[1], - &bs->lastgoal_teamgoal.mins[2], - &bs->lastgoal_teamgoal.maxs[0], - &bs->lastgoal_teamgoal.maxs[1], - &bs->lastgoal_teamgoal.maxs[2] - ); -} - -/* -============== -BotAISetupClient -============== -*/ -int BotAISetupClient(int client, struct bot_settings_s *settings, qboolean restart) { - char filename[MAX_PATH], name[MAX_PATH], gender[MAX_PATH]; - bot_state_t *bs; - int errnum; - - if (!botstates[client]) botstates[client] = G_Alloc(sizeof(bot_state_t)); - bs = botstates[client]; - - if (bs && bs->inuse) { - BotAI_Print(PRT_FATAL, "BotAISetupClient: client %d already setup\n", client); - return qfalse; - } - - if (!trap_AAS_Initialized()) { - BotAI_Print(PRT_FATAL, "AAS not initialized\n"); - return qfalse; - } - - //load the bot character - bs->character = trap_BotLoadCharacter(settings->characterfile, settings->skill); - if (!bs->character) { - BotAI_Print(PRT_FATAL, "couldn't load skill %f from %s\n", settings->skill, settings->characterfile); - return qfalse; - } - //copy the settings - memcpy(&bs->settings, settings, sizeof(bot_settings_t)); - //allocate a goal state - bs->gs = trap_BotAllocGoalState(client); - //load the item weights - trap_Characteristic_String(bs->character, CHARACTERISTIC_ITEMWEIGHTS, filename, MAX_PATH); - errnum = trap_BotLoadItemWeights(bs->gs, filename); - if (errnum != BLERR_NOERROR) { - trap_BotFreeGoalState(bs->gs); - return qfalse; - } - //allocate a weapon state - bs->ws = trap_BotAllocWeaponState(); - //load the weapon weights - trap_Characteristic_String(bs->character, CHARACTERISTIC_WEAPONWEIGHTS, filename, MAX_PATH); - errnum = trap_BotLoadWeaponWeights(bs->ws, filename); - if (errnum != BLERR_NOERROR) { - trap_BotFreeGoalState(bs->gs); - trap_BotFreeWeaponState(bs->ws); - return qfalse; - } - //allocate a chat state - bs->cs = trap_BotAllocChatState(); - //load the chat file - trap_Characteristic_String(bs->character, CHARACTERISTIC_CHAT_FILE, filename, MAX_PATH); - trap_Characteristic_String(bs->character, CHARACTERISTIC_CHAT_NAME, name, MAX_PATH); - errnum = trap_BotLoadChatFile(bs->cs, filename, name); - if (errnum != BLERR_NOERROR) { - trap_BotFreeChatState(bs->cs); - trap_BotFreeGoalState(bs->gs); - trap_BotFreeWeaponState(bs->ws); - return qfalse; - } - //get the gender characteristic - trap_Characteristic_String(bs->character, CHARACTERISTIC_GENDER, gender, MAX_PATH); - //set the chat gender - if (*gender == 'f' || *gender == 'F') trap_BotSetChatGender(bs->cs, CHAT_GENDERFEMALE); - else if (*gender == 'm' || *gender == 'M') trap_BotSetChatGender(bs->cs, CHAT_GENDERMALE); - else trap_BotSetChatGender(bs->cs, CHAT_GENDERLESS); - - bs->inuse = qtrue; - bs->client = client; - bs->entitynum = client; - bs->setupcount = 4; - bs->entergame_time = FloatTime(); - bs->ms = trap_BotAllocMoveState(); - bs->walker = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_WALKER, 0, 1); - numbots++; - - if (trap_Cvar_VariableIntegerValue("bot_testichat")) { - trap_BotLibVarSet("bot_testichat", "1"); - BotChatTest(bs); - } - //NOTE: reschedule the bot thinking - BotScheduleBotThink(); - //if interbreeding start with a mutation - if (bot_interbreed) { - trap_BotMutateGoalFuzzyLogic(bs->gs, 1); - } - // if we kept the bot client - if (restart) { - BotReadSessionData(bs); - } - //bot has been setup succesfully - return qtrue; -} - -/* -============== -BotAIShutdownClient -============== -*/ -int BotAIShutdownClient(int client, qboolean restart) { - bot_state_t *bs; - - bs = botstates[client]; - if (!bs || !bs->inuse) { - //BotAI_Print(PRT_ERROR, "BotAIShutdownClient: client %d already shutdown\n", client); - return qfalse; - } - - if (restart) { - BotWriteSessionData(bs); - } - - if (BotChat_ExitGame(bs)) { - trap_BotEnterChat(bs->cs, bs->client, CHAT_ALL); - } - - trap_BotFreeMoveState(bs->ms); - //free the goal state` - trap_BotFreeGoalState(bs->gs); - //free the chat file - trap_BotFreeChatState(bs->cs); - //free the weapon weights - trap_BotFreeWeaponState(bs->ws); - //free the bot character - trap_BotFreeCharacter(bs->character); - // - BotFreeWaypoints(bs->checkpoints); - BotFreeWaypoints(bs->patrolpoints); - //clear activate goal stack - BotClearActivateGoalStack(bs); - //clear the bot state - memset(bs, 0, sizeof(bot_state_t)); - //set the inuse flag to qfalse - bs->inuse = qfalse; - //there's one bot less - numbots--; - //everything went ok - return qtrue; -} - -/* -============== -BotResetState - -called when a bot enters the intermission or observer mode and -when the level is changed -============== -*/ -void BotResetState(bot_state_t *bs) { - int client, entitynum, inuse; - int movestate, goalstate, chatstate, weaponstate; - bot_settings_t settings; - int character; - playerState_t ps; //current player state - float entergame_time; - - //save some things that should not be reset here - memcpy(&settings, &bs->settings, sizeof(bot_settings_t)); - memcpy(&ps, &bs->cur_ps, sizeof(playerState_t)); - inuse = bs->inuse; - client = bs->client; - entitynum = bs->entitynum; - character = bs->character; - movestate = bs->ms; - goalstate = bs->gs; - chatstate = bs->cs; - weaponstate = bs->ws; - entergame_time = bs->entergame_time; - //free checkpoints and patrol points - BotFreeWaypoints(bs->checkpoints); - BotFreeWaypoints(bs->patrolpoints); - //reset the whole state - memset(bs, 0, sizeof(bot_state_t)); - //copy back some state stuff that should not be reset - bs->ms = movestate; - bs->gs = goalstate; - bs->cs = chatstate; - bs->ws = weaponstate; - memcpy(&bs->cur_ps, &ps, sizeof(playerState_t)); - memcpy(&bs->settings, &settings, sizeof(bot_settings_t)); - bs->inuse = inuse; - bs->client = client; - bs->entitynum = entitynum; - bs->character = character; - bs->entergame_time = entergame_time; - //reset several states - if (bs->ms) trap_BotResetMoveState(bs->ms); - if (bs->gs) trap_BotResetGoalState(bs->gs); - if (bs->ws) trap_BotResetWeaponState(bs->ws); - if (bs->gs) trap_BotResetAvoidGoals(bs->gs); - if (bs->ms) trap_BotResetAvoidReach(bs->ms); -} - -/* -============== -BotAILoadMap -============== -*/ -int BotAILoadMap( int restart ) { - int i; - vmCvar_t mapname; - - if (!restart) { - trap_Cvar_Register( &mapname, "mapname", "", CVAR_SERVERINFO | CVAR_ROM ); - trap_BotLibLoadMap( mapname.string ); - } - - for (i = 0; i < MAX_CLIENTS; i++) { - if (botstates[i] && botstates[i]->inuse) { - BotResetState( botstates[i] ); - botstates[i]->setupcount = 4; - } - } - - BotSetupDeathmatchAI(); - - return qtrue; -} - -#ifdef MISSIONPACK -void ProximityMine_Trigger( gentity_t *trigger, gentity_t *other, trace_t *trace ); -#endif - -/* -================== -BotAIStartFrame -================== -*/ -int BotAIStartFrame(int time) { - int i; - gentity_t *ent; - bot_entitystate_t state; - int elapsed_time, thinktime; - static int local_time; - static int botlib_residual; - static int lastbotthink_time; - - G_CheckBotSpawn(); - - trap_Cvar_Update(&bot_rocketjump); - trap_Cvar_Update(&bot_grapple); - trap_Cvar_Update(&bot_fastchat); - trap_Cvar_Update(&bot_nochat); - trap_Cvar_Update(&bot_testrchat); - trap_Cvar_Update(&bot_thinktime); - trap_Cvar_Update(&bot_memorydump); - trap_Cvar_Update(&bot_saveroutingcache); - trap_Cvar_Update(&bot_pause); - trap_Cvar_Update(&bot_report); - - if (bot_report.integer) { -// BotTeamplayReport(); -// trap_Cvar_Set("bot_report", "0"); - BotUpdateInfoConfigStrings(); - } - - if (bot_pause.integer) { - // execute bot user commands every frame - for( i = 0; i < MAX_CLIENTS; i++ ) { - if( !botstates[i] || !botstates[i]->inuse ) { - continue; - } - if( g_entities[i].client->pers.connected != CON_CONNECTED ) { - continue; - } - botstates[i]->lastucmd.forwardmove = 0; - botstates[i]->lastucmd.rightmove = 0; - botstates[i]->lastucmd.upmove = 0; - botstates[i]->lastucmd.buttons = 0; - botstates[i]->lastucmd.serverTime = time; - trap_BotUserCommand(botstates[i]->client, &botstates[i]->lastucmd); - } - return qtrue; - } - - if (bot_memorydump.integer) { - trap_BotLibVarSet("memorydump", "1"); - trap_Cvar_Set("bot_memorydump", "0"); - } - if (bot_saveroutingcache.integer) { - trap_BotLibVarSet("saveroutingcache", "1"); - trap_Cvar_Set("bot_saveroutingcache", "0"); - } - //check if bot interbreeding is activated - BotInterbreeding(); - //cap the bot think time - if (bot_thinktime.integer > 200) { - trap_Cvar_Set("bot_thinktime", "200"); - } - //if the bot think time changed we should reschedule the bots - if (bot_thinktime.integer != lastbotthink_time) { - lastbotthink_time = bot_thinktime.integer; - BotScheduleBotThink(); - } - - elapsed_time = time - local_time; - local_time = time; - - botlib_residual += elapsed_time; - - if (elapsed_time > bot_thinktime.integer) thinktime = elapsed_time; - else thinktime = bot_thinktime.integer; - - // update the bot library - if ( botlib_residual >= thinktime ) { - botlib_residual -= thinktime; - - trap_BotLibStartFrame((float) time / 1000); - - if (!trap_AAS_Initialized()) return qfalse; - - //update entities in the botlib - for (i = 0; i < MAX_GENTITIES; i++) { - ent = &g_entities[i]; - if (!ent->inuse) { - trap_BotLibUpdateEntity(i, NULL); - continue; - } - if (!ent->r.linked) { - trap_BotLibUpdateEntity(i, NULL); - continue; - } - if (ent->r.svFlags & SVF_NOCLIENT) { - trap_BotLibUpdateEntity(i, NULL); - continue; - } - // do not update missiles - if (ent->s.eType == ET_MISSILE && ent->s.weapon != WP_GRAPPLING_HOOK) { - trap_BotLibUpdateEntity(i, NULL); - continue; - } - // do not update event only entities - if (ent->s.eType > ET_EVENTS) { - trap_BotLibUpdateEntity(i, NULL); - continue; - } -#ifdef MISSIONPACK - // never link prox mine triggers - if (ent->r.contents == CONTENTS_TRIGGER) { - if (ent->touch == ProximityMine_Trigger) { - trap_BotLibUpdateEntity(i, NULL); - continue; - } - } -#endif - // - memset(&state, 0, sizeof(bot_entitystate_t)); - // - VectorCopy(ent->r.currentOrigin, state.origin); - if (i < MAX_CLIENTS) { - VectorCopy(ent->s.apos.trBase, state.angles); - } else { - VectorCopy(ent->r.currentAngles, state.angles); - } - VectorCopy(ent->s.origin2, state.old_origin); - VectorCopy(ent->r.mins, state.mins); - VectorCopy(ent->r.maxs, state.maxs); - state.type = ent->s.eType; - state.flags = ent->s.eFlags; - if (ent->r.bmodel) state.solid = SOLID_BSP; - else state.solid = SOLID_BBOX; - state.groundent = ent->s.groundEntityNum; - state.modelindex = ent->s.modelindex; - state.modelindex2 = ent->s.modelindex2; - state.frame = ent->s.frame; - state.event = ent->s.event; - state.eventParm = ent->s.eventParm; - state.powerups = ent->s.powerups; - state.legsAnim = ent->s.legsAnim; - state.torsoAnim = ent->s.torsoAnim; - state.weapon = ent->s.weapon; - // - trap_BotLibUpdateEntity(i, &state); - } - - BotAIRegularUpdate(); - } - - floattime = trap_AAS_Time(); - - // execute scheduled bot AI - for( i = 0; i < MAX_CLIENTS; i++ ) { - if( !botstates[i] || !botstates[i]->inuse ) { - continue; - } - // - botstates[i]->botthink_residual += elapsed_time; - // - if ( botstates[i]->botthink_residual >= thinktime ) { - botstates[i]->botthink_residual -= thinktime; - - if (!trap_AAS_Initialized()) return qfalse; - - if (g_entities[i].client->pers.connected == CON_CONNECTED) { - BotAI(i, (float) thinktime / 1000); - } - } - } - - - // execute bot user commands every frame - for( i = 0; i < MAX_CLIENTS; i++ ) { - if( !botstates[i] || !botstates[i]->inuse ) { - continue; - } - if( g_entities[i].client->pers.connected != CON_CONNECTED ) { - continue; - } - - BotUpdateInput(botstates[i], time, elapsed_time); - trap_BotUserCommand(botstates[i]->client, &botstates[i]->lastucmd); - } - - return qtrue; -} - -/* -============== -BotInitLibrary -============== -*/ -int BotInitLibrary(void) { - char buf[144]; - - //set the maxclients and maxentities library variables before calling BotSetupLibrary - trap_Cvar_VariableStringBuffer("sv_maxclients", buf, sizeof(buf)); - if (!strlen(buf)) strcpy(buf, "8"); - trap_BotLibVarSet("maxclients", buf); - Com_sprintf(buf, sizeof(buf), "%d", MAX_GENTITIES); - trap_BotLibVarSet("maxentities", buf); - //bsp checksum - trap_Cvar_VariableStringBuffer("sv_mapChecksum", buf, sizeof(buf)); - if (strlen(buf)) trap_BotLibVarSet("sv_mapChecksum", buf); - //maximum number of aas links - trap_Cvar_VariableStringBuffer("max_aaslinks", buf, sizeof(buf)); - if (strlen(buf)) trap_BotLibVarSet("max_aaslinks", buf); - //maximum number of items in a level - trap_Cvar_VariableStringBuffer("max_levelitems", buf, sizeof(buf)); - if (strlen(buf)) trap_BotLibVarSet("max_levelitems", buf); - //game type - trap_Cvar_VariableStringBuffer("g_gametype", buf, sizeof(buf)); - if (!strlen(buf)) strcpy(buf, "0"); - trap_BotLibVarSet("g_gametype", buf); - //bot developer mode and log file - trap_BotLibVarSet("bot_developer", bot_developer.string); - trap_BotLibVarSet("log", buf); - //no chatting - trap_Cvar_VariableStringBuffer("bot_nochat", buf, sizeof(buf)); - if (strlen(buf)) trap_BotLibVarSet("nochat", "0"); - //visualize jump pads - trap_Cvar_VariableStringBuffer("bot_visualizejumppads", buf, sizeof(buf)); - if (strlen(buf)) trap_BotLibVarSet("bot_visualizejumppads", buf); - //forced clustering calculations - trap_Cvar_VariableStringBuffer("bot_forceclustering", buf, sizeof(buf)); - if (strlen(buf)) trap_BotLibVarSet("forceclustering", buf); - //forced reachability calculations - trap_Cvar_VariableStringBuffer("bot_forcereachability", buf, sizeof(buf)); - if (strlen(buf)) trap_BotLibVarSet("forcereachability", buf); - //force writing of AAS to file - trap_Cvar_VariableStringBuffer("bot_forcewrite", buf, sizeof(buf)); - if (strlen(buf)) trap_BotLibVarSet("forcewrite", buf); - //no AAS optimization - trap_Cvar_VariableStringBuffer("bot_aasoptimize", buf, sizeof(buf)); - if (strlen(buf)) trap_BotLibVarSet("aasoptimize", buf); - // - trap_Cvar_VariableStringBuffer("bot_saveroutingcache", buf, sizeof(buf)); - if (strlen(buf)) trap_BotLibVarSet("saveroutingcache", buf); - //reload instead of cache bot character files - trap_Cvar_VariableStringBuffer("bot_reloadcharacters", buf, sizeof(buf)); - if (!strlen(buf)) strcpy(buf, "0"); - trap_BotLibVarSet("bot_reloadcharacters", buf); - //base directory - trap_Cvar_VariableStringBuffer("fs_basepath", buf, sizeof(buf)); - if (strlen(buf)) trap_BotLibVarSet("basedir", buf); - //game directory - trap_Cvar_VariableStringBuffer("fs_game", buf, sizeof(buf)); - if (strlen(buf)) trap_BotLibVarSet("gamedir", buf); - //cd directory - trap_Cvar_VariableStringBuffer("fs_cdpath", buf, sizeof(buf)); - if (strlen(buf)) trap_BotLibVarSet("cddir", buf); - // -#ifdef MISSIONPACK - trap_BotLibDefine("MISSIONPACK"); -#endif - //setup the bot library - return trap_BotLibSetup(); -} - -/* -============== -BotAISetup -============== -*/ -int BotAISetup( int restart ) { - int errnum; - - trap_Cvar_Register(&bot_thinktime, "bot_thinktime", "100", CVAR_CHEAT); - trap_Cvar_Register(&bot_memorydump, "bot_memorydump", "0", CVAR_CHEAT); - trap_Cvar_Register(&bot_saveroutingcache, "bot_saveroutingcache", "0", CVAR_CHEAT); - trap_Cvar_Register(&bot_pause, "bot_pause", "0", CVAR_CHEAT); - trap_Cvar_Register(&bot_report, "bot_report", "0", CVAR_CHEAT); - trap_Cvar_Register(&bot_testsolid, "bot_testsolid", "0", CVAR_CHEAT); - trap_Cvar_Register(&bot_testclusters, "bot_testclusters", "0", CVAR_CHEAT); - trap_Cvar_Register(&bot_developer, "bot_developer", "0", CVAR_CHEAT); - trap_Cvar_Register(&bot_interbreedchar, "bot_interbreedchar", "", 0); - trap_Cvar_Register(&bot_interbreedbots, "bot_interbreedbots", "10", 0); - trap_Cvar_Register(&bot_interbreedcycle, "bot_interbreedcycle", "20", 0); - trap_Cvar_Register(&bot_interbreedwrite, "bot_interbreedwrite", "", 0); - - //if the game is restarted for a tournament - if (restart) { - return qtrue; - } - - //initialize the bot states - memset( botstates, 0, sizeof(botstates) ); - - errnum = BotInitLibrary(); - if (errnum != BLERR_NOERROR) return qfalse; - return qtrue; -} - -/* -============== -BotAIShutdown -============== -*/ -int BotAIShutdown( int restart ) { - - int i; - - //if the game is restarted for a tournament - if ( restart ) { - //shutdown all the bots in the botlib - for (i = 0; i < MAX_CLIENTS; i++) { - if (botstates[i] && botstates[i]->inuse) { - BotAIShutdownClient(botstates[i]->client, restart); - } - } - //don't shutdown the bot library - } - else { - trap_BotLibShutdown(); - } - return qtrue; -} - +/* +=========================================================================== +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 Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// + +/***************************************************************************** + * name: ai_main.c + * + * desc: Quake3 bot AI + * + * $Archive: /MissionPack/code/game/ai_main.c $ + * + *****************************************************************************/ + + +#include "g_local.h" +#include "q_shared.h" +#include "botlib.h" //bot lib interface +#include "be_aas.h" +#include "be_ea.h" +#include "be_ai_char.h" +#include "be_ai_chat.h" +#include "be_ai_gen.h" +#include "be_ai_goal.h" +#include "be_ai_move.h" +#include "be_ai_weap.h" +// +#include "ai_main.h" +#include "ai_dmq3.h" +#include "ai_chat.h" +#include "ai_cmd.h" +#include "ai_dmnet.h" +#include "ai_vcmd.h" + +// +#include "chars.h" +#include "inv.h" +#include "syn.h" + +#define MAX_PATH 144 + + +//bot states +bot_state_t *botstates[MAX_CLIENTS]; +//number of bots +int numbots; +//floating point time +float floattime; +//time to do a regular update +float regularupdate_time; +// +int bot_interbreed; +int bot_interbreedmatchcount; +// +vmCvar_t bot_thinktime; +vmCvar_t bot_memorydump; +vmCvar_t bot_saveroutingcache; +vmCvar_t bot_pause; +vmCvar_t bot_report; +vmCvar_t bot_testsolid; +vmCvar_t bot_testclusters; +vmCvar_t bot_developer; +vmCvar_t bot_interbreedchar; +vmCvar_t bot_interbreedbots; +vmCvar_t bot_interbreedcycle; +vmCvar_t bot_interbreedwrite; + + +void ExitLevel( void ); + + +/* +================== +BotAI_Print +================== +*/ +void QDECL BotAI_Print(int type, char *fmt, ...) { + char str[2048]; + va_list ap; + + va_start(ap, fmt); + vsprintf(str, fmt, ap); + va_end(ap); + + switch(type) { + case PRT_MESSAGE: { + G_Printf("%s", str); + break; + } + case PRT_WARNING: { + G_Printf( S_COLOR_YELLOW "Warning: %s", str ); + break; + } + case PRT_ERROR: { + G_Printf( S_COLOR_RED "Error: %s", str ); + break; + } + case PRT_FATAL: { + G_Printf( S_COLOR_RED "Fatal: %s", str ); + break; + } + case PRT_EXIT: { + G_Error( S_COLOR_RED "Exit: %s", str ); + break; + } + default: { + G_Printf( "unknown print type\n" ); + break; + } + } +} + + +/* +================== +BotAI_Trace +================== +*/ +void BotAI_Trace(bsp_trace_t *bsptrace, vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, int passent, int contentmask) { + trace_t trace; + + trap_Trace(&trace, start, mins, maxs, end, passent, contentmask); + //copy the trace information + bsptrace->allsolid = trace.allsolid; + bsptrace->startsolid = trace.startsolid; + bsptrace->fraction = trace.fraction; + VectorCopy(trace.endpos, bsptrace->endpos); + bsptrace->plane.dist = trace.plane.dist; + VectorCopy(trace.plane.normal, bsptrace->plane.normal); + bsptrace->plane.signbits = trace.plane.signbits; + bsptrace->plane.type = trace.plane.type; + bsptrace->surface.value = trace.surfaceFlags; + bsptrace->ent = trace.entityNum; + bsptrace->exp_dist = 0; + bsptrace->sidenum = 0; + bsptrace->contents = 0; +} + +/* +================== +BotAI_GetClientState +================== +*/ +int BotAI_GetClientState( int clientNum, playerState_t *state ) { + gentity_t *ent; + + ent = &g_entities[clientNum]; + if ( !ent->inuse ) { + return qfalse; + } + if ( !ent->client ) { + return qfalse; + } + + memcpy( state, &ent->client->ps, sizeof(playerState_t) ); + return qtrue; +} + +/* +================== +BotAI_GetEntityState +================== +*/ +int BotAI_GetEntityState( int entityNum, entityState_t *state ) { + gentity_t *ent; + + ent = &g_entities[entityNum]; + memset( state, 0, sizeof(entityState_t) ); + if (!ent->inuse) return qfalse; + if (!ent->r.linked) return qfalse; + if (ent->r.svFlags & SVF_NOCLIENT) return qfalse; + memcpy( state, &ent->s, sizeof(entityState_t) ); + return qtrue; +} + +/* +================== +BotAI_GetSnapshotEntity +================== +*/ +int BotAI_GetSnapshotEntity( int clientNum, int sequence, entityState_t *state ) { + int entNum; + + entNum = trap_BotGetSnapshotEntity( clientNum, sequence ); + if ( entNum == -1 ) { + memset(state, 0, sizeof(entityState_t)); + return -1; + } + + BotAI_GetEntityState( entNum, state ); + + return sequence + 1; +} + +/* +================== +BotAI_BotInitialChat +================== +*/ +void QDECL BotAI_BotInitialChat( bot_state_t *bs, char *type, ... ) { + int i, mcontext; + va_list ap; + char *p; + char *vars[MAX_MATCHVARIABLES]; + + memset(vars, 0, sizeof(vars)); + va_start(ap, type); + p = va_arg(ap, char *); + for (i = 0; i < MAX_MATCHVARIABLES; i++) { + if( !p ) { + break; + } + vars[i] = p; + p = va_arg(ap, char *); + } + va_end(ap); + + mcontext = BotSynonymContext(bs); + + trap_BotInitialChat( bs->cs, type, mcontext, vars[0], vars[1], vars[2], vars[3], vars[4], vars[5], vars[6], vars[7] ); +} + + +/* +================== +BotTestAAS +================== +*/ +void BotTestAAS(vec3_t origin) { + int areanum; + aas_areainfo_t info; + + trap_Cvar_Update(&bot_testsolid); + trap_Cvar_Update(&bot_testclusters); + if (bot_testsolid.integer) { + if (!trap_AAS_Initialized()) return; + areanum = BotPointAreaNum(origin); + if (areanum) BotAI_Print(PRT_MESSAGE, "\remtpy area"); + else BotAI_Print(PRT_MESSAGE, "\r^1SOLID area"); + } + else if (bot_testclusters.integer) { + if (!trap_AAS_Initialized()) return; + areanum = BotPointAreaNum(origin); + if (!areanum) + BotAI_Print(PRT_MESSAGE, "\r^1Solid! "); + else { + trap_AAS_AreaInfo(areanum, &info); + BotAI_Print(PRT_MESSAGE, "\rarea %d, cluster %d ", areanum, info.cluster); + } + } +} + +/* +================== +BotReportStatus +================== +*/ +void BotReportStatus(bot_state_t *bs) { + char goalname[MAX_MESSAGE_SIZE]; + char netname[MAX_MESSAGE_SIZE]; + char *leader, flagstatus[32]; + // + ClientName(bs->client, netname, sizeof(netname)); + if (Q_stricmp(netname, bs->teamleader) == 0) leader = "L"; + else leader = " "; + + strcpy(flagstatus, " "); + if (gametype == GT_CTF) { + if (BotCTFCarryingFlag(bs)) { + if (BotTeam(bs) == TEAM_RED) strcpy(flagstatus, S_COLOR_RED"F "); + else strcpy(flagstatus, S_COLOR_BLUE"F "); + } + } +#ifdef MISSIONPACK + else if (gametype == GT_1FCTF) { + if (Bot1FCTFCarryingFlag(bs)) { + if (BotTeam(bs) == TEAM_RED) strcpy(flagstatus, S_COLOR_RED"F "); + else strcpy(flagstatus, S_COLOR_BLUE"F "); + } + } + else if (gametype == GT_HARVESTER) { + if (BotHarvesterCarryingCubes(bs)) { + if (BotTeam(bs) == TEAM_RED) Com_sprintf(flagstatus, sizeof(flagstatus), S_COLOR_RED"%2d", bs->inventory[INVENTORY_REDCUBE]); + else Com_sprintf(flagstatus, sizeof(flagstatus), S_COLOR_BLUE"%2d", bs->inventory[INVENTORY_BLUECUBE]); + } + } +#endif + + switch(bs->ltgtype) { + case LTG_TEAMHELP: + { + EasyClientName(bs->teammate, goalname, sizeof(goalname)); + BotAI_Print(PRT_MESSAGE, "%-20s%s%s: helping %s\n", netname, leader, flagstatus, goalname); + break; + } + case LTG_TEAMACCOMPANY: + { + EasyClientName(bs->teammate, goalname, sizeof(goalname)); + BotAI_Print(PRT_MESSAGE, "%-20s%s%s: accompanying %s\n", netname, leader, flagstatus, goalname); + break; + } + case LTG_DEFENDKEYAREA: + { + trap_BotGoalName(bs->teamgoal.number, goalname, sizeof(goalname)); + BotAI_Print(PRT_MESSAGE, "%-20s%s%s: defending %s\n", netname, leader, flagstatus, goalname); + break; + } + case LTG_GETITEM: + { + trap_BotGoalName(bs->teamgoal.number, goalname, sizeof(goalname)); + BotAI_Print(PRT_MESSAGE, "%-20s%s%s: getting item %s\n", netname, leader, flagstatus, goalname); + break; + } + case LTG_KILL: + { + ClientName(bs->teamgoal.entitynum, goalname, sizeof(goalname)); + BotAI_Print(PRT_MESSAGE, "%-20s%s%s: killing %s\n", netname, leader, flagstatus, goalname); + break; + } + case LTG_CAMP: + case LTG_CAMPORDER: + { + BotAI_Print(PRT_MESSAGE, "%-20s%s%s: camping\n", netname, leader, flagstatus); + break; + } + case LTG_PATROL: + { + BotAI_Print(PRT_MESSAGE, "%-20s%s%s: patrolling\n", netname, leader, flagstatus); + break; + } + case LTG_GETFLAG: + { + BotAI_Print(PRT_MESSAGE, "%-20s%s%s: capturing flag\n", netname, leader, flagstatus); + break; + } + case LTG_RUSHBASE: + { + BotAI_Print(PRT_MESSAGE, "%-20s%s%s: rushing base\n", netname, leader, flagstatus); + break; + } + case LTG_RETURNFLAG: + { + BotAI_Print(PRT_MESSAGE, "%-20s%s%s: returning flag\n", netname, leader, flagstatus); + break; + } + case LTG_ATTACKENEMYBASE: + { + BotAI_Print(PRT_MESSAGE, "%-20s%s%s: attacking the enemy base\n", netname, leader, flagstatus); + break; + } + case LTG_HARVEST: + { + BotAI_Print(PRT_MESSAGE, "%-20s%s%s: harvesting\n", netname, leader, flagstatus); + break; + } + default: + { + BotAI_Print(PRT_MESSAGE, "%-20s%s%s: roaming\n", netname, leader, flagstatus); + break; + } + } +} + +/* +================== +BotTeamplayReport +================== +*/ +void BotTeamplayReport(void) { + int i; + char buf[MAX_INFO_STRING]; + + BotAI_Print(PRT_MESSAGE, S_COLOR_RED"RED\n"); + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + // + if ( !botstates[i] || !botstates[i]->inuse ) continue; + // + trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); + //if no config string or no name + if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue; + //skip spectators + if (atoi(Info_ValueForKey(buf, "t")) == TEAM_RED) { + BotReportStatus(botstates[i]); + } + } + BotAI_Print(PRT_MESSAGE, S_COLOR_BLUE"BLUE\n"); + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + // + if ( !botstates[i] || !botstates[i]->inuse ) continue; + // + trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); + //if no config string or no name + if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue; + //skip spectators + if (atoi(Info_ValueForKey(buf, "t")) == TEAM_BLUE) { + BotReportStatus(botstates[i]); + } + } +} + +/* +================== +BotSetInfoConfigString +================== +*/ +void BotSetInfoConfigString(bot_state_t *bs) { + char goalname[MAX_MESSAGE_SIZE]; + char netname[MAX_MESSAGE_SIZE]; + char action[MAX_MESSAGE_SIZE]; + char *leader, carrying[32], *cs; + bot_goal_t goal; + // + ClientName(bs->client, netname, sizeof(netname)); + if (Q_stricmp(netname, bs->teamleader) == 0) leader = "L"; + else leader = " "; + + strcpy(carrying, " "); + if (gametype == GT_CTF) { + if (BotCTFCarryingFlag(bs)) { + strcpy(carrying, "F "); + } + } +#ifdef MISSIONPACK + else if (gametype == GT_1FCTF) { + if (Bot1FCTFCarryingFlag(bs)) { + strcpy(carrying, "F "); + } + } + else if (gametype == GT_HARVESTER) { + if (BotHarvesterCarryingCubes(bs)) { + if (BotTeam(bs) == TEAM_RED) Com_sprintf(carrying, sizeof(carrying), "%2d", bs->inventory[INVENTORY_REDCUBE]); + else Com_sprintf(carrying, sizeof(carrying), "%2d", bs->inventory[INVENTORY_BLUECUBE]); + } + } +#endif + + switch(bs->ltgtype) { + case LTG_TEAMHELP: + { + EasyClientName(bs->teammate, goalname, sizeof(goalname)); + Com_sprintf(action, sizeof(action), "helping %s", goalname); + break; + } + case LTG_TEAMACCOMPANY: + { + EasyClientName(bs->teammate, goalname, sizeof(goalname)); + Com_sprintf(action, sizeof(action), "accompanying %s", goalname); + break; + } + case LTG_DEFENDKEYAREA: + { + trap_BotGoalName(bs->teamgoal.number, goalname, sizeof(goalname)); + Com_sprintf(action, sizeof(action), "defending %s", goalname); + break; + } + case LTG_GETITEM: + { + trap_BotGoalName(bs->teamgoal.number, goalname, sizeof(goalname)); + Com_sprintf(action, sizeof(action), "getting item %s", goalname); + break; + } + case LTG_KILL: + { + ClientName(bs->teamgoal.entitynum, goalname, sizeof(goalname)); + Com_sprintf(action, sizeof(action), "killing %s", goalname); + break; + } + case LTG_CAMP: + case LTG_CAMPORDER: + { + Com_sprintf(action, sizeof(action), "camping"); + break; + } + case LTG_PATROL: + { + Com_sprintf(action, sizeof(action), "patrolling"); + break; + } + case LTG_GETFLAG: + { + Com_sprintf(action, sizeof(action), "capturing flag"); + break; + } + case LTG_RUSHBASE: + { + Com_sprintf(action, sizeof(action), "rushing base"); + break; + } + case LTG_RETURNFLAG: + { + Com_sprintf(action, sizeof(action), "returning flag"); + break; + } + case LTG_ATTACKENEMYBASE: + { + Com_sprintf(action, sizeof(action), "attacking the enemy base"); + break; + } + case LTG_HARVEST: + { + Com_sprintf(action, sizeof(action), "harvesting"); + break; + } + default: + { + trap_BotGetTopGoal(bs->gs, &goal); + trap_BotGoalName(goal.number, goalname, sizeof(goalname)); + Com_sprintf(action, sizeof(action), "roaming %s", goalname); + break; + } + } + cs = va("l\\%s\\c\\%s\\a\\%s", + leader, + carrying, + action); + trap_SetConfigstring (CS_BOTINFO + bs->client, cs); +} + +/* +============== +BotUpdateInfoConfigStrings +============== +*/ +void BotUpdateInfoConfigStrings(void) { + int i; + char buf[MAX_INFO_STRING]; + + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + // + if ( !botstates[i] || !botstates[i]->inuse ) + continue; + // + trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); + //if no config string or no name + if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) + continue; + BotSetInfoConfigString(botstates[i]); + } +} + +/* +============== +BotInterbreedBots +============== +*/ +void BotInterbreedBots(void) { + float ranks[MAX_CLIENTS]; + int parent1, parent2, child; + int i; + + // get rankings for all the bots + for (i = 0; i < MAX_CLIENTS; i++) { + if ( botstates[i] && botstates[i]->inuse ) { + ranks[i] = botstates[i]->num_kills * 2 - botstates[i]->num_deaths; + } + else { + ranks[i] = -1; + } + } + + if (trap_GeneticParentsAndChildSelection(MAX_CLIENTS, ranks, &parent1, &parent2, &child)) { + trap_BotInterbreedGoalFuzzyLogic(botstates[parent1]->gs, botstates[parent2]->gs, botstates[child]->gs); + trap_BotMutateGoalFuzzyLogic(botstates[child]->gs, 1); + } + // reset the kills and deaths + for (i = 0; i < MAX_CLIENTS; i++) { + if (botstates[i] && botstates[i]->inuse) { + botstates[i]->num_kills = 0; + botstates[i]->num_deaths = 0; + } + } +} + +/* +============== +BotWriteInterbreeded +============== +*/ +void BotWriteInterbreeded(char *filename) { + float rank, bestrank; + int i, bestbot; + + bestrank = 0; + bestbot = -1; + // get the best bot + for (i = 0; i < MAX_CLIENTS; i++) { + if ( botstates[i] && botstates[i]->inuse ) { + rank = botstates[i]->num_kills * 2 - botstates[i]->num_deaths; + } + else { + rank = -1; + } + if (rank > bestrank) { + bestrank = rank; + bestbot = i; + } + } + if (bestbot >= 0) { + //write out the new goal fuzzy logic + trap_BotSaveGoalFuzzyLogic(botstates[bestbot]->gs, filename); + } +} + +/* +============== +BotInterbreedEndMatch + +add link back into ExitLevel? +============== +*/ +void BotInterbreedEndMatch(void) { + + if (!bot_interbreed) return; + bot_interbreedmatchcount++; + if (bot_interbreedmatchcount >= bot_interbreedcycle.integer) { + bot_interbreedmatchcount = 0; + // + trap_Cvar_Update(&bot_interbreedwrite); + if (strlen(bot_interbreedwrite.string)) { + BotWriteInterbreeded(bot_interbreedwrite.string); + trap_Cvar_Set("bot_interbreedwrite", ""); + } + BotInterbreedBots(); + } +} + +/* +============== +BotInterbreeding +============== +*/ +void BotInterbreeding(void) { + int i; + + trap_Cvar_Update(&bot_interbreedchar); + if (!strlen(bot_interbreedchar.string)) return; + //make sure we are in tournament mode + if (gametype != GT_TOURNAMENT) { + trap_Cvar_Set("g_gametype", va("%d", GT_TOURNAMENT)); + ExitLevel(); + return; + } + //shutdown all the bots + for (i = 0; i < MAX_CLIENTS; i++) { + if (botstates[i] && botstates[i]->inuse) { + BotAIShutdownClient(botstates[i]->client, qfalse); + } + } + //make sure all item weight configs are reloaded and Not shared + trap_BotLibVarSet("bot_reloadcharacters", "1"); + //add a number of bots using the desired bot character + for (i = 0; i < bot_interbreedbots.integer; i++) { + trap_SendConsoleCommand( EXEC_INSERT, va("addbot %s 4 free %i %s%d\n", + bot_interbreedchar.string, i * 50, bot_interbreedchar.string, i) ); + } + // + trap_Cvar_Set("bot_interbreedchar", ""); + bot_interbreed = qtrue; +} + +/* +============== +BotEntityInfo +============== +*/ +void BotEntityInfo(int entnum, aas_entityinfo_t *info) { + trap_AAS_EntityInfo(entnum, info); +} + +/* +============== +NumBots +============== +*/ +int NumBots(void) { + return numbots; +} + +/* +============== +BotTeamLeader +============== +*/ +int BotTeamLeader(bot_state_t *bs) { + int leader; + + leader = ClientFromName(bs->teamleader); + if (leader < 0) return qfalse; + if (!botstates[leader] || !botstates[leader]->inuse) return qfalse; + return qtrue; +} + +/* +============== +AngleDifference +============== +*/ +float AngleDifference(float ang1, float ang2) { + float diff; + + diff = ang1 - ang2; + if (ang1 > ang2) { + if (diff > 180.0) diff -= 360.0; + } + else { + if (diff < -180.0) diff += 360.0; + } + return diff; +} + +/* +============== +BotChangeViewAngle +============== +*/ +float BotChangeViewAngle(float angle, float ideal_angle, float speed) { + float move; + + angle = AngleMod(angle); + ideal_angle = AngleMod(ideal_angle); + if (angle == ideal_angle) return angle; + move = ideal_angle - angle; + if (ideal_angle > angle) { + if (move > 180.0) move -= 360.0; + } + else { + if (move < -180.0) move += 360.0; + } + if (move > 0) { + if (move > speed) move = speed; + } + else { + if (move < -speed) move = -speed; + } + return AngleMod(angle + move); +} + +/* +============== +BotChangeViewAngles +============== +*/ +void BotChangeViewAngles(bot_state_t *bs, float thinktime) { + float diff, factor, maxchange, anglespeed, disired_speed; + int i; + + if (bs->ideal_viewangles[PITCH] > 180) bs->ideal_viewangles[PITCH] -= 360; + // + if (bs->enemy >= 0) { + factor = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_VIEW_FACTOR, 0.01f, 1); + maxchange = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_VIEW_MAXCHANGE, 1, 1800); + } + else { + factor = 0.05f; + maxchange = 360; + } + if (maxchange < 240) maxchange = 240; + maxchange *= thinktime; + for (i = 0; i < 2; i++) { + // + if (bot_challenge.integer) { + //smooth slowdown view model + diff = abs(AngleDifference(bs->viewangles[i], bs->ideal_viewangles[i])); + anglespeed = diff * factor; + if (anglespeed > maxchange) anglespeed = maxchange; + bs->viewangles[i] = BotChangeViewAngle(bs->viewangles[i], + bs->ideal_viewangles[i], anglespeed); + } + else { + //over reaction view model + bs->viewangles[i] = AngleMod(bs->viewangles[i]); + bs->ideal_viewangles[i] = AngleMod(bs->ideal_viewangles[i]); + diff = AngleDifference(bs->viewangles[i], bs->ideal_viewangles[i]); + disired_speed = diff * factor; + bs->viewanglespeed[i] += (bs->viewanglespeed[i] - disired_speed); + if (bs->viewanglespeed[i] > 180) bs->viewanglespeed[i] = maxchange; + if (bs->viewanglespeed[i] < -180) bs->viewanglespeed[i] = -maxchange; + anglespeed = bs->viewanglespeed[i]; + if (anglespeed > maxchange) anglespeed = maxchange; + if (anglespeed < -maxchange) anglespeed = -maxchange; + bs->viewangles[i] += anglespeed; + bs->viewangles[i] = AngleMod(bs->viewangles[i]); + //demping + bs->viewanglespeed[i] *= 0.45 * (1 - factor); + } + //BotAI_Print(PRT_MESSAGE, "ideal_angles %f %f\n", bs->ideal_viewangles[0], bs->ideal_viewangles[1], bs->ideal_viewangles[2]);` + //bs->viewangles[i] = bs->ideal_viewangles[i]; + } + //bs->viewangles[PITCH] = 0; + if (bs->viewangles[PITCH] > 180) bs->viewangles[PITCH] -= 360; + //elementary action: view + trap_EA_View(bs->client, bs->viewangles); +} + +/* +============== +BotInputToUserCommand +============== +*/ +void BotInputToUserCommand(bot_input_t *bi, usercmd_t *ucmd, int delta_angles[3], int time) { + vec3_t angles, forward, right; + short temp; + int j; + + //clear the whole structure + memset(ucmd, 0, sizeof(usercmd_t)); + // + //Com_Printf("dir = %f %f %f speed = %f\n", bi->dir[0], bi->dir[1], bi->dir[2], bi->speed); + //the duration for the user command in milli seconds + ucmd->serverTime = time; + // + if (bi->actionflags & ACTION_DELAYEDJUMP) { + bi->actionflags |= ACTION_JUMP; + bi->actionflags &= ~ACTION_DELAYEDJUMP; + } + //set the buttons + if (bi->actionflags & ACTION_RESPAWN) ucmd->buttons = BUTTON_ATTACK; + if (bi->actionflags & ACTION_ATTACK) ucmd->buttons |= BUTTON_ATTACK; + if (bi->actionflags & ACTION_TALK) ucmd->buttons |= BUTTON_TALK; + if (bi->actionflags & ACTION_GESTURE) ucmd->buttons |= BUTTON_GESTURE; + if (bi->actionflags & ACTION_USE) ucmd->buttons |= BUTTON_USE_HOLDABLE; + if (bi->actionflags & ACTION_WALK) ucmd->buttons |= BUTTON_WALKING; + if (bi->actionflags & ACTION_AFFIRMATIVE) ucmd->buttons |= BUTTON_AFFIRMATIVE; + if (bi->actionflags & ACTION_NEGATIVE) ucmd->buttons |= BUTTON_NEGATIVE; + if (bi->actionflags & ACTION_GETFLAG) ucmd->buttons |= BUTTON_GETFLAG; + if (bi->actionflags & ACTION_GUARDBASE) ucmd->buttons |= BUTTON_GUARDBASE; + if (bi->actionflags & ACTION_PATROL) ucmd->buttons |= BUTTON_PATROL; + if (bi->actionflags & ACTION_FOLLOWME) ucmd->buttons |= BUTTON_FOLLOWME; + // + ucmd->weapon = bi->weapon; + //set the view angles + //NOTE: the ucmd->angles are the angles WITHOUT the delta angles + ucmd->angles[PITCH] = ANGLE2SHORT(bi->viewangles[PITCH]); + ucmd->angles[YAW] = ANGLE2SHORT(bi->viewangles[YAW]); + ucmd->angles[ROLL] = ANGLE2SHORT(bi->viewangles[ROLL]); + //subtract the delta angles + for (j = 0; j < 3; j++) { + temp = ucmd->angles[j] - delta_angles[j]; + /*NOTE: disabled because temp should be mod first + if ( j == PITCH ) { + // don't let the player look up or down more than 90 degrees + if ( temp > 16000 ) temp = 16000; + else if ( temp < -16000 ) temp = -16000; + } + */ + ucmd->angles[j] = temp; + } + //NOTE: movement is relative to the REAL view angles + //get the horizontal forward and right vector + //get the pitch in the range [-180, 180] + if (bi->dir[2]) angles[PITCH] = bi->viewangles[PITCH]; + else angles[PITCH] = 0; + angles[YAW] = bi->viewangles[YAW]; + angles[ROLL] = 0; + AngleVectors(angles, forward, right, NULL); + //bot input speed is in the range [0, 400] + bi->speed = bi->speed * 127 / 400; + //set the view independent movement + ucmd->forwardmove = DotProduct(forward, bi->dir) * bi->speed; + ucmd->rightmove = DotProduct(right, bi->dir) * bi->speed; + ucmd->upmove = abs(forward[2]) * bi->dir[2] * bi->speed; + //normal keyboard movement + if (bi->actionflags & ACTION_MOVEFORWARD) ucmd->forwardmove += 127; + if (bi->actionflags & ACTION_MOVEBACK) ucmd->forwardmove -= 127; + if (bi->actionflags & ACTION_MOVELEFT) ucmd->rightmove -= 127; + if (bi->actionflags & ACTION_MOVERIGHT) ucmd->rightmove += 127; + //jump/moveup + if (bi->actionflags & ACTION_JUMP) ucmd->upmove += 127; + //crouch/movedown + if (bi->actionflags & ACTION_CROUCH) ucmd->upmove -= 127; + // + //Com_Printf("forward = %d right = %d up = %d\n", ucmd.forwardmove, ucmd.rightmove, ucmd.upmove); + //Com_Printf("ucmd->serverTime = %d\n", ucmd->serverTime); +} + +/* +============== +BotUpdateInput +============== +*/ +void BotUpdateInput(bot_state_t *bs, int time, int elapsed_time) { + bot_input_t bi; + int j; + + //add the delta angles to the bot's current view angles + for (j = 0; j < 3; j++) { + bs->viewangles[j] = AngleMod(bs->viewangles[j] + SHORT2ANGLE(bs->cur_ps.delta_angles[j])); + } + //change the bot view angles + BotChangeViewAngles(bs, (float) elapsed_time / 1000); + //retrieve the bot input + trap_EA_GetInput(bs->client, (float) time / 1000, &bi); + //respawn hack + if (bi.actionflags & ACTION_RESPAWN) { + if (bs->lastucmd.buttons & BUTTON_ATTACK) bi.actionflags &= ~(ACTION_RESPAWN|ACTION_ATTACK); + } + //convert the bot input to a usercmd + BotInputToUserCommand(&bi, &bs->lastucmd, bs->cur_ps.delta_angles, time); + //subtract the delta angles + for (j = 0; j < 3; j++) { + bs->viewangles[j] = AngleMod(bs->viewangles[j] - SHORT2ANGLE(bs->cur_ps.delta_angles[j])); + } +} + +/* +============== +BotAIRegularUpdate +============== +*/ +void BotAIRegularUpdate(void) { + if (regularupdate_time < FloatTime()) { + trap_BotUpdateEntityItems(); + regularupdate_time = FloatTime() + 0.3; + } +} + +/* +============== +RemoveColorEscapeSequences +============== +*/ +void RemoveColorEscapeSequences( char *text ) { + int i, l; + + l = 0; + for ( i = 0; text[i]; i++ ) { + if (Q_IsColorString(&text[i])) { + i++; + continue; + } + if (text[i] > 0x7E) + continue; + text[l++] = text[i]; + } + text[l] = '\0'; +} + +/* +============== +BotAI +============== +*/ +int BotAI(int client, float thinktime) { + bot_state_t *bs; + char buf[1024], *args; + int j; + + trap_EA_ResetInput(client); + // + bs = botstates[client]; + if (!bs || !bs->inuse) { + BotAI_Print(PRT_FATAL, "BotAI: client %d is not setup\n", client); + return qfalse; + } + + //retrieve the current client state + BotAI_GetClientState( client, &bs->cur_ps ); + + //retrieve any waiting server commands + while( trap_BotGetServerCommand(client, buf, sizeof(buf)) ) { + //have buf point to the command and args to the command arguments + args = strchr( buf, ' '); + if (!args) continue; + *args++ = '\0'; + + //remove color espace sequences from the arguments + RemoveColorEscapeSequences( args ); + + if (!Q_stricmp(buf, "cp ")) + { /*CenterPrintf*/ } + else if (!Q_stricmp(buf, "cs")) + { /*ConfigStringModified*/ } + else if (!Q_stricmp(buf, "print")) { + //remove first and last quote from the chat message + memmove(args, args+1, strlen(args)); + args[strlen(args)-1] = '\0'; + trap_BotQueueConsoleMessage(bs->cs, CMS_NORMAL, args); + } + else if (!Q_stricmp(buf, "chat")) { + //remove first and last quote from the chat message + memmove(args, args+1, strlen(args)); + args[strlen(args)-1] = '\0'; + trap_BotQueueConsoleMessage(bs->cs, CMS_CHAT, args); + } + else if (!Q_stricmp(buf, "tchat")) { + //remove first and last quote from the chat message + memmove(args, args+1, strlen(args)); + args[strlen(args)-1] = '\0'; + trap_BotQueueConsoleMessage(bs->cs, CMS_CHAT, args); + } +#ifdef MISSIONPACK + else if (!Q_stricmp(buf, "vchat")) { + BotVoiceChatCommand(bs, SAY_ALL, args); + } + else if (!Q_stricmp(buf, "vtchat")) { + BotVoiceChatCommand(bs, SAY_TEAM, args); + } + else if (!Q_stricmp(buf, "vtell")) { + BotVoiceChatCommand(bs, SAY_TELL, args); + } +#endif + else if (!Q_stricmp(buf, "scores")) + { /*FIXME: parse scores?*/ } + else if (!Q_stricmp(buf, "clientLevelShot")) + { /*ignore*/ } + } + //add the delta angles to the bot's current view angles + for (j = 0; j < 3; j++) { + bs->viewangles[j] = AngleMod(bs->viewangles[j] + SHORT2ANGLE(bs->cur_ps.delta_angles[j])); + } + //increase the local time of the bot + bs->ltime += thinktime; + // + bs->thinktime = thinktime; + //origin of the bot + VectorCopy(bs->cur_ps.origin, bs->origin); + //eye coordinates of the bot + VectorCopy(bs->cur_ps.origin, bs->eye); + bs->eye[2] += bs->cur_ps.viewheight; + //get the area the bot is in + bs->areanum = BotPointAreaNum(bs->origin); + //the real AI + BotDeathmatchAI(bs, thinktime); + //set the weapon selection every AI frame + trap_EA_SelectWeapon(bs->client, bs->weaponnum); + //subtract the delta angles + for (j = 0; j < 3; j++) { + bs->viewangles[j] = AngleMod(bs->viewangles[j] - SHORT2ANGLE(bs->cur_ps.delta_angles[j])); + } + //everything was ok + return qtrue; +} + +/* +================== +BotScheduleBotThink +================== +*/ +void BotScheduleBotThink(void) { + int i, botnum; + + botnum = 0; + + for( i = 0; i < MAX_CLIENTS; i++ ) { + if( !botstates[i] || !botstates[i]->inuse ) { + continue; + } + //initialize the bot think residual time + botstates[i]->botthink_residual = bot_thinktime.integer * botnum / numbots; + botnum++; + } +} + +/* +============== +BotWriteSessionData +============== +*/ +void BotWriteSessionData(bot_state_t *bs) { + const char *s; + const char *var; + + s = va( + "%i %i %i %i %i %i %i %i" + " %f %f %f" + " %f %f %f" + " %f %f %f", + bs->lastgoal_decisionmaker, + bs->lastgoal_ltgtype, + bs->lastgoal_teammate, + bs->lastgoal_teamgoal.areanum, + bs->lastgoal_teamgoal.entitynum, + bs->lastgoal_teamgoal.flags, + bs->lastgoal_teamgoal.iteminfo, + bs->lastgoal_teamgoal.number, + bs->lastgoal_teamgoal.origin[0], + bs->lastgoal_teamgoal.origin[1], + bs->lastgoal_teamgoal.origin[2], + bs->lastgoal_teamgoal.mins[0], + bs->lastgoal_teamgoal.mins[1], + bs->lastgoal_teamgoal.mins[2], + bs->lastgoal_teamgoal.maxs[0], + bs->lastgoal_teamgoal.maxs[1], + bs->lastgoal_teamgoal.maxs[2] + ); + + var = va( "botsession%i", bs->client ); + + trap_Cvar_Set( var, s ); +} + +/* +============== +BotReadSessionData +============== +*/ +void BotReadSessionData(bot_state_t *bs) { + char s[MAX_STRING_CHARS]; + const char *var; + + var = va( "botsession%i", bs->client ); + trap_Cvar_VariableStringBuffer( var, s, sizeof(s) ); + + sscanf(s, + "%i %i %i %i %i %i %i %i" + " %f %f %f" + " %f %f %f" + " %f %f %f", + &bs->lastgoal_decisionmaker, + &bs->lastgoal_ltgtype, + &bs->lastgoal_teammate, + &bs->lastgoal_teamgoal.areanum, + &bs->lastgoal_teamgoal.entitynum, + &bs->lastgoal_teamgoal.flags, + &bs->lastgoal_teamgoal.iteminfo, + &bs->lastgoal_teamgoal.number, + &bs->lastgoal_teamgoal.origin[0], + &bs->lastgoal_teamgoal.origin[1], + &bs->lastgoal_teamgoal.origin[2], + &bs->lastgoal_teamgoal.mins[0], + &bs->lastgoal_teamgoal.mins[1], + &bs->lastgoal_teamgoal.mins[2], + &bs->lastgoal_teamgoal.maxs[0], + &bs->lastgoal_teamgoal.maxs[1], + &bs->lastgoal_teamgoal.maxs[2] + ); +} + +/* +============== +BotAISetupClient +============== +*/ +int BotAISetupClient(int client, struct bot_settings_s *settings, qboolean restart) { + char filename[MAX_PATH], name[MAX_PATH], gender[MAX_PATH]; + bot_state_t *bs; + int errnum; + + if (!botstates[client]) botstates[client] = G_Alloc(sizeof(bot_state_t)); + bs = botstates[client]; + + if (bs && bs->inuse) { + BotAI_Print(PRT_FATAL, "BotAISetupClient: client %d already setup\n", client); + return qfalse; + } + + if (!trap_AAS_Initialized()) { + BotAI_Print(PRT_FATAL, "AAS not initialized\n"); + return qfalse; + } + + //load the bot character + bs->character = trap_BotLoadCharacter(settings->characterfile, settings->skill); + if (!bs->character) { + BotAI_Print(PRT_FATAL, "couldn't load skill %f from %s\n", settings->skill, settings->characterfile); + return qfalse; + } + //copy the settings + memcpy(&bs->settings, settings, sizeof(bot_settings_t)); + //allocate a goal state + bs->gs = trap_BotAllocGoalState(client); + //load the item weights + trap_Characteristic_String(bs->character, CHARACTERISTIC_ITEMWEIGHTS, filename, MAX_PATH); + errnum = trap_BotLoadItemWeights(bs->gs, filename); + if (errnum != BLERR_NOERROR) { + trap_BotFreeGoalState(bs->gs); + return qfalse; + } + //allocate a weapon state + bs->ws = trap_BotAllocWeaponState(); + //load the weapon weights + trap_Characteristic_String(bs->character, CHARACTERISTIC_WEAPONWEIGHTS, filename, MAX_PATH); + errnum = trap_BotLoadWeaponWeights(bs->ws, filename); + if (errnum != BLERR_NOERROR) { + trap_BotFreeGoalState(bs->gs); + trap_BotFreeWeaponState(bs->ws); + return qfalse; + } + //allocate a chat state + bs->cs = trap_BotAllocChatState(); + //load the chat file + trap_Characteristic_String(bs->character, CHARACTERISTIC_CHAT_FILE, filename, MAX_PATH); + trap_Characteristic_String(bs->character, CHARACTERISTIC_CHAT_NAME, name, MAX_PATH); + errnum = trap_BotLoadChatFile(bs->cs, filename, name); + if (errnum != BLERR_NOERROR) { + trap_BotFreeChatState(bs->cs); + trap_BotFreeGoalState(bs->gs); + trap_BotFreeWeaponState(bs->ws); + return qfalse; + } + //get the gender characteristic + trap_Characteristic_String(bs->character, CHARACTERISTIC_GENDER, gender, MAX_PATH); + //set the chat gender + if (*gender == 'f' || *gender == 'F') trap_BotSetChatGender(bs->cs, CHAT_GENDERFEMALE); + else if (*gender == 'm' || *gender == 'M') trap_BotSetChatGender(bs->cs, CHAT_GENDERMALE); + else trap_BotSetChatGender(bs->cs, CHAT_GENDERLESS); + + bs->inuse = qtrue; + bs->client = client; + bs->entitynum = client; + bs->setupcount = 4; + bs->entergame_time = FloatTime(); + bs->ms = trap_BotAllocMoveState(); + bs->walker = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_WALKER, 0, 1); + numbots++; + + if (trap_Cvar_VariableIntegerValue("bot_testichat")) { + trap_BotLibVarSet("bot_testichat", "1"); + BotChatTest(bs); + } + //NOTE: reschedule the bot thinking + BotScheduleBotThink(); + //if interbreeding start with a mutation + if (bot_interbreed) { + trap_BotMutateGoalFuzzyLogic(bs->gs, 1); + } + // if we kept the bot client + if (restart) { + BotReadSessionData(bs); + } + //bot has been setup succesfully + return qtrue; +} + +/* +============== +BotAIShutdownClient +============== +*/ +int BotAIShutdownClient(int client, qboolean restart) { + bot_state_t *bs; + + bs = botstates[client]; + if (!bs || !bs->inuse) { + //BotAI_Print(PRT_ERROR, "BotAIShutdownClient: client %d already shutdown\n", client); + return qfalse; + } + + if (restart) { + BotWriteSessionData(bs); + } + + if (BotChat_ExitGame(bs)) { + trap_BotEnterChat(bs->cs, bs->client, CHAT_ALL); + } + + trap_BotFreeMoveState(bs->ms); + //free the goal state` + trap_BotFreeGoalState(bs->gs); + //free the chat file + trap_BotFreeChatState(bs->cs); + //free the weapon weights + trap_BotFreeWeaponState(bs->ws); + //free the bot character + trap_BotFreeCharacter(bs->character); + // + BotFreeWaypoints(bs->checkpoints); + BotFreeWaypoints(bs->patrolpoints); + //clear activate goal stack + BotClearActivateGoalStack(bs); + //clear the bot state + memset(bs, 0, sizeof(bot_state_t)); + //set the inuse flag to qfalse + bs->inuse = qfalse; + //there's one bot less + numbots--; + //everything went ok + return qtrue; +} + +/* +============== +BotResetState + +called when a bot enters the intermission or observer mode and +when the level is changed +============== +*/ +void BotResetState(bot_state_t *bs) { + int client, entitynum, inuse; + int movestate, goalstate, chatstate, weaponstate; + bot_settings_t settings; + int character; + playerState_t ps; //current player state + float entergame_time; + + //save some things that should not be reset here + memcpy(&settings, &bs->settings, sizeof(bot_settings_t)); + memcpy(&ps, &bs->cur_ps, sizeof(playerState_t)); + inuse = bs->inuse; + client = bs->client; + entitynum = bs->entitynum; + character = bs->character; + movestate = bs->ms; + goalstate = bs->gs; + chatstate = bs->cs; + weaponstate = bs->ws; + entergame_time = bs->entergame_time; + //free checkpoints and patrol points + BotFreeWaypoints(bs->checkpoints); + BotFreeWaypoints(bs->patrolpoints); + //reset the whole state + memset(bs, 0, sizeof(bot_state_t)); + //copy back some state stuff that should not be reset + bs->ms = movestate; + bs->gs = goalstate; + bs->cs = chatstate; + bs->ws = weaponstate; + memcpy(&bs->cur_ps, &ps, sizeof(playerState_t)); + memcpy(&bs->settings, &settings, sizeof(bot_settings_t)); + bs->inuse = inuse; + bs->client = client; + bs->entitynum = entitynum; + bs->character = character; + bs->entergame_time = entergame_time; + //reset several states + if (bs->ms) trap_BotResetMoveState(bs->ms); + if (bs->gs) trap_BotResetGoalState(bs->gs); + if (bs->ws) trap_BotResetWeaponState(bs->ws); + if (bs->gs) trap_BotResetAvoidGoals(bs->gs); + if (bs->ms) trap_BotResetAvoidReach(bs->ms); +} + +/* +============== +BotAILoadMap +============== +*/ +int BotAILoadMap( int restart ) { + int i; + vmCvar_t mapname; + + if (!restart) { + trap_Cvar_Register( &mapname, "mapname", "", CVAR_SERVERINFO | CVAR_ROM ); + trap_BotLibLoadMap( mapname.string ); + } + + for (i = 0; i < MAX_CLIENTS; i++) { + if (botstates[i] && botstates[i]->inuse) { + BotResetState( botstates[i] ); + botstates[i]->setupcount = 4; + } + } + + BotSetupDeathmatchAI(); + + return qtrue; +} + +#ifdef MISSIONPACK +void ProximityMine_Trigger( gentity_t *trigger, gentity_t *other, trace_t *trace ); +#endif + +/* +================== +BotAIStartFrame +================== +*/ +int BotAIStartFrame(int time) { + int i; + gentity_t *ent; + bot_entitystate_t state; + int elapsed_time, thinktime; + static int local_time; + static int botlib_residual; + static int lastbotthink_time; + + G_CheckBotSpawn(); + + trap_Cvar_Update(&bot_rocketjump); + trap_Cvar_Update(&bot_grapple); + trap_Cvar_Update(&bot_fastchat); + trap_Cvar_Update(&bot_nochat); + trap_Cvar_Update(&bot_testrchat); + trap_Cvar_Update(&bot_thinktime); + trap_Cvar_Update(&bot_memorydump); + trap_Cvar_Update(&bot_saveroutingcache); + trap_Cvar_Update(&bot_pause); + trap_Cvar_Update(&bot_report); + + if (bot_report.integer) { +// BotTeamplayReport(); +// trap_Cvar_Set("bot_report", "0"); + BotUpdateInfoConfigStrings(); + } + + if (bot_pause.integer) { + // execute bot user commands every frame + for( i = 0; i < MAX_CLIENTS; i++ ) { + if( !botstates[i] || !botstates[i]->inuse ) { + continue; + } + if( g_entities[i].client->pers.connected != CON_CONNECTED ) { + continue; + } + botstates[i]->lastucmd.forwardmove = 0; + botstates[i]->lastucmd.rightmove = 0; + botstates[i]->lastucmd.upmove = 0; + botstates[i]->lastucmd.buttons = 0; + botstates[i]->lastucmd.serverTime = time; + trap_BotUserCommand(botstates[i]->client, &botstates[i]->lastucmd); + } + return qtrue; + } + + if (bot_memorydump.integer) { + trap_BotLibVarSet("memorydump", "1"); + trap_Cvar_Set("bot_memorydump", "0"); + } + if (bot_saveroutingcache.integer) { + trap_BotLibVarSet("saveroutingcache", "1"); + trap_Cvar_Set("bot_saveroutingcache", "0"); + } + //check if bot interbreeding is activated + BotInterbreeding(); + //cap the bot think time + if (bot_thinktime.integer > 200) { + trap_Cvar_Set("bot_thinktime", "200"); + } + //if the bot think time changed we should reschedule the bots + if (bot_thinktime.integer != lastbotthink_time) { + lastbotthink_time = bot_thinktime.integer; + BotScheduleBotThink(); + } + + elapsed_time = time - local_time; + local_time = time; + + botlib_residual += elapsed_time; + + if (elapsed_time > bot_thinktime.integer) thinktime = elapsed_time; + else thinktime = bot_thinktime.integer; + + // update the bot library + if ( botlib_residual >= thinktime ) { + botlib_residual -= thinktime; + + trap_BotLibStartFrame((float) time / 1000); + + if (!trap_AAS_Initialized()) return qfalse; + + //update entities in the botlib + for (i = 0; i < MAX_GENTITIES; i++) { + ent = &g_entities[i]; + if (!ent->inuse) { + trap_BotLibUpdateEntity(i, NULL); + continue; + } + if (!ent->r.linked) { + trap_BotLibUpdateEntity(i, NULL); + continue; + } + if (ent->r.svFlags & SVF_NOCLIENT) { + trap_BotLibUpdateEntity(i, NULL); + continue; + } + // do not update missiles + if (ent->s.eType == ET_MISSILE && ent->s.weapon != WP_GRAPPLING_HOOK) { + trap_BotLibUpdateEntity(i, NULL); + continue; + } + // do not update event only entities + if (ent->s.eType > ET_EVENTS) { + trap_BotLibUpdateEntity(i, NULL); + continue; + } +#ifdef MISSIONPACK + // never link prox mine triggers + if (ent->r.contents == CONTENTS_TRIGGER) { + if (ent->touch == ProximityMine_Trigger) { + trap_BotLibUpdateEntity(i, NULL); + continue; + } + } +#endif + // + memset(&state, 0, sizeof(bot_entitystate_t)); + // + VectorCopy(ent->r.currentOrigin, state.origin); + if (i < MAX_CLIENTS) { + VectorCopy(ent->s.apos.trBase, state.angles); + } else { + VectorCopy(ent->r.currentAngles, state.angles); + } + VectorCopy(ent->s.origin2, state.old_origin); + VectorCopy(ent->r.mins, state.mins); + VectorCopy(ent->r.maxs, state.maxs); + state.type = ent->s.eType; + state.flags = ent->s.eFlags; + if (ent->r.bmodel) state.solid = SOLID_BSP; + else state.solid = SOLID_BBOX; + state.groundent = ent->s.groundEntityNum; + state.modelindex = ent->s.modelindex; + state.modelindex2 = ent->s.modelindex2; + state.frame = ent->s.frame; + state.event = ent->s.event; + state.eventParm = ent->s.eventParm; + state.powerups = ent->s.powerups; + state.legsAnim = ent->s.legsAnim; + state.torsoAnim = ent->s.torsoAnim; + state.weapon = ent->s.weapon; + // + trap_BotLibUpdateEntity(i, &state); + } + + BotAIRegularUpdate(); + } + + floattime = trap_AAS_Time(); + + // execute scheduled bot AI + for( i = 0; i < MAX_CLIENTS; i++ ) { + if( !botstates[i] || !botstates[i]->inuse ) { + continue; + } + // + botstates[i]->botthink_residual += elapsed_time; + // + if ( botstates[i]->botthink_residual >= thinktime ) { + botstates[i]->botthink_residual -= thinktime; + + if (!trap_AAS_Initialized()) return qfalse; + + if (g_entities[i].client->pers.connected == CON_CONNECTED) { + BotAI(i, (float) thinktime / 1000); + } + } + } + + + // execute bot user commands every frame + for( i = 0; i < MAX_CLIENTS; i++ ) { + if( !botstates[i] || !botstates[i]->inuse ) { + continue; + } + if( g_entities[i].client->pers.connected != CON_CONNECTED ) { + continue; + } + + BotUpdateInput(botstates[i], time, elapsed_time); + trap_BotUserCommand(botstates[i]->client, &botstates[i]->lastucmd); + } + + return qtrue; +} + +/* +============== +BotInitLibrary +============== +*/ +int BotInitLibrary(void) { + char buf[144]; + + //set the maxclients and maxentities library variables before calling BotSetupLibrary + trap_Cvar_VariableStringBuffer("sv_maxclients", buf, sizeof(buf)); + if (!strlen(buf)) strcpy(buf, "8"); + trap_BotLibVarSet("maxclients", buf); + Com_sprintf(buf, sizeof(buf), "%d", MAX_GENTITIES); + trap_BotLibVarSet("maxentities", buf); + //bsp checksum + trap_Cvar_VariableStringBuffer("sv_mapChecksum", buf, sizeof(buf)); + if (strlen(buf)) trap_BotLibVarSet("sv_mapChecksum", buf); + //maximum number of aas links + trap_Cvar_VariableStringBuffer("max_aaslinks", buf, sizeof(buf)); + if (strlen(buf)) trap_BotLibVarSet("max_aaslinks", buf); + //maximum number of items in a level + trap_Cvar_VariableStringBuffer("max_levelitems", buf, sizeof(buf)); + if (strlen(buf)) trap_BotLibVarSet("max_levelitems", buf); + //game type + trap_Cvar_VariableStringBuffer("g_gametype", buf, sizeof(buf)); + if (!strlen(buf)) strcpy(buf, "0"); + trap_BotLibVarSet("g_gametype", buf); + //bot developer mode and log file + trap_BotLibVarSet("bot_developer", bot_developer.string); + trap_BotLibVarSet("log", buf); + //no chatting + trap_Cvar_VariableStringBuffer("bot_nochat", buf, sizeof(buf)); + if (strlen(buf)) trap_BotLibVarSet("nochat", "0"); + //visualize jump pads + trap_Cvar_VariableStringBuffer("bot_visualizejumppads", buf, sizeof(buf)); + if (strlen(buf)) trap_BotLibVarSet("bot_visualizejumppads", buf); + //forced clustering calculations + trap_Cvar_VariableStringBuffer("bot_forceclustering", buf, sizeof(buf)); + if (strlen(buf)) trap_BotLibVarSet("forceclustering", buf); + //forced reachability calculations + trap_Cvar_VariableStringBuffer("bot_forcereachability", buf, sizeof(buf)); + if (strlen(buf)) trap_BotLibVarSet("forcereachability", buf); + //force writing of AAS to file + trap_Cvar_VariableStringBuffer("bot_forcewrite", buf, sizeof(buf)); + if (strlen(buf)) trap_BotLibVarSet("forcewrite", buf); + //no AAS optimization + trap_Cvar_VariableStringBuffer("bot_aasoptimize", buf, sizeof(buf)); + if (strlen(buf)) trap_BotLibVarSet("aasoptimize", buf); + // + trap_Cvar_VariableStringBuffer("bot_saveroutingcache", buf, sizeof(buf)); + if (strlen(buf)) trap_BotLibVarSet("saveroutingcache", buf); + //reload instead of cache bot character files + trap_Cvar_VariableStringBuffer("bot_reloadcharacters", buf, sizeof(buf)); + if (!strlen(buf)) strcpy(buf, "0"); + trap_BotLibVarSet("bot_reloadcharacters", buf); + //base directory + trap_Cvar_VariableStringBuffer("fs_basepath", buf, sizeof(buf)); + if (strlen(buf)) trap_BotLibVarSet("basedir", buf); + //game directory + trap_Cvar_VariableStringBuffer("fs_game", buf, sizeof(buf)); + if (strlen(buf)) trap_BotLibVarSet("gamedir", buf); + //cd directory + trap_Cvar_VariableStringBuffer("fs_cdpath", buf, sizeof(buf)); + if (strlen(buf)) trap_BotLibVarSet("cddir", buf); + // +#ifdef MISSIONPACK + trap_BotLibDefine("MISSIONPACK"); +#endif + //setup the bot library + return trap_BotLibSetup(); +} + +/* +============== +BotAISetup +============== +*/ +int BotAISetup( int restart ) { + int errnum; + + trap_Cvar_Register(&bot_thinktime, "bot_thinktime", "100", CVAR_CHEAT); + trap_Cvar_Register(&bot_memorydump, "bot_memorydump", "0", CVAR_CHEAT); + trap_Cvar_Register(&bot_saveroutingcache, "bot_saveroutingcache", "0", CVAR_CHEAT); + trap_Cvar_Register(&bot_pause, "bot_pause", "0", CVAR_CHEAT); + trap_Cvar_Register(&bot_report, "bot_report", "0", CVAR_CHEAT); + trap_Cvar_Register(&bot_testsolid, "bot_testsolid", "0", CVAR_CHEAT); + trap_Cvar_Register(&bot_testclusters, "bot_testclusters", "0", CVAR_CHEAT); + trap_Cvar_Register(&bot_developer, "bot_developer", "0", CVAR_CHEAT); + trap_Cvar_Register(&bot_interbreedchar, "bot_interbreedchar", "", 0); + trap_Cvar_Register(&bot_interbreedbots, "bot_interbreedbots", "10", 0); + trap_Cvar_Register(&bot_interbreedcycle, "bot_interbreedcycle", "20", 0); + trap_Cvar_Register(&bot_interbreedwrite, "bot_interbreedwrite", "", 0); + + //if the game is restarted for a tournament + if (restart) { + return qtrue; + } + + //initialize the bot states + memset( botstates, 0, sizeof(botstates) ); + + errnum = BotInitLibrary(); + if (errnum != BLERR_NOERROR) return qfalse; + return qtrue; +} + +/* +============== +BotAIShutdown +============== +*/ +int BotAIShutdown( int restart ) { + + int i; + + //if the game is restarted for a tournament + if ( restart ) { + //shutdown all the bots in the botlib + for (i = 0; i < MAX_CLIENTS; i++) { + if (botstates[i] && botstates[i]->inuse) { + BotAIShutdownClient(botstates[i]->client, restart); + } + } + //don't shutdown the bot library + } + else { + trap_BotLibShutdown(); + } + return qtrue; +} + diff --git a/code/game/ai_main.h b/code/game/ai_main.h index 91e8895..5d0e9ec 100755 --- a/code/game/ai_main.h +++ b/code/game/ai_main.h @@ -1,299 +1,299 @@ -/* -=========================================================================== -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 Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// - -/***************************************************************************** - * name: ai_main.h - * - * desc: Quake3 bot AI - * - * $Archive: /source/code/botai/ai_chat.c $ - * - *****************************************************************************/ - -//#define DEBUG -#define CTF - -#define MAX_ITEMS 256 -//bot flags -#define BFL_STRAFERIGHT 1 //strafe to the right -#define BFL_ATTACKED 2 //bot has attacked last ai frame -#define BFL_ATTACKJUMPED 4 //bot jumped during attack last frame -#define BFL_AIMATENEMY 8 //bot aimed at the enemy this frame -#define BFL_AVOIDRIGHT 16 //avoid obstacles by going to the right -#define BFL_IDEALVIEWSET 32 //bot has ideal view angles set -#define BFL_FIGHTSUICIDAL 64 //bot is in a suicidal fight -//long term goal types -#define LTG_TEAMHELP 1 //help a team mate -#define LTG_TEAMACCOMPANY 2 //accompany a team mate -#define LTG_DEFENDKEYAREA 3 //defend a key area -#define LTG_GETFLAG 4 //get the enemy flag -#define LTG_RUSHBASE 5 //rush to the base -#define LTG_RETURNFLAG 6 //return the flag -#define LTG_CAMP 7 //camp somewhere -#define LTG_CAMPORDER 8 //ordered to camp somewhere -#define LTG_PATROL 9 //patrol -#define LTG_GETITEM 10 //get an item -#define LTG_KILL 11 //kill someone -#define LTG_HARVEST 12 //harvest skulls -#define LTG_ATTACKENEMYBASE 13 //attack the enemy base -#define LTG_MAKELOVE_UNDER 14 -#define LTG_MAKELOVE_ONTOP 15 -//some goal dedication times -#define TEAM_HELP_TIME 60 //1 minute teamplay help time -#define TEAM_ACCOMPANY_TIME 600 //10 minutes teamplay accompany time -#define TEAM_DEFENDKEYAREA_TIME 600 //10 minutes ctf defend base time -#define TEAM_CAMP_TIME 600 //10 minutes camping time -#define TEAM_PATROL_TIME 600 //10 minutes patrolling time -#define TEAM_LEAD_TIME 600 //10 minutes taking the lead -#define TEAM_GETITEM_TIME 60 //1 minute -#define TEAM_KILL_SOMEONE 180 //3 minute to kill someone -#define TEAM_ATTACKENEMYBASE_TIME 600 //10 minutes -#define TEAM_HARVEST_TIME 120 //2 minutes -#define CTF_GETFLAG_TIME 600 //10 minutes ctf get flag time -#define CTF_RUSHBASE_TIME 120 //2 minutes ctf rush base time -#define CTF_RETURNFLAG_TIME 180 //3 minutes to return the flag -#define CTF_ROAM_TIME 60 //1 minute ctf roam time -//patrol flags -#define PATROL_LOOP 1 -#define PATROL_REVERSE 2 -#define PATROL_BACK 4 -//teamplay task preference -#define TEAMTP_DEFENDER 1 -#define TEAMTP_ATTACKER 2 -//CTF strategy -#define CTFS_AGRESSIVE 1 -//copied from the aas file header -#define PRESENCE_NONE 1 -#define PRESENCE_NORMAL 2 -#define PRESENCE_CROUCH 4 -// -#define MAX_PROXMINES 64 - -//check points -typedef struct bot_waypoint_s -{ - int inuse; - char name[32]; - bot_goal_t goal; - struct bot_waypoint_s *next, *prev; -} bot_waypoint_t; - -#define MAX_ACTIVATESTACK 8 -#define MAX_ACTIVATEAREAS 32 - -typedef struct bot_activategoal_s -{ - int inuse; - bot_goal_t goal; //goal to activate (buttons etc.) - float time; //time to activate something - float start_time; //time starting to activate something - float justused_time; //time the goal was used - int shoot; //true if bot has to shoot to activate - int weapon; //weapon to be used for activation - vec3_t target; //target to shoot at to activate something - vec3_t origin; //origin of the blocking entity to activate - int areas[MAX_ACTIVATEAREAS]; //routing areas disabled by blocking entity - int numareas; //number of disabled routing areas - int areasdisabled; //true if the areas are disabled for the routing - struct bot_activategoal_s *next; //next activate goal on stack -} bot_activategoal_t; - -//bot state -typedef struct bot_state_s -{ - int inuse; //true if this state is used by a bot client - int botthink_residual; //residual for the bot thinks - int client; //client number of the bot - int entitynum; //entity number of the bot - playerState_t cur_ps; //current player state - int last_eFlags; //last ps flags - usercmd_t lastucmd; //usercmd from last frame - int entityeventTime[1024]; //last entity event time - // - bot_settings_t settings; //several bot settings - int (*ainode)(struct bot_state_s *bs); //current AI node - float thinktime; //time the bot thinks this frame - vec3_t origin; //origin of the bot - vec3_t velocity; //velocity of the bot - int presencetype; //presence type of the bot - vec3_t eye; //eye coordinates of the bot - int areanum; //the number of the area the bot is in - int inventory[MAX_ITEMS]; //string with items amounts the bot has - int tfl; //the travel flags the bot uses - int flags; //several flags - int respawn_wait; //wait until respawned - int lasthealth; //health value previous frame - int lastkilledplayer; //last killed player - int lastkilledby; //player that last killed this bot - int botdeathtype; //the death type of the bot - int enemydeathtype; //the death type of the enemy - int botsuicide; //true when the bot suicides - int enemysuicide; //true when the enemy of the bot suicides - int setupcount; //true when the bot has just been setup - int map_restart; //true when the map is being restarted - int entergamechat; //true when the bot used an enter game chat - int num_deaths; //number of time this bot died - int num_kills; //number of kills of this bot - int revenge_enemy; //the revenge enemy - int revenge_kills; //number of kills the enemy made - int lastframe_health; //health value the last frame - int lasthitcount; //number of hits last frame - int chatto; //chat to all or team - float walker; //walker charactertic - float ltime; //local bot time - float entergame_time; //time the bot entered the game - float ltg_time; //long term goal time - float nbg_time; //nearby goal time - float respawn_time; //time the bot takes to respawn - float respawnchat_time; //time the bot started a chat during respawn - float chase_time; //time the bot will chase the enemy - float enemyvisible_time; //time the enemy was last visible - float check_time; //time to check for nearby items - float stand_time; //time the bot is standing still - float lastchat_time; //time the bot last selected a chat - float kamikaze_time; //time to check for kamikaze usage - float invulnerability_time; //time to check for invulnerability usage - float standfindenemy_time; //time to find enemy while standing - float attackstrafe_time; //time the bot is strafing in one dir - float attackcrouch_time; //time the bot will stop crouching - float attackchase_time; //time the bot chases during actual attack - float attackjump_time; //time the bot jumped during attack - float enemysight_time; //time before reacting to enemy - float enemydeath_time; //time the enemy died - float enemyposition_time; //time the position and velocity of the enemy were stored - float defendaway_time; //time away while defending - float defendaway_range; //max travel time away from defend area - float rushbaseaway_time; //time away from rushing to the base - float attackaway_time; //time away from attacking the enemy base - float harvestaway_time; //time away from harvesting - float ctfroam_time; //time the bot is roaming in ctf - float killedenemy_time; //time the bot killed the enemy - float arrive_time; //time arrived (at companion) - float lastair_time; //last time the bot had air - float teleport_time; //last time the bot teleported - float camp_time; //last time camped - float camp_range; //camp range - float weaponchange_time; //time the bot started changing weapons - float firethrottlewait_time; //amount of time to wait - float firethrottleshoot_time; //amount of time to shoot - float notblocked_time; //last time the bot was not blocked - float blockedbyavoidspot_time; //time blocked by an avoid spot - float predictobstacles_time; //last time the bot predicted obstacles - int predictobstacles_goalareanum; //last goal areanum the bot predicted obstacles for - vec3_t aimtarget; - vec3_t enemyvelocity; //enemy velocity 0.5 secs ago during battle - vec3_t enemyorigin; //enemy origin 0.5 secs ago during battle - // - int kamikazebody; //kamikaze body - int proxmines[MAX_PROXMINES]; - int numproxmines; - // - int character; //the bot character - int ms; //move state of the bot - int gs; //goal state of the bot - int cs; //chat state of the bot - int ws; //weapon state of the bot - // - int enemy; //enemy entity number - int lastenemyareanum; //last reachability area the enemy was in - vec3_t lastenemyorigin; //last origin of the enemy in the reachability area - int weaponnum; //current weapon number - vec3_t viewangles; //current view angles - vec3_t ideal_viewangles; //ideal view angles - vec3_t viewanglespeed; - // - int ltgtype; //long term goal type - // team goals - int teammate; //team mate involved in this team goal - int decisionmaker; //player who decided to go for this goal - int ordered; //true if ordered to do something - float order_time; //time ordered to do something - int owndecision_time; //time the bot made it's own decision - bot_goal_t teamgoal; //the team goal - bot_goal_t altroutegoal; //alternative route goal - float reachedaltroutegoal_time; //time the bot reached the alt route goal - float teammessage_time; //time to message team mates what the bot is doing - float teamgoal_time; //time to stop helping team mate - float teammatevisible_time; //last time the team mate was NOT visible - int teamtaskpreference; //team task preference - // last ordered team goal - int lastgoal_decisionmaker; - int lastgoal_ltgtype; - int lastgoal_teammate; - bot_goal_t lastgoal_teamgoal; - // for leading team mates - int lead_teammate; //team mate the bot is leading - bot_goal_t lead_teamgoal; //team goal while leading - float lead_time; //time leading someone - float leadvisible_time; //last time the team mate was visible - float leadmessage_time; //last time a messaged was sent to the team mate - float leadbackup_time; //time backing up towards team mate - // - char teamleader[32]; //netname of the team leader - float askteamleader_time; //time asked for team leader - float becometeamleader_time; //time the bot will become the team leader - float teamgiveorders_time; //time to give team orders - float lastflagcapture_time; //last time a flag was captured - int numteammates; //number of team mates - int redflagstatus; //0 = at base, 1 = not at base - int blueflagstatus; //0 = at base, 1 = not at base - int neutralflagstatus; //0 = at base, 1 = our team has flag, 2 = enemy team has flag, 3 = enemy team dropped the flag - int flagstatuschanged; //flag status changed - int forceorders; //true if forced to give orders - int flagcarrier; //team mate carrying the enemy flag - int ctfstrategy; //ctf strategy - char subteam[32]; //sub team name - float formation_dist; //formation team mate intervening space - char formation_teammate[16]; //netname of the team mate the bot uses for relative positioning - float formation_angle; //angle relative to the formation team mate - vec3_t formation_dir; //the direction the formation is moving in - vec3_t formation_origin; //origin the bot uses for relative positioning - bot_goal_t formation_goal; //formation goal - - bot_activategoal_t *activatestack; //first activate goal on the stack - bot_activategoal_t activategoalheap[MAX_ACTIVATESTACK]; //activate goal heap - - bot_waypoint_t *checkpoints; //check points - bot_waypoint_t *patrolpoints; //patrol points - bot_waypoint_t *curpatrolpoint; //current patrol point the bot is going for - int patrolflags; //patrol flags -} bot_state_t; - -//resets the whole bot state -void BotResetState(bot_state_t *bs); -//returns the number of bots in the game -int NumBots(void); -//returns info about the entity -void BotEntityInfo(int entnum, aas_entityinfo_t *info); - -extern float floattime; -#define FloatTime() floattime - -// from the game source -void QDECL BotAI_Print(int type, char *fmt, ...); -void QDECL QDECL BotAI_BotInitialChat( bot_state_t *bs, char *type, ... ); -void BotAI_Trace(bsp_trace_t *bsptrace, vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, int passent, int contentmask); -int BotAI_GetClientState( int clientNum, playerState_t *state ); -int BotAI_GetEntityState( int entityNum, entityState_t *state ); -int BotAI_GetSnapshotEntity( int clientNum, int sequence, entityState_t *state ); -int BotTeamLeader(bot_state_t *bs); +/* +=========================================================================== +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 Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// + +/***************************************************************************** + * name: ai_main.h + * + * desc: Quake3 bot AI + * + * $Archive: /source/code/botai/ai_chat.c $ + * + *****************************************************************************/ + +//#define DEBUG +#define CTF + +#define MAX_ITEMS 256 +//bot flags +#define BFL_STRAFERIGHT 1 //strafe to the right +#define BFL_ATTACKED 2 //bot has attacked last ai frame +#define BFL_ATTACKJUMPED 4 //bot jumped during attack last frame +#define BFL_AIMATENEMY 8 //bot aimed at the enemy this frame +#define BFL_AVOIDRIGHT 16 //avoid obstacles by going to the right +#define BFL_IDEALVIEWSET 32 //bot has ideal view angles set +#define BFL_FIGHTSUICIDAL 64 //bot is in a suicidal fight +//long term goal types +#define LTG_TEAMHELP 1 //help a team mate +#define LTG_TEAMACCOMPANY 2 //accompany a team mate +#define LTG_DEFENDKEYAREA 3 //defend a key area +#define LTG_GETFLAG 4 //get the enemy flag +#define LTG_RUSHBASE 5 //rush to the base +#define LTG_RETURNFLAG 6 //return the flag +#define LTG_CAMP 7 //camp somewhere +#define LTG_CAMPORDER 8 //ordered to camp somewhere +#define LTG_PATROL 9 //patrol +#define LTG_GETITEM 10 //get an item +#define LTG_KILL 11 //kill someone +#define LTG_HARVEST 12 //harvest skulls +#define LTG_ATTACKENEMYBASE 13 //attack the enemy base +#define LTG_MAKELOVE_UNDER 14 +#define LTG_MAKELOVE_ONTOP 15 +//some goal dedication times +#define TEAM_HELP_TIME 60 //1 minute teamplay help time +#define TEAM_ACCOMPANY_TIME 600 //10 minutes teamplay accompany time +#define TEAM_DEFENDKEYAREA_TIME 600 //10 minutes ctf defend base time +#define TEAM_CAMP_TIME 600 //10 minutes camping time +#define TEAM_PATROL_TIME 600 //10 minutes patrolling time +#define TEAM_LEAD_TIME 600 //10 minutes taking the lead +#define TEAM_GETITEM_TIME 60 //1 minute +#define TEAM_KILL_SOMEONE 180 //3 minute to kill someone +#define TEAM_ATTACKENEMYBASE_TIME 600 //10 minutes +#define TEAM_HARVEST_TIME 120 //2 minutes +#define CTF_GETFLAG_TIME 600 //10 minutes ctf get flag time +#define CTF_RUSHBASE_TIME 120 //2 minutes ctf rush base time +#define CTF_RETURNFLAG_TIME 180 //3 minutes to return the flag +#define CTF_ROAM_TIME 60 //1 minute ctf roam time +//patrol flags +#define PATROL_LOOP 1 +#define PATROL_REVERSE 2 +#define PATROL_BACK 4 +//teamplay task preference +#define TEAMTP_DEFENDER 1 +#define TEAMTP_ATTACKER 2 +//CTF strategy +#define CTFS_AGRESSIVE 1 +//copied from the aas file header +#define PRESENCE_NONE 1 +#define PRESENCE_NORMAL 2 +#define PRESENCE_CROUCH 4 +// +#define MAX_PROXMINES 64 + +//check points +typedef struct bot_waypoint_s +{ + int inuse; + char name[32]; + bot_goal_t goal; + struct bot_waypoint_s *next, *prev; +} bot_waypoint_t; + +#define MAX_ACTIVATESTACK 8 +#define MAX_ACTIVATEAREAS 32 + +typedef struct bot_activategoal_s +{ + int inuse; + bot_goal_t goal; //goal to activate (buttons etc.) + float time; //time to activate something + float start_time; //time starting to activate something + float justused_time; //time the goal was used + int shoot; //true if bot has to shoot to activate + int weapon; //weapon to be used for activation + vec3_t target; //target to shoot at to activate something + vec3_t origin; //origin of the blocking entity to activate + int areas[MAX_ACTIVATEAREAS]; //routing areas disabled by blocking entity + int numareas; //number of disabled routing areas + int areasdisabled; //true if the areas are disabled for the routing + struct bot_activategoal_s *next; //next activate goal on stack +} bot_activategoal_t; + +//bot state +typedef struct bot_state_s +{ + int inuse; //true if this state is used by a bot client + int botthink_residual; //residual for the bot thinks + int client; //client number of the bot + int entitynum; //entity number of the bot + playerState_t cur_ps; //current player state + int last_eFlags; //last ps flags + usercmd_t lastucmd; //usercmd from last frame + int entityeventTime[1024]; //last entity event time + // + bot_settings_t settings; //several bot settings + int (*ainode)(struct bot_state_s *bs); //current AI node + float thinktime; //time the bot thinks this frame + vec3_t origin; //origin of the bot + vec3_t velocity; //velocity of the bot + int presencetype; //presence type of the bot + vec3_t eye; //eye coordinates of the bot + int areanum; //the number of the area the bot is in + int inventory[MAX_ITEMS]; //string with items amounts the bot has + int tfl; //the travel flags the bot uses + int flags; //several flags + int respawn_wait; //wait until respawned + int lasthealth; //health value previous frame + int lastkilledplayer; //last killed player + int lastkilledby; //player that last killed this bot + int botdeathtype; //the death type of the bot + int enemydeathtype; //the death type of the enemy + int botsuicide; //true when the bot suicides + int enemysuicide; //true when the enemy of the bot suicides + int setupcount; //true when the bot has just been setup + int map_restart; //true when the map is being restarted + int entergamechat; //true when the bot used an enter game chat + int num_deaths; //number of time this bot died + int num_kills; //number of kills of this bot + int revenge_enemy; //the revenge enemy + int revenge_kills; //number of kills the enemy made + int lastframe_health; //health value the last frame + int lasthitcount; //number of hits last frame + int chatto; //chat to all or team + float walker; //walker charactertic + float ltime; //local bot time + float entergame_time; //time the bot entered the game + float ltg_time; //long term goal time + float nbg_time; //nearby goal time + float respawn_time; //time the bot takes to respawn + float respawnchat_time; //time the bot started a chat during respawn + float chase_time; //time the bot will chase the enemy + float enemyvisible_time; //time the enemy was last visible + float check_time; //time to check for nearby items + float stand_time; //time the bot is standing still + float lastchat_time; //time the bot last selected a chat + float kamikaze_time; //time to check for kamikaze usage + float invulnerability_time; //time to check for invulnerability usage + float standfindenemy_time; //time to find enemy while standing + float attackstrafe_time; //time the bot is strafing in one dir + float attackcrouch_time; //time the bot will stop crouching + float attackchase_time; //time the bot chases during actual attack + float attackjump_time; //time the bot jumped during attack + float enemysight_time; //time before reacting to enemy + float enemydeath_time; //time the enemy died + float enemyposition_time; //time the position and velocity of the enemy were stored + float defendaway_time; //time away while defending + float defendaway_range; //max travel time away from defend area + float rushbaseaway_time; //time away from rushing to the base + float attackaway_time; //time away from attacking the enemy base + float harvestaway_time; //time away from harvesting + float ctfroam_time; //time the bot is roaming in ctf + float killedenemy_time; //time the bot killed the enemy + float arrive_time; //time arrived (at companion) + float lastair_time; //last time the bot had air + float teleport_time; //last time the bot teleported + float camp_time; //last time camped + float camp_range; //camp range + float weaponchange_time; //time the bot started changing weapons + float firethrottlewait_time; //amount of time to wait + float firethrottleshoot_time; //amount of time to shoot + float notblocked_time; //last time the bot was not blocked + float blockedbyavoidspot_time; //time blocked by an avoid spot + float predictobstacles_time; //last time the bot predicted obstacles + int predictobstacles_goalareanum; //last goal areanum the bot predicted obstacles for + vec3_t aimtarget; + vec3_t enemyvelocity; //enemy velocity 0.5 secs ago during battle + vec3_t enemyorigin; //enemy origin 0.5 secs ago during battle + // + int kamikazebody; //kamikaze body + int proxmines[MAX_PROXMINES]; + int numproxmines; + // + int character; //the bot character + int ms; //move state of the bot + int gs; //goal state of the bot + int cs; //chat state of the bot + int ws; //weapon state of the bot + // + int enemy; //enemy entity number + int lastenemyareanum; //last reachability area the enemy was in + vec3_t lastenemyorigin; //last origin of the enemy in the reachability area + int weaponnum; //current weapon number + vec3_t viewangles; //current view angles + vec3_t ideal_viewangles; //ideal view angles + vec3_t viewanglespeed; + // + int ltgtype; //long term goal type + // team goals + int teammate; //team mate involved in this team goal + int decisionmaker; //player who decided to go for this goal + int ordered; //true if ordered to do something + float order_time; //time ordered to do something + int owndecision_time; //time the bot made it's own decision + bot_goal_t teamgoal; //the team goal + bot_goal_t altroutegoal; //alternative route goal + float reachedaltroutegoal_time; //time the bot reached the alt route goal + float teammessage_time; //time to message team mates what the bot is doing + float teamgoal_time; //time to stop helping team mate + float teammatevisible_time; //last time the team mate was NOT visible + int teamtaskpreference; //team task preference + // last ordered team goal + int lastgoal_decisionmaker; + int lastgoal_ltgtype; + int lastgoal_teammate; + bot_goal_t lastgoal_teamgoal; + // for leading team mates + int lead_teammate; //team mate the bot is leading + bot_goal_t lead_teamgoal; //team goal while leading + float lead_time; //time leading someone + float leadvisible_time; //last time the team mate was visible + float leadmessage_time; //last time a messaged was sent to the team mate + float leadbackup_time; //time backing up towards team mate + // + char teamleader[32]; //netname of the team leader + float askteamleader_time; //time asked for team leader + float becometeamleader_time; //time the bot will become the team leader + float teamgiveorders_time; //time to give team orders + float lastflagcapture_time; //last time a flag was captured + int numteammates; //number of team mates + int redflagstatus; //0 = at base, 1 = not at base + int blueflagstatus; //0 = at base, 1 = not at base + int neutralflagstatus; //0 = at base, 1 = our team has flag, 2 = enemy team has flag, 3 = enemy team dropped the flag + int flagstatuschanged; //flag status changed + int forceorders; //true if forced to give orders + int flagcarrier; //team mate carrying the enemy flag + int ctfstrategy; //ctf strategy + char subteam[32]; //sub team name + float formation_dist; //formation team mate intervening space + char formation_teammate[16]; //netname of the team mate the bot uses for relative positioning + float formation_angle; //angle relative to the formation team mate + vec3_t formation_dir; //the direction the formation is moving in + vec3_t formation_origin; //origin the bot uses for relative positioning + bot_goal_t formation_goal; //formation goal + + bot_activategoal_t *activatestack; //first activate goal on the stack + bot_activategoal_t activategoalheap[MAX_ACTIVATESTACK]; //activate goal heap + + bot_waypoint_t *checkpoints; //check points + bot_waypoint_t *patrolpoints; //patrol points + bot_waypoint_t *curpatrolpoint; //current patrol point the bot is going for + int patrolflags; //patrol flags +} bot_state_t; + +//resets the whole bot state +void BotResetState(bot_state_t *bs); +//returns the number of bots in the game +int NumBots(void); +//returns info about the entity +void BotEntityInfo(int entnum, aas_entityinfo_t *info); + +extern float floattime; +#define FloatTime() floattime + +// from the game source +void QDECL BotAI_Print(int type, char *fmt, ...); +void QDECL QDECL BotAI_BotInitialChat( bot_state_t *bs, char *type, ... ); +void BotAI_Trace(bsp_trace_t *bsptrace, vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, int passent, int contentmask); +int BotAI_GetClientState( int clientNum, playerState_t *state ); +int BotAI_GetEntityState( int entityNum, entityState_t *state ); +int BotAI_GetSnapshotEntity( int clientNum, int sequence, entityState_t *state ); +int BotTeamLeader(bot_state_t *bs); diff --git a/code/game/ai_team.c b/code/game/ai_team.c index 9215702..44250d8 100755 --- a/code/game/ai_team.c +++ b/code/game/ai_team.c @@ -1,2080 +1,2080 @@ -/* -=========================================================================== -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 Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// - -/***************************************************************************** - * name: ai_team.c - * - * desc: Quake3 bot AI - * - * $Archive: /MissionPack/code/game/ai_team.c $ - * - *****************************************************************************/ - -#include "g_local.h" -#include "botlib.h" -#include "be_aas.h" -#include "be_ea.h" -#include "be_ai_char.h" -#include "be_ai_chat.h" -#include "be_ai_gen.h" -#include "be_ai_goal.h" -#include "be_ai_move.h" -#include "be_ai_weap.h" -// -#include "ai_main.h" -#include "ai_dmq3.h" -#include "ai_chat.h" -#include "ai_cmd.h" -#include "ai_dmnet.h" -#include "ai_team.h" -#include "ai_vcmd.h" - -#include "match.h" - -// for the voice chats -#include "../../ui/menudef.h" - -//ctf task preferences for a client -typedef struct bot_ctftaskpreference_s -{ - char name[36]; - int preference; -} bot_ctftaskpreference_t; - -bot_ctftaskpreference_t ctftaskpreferences[MAX_CLIENTS]; - - -/* -================== -BotValidTeamLeader -================== -*/ -int BotValidTeamLeader(bot_state_t *bs) { - if (!strlen(bs->teamleader)) return qfalse; - if (ClientFromName(bs->teamleader) == -1) return qfalse; - return qtrue; -} - -/* -================== -BotNumTeamMates -================== -*/ -int BotNumTeamMates(bot_state_t *bs) { - int i, numplayers; - char buf[MAX_INFO_STRING]; - static int maxclients; - - if (!maxclients) - maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); - - numplayers = 0; - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { - trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); - //if no config string or no name - if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue; - //skip spectators - if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue; - // - if (BotSameTeam(bs, i)) { - numplayers++; - } - } - return numplayers; -} - -/* -================== -BotClientTravelTimeToGoal -================== -*/ -int BotClientTravelTimeToGoal(int client, bot_goal_t *goal) { - playerState_t ps; - int areanum; - - BotAI_GetClientState(client, &ps); - areanum = BotPointAreaNum(ps.origin); - if (!areanum) return 1; - return trap_AAS_AreaTravelTimeToGoalArea(areanum, ps.origin, goal->areanum, TFL_DEFAULT); -} - -/* -================== -BotSortTeamMatesByBaseTravelTime -================== -*/ -int BotSortTeamMatesByBaseTravelTime(bot_state_t *bs, int *teammates, int maxteammates) { - - int i, j, k, numteammates, traveltime; - char buf[MAX_INFO_STRING]; - static int maxclients; - int traveltimes[MAX_CLIENTS]; - bot_goal_t *goal = NULL; - - if (gametype == GT_CTF || gametype == GT_1FCTF) { - if (BotTeam(bs) == TEAM_RED) - goal = &ctf_redflag; - else - goal = &ctf_blueflag; - } -#ifdef MISSIONPACK - else { - if (BotTeam(bs) == TEAM_RED) - goal = &redobelisk; - else - goal = &blueobelisk; - } -#endif - if (!maxclients) - maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); - - numteammates = 0; - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { - trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); - //if no config string or no name - if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue; - //skip spectators - if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue; - // - if (BotSameTeam(bs, i)) { - // - traveltime = BotClientTravelTimeToGoal(i, goal); - // - for (j = 0; j < numteammates; j++) { - if (traveltime < traveltimes[j]) { - for (k = numteammates; k > j; k--) { - traveltimes[k] = traveltimes[k-1]; - teammates[k] = teammates[k-1]; - } - break; - } - } - traveltimes[j] = traveltime; - teammates[j] = i; - numteammates++; - if (numteammates >= maxteammates) break; - } - } - return numteammates; -} - -/* -================== -BotSetTeamMateTaskPreference -================== -*/ -void BotSetTeamMateTaskPreference(bot_state_t *bs, int teammate, int preference) { - char teammatename[MAX_NETNAME]; - - ctftaskpreferences[teammate].preference = preference; - ClientName(teammate, teammatename, sizeof(teammatename)); - strcpy(ctftaskpreferences[teammate].name, teammatename); -} - -/* -================== -BotGetTeamMateTaskPreference -================== -*/ -int BotGetTeamMateTaskPreference(bot_state_t *bs, int teammate) { - char teammatename[MAX_NETNAME]; - - if (!ctftaskpreferences[teammate].preference) return 0; - ClientName(teammate, teammatename, sizeof(teammatename)); - if (Q_stricmp(teammatename, ctftaskpreferences[teammate].name)) return 0; - return ctftaskpreferences[teammate].preference; -} - -/* -================== -BotSortTeamMatesByTaskPreference -================== -*/ -int BotSortTeamMatesByTaskPreference(bot_state_t *bs, int *teammates, int numteammates) { - int defenders[MAX_CLIENTS], numdefenders; - int attackers[MAX_CLIENTS], numattackers; - int roamers[MAX_CLIENTS], numroamers; - int i, preference; - - numdefenders = numattackers = numroamers = 0; - for (i = 0; i < numteammates; i++) { - preference = BotGetTeamMateTaskPreference(bs, teammates[i]); - if (preference & TEAMTP_DEFENDER) { - defenders[numdefenders++] = teammates[i]; - } - else if (preference & TEAMTP_ATTACKER) { - attackers[numattackers++] = teammates[i]; - } - else { - roamers[numroamers++] = teammates[i]; - } - } - numteammates = 0; - //defenders at the front of the list - memcpy(&teammates[numteammates], defenders, numdefenders * sizeof(int)); - numteammates += numdefenders; - //roamers in the middle - memcpy(&teammates[numteammates], roamers, numroamers * sizeof(int)); - numteammates += numroamers; - //attacker in the back of the list - memcpy(&teammates[numteammates], attackers, numattackers * sizeof(int)); - numteammates += numattackers; - - return numteammates; -} - -/* -================== -BotSayTeamOrders -================== -*/ -void BotSayTeamOrderAlways(bot_state_t *bs, int toclient) { - char teamchat[MAX_MESSAGE_SIZE]; - char buf[MAX_MESSAGE_SIZE]; - char name[MAX_NETNAME]; - - //if the bot is talking to itself - if (bs->client == toclient) { - //don't show the message just put it in the console message queue - trap_BotGetChatMessage(bs->cs, buf, sizeof(buf)); - ClientName(bs->client, name, sizeof(name)); - Com_sprintf(teamchat, sizeof(teamchat), EC"(%s"EC")"EC": %s", name, buf); - trap_BotQueueConsoleMessage(bs->cs, CMS_CHAT, teamchat); - } - else { - trap_BotEnterChat(bs->cs, toclient, CHAT_TELL); - } -} - -/* -================== -BotSayTeamOrders -================== -*/ -void BotSayTeamOrder(bot_state_t *bs, int toclient) { -#ifdef MISSIONPACK - // voice chats only - char buf[MAX_MESSAGE_SIZE]; - - trap_BotGetChatMessage(bs->cs, buf, sizeof(buf)); -#else - BotSayTeamOrderAlways(bs, toclient); -#endif -} - -/* -================== -BotVoiceChat -================== -*/ -void BotVoiceChat(bot_state_t *bs, int toclient, char *voicechat) { -#ifdef MISSIONPACK - if (toclient == -1) - // voice only say team - trap_EA_Command(bs->client, va("vsay_team %s", voicechat)); - else - // voice only tell single player - trap_EA_Command(bs->client, va("vtell %d %s", toclient, voicechat)); -#endif -} - -/* -================== -BotVoiceChatOnly -================== -*/ -void BotVoiceChatOnly(bot_state_t *bs, int toclient, char *voicechat) { -#ifdef MISSIONPACK - if (toclient == -1) - // voice only say team - trap_EA_Command(bs->client, va("vosay_team %s", voicechat)); - else - // voice only tell single player - trap_EA_Command(bs->client, va("votell %d %s", toclient, voicechat)); -#endif -} - -/* -================== -BotSayVoiceTeamOrder -================== -*/ -void BotSayVoiceTeamOrder(bot_state_t *bs, int toclient, char *voicechat) { -#ifdef MISSIONPACK - BotVoiceChat(bs, toclient, voicechat); -#endif -} - -/* -================== -BotCTFOrders -================== -*/ -void BotCTFOrders_BothFlagsNotAtBase(bot_state_t *bs) { - int numteammates, defenders, attackers, i, other; - int teammates[MAX_CLIENTS]; - char name[MAX_NETNAME], carriername[MAX_NETNAME]; - - numteammates = BotSortTeamMatesByBaseTravelTime(bs, teammates, sizeof(teammates)); - BotSortTeamMatesByTaskPreference(bs, teammates, numteammates); - //different orders based on the number of team mates - switch(bs->numteammates) { - case 1: break; - case 2: - { - //tell the one not carrying the flag to attack the enemy base - if (teammates[0] != bs->flagcarrier) other = teammates[0]; - else other = teammates[1]; - ClientName(other, name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); - BotSayTeamOrder(bs, other); - BotSayVoiceTeamOrder(bs, other, VOICECHAT_GETFLAG); - break; - } - case 3: - { - //tell the one closest to the base not carrying the flag to accompany the flag carrier - if (teammates[0] != bs->flagcarrier) other = teammates[0]; - else other = teammates[1]; - ClientName(other, name, sizeof(name)); - if ( bs->flagcarrier != -1 ) { - ClientName(bs->flagcarrier, carriername, sizeof(carriername)); - if (bs->flagcarrier == bs->client) { - BotAI_BotInitialChat(bs, "cmd_accompanyme", name, NULL); - BotSayVoiceTeamOrder(bs, other, VOICECHAT_FOLLOWME); - } - else { - BotAI_BotInitialChat(bs, "cmd_accompany", name, carriername, NULL); - BotSayVoiceTeamOrder(bs, other, VOICECHAT_FOLLOWFLAGCARRIER); - } - } - else { - // - BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); - BotSayVoiceTeamOrder(bs, other, VOICECHAT_GETFLAG); - } - BotSayTeamOrder(bs, other); - //tell the one furthest from the the base not carrying the flag to get the enemy flag - if (teammates[2] != bs->flagcarrier) other = teammates[2]; - else other = teammates[1]; - ClientName(other, name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); - BotSayTeamOrder(bs, other); - BotSayVoiceTeamOrder(bs, other, VOICECHAT_RETURNFLAG); - break; - } - default: - { - defenders = (int) (float) numteammates * 0.4 + 0.5; - if (defenders > 4) defenders = 4; - attackers = (int) (float) numteammates * 0.5 + 0.5; - if (attackers > 5) attackers = 5; - if (bs->flagcarrier != -1) { - ClientName(bs->flagcarrier, carriername, sizeof(carriername)); - for (i = 0; i < defenders; i++) { - // - if (teammates[i] == bs->flagcarrier) { - continue; - } - // - ClientName(teammates[i], name, sizeof(name)); - if (bs->flagcarrier == bs->client) { - BotAI_BotInitialChat(bs, "cmd_accompanyme", name, NULL); - BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_FOLLOWME); - } - else { - BotAI_BotInitialChat(bs, "cmd_accompany", name, carriername, NULL); - BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_FOLLOWFLAGCARRIER); - } - BotSayTeamOrder(bs, teammates[i]); - } - } - else { - for (i = 0; i < defenders; i++) { - // - if (teammates[i] == bs->flagcarrier) { - continue; - } - // - ClientName(teammates[i], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); - BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_GETFLAG); - BotSayTeamOrder(bs, teammates[i]); - } - } - for (i = 0; i < attackers; i++) { - // - if (teammates[numteammates - i - 1] == bs->flagcarrier) { - continue; - } - // - ClientName(teammates[numteammates - i - 1], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); - BotSayTeamOrder(bs, teammates[numteammates - i - 1]); - BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_RETURNFLAG); - } - // - break; - } - } -} - -/* -================== -BotCTFOrders -================== -*/ -void BotCTFOrders_FlagNotAtBase(bot_state_t *bs) { - int numteammates, defenders, attackers, i; - int teammates[MAX_CLIENTS]; - char name[MAX_NETNAME]; - - numteammates = BotSortTeamMatesByBaseTravelTime(bs, teammates, sizeof(teammates)); - BotSortTeamMatesByTaskPreference(bs, teammates, numteammates); - //passive strategy - if (!(bs->ctfstrategy & CTFS_AGRESSIVE)) { - //different orders based on the number of team mates - switch(bs->numteammates) { - case 1: break; - case 2: - { - //both will go for the enemy flag - ClientName(teammates[0], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[0]); - BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_GETFLAG); - // - ClientName(teammates[1], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); - BotSayTeamOrder(bs, teammates[1]); - BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_GETFLAG); - break; - } - case 3: - { - //keep one near the base for when the flag is returned - ClientName(teammates[0], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[0]); - BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); - //the other two get the flag - ClientName(teammates[1], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); - BotSayTeamOrder(bs, teammates[1]); - BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_GETFLAG); - // - ClientName(teammates[2], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); - BotSayTeamOrder(bs, teammates[2]); - BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_GETFLAG); - break; - } - default: - { - //keep some people near the base for when the flag is returned - defenders = (int) (float) numteammates * 0.3 + 0.5; - if (defenders > 3) defenders = 3; - attackers = (int) (float) numteammates * 0.7 + 0.5; - if (attackers > 6) attackers = 6; - for (i = 0; i < defenders; i++) { - // - ClientName(teammates[i], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[i]); - BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); - } - for (i = 0; i < attackers; i++) { - // - ClientName(teammates[numteammates - i - 1], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); - BotSayTeamOrder(bs, teammates[numteammates - i - 1]); - BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_GETFLAG); - } - // - break; - } - } - } - else { - //different orders based on the number of team mates - switch(bs->numteammates) { - case 1: break; - case 2: - { - //both will go for the enemy flag - ClientName(teammates[0], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); - BotSayTeamOrder(bs, teammates[0]); - BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_GETFLAG); - // - ClientName(teammates[1], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); - BotSayTeamOrder(bs, teammates[1]); - BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_GETFLAG); - break; - } - case 3: - { - //everyone go for the flag - ClientName(teammates[0], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[0]); - BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_GETFLAG); - // - ClientName(teammates[1], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); - BotSayTeamOrder(bs, teammates[1]); - BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_GETFLAG); - // - ClientName(teammates[2], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); - BotSayTeamOrder(bs, teammates[2]); - BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_GETFLAG); - break; - } - default: - { - //keep some people near the base for when the flag is returned - defenders = (int) (float) numteammates * 0.2 + 0.5; - if (defenders > 2) defenders = 2; - attackers = (int) (float) numteammates * 0.7 + 0.5; - if (attackers > 7) attackers = 7; - for (i = 0; i < defenders; i++) { - // - ClientName(teammates[i], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[i]); - BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); - } - for (i = 0; i < attackers; i++) { - // - ClientName(teammates[numteammates - i - 1], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); - BotSayTeamOrder(bs, teammates[numteammates - i - 1]); - BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_GETFLAG); - } - // - break; - } - } - } -} - -/* -================== -BotCTFOrders -================== -*/ -void BotCTFOrders_EnemyFlagNotAtBase(bot_state_t *bs) { - int numteammates, defenders, attackers, i, other; - int teammates[MAX_CLIENTS]; - char name[MAX_NETNAME], carriername[MAX_NETNAME]; - - numteammates = BotSortTeamMatesByBaseTravelTime(bs, teammates, sizeof(teammates)); - BotSortTeamMatesByTaskPreference(bs, teammates, numteammates); - //different orders based on the number of team mates - switch(numteammates) { - case 1: break; - case 2: - { - //tell the one not carrying the flag to defend the base - if (teammates[0] == bs->flagcarrier) other = teammates[1]; - else other = teammates[0]; - ClientName(other, name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, other); - BotSayVoiceTeamOrder(bs, other, VOICECHAT_DEFEND); - break; - } - case 3: - { - //tell the one closest to the base not carrying the flag to defend the base - if (teammates[0] != bs->flagcarrier) other = teammates[0]; - else other = teammates[1]; - ClientName(other, name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, other); - BotSayVoiceTeamOrder(bs, other, VOICECHAT_DEFEND); - //tell the other also to defend the base - if (teammates[2] != bs->flagcarrier) other = teammates[2]; - else other = teammates[1]; - ClientName(other, name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, other); - BotSayVoiceTeamOrder(bs, other, VOICECHAT_DEFEND); - break; - } - default: - { - //60% will defend the base - defenders = (int) (float) numteammates * 0.6 + 0.5; - if (defenders > 6) defenders = 6; - //30% accompanies the flag carrier - attackers = (int) (float) numteammates * 0.3 + 0.5; - if (attackers > 3) attackers = 3; - for (i = 0; i < defenders; i++) { - // - if (teammates[i] == bs->flagcarrier) { - continue; - } - ClientName(teammates[i], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[i]); - BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); - } - // if we have a flag carrier - if ( bs->flagcarrier != -1 ) { - ClientName(bs->flagcarrier, carriername, sizeof(carriername)); - for (i = 0; i < attackers; i++) { - // - if (teammates[numteammates - i - 1] == bs->flagcarrier) { - continue; - } - // - ClientName(teammates[numteammates - i - 1], name, sizeof(name)); - if (bs->flagcarrier == bs->client) { - BotAI_BotInitialChat(bs, "cmd_accompanyme", name, NULL); - BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_FOLLOWME); - } - else { - BotAI_BotInitialChat(bs, "cmd_accompany", name, carriername, NULL); - BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_FOLLOWFLAGCARRIER); - } - BotSayTeamOrder(bs, teammates[numteammates - i - 1]); - } - } - else { - for (i = 0; i < attackers; i++) { - // - if (teammates[numteammates - i - 1] == bs->flagcarrier) { - continue; - } - // - ClientName(teammates[numteammates - i - 1], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); - BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_GETFLAG); - BotSayTeamOrder(bs, teammates[numteammates - i - 1]); - } - } - // - break; - } - } -} - - -/* -================== -BotCTFOrders -================== -*/ -void BotCTFOrders_BothFlagsAtBase(bot_state_t *bs) { - int numteammates, defenders, attackers, i; - int teammates[MAX_CLIENTS]; - char name[MAX_NETNAME]; - - //sort team mates by travel time to base - numteammates = BotSortTeamMatesByBaseTravelTime(bs, teammates, sizeof(teammates)); - //sort team mates by CTF preference - BotSortTeamMatesByTaskPreference(bs, teammates, numteammates); - //passive strategy - if (!(bs->ctfstrategy & CTFS_AGRESSIVE)) { - //different orders based on the number of team mates - switch(numteammates) { - case 1: break; - case 2: - { - //the one closest to the base will defend the base - ClientName(teammates[0], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[0]); - BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); - //the other will get the flag - ClientName(teammates[1], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); - BotSayTeamOrder(bs, teammates[1]); - BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_GETFLAG); - break; - } - case 3: - { - //the one closest to the base will defend the base - ClientName(teammates[0], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[0]); - BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); - //the second one closest to the base will defend the base - ClientName(teammates[1], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[1]); - BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_DEFEND); - //the other will get the flag - ClientName(teammates[2], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); - BotSayTeamOrder(bs, teammates[2]); - BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_GETFLAG); - break; - } - default: - { - defenders = (int) (float) numteammates * 0.5 + 0.5; - if (defenders > 5) defenders = 5; - attackers = (int) (float) numteammates * 0.4 + 0.5; - if (attackers > 4) attackers = 4; - for (i = 0; i < defenders; i++) { - // - ClientName(teammates[i], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[i]); - BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); - } - for (i = 0; i < attackers; i++) { - // - ClientName(teammates[numteammates - i - 1], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); - BotSayTeamOrder(bs, teammates[numteammates - i - 1]); - BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_GETFLAG); - } - // - break; - } - } - } - else { - //different orders based on the number of team mates - switch(numteammates) { - case 1: break; - case 2: - { - //the one closest to the base will defend the base - ClientName(teammates[0], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[0]); - BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); - //the other will get the flag - ClientName(teammates[1], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); - BotSayTeamOrder(bs, teammates[1]); - BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_GETFLAG); - break; - } - case 3: - { - //the one closest to the base will defend the base - ClientName(teammates[0], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[0]); - BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); - //the others should go for the enemy flag - ClientName(teammates[1], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); - BotSayTeamOrder(bs, teammates[1]); - BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_GETFLAG); - // - ClientName(teammates[2], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); - BotSayTeamOrder(bs, teammates[2]); - BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_GETFLAG); - break; - } - default: - { - defenders = (int) (float) numteammates * 0.4 + 0.5; - if (defenders > 4) defenders = 4; - attackers = (int) (float) numteammates * 0.5 + 0.5; - if (attackers > 5) attackers = 5; - for (i = 0; i < defenders; i++) { - // - ClientName(teammates[i], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[i]); - BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); - } - for (i = 0; i < attackers; i++) { - // - ClientName(teammates[numteammates - i - 1], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); - BotSayTeamOrder(bs, teammates[numteammates - i - 1]); - BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_GETFLAG); - } - // - break; - } - } - } -} - -/* -================== -BotCTFOrders -================== -*/ -void BotCTFOrders(bot_state_t *bs) { - int flagstatus; - - // - if (BotTeam(bs) == TEAM_RED) flagstatus = bs->redflagstatus * 2 + bs->blueflagstatus; - else flagstatus = bs->blueflagstatus * 2 + bs->redflagstatus; - // - switch(flagstatus) { - case 0: BotCTFOrders_BothFlagsAtBase(bs); break; - case 1: BotCTFOrders_EnemyFlagNotAtBase(bs); break; - case 2: BotCTFOrders_FlagNotAtBase(bs); break; - case 3: BotCTFOrders_BothFlagsNotAtBase(bs); break; - } -} - - -/* -================== -BotCreateGroup -================== -*/ -void BotCreateGroup(bot_state_t *bs, int *teammates, int groupsize) { - char name[MAX_NETNAME], leadername[MAX_NETNAME]; - int i; - - // the others in the group will follow the teammates[0] - ClientName(teammates[0], leadername, sizeof(leadername)); - for (i = 1; i < groupsize; i++) - { - ClientName(teammates[i], name, sizeof(name)); - if (teammates[0] == bs->client) { - BotAI_BotInitialChat(bs, "cmd_accompanyme", name, NULL); - } - else { - BotAI_BotInitialChat(bs, "cmd_accompany", name, leadername, NULL); - } - BotSayTeamOrderAlways(bs, teammates[i]); - } -} - -/* -================== -BotTeamOrders - - FIXME: defend key areas? -================== -*/ -void BotTeamOrders(bot_state_t *bs) { - int teammates[MAX_CLIENTS]; - int numteammates, i; - char buf[MAX_INFO_STRING]; - static int maxclients; - - if (!maxclients) - maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); - - numteammates = 0; - for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { - trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); - //if no config string or no name - if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue; - //skip spectators - if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue; - // - if (BotSameTeam(bs, i)) { - teammates[numteammates] = i; - numteammates++; - } - } - // - switch(numteammates) { - case 1: break; - case 2: - { - //nothing special - break; - } - case 3: - { - //have one follow another and one free roaming - BotCreateGroup(bs, teammates, 2); - break; - } - case 4: - { - BotCreateGroup(bs, teammates, 2); //a group of 2 - BotCreateGroup(bs, &teammates[2], 2); //a group of 2 - break; - } - case 5: - { - BotCreateGroup(bs, teammates, 2); //a group of 2 - BotCreateGroup(bs, &teammates[2], 3); //a group of 3 - break; - } - default: - { - if (numteammates <= 10) { - for (i = 0; i < numteammates / 2; i++) { - BotCreateGroup(bs, &teammates[i*2], 2); //groups of 2 - } - } - break; - } - } -} - -#ifdef MISSIONPACK - -/* -================== -Bot1FCTFOrders_FlagAtCenter - - X% defend the base, Y% get the flag -================== -*/ -void Bot1FCTFOrders_FlagAtCenter(bot_state_t *bs) { - int numteammates, defenders, attackers, i; - int teammates[MAX_CLIENTS]; - char name[MAX_NETNAME]; - - //sort team mates by travel time to base - numteammates = BotSortTeamMatesByBaseTravelTime(bs, teammates, sizeof(teammates)); - //sort team mates by CTF preference - BotSortTeamMatesByTaskPreference(bs, teammates, numteammates); - //passive strategy - if (!(bs->ctfstrategy & CTFS_AGRESSIVE)) { - //different orders based on the number of team mates - switch(numteammates) { - case 1: break; - case 2: - { - //the one closest to the base will defend the base - ClientName(teammates[0], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[0]); - BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); - //the other will get the flag - ClientName(teammates[1], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); - BotSayTeamOrder(bs, teammates[1]); - BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_GETFLAG); - break; - } - case 3: - { - //the one closest to the base will defend the base - ClientName(teammates[0], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[0]); - BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); - //the second one closest to the base will defend the base - ClientName(teammates[1], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[1]); - BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); - //the other will get the flag - ClientName(teammates[2], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); - BotSayTeamOrder(bs, teammates[2]); - BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_GETFLAG); - break; - } - default: - { - //50% defend the base - defenders = (int) (float) numteammates * 0.5 + 0.5; - if (defenders > 5) defenders = 5; - //40% get the flag - attackers = (int) (float) numteammates * 0.4 + 0.5; - if (attackers > 4) attackers = 4; - for (i = 0; i < defenders; i++) { - // - ClientName(teammates[i], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[i]); - BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); - } - for (i = 0; i < attackers; i++) { - // - ClientName(teammates[numteammates - i - 1], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); - BotSayTeamOrder(bs, teammates[numteammates - i - 1]); - BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_GETFLAG); - } - // - break; - } - } - } - else { //agressive - //different orders based on the number of team mates - switch(numteammates) { - case 1: break; - case 2: - { - //the one closest to the base will defend the base - ClientName(teammates[0], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[0]); - BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); - //the other will get the flag - ClientName(teammates[1], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); - BotSayTeamOrder(bs, teammates[1]); - BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_GETFLAG); - break; - } - case 3: - { - //the one closest to the base will defend the base - ClientName(teammates[0], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[0]); - BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); - //the others should go for the enemy flag - ClientName(teammates[1], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); - BotSayTeamOrder(bs, teammates[1]); - BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_GETFLAG); - // - ClientName(teammates[2], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); - BotSayTeamOrder(bs, teammates[2]); - BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_GETFLAG); - break; - } - default: - { - //30% defend the base - defenders = (int) (float) numteammates * 0.3 + 0.5; - if (defenders > 3) defenders = 3; - //60% get the flag - attackers = (int) (float) numteammates * 0.6 + 0.5; - if (attackers > 6) attackers = 6; - for (i = 0; i < defenders; i++) { - // - ClientName(teammates[i], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[i]); - BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); - } - for (i = 0; i < attackers; i++) { - // - ClientName(teammates[numteammates - i - 1], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); - BotSayTeamOrder(bs, teammates[numteammates - i - 1]); - BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_GETFLAG); - } - // - break; - } - } - } -} - -/* -================== -Bot1FCTFOrders_TeamHasFlag - - X% towards neutral flag, Y% go towards enemy base and accompany flag carrier if visible -================== -*/ -void Bot1FCTFOrders_TeamHasFlag(bot_state_t *bs) { - int numteammates, defenders, attackers, i, other; - int teammates[MAX_CLIENTS]; - char name[MAX_NETNAME], carriername[MAX_NETNAME]; - - //sort team mates by travel time to base - numteammates = BotSortTeamMatesByBaseTravelTime(bs, teammates, sizeof(teammates)); - //sort team mates by CTF preference - BotSortTeamMatesByTaskPreference(bs, teammates, numteammates); - //passive strategy - if (!(bs->ctfstrategy & CTFS_AGRESSIVE)) { - //different orders based on the number of team mates - switch(numteammates) { - case 1: break; - case 2: - { - //tell the one not carrying the flag to attack the enemy base - if (teammates[0] == bs->flagcarrier) other = teammates[1]; - else other = teammates[0]; - ClientName(other, name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_attackenemybase", name, NULL); - BotSayTeamOrder(bs, other); - BotSayVoiceTeamOrder(bs, other, VOICECHAT_OFFENSE); - break; - } - case 3: - { - //tell the one closest to the base not carrying the flag to defend the base - if (teammates[0] != bs->flagcarrier) other = teammates[0]; - else other = teammates[1]; - ClientName(other, name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, other); - BotSayVoiceTeamOrder(bs, other, VOICECHAT_DEFEND); - //tell the one furthest from the base not carrying the flag to accompany the flag carrier - if (teammates[2] != bs->flagcarrier) other = teammates[2]; - else other = teammates[1]; - ClientName(other, name, sizeof(name)); - if ( bs->flagcarrier != -1 ) { - ClientName(bs->flagcarrier, carriername, sizeof(carriername)); - if (bs->flagcarrier == bs->client) { - BotAI_BotInitialChat(bs, "cmd_accompanyme", name, NULL); - BotSayVoiceTeamOrder(bs, other, VOICECHAT_FOLLOWME); - } - else { - BotAI_BotInitialChat(bs, "cmd_accompany", name, carriername, NULL); - BotSayVoiceTeamOrder(bs, other, VOICECHAT_FOLLOWFLAGCARRIER); - } - } - else { - // - BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); - BotSayVoiceTeamOrder(bs, other, VOICECHAT_GETFLAG); - } - BotSayTeamOrder(bs, other); - break; - } - default: - { - //30% will defend the base - defenders = (int) (float) numteammates * 0.3 + 0.5; - if (defenders > 3) defenders = 3; - //70% accompanies the flag carrier - attackers = (int) (float) numteammates * 0.7 + 0.5; - if (attackers > 7) attackers = 7; - for (i = 0; i < defenders; i++) { - // - if (teammates[i] == bs->flagcarrier) { - continue; - } - ClientName(teammates[i], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[i]); - BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); - } - if (bs->flagcarrier != -1) { - ClientName(bs->flagcarrier, carriername, sizeof(carriername)); - for (i = 0; i < attackers; i++) { - // - if (teammates[numteammates - i - 1] == bs->flagcarrier) { - continue; - } - // - ClientName(teammates[numteammates - i - 1], name, sizeof(name)); - if (bs->flagcarrier == bs->client) { - BotAI_BotInitialChat(bs, "cmd_accompanyme", name, NULL); - BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_FOLLOWME); - } - else { - BotAI_BotInitialChat(bs, "cmd_accompany", name, carriername, NULL); - BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_FOLLOWFLAGCARRIER); - } - BotSayTeamOrder(bs, teammates[numteammates - i - 1]); - } - } - else { - for (i = 0; i < attackers; i++) { - // - if (teammates[numteammates - i - 1] == bs->flagcarrier) { - continue; - } - // - ClientName(teammates[numteammates - i - 1], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); - BotSayTeamOrder(bs, teammates[numteammates - i - 1]); - BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_GETFLAG); - } - } - // - break; - } - } - } - else { //agressive - //different orders based on the number of team mates - switch(numteammates) { - case 1: break; - case 2: - { - //tell the one not carrying the flag to defend the base - if (teammates[0] == bs->flagcarrier) other = teammates[1]; - else other = teammates[0]; - ClientName(other, name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, other); - BotSayVoiceTeamOrder(bs, other, VOICECHAT_DEFEND); - break; - } - case 3: - { - //tell the one closest to the base not carrying the flag to defend the base - if (teammates[0] != bs->flagcarrier) other = teammates[0]; - else other = teammates[1]; - ClientName(other, name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, other); - BotSayVoiceTeamOrder(bs, other, VOICECHAT_DEFEND); - //tell the one furthest from the base not carrying the flag to accompany the flag carrier - if (teammates[2] != bs->flagcarrier) other = teammates[2]; - else other = teammates[1]; - ClientName(other, name, sizeof(name)); - ClientName(bs->flagcarrier, carriername, sizeof(carriername)); - if (bs->flagcarrier == bs->client) { - BotAI_BotInitialChat(bs, "cmd_accompanyme", name, NULL); - BotSayVoiceTeamOrder(bs, other, VOICECHAT_FOLLOWME); - } - else { - BotAI_BotInitialChat(bs, "cmd_accompany", name, carriername, NULL); - BotSayVoiceTeamOrder(bs, other, VOICECHAT_FOLLOWFLAGCARRIER); - } - BotSayTeamOrder(bs, other); - break; - } - default: - { - //20% will defend the base - defenders = (int) (float) numteammates * 0.2 + 0.5; - if (defenders > 2) defenders = 2; - //80% accompanies the flag carrier - attackers = (int) (float) numteammates * 0.8 + 0.5; - if (attackers > 8) attackers = 8; - for (i = 0; i < defenders; i++) { - // - if (teammates[i] == bs->flagcarrier) { - continue; - } - ClientName(teammates[i], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[i]); - BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); - } - ClientName(bs->flagcarrier, carriername, sizeof(carriername)); - for (i = 0; i < attackers; i++) { - // - if (teammates[numteammates - i - 1] == bs->flagcarrier) { - continue; - } - // - ClientName(teammates[numteammates - i - 1], name, sizeof(name)); - if (bs->flagcarrier == bs->client) { - BotAI_BotInitialChat(bs, "cmd_accompanyme", name, NULL); - BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_FOLLOWME); - } - else { - BotAI_BotInitialChat(bs, "cmd_accompany", name, carriername, NULL); - BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_FOLLOWFLAGCARRIER); - } - BotSayTeamOrder(bs, teammates[numteammates - i - 1]); - } - // - break; - } - } - } -} - -/* -================== -Bot1FCTFOrders_EnemyHasFlag - - X% defend the base, Y% towards neutral flag -================== -*/ -void Bot1FCTFOrders_EnemyHasFlag(bot_state_t *bs) { - int numteammates, defenders, attackers, i; - int teammates[MAX_CLIENTS]; - char name[MAX_NETNAME]; - - //sort team mates by travel time to base - numteammates = BotSortTeamMatesByBaseTravelTime(bs, teammates, sizeof(teammates)); - //sort team mates by CTF preference - BotSortTeamMatesByTaskPreference(bs, teammates, numteammates); - //passive strategy - if (!(bs->ctfstrategy & CTFS_AGRESSIVE)) { - //different orders based on the number of team mates - switch(numteammates) { - case 1: break; - case 2: - { - //both defend the base - ClientName(teammates[0], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[0]); - BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); - // - ClientName(teammates[1], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[1]); - BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_DEFEND); - break; - } - case 3: - { - //the one closest to the base will defend the base - ClientName(teammates[0], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[0]); - BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); - //the second one closest to the base will defend the base - ClientName(teammates[1], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[1]); - BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_DEFEND); - //the other will also defend the base - ClientName(teammates[2], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[2]); - BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_DEFEND); - break; - } - default: - { - //80% will defend the base - defenders = (int) (float) numteammates * 0.8 + 0.5; - if (defenders > 8) defenders = 8; - //10% will try to return the flag - attackers = (int) (float) numteammates * 0.1 + 0.5; - if (attackers > 2) attackers = 2; - for (i = 0; i < defenders; i++) { - // - ClientName(teammates[i], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[i]); - BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); - } - for (i = 0; i < attackers; i++) { - // - ClientName(teammates[numteammates - i - 1], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_returnflag", name, NULL); - BotSayTeamOrder(bs, teammates[numteammates - i - 1]); - BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_GETFLAG); - } - // - break; - } - } - } - else { //agressive - //different orders based on the number of team mates - switch(numteammates) { - case 1: break; - case 2: - { - //the one closest to the base will defend the base - ClientName(teammates[0], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[0]); - BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); - //the other will get the flag - ClientName(teammates[1], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[1]); - BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_DEFEND); - break; - } - case 3: - { - //the one closest to the base will defend the base - ClientName(teammates[0], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[0]); - BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); - //the others should go for the enemy flag - ClientName(teammates[1], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[1]); - BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_DEFEND); - // - ClientName(teammates[2], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_returnflag", name, NULL); - BotSayTeamOrder(bs, teammates[2]); - BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_GETFLAG); - break; - } - default: - { - //70% defend the base - defenders = (int) (float) numteammates * 0.7 + 0.5; - if (defenders > 8) defenders = 8; - //20% try to return the flag - attackers = (int) (float) numteammates * 0.2 + 0.5; - if (attackers > 2) attackers = 2; - for (i = 0; i < defenders; i++) { - // - ClientName(teammates[i], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[i]); - BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); - } - for (i = 0; i < attackers; i++) { - // - ClientName(teammates[numteammates - i - 1], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_returnflag", name, NULL); - BotSayTeamOrder(bs, teammates[numteammates - i - 1]); - BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_GETFLAG); - } - // - break; - } - } - } -} - -/* -================== -Bot1FCTFOrders_EnemyDroppedFlag - - X% defend the base, Y% get the flag -================== -*/ -void Bot1FCTFOrders_EnemyDroppedFlag(bot_state_t *bs) { - int numteammates, defenders, attackers, i; - int teammates[MAX_CLIENTS]; - char name[MAX_NETNAME]; - - //sort team mates by travel time to base - numteammates = BotSortTeamMatesByBaseTravelTime(bs, teammates, sizeof(teammates)); - //sort team mates by CTF preference - BotSortTeamMatesByTaskPreference(bs, teammates, numteammates); - //passive strategy - if (!(bs->ctfstrategy & CTFS_AGRESSIVE)) { - //different orders based on the number of team mates - switch(numteammates) { - case 1: break; - case 2: - { - //the one closest to the base will defend the base - ClientName(teammates[0], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[0]); - BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); - //the other will get the flag - ClientName(teammates[1], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); - BotSayTeamOrder(bs, teammates[1]); - BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_GETFLAG); - break; - } - case 3: - { - //the one closest to the base will defend the base - ClientName(teammates[0], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[0]); - BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); - //the second one closest to the base will defend the base - ClientName(teammates[1], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[1]); - BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_DEFEND); - //the other will get the flag - ClientName(teammates[2], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); - BotSayTeamOrder(bs, teammates[2]); - BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_GETFLAG); - break; - } - default: - { - //50% defend the base - defenders = (int) (float) numteammates * 0.5 + 0.5; - if (defenders > 5) defenders = 5; - //40% get the flag - attackers = (int) (float) numteammates * 0.4 + 0.5; - if (attackers > 4) attackers = 4; - for (i = 0; i < defenders; i++) { - // - ClientName(teammates[i], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[i]); - BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); - } - for (i = 0; i < attackers; i++) { - // - ClientName(teammates[numteammates - i - 1], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); - BotSayTeamOrder(bs, teammates[numteammates - i - 1]); - BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_GETFLAG); - } - // - break; - } - } - } - else { //agressive - //different orders based on the number of team mates - switch(numteammates) { - case 1: break; - case 2: - { - //the one closest to the base will defend the base - ClientName(teammates[0], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[0]); - BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); - //the other will get the flag - ClientName(teammates[1], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); - BotSayTeamOrder(bs, teammates[1]); - BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_GETFLAG); - break; - } - case 3: - { - //the one closest to the base will defend the base - ClientName(teammates[0], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[0]); - BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); - //the others should go for the enemy flag - ClientName(teammates[1], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); - BotSayTeamOrder(bs, teammates[1]); - BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_GETFLAG); - // - ClientName(teammates[2], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); - BotSayTeamOrder(bs, teammates[2]); - BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_GETFLAG); - break; - } - default: - { - //30% defend the base - defenders = (int) (float) numteammates * 0.3 + 0.5; - if (defenders > 3) defenders = 3; - //60% get the flag - attackers = (int) (float) numteammates * 0.6 + 0.5; - if (attackers > 6) attackers = 6; - for (i = 0; i < defenders; i++) { - // - ClientName(teammates[i], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[i]); - BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); - } - for (i = 0; i < attackers; i++) { - // - ClientName(teammates[numteammates - i - 1], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); - BotSayTeamOrder(bs, teammates[numteammates - i - 1]); - BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_DEFEND); - } - // - break; - } - } - } -} - -/* -================== -Bot1FCTFOrders -================== -*/ -void Bot1FCTFOrders(bot_state_t *bs) { - switch(bs->neutralflagstatus) { - case 0: Bot1FCTFOrders_FlagAtCenter(bs); break; - case 1: Bot1FCTFOrders_TeamHasFlag(bs); break; - case 2: Bot1FCTFOrders_EnemyHasFlag(bs); break; - case 3: Bot1FCTFOrders_EnemyDroppedFlag(bs); break; - } -} - -/* -================== -BotObeliskOrders - - X% in defence Y% in offence -================== -*/ -void BotObeliskOrders(bot_state_t *bs) { - int numteammates, defenders, attackers, i; - int teammates[MAX_CLIENTS]; - char name[MAX_NETNAME]; - - //sort team mates by travel time to base - numteammates = BotSortTeamMatesByBaseTravelTime(bs, teammates, sizeof(teammates)); - //sort team mates by CTF preference - BotSortTeamMatesByTaskPreference(bs, teammates, numteammates); - //passive strategy - if (!(bs->ctfstrategy & CTFS_AGRESSIVE)) { - //different orders based on the number of team mates - switch(numteammates) { - case 1: break; - case 2: - { - //the one closest to the base will defend the base - ClientName(teammates[0], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[0]); - BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); - //the other will attack the enemy base - ClientName(teammates[1], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_attackenemybase", name, NULL); - BotSayTeamOrder(bs, teammates[1]); - BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_OFFENSE); - break; - } - case 3: - { - //the one closest to the base will defend the base - ClientName(teammates[0], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[0]); - BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); - //the one second closest to the base also defends the base - ClientName(teammates[1], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[1]); - BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_DEFEND); - //the other one attacks the enemy base - ClientName(teammates[2], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_attackenemybase", name, NULL); - BotSayTeamOrder(bs, teammates[2]); - BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_OFFENSE); - break; - } - default: - { - //50% defend the base - defenders = (int) (float) numteammates * 0.5 + 0.5; - if (defenders > 5) defenders = 5; - //40% attack the enemy base - attackers = (int) (float) numteammates * 0.4 + 0.5; - if (attackers > 4) attackers = 4; - for (i = 0; i < defenders; i++) { - // - ClientName(teammates[i], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[i]); - BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); - } - for (i = 0; i < attackers; i++) { - // - ClientName(teammates[numteammates - i - 1], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_attackenemybase", name, NULL); - BotSayTeamOrder(bs, teammates[numteammates - i - 1]); - BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_OFFENSE); - } - // - break; - } - } - } - else { - //different orders based on the number of team mates - switch(numteammates) { - case 1: break; - case 2: - { - //the one closest to the base will defend the base - ClientName(teammates[0], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[0]); - BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); - //the other will attack the enemy base - ClientName(teammates[1], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_attackenemybase", name, NULL); - BotSayTeamOrder(bs, teammates[1]); - BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_OFFENSE); - break; - } - case 3: - { - //the one closest to the base will defend the base - ClientName(teammates[0], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[0]); - BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); - //the others attack the enemy base - ClientName(teammates[1], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_attackenemybase", name, NULL); - BotSayTeamOrder(bs, teammates[1]); - BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_OFFENSE); - // - ClientName(teammates[2], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_attackenemybase", name, NULL); - BotSayTeamOrder(bs, teammates[2]); - BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_OFFENSE); - break; - } - default: - { - //30% defend the base - defenders = (int) (float) numteammates * 0.3 + 0.5; - if (defenders > 3) defenders = 3; - //70% attack the enemy base - attackers = (int) (float) numteammates * 0.7 + 0.5; - if (attackers > 7) attackers = 7; - for (i = 0; i < defenders; i++) { - // - ClientName(teammates[i], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[i]); - BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); - } - for (i = 0; i < attackers; i++) { - // - ClientName(teammates[numteammates - i - 1], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_attackenemybase", name, NULL); - BotSayTeamOrder(bs, teammates[numteammates - i - 1]); - BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_OFFENSE); - } - // - break; - } - } - } -} - -/* -================== -BotHarvesterOrders - - X% defend the base, Y% harvest -================== -*/ -void BotHarvesterOrders(bot_state_t *bs) { - int numteammates, defenders, attackers, i; - int teammates[MAX_CLIENTS]; - char name[MAX_NETNAME]; - - //sort team mates by travel time to base - numteammates = BotSortTeamMatesByBaseTravelTime(bs, teammates, sizeof(teammates)); - //sort team mates by CTF preference - BotSortTeamMatesByTaskPreference(bs, teammates, numteammates); - //passive strategy - if (!(bs->ctfstrategy & CTFS_AGRESSIVE)) { - //different orders based on the number of team mates - switch(numteammates) { - case 1: break; - case 2: - { - //the one closest to the base will defend the base - ClientName(teammates[0], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[0]); - BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); - //the other will harvest - ClientName(teammates[1], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_harvest", name, NULL); - BotSayTeamOrder(bs, teammates[1]); - BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_OFFENSE); - break; - } - case 3: - { - //the one closest to the base will defend the base - ClientName(teammates[0], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[0]); - BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); - //the one second closest to the base also defends the base - ClientName(teammates[1], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[1]); - BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_DEFEND); - //the other one goes harvesting - ClientName(teammates[2], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_harvest", name, NULL); - BotSayTeamOrder(bs, teammates[2]); - BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_OFFENSE); - break; - } - default: - { - //50% defend the base - defenders = (int) (float) numteammates * 0.5 + 0.5; - if (defenders > 5) defenders = 5; - //40% goes harvesting - attackers = (int) (float) numteammates * 0.4 + 0.5; - if (attackers > 4) attackers = 4; - for (i = 0; i < defenders; i++) { - // - ClientName(teammates[i], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[i]); - BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); - } - for (i = 0; i < attackers; i++) { - // - ClientName(teammates[numteammates - i - 1], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_harvest", name, NULL); - BotSayTeamOrder(bs, teammates[numteammates - i - 1]); - BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_OFFENSE); - } - // - break; - } - } - } - else { - //different orders based on the number of team mates - switch(numteammates) { - case 1: break; - case 2: - { - //the one closest to the base will defend the base - ClientName(teammates[0], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[0]); - BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); - //the other will harvest - ClientName(teammates[1], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_harvest", name, NULL); - BotSayTeamOrder(bs, teammates[1]); - BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_OFFENSE); - break; - } - case 3: - { - //the one closest to the base will defend the base - ClientName(teammates[0], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[0]); - BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); - //the others go harvesting - ClientName(teammates[1], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_harvest", name, NULL); - BotSayTeamOrder(bs, teammates[1]); - BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_OFFENSE); - // - ClientName(teammates[2], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_harvest", name, NULL); - BotSayTeamOrder(bs, teammates[2]); - BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_OFFENSE); - break; - } - default: - { - //30% defend the base - defenders = (int) (float) numteammates * 0.3 + 0.5; - if (defenders > 3) defenders = 3; - //70% go harvesting - attackers = (int) (float) numteammates * 0.7 + 0.5; - if (attackers > 7) attackers = 7; - for (i = 0; i < defenders; i++) { - // - ClientName(teammates[i], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); - BotSayTeamOrder(bs, teammates[i]); - BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); - } - for (i = 0; i < attackers; i++) { - // - ClientName(teammates[numteammates - i - 1], name, sizeof(name)); - BotAI_BotInitialChat(bs, "cmd_harvest", name, NULL); - BotSayTeamOrder(bs, teammates[numteammates - i - 1]); - BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_OFFENSE); - } - // - break; - } - } - } -} -#endif - -/* -================== -FindHumanTeamLeader -================== -*/ -int FindHumanTeamLeader(bot_state_t *bs) { - int i; - - for (i = 0; i < MAX_CLIENTS; i++) { - if ( g_entities[i].inuse ) { - // if this player is not a bot - if ( !(g_entities[i].r.svFlags & SVF_BOT) ) { - // if this player is ok with being the leader - if (!notleader[i]) { - // if this player is on the same team - if ( BotSameTeam(bs, i) ) { - ClientName(i, bs->teamleader, sizeof(bs->teamleader)); - // if not yet ordered to do anything - if ( !BotSetLastOrderedTask(bs) ) { - // go on defense by default - BotVoiceChat_Defend(bs, i, SAY_TELL); - } - return qtrue; - } - } - } - } - } - return qfalse; -} - -/* -================== -BotTeamAI -================== -*/ -void BotTeamAI(bot_state_t *bs) { - int numteammates; - char netname[MAX_NETNAME]; - - // - if ( gametype < GT_TEAM ) - return; - // make sure we've got a valid team leader - if (!BotValidTeamLeader(bs)) { - // - if (!FindHumanTeamLeader(bs)) { - // - if (!bs->askteamleader_time && !bs->becometeamleader_time) { - if (bs->entergame_time + 10 > FloatTime()) { - bs->askteamleader_time = FloatTime() + 5 + random() * 10; - } - else { - bs->becometeamleader_time = FloatTime() + 5 + random() * 10; - } - } - if (bs->askteamleader_time && bs->askteamleader_time < FloatTime()) { - // if asked for a team leader and no response - BotAI_BotInitialChat(bs, "whoisteamleader", NULL); - trap_BotEnterChat(bs->cs, 0, CHAT_TEAM); - bs->askteamleader_time = 0; - bs->becometeamleader_time = FloatTime() + 8 + random() * 10; - } - if (bs->becometeamleader_time && bs->becometeamleader_time < FloatTime()) { - BotAI_BotInitialChat(bs, "iamteamleader", NULL); - trap_BotEnterChat(bs->cs, 0, CHAT_TEAM); - BotSayVoiceTeamOrder(bs, -1, VOICECHAT_STARTLEADER); - ClientName(bs->client, netname, sizeof(netname)); - strncpy(bs->teamleader, netname, sizeof(bs->teamleader)); - bs->teamleader[sizeof(bs->teamleader)] = '\0'; - bs->becometeamleader_time = 0; - } - return; - } - } - bs->askteamleader_time = 0; - bs->becometeamleader_time = 0; - - //return if this bot is NOT the team leader - ClientName(bs->client, netname, sizeof(netname)); - if (Q_stricmp(netname, bs->teamleader) != 0) return; - // - numteammates = BotNumTeamMates(bs); - //give orders - switch(gametype) { - case GT_TEAM: - { - if (bs->numteammates != numteammates || bs->forceorders) { - bs->teamgiveorders_time = FloatTime(); - bs->numteammates = numteammates; - bs->forceorders = qfalse; - } - //if it's time to give orders - if (bs->teamgiveorders_time && bs->teamgiveorders_time < FloatTime() - 5) { - BotTeamOrders(bs); - //give orders again after 120 seconds - bs->teamgiveorders_time = FloatTime() + 120; - } - break; - } - case GT_CTF: - { - //if the number of team mates changed or the flag status changed - //or someone wants to know what to do - if (bs->numteammates != numteammates || bs->flagstatuschanged || bs->forceorders) { - bs->teamgiveorders_time = FloatTime(); - bs->numteammates = numteammates; - bs->flagstatuschanged = qfalse; - bs->forceorders = qfalse; - } - //if there were no flag captures the last 3 minutes - if (bs->lastflagcapture_time < FloatTime() - 240) { - bs->lastflagcapture_time = FloatTime(); - //randomly change the CTF strategy - if (random() < 0.4) { - bs->ctfstrategy ^= CTFS_AGRESSIVE; - bs->teamgiveorders_time = FloatTime(); - } - } - //if it's time to give orders - if (bs->teamgiveorders_time && bs->teamgiveorders_time < FloatTime() - 3) { - BotCTFOrders(bs); - // - bs->teamgiveorders_time = 0; - } - break; - } -#ifdef MISSIONPACK - case GT_1FCTF: - { - if (bs->numteammates != numteammates || bs->flagstatuschanged || bs->forceorders) { - bs->teamgiveorders_time = FloatTime(); - bs->numteammates = numteammates; - bs->flagstatuschanged = qfalse; - bs->forceorders = qfalse; - } - //if there were no flag captures the last 4 minutes - if (bs->lastflagcapture_time < FloatTime() - 240) { - bs->lastflagcapture_time = FloatTime(); - //randomly change the CTF strategy - if (random() < 0.4) { - bs->ctfstrategy ^= CTFS_AGRESSIVE; - bs->teamgiveorders_time = FloatTime(); - } - } - //if it's time to give orders - if (bs->teamgiveorders_time && bs->teamgiveorders_time < FloatTime() - 2) { - Bot1FCTFOrders(bs); - // - bs->teamgiveorders_time = 0; - } - break; - } - case GT_OBELISK: - { - if (bs->numteammates != numteammates || bs->forceorders) { - bs->teamgiveorders_time = FloatTime(); - bs->numteammates = numteammates; - bs->forceorders = qfalse; - } - //if it's time to give orders - if (bs->teamgiveorders_time && bs->teamgiveorders_time < FloatTime() - 5) { - BotObeliskOrders(bs); - //give orders again after 30 seconds - bs->teamgiveorders_time = FloatTime() + 30; - } - break; - } - case GT_HARVESTER: - { - if (bs->numteammates != numteammates || bs->forceorders) { - bs->teamgiveorders_time = FloatTime(); - bs->numteammates = numteammates; - bs->forceorders = qfalse; - } - //if it's time to give orders - if (bs->teamgiveorders_time && bs->teamgiveorders_time < FloatTime() - 5) { - BotHarvesterOrders(bs); - //give orders again after 30 seconds - bs->teamgiveorders_time = FloatTime() + 30; - } - break; - } -#endif - } -} - +/* +=========================================================================== +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 Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// + +/***************************************************************************** + * name: ai_team.c + * + * desc: Quake3 bot AI + * + * $Archive: /MissionPack/code/game/ai_team.c $ + * + *****************************************************************************/ + +#include "g_local.h" +#include "botlib.h" +#include "be_aas.h" +#include "be_ea.h" +#include "be_ai_char.h" +#include "be_ai_chat.h" +#include "be_ai_gen.h" +#include "be_ai_goal.h" +#include "be_ai_move.h" +#include "be_ai_weap.h" +// +#include "ai_main.h" +#include "ai_dmq3.h" +#include "ai_chat.h" +#include "ai_cmd.h" +#include "ai_dmnet.h" +#include "ai_team.h" +#include "ai_vcmd.h" + +#include "match.h" + +// for the voice chats +#include "../../ui/menudef.h" + +//ctf task preferences for a client +typedef struct bot_ctftaskpreference_s +{ + char name[36]; + int preference; +} bot_ctftaskpreference_t; + +bot_ctftaskpreference_t ctftaskpreferences[MAX_CLIENTS]; + + +/* +================== +BotValidTeamLeader +================== +*/ +int BotValidTeamLeader(bot_state_t *bs) { + if (!strlen(bs->teamleader)) return qfalse; + if (ClientFromName(bs->teamleader) == -1) return qfalse; + return qtrue; +} + +/* +================== +BotNumTeamMates +================== +*/ +int BotNumTeamMates(bot_state_t *bs) { + int i, numplayers; + char buf[MAX_INFO_STRING]; + static int maxclients; + + if (!maxclients) + maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); + + numplayers = 0; + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); + //if no config string or no name + if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue; + //skip spectators + if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue; + // + if (BotSameTeam(bs, i)) { + numplayers++; + } + } + return numplayers; +} + +/* +================== +BotClientTravelTimeToGoal +================== +*/ +int BotClientTravelTimeToGoal(int client, bot_goal_t *goal) { + playerState_t ps; + int areanum; + + BotAI_GetClientState(client, &ps); + areanum = BotPointAreaNum(ps.origin); + if (!areanum) return 1; + return trap_AAS_AreaTravelTimeToGoalArea(areanum, ps.origin, goal->areanum, TFL_DEFAULT); +} + +/* +================== +BotSortTeamMatesByBaseTravelTime +================== +*/ +int BotSortTeamMatesByBaseTravelTime(bot_state_t *bs, int *teammates, int maxteammates) { + + int i, j, k, numteammates, traveltime; + char buf[MAX_INFO_STRING]; + static int maxclients; + int traveltimes[MAX_CLIENTS]; + bot_goal_t *goal = NULL; + + if (gametype == GT_CTF || gametype == GT_1FCTF) { + if (BotTeam(bs) == TEAM_RED) + goal = &ctf_redflag; + else + goal = &ctf_blueflag; + } +#ifdef MISSIONPACK + else { + if (BotTeam(bs) == TEAM_RED) + goal = &redobelisk; + else + goal = &blueobelisk; + } +#endif + if (!maxclients) + maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); + + numteammates = 0; + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); + //if no config string or no name + if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue; + //skip spectators + if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue; + // + if (BotSameTeam(bs, i)) { + // + traveltime = BotClientTravelTimeToGoal(i, goal); + // + for (j = 0; j < numteammates; j++) { + if (traveltime < traveltimes[j]) { + for (k = numteammates; k > j; k--) { + traveltimes[k] = traveltimes[k-1]; + teammates[k] = teammates[k-1]; + } + break; + } + } + traveltimes[j] = traveltime; + teammates[j] = i; + numteammates++; + if (numteammates >= maxteammates) break; + } + } + return numteammates; +} + +/* +================== +BotSetTeamMateTaskPreference +================== +*/ +void BotSetTeamMateTaskPreference(bot_state_t *bs, int teammate, int preference) { + char teammatename[MAX_NETNAME]; + + ctftaskpreferences[teammate].preference = preference; + ClientName(teammate, teammatename, sizeof(teammatename)); + strcpy(ctftaskpreferences[teammate].name, teammatename); +} + +/* +================== +BotGetTeamMateTaskPreference +================== +*/ +int BotGetTeamMateTaskPreference(bot_state_t *bs, int teammate) { + char teammatename[MAX_NETNAME]; + + if (!ctftaskpreferences[teammate].preference) return 0; + ClientName(teammate, teammatename, sizeof(teammatename)); + if (Q_stricmp(teammatename, ctftaskpreferences[teammate].name)) return 0; + return ctftaskpreferences[teammate].preference; +} + +/* +================== +BotSortTeamMatesByTaskPreference +================== +*/ +int BotSortTeamMatesByTaskPreference(bot_state_t *bs, int *teammates, int numteammates) { + int defenders[MAX_CLIENTS], numdefenders; + int attackers[MAX_CLIENTS], numattackers; + int roamers[MAX_CLIENTS], numroamers; + int i, preference; + + numdefenders = numattackers = numroamers = 0; + for (i = 0; i < numteammates; i++) { + preference = BotGetTeamMateTaskPreference(bs, teammates[i]); + if (preference & TEAMTP_DEFENDER) { + defenders[numdefenders++] = teammates[i]; + } + else if (preference & TEAMTP_ATTACKER) { + attackers[numattackers++] = teammates[i]; + } + else { + roamers[numroamers++] = teammates[i]; + } + } + numteammates = 0; + //defenders at the front of the list + memcpy(&teammates[numteammates], defenders, numdefenders * sizeof(int)); + numteammates += numdefenders; + //roamers in the middle + memcpy(&teammates[numteammates], roamers, numroamers * sizeof(int)); + numteammates += numroamers; + //attacker in the back of the list + memcpy(&teammates[numteammates], attackers, numattackers * sizeof(int)); + numteammates += numattackers; + + return numteammates; +} + +/* +================== +BotSayTeamOrders +================== +*/ +void BotSayTeamOrderAlways(bot_state_t *bs, int toclient) { + char teamchat[MAX_MESSAGE_SIZE]; + char buf[MAX_MESSAGE_SIZE]; + char name[MAX_NETNAME]; + + //if the bot is talking to itself + if (bs->client == toclient) { + //don't show the message just put it in the console message queue + trap_BotGetChatMessage(bs->cs, buf, sizeof(buf)); + ClientName(bs->client, name, sizeof(name)); + Com_sprintf(teamchat, sizeof(teamchat), EC"(%s"EC")"EC": %s", name, buf); + trap_BotQueueConsoleMessage(bs->cs, CMS_CHAT, teamchat); + } + else { + trap_BotEnterChat(bs->cs, toclient, CHAT_TELL); + } +} + +/* +================== +BotSayTeamOrders +================== +*/ +void BotSayTeamOrder(bot_state_t *bs, int toclient) { +#ifdef MISSIONPACK + // voice chats only + char buf[MAX_MESSAGE_SIZE]; + + trap_BotGetChatMessage(bs->cs, buf, sizeof(buf)); +#else + BotSayTeamOrderAlways(bs, toclient); +#endif +} + +/* +================== +BotVoiceChat +================== +*/ +void BotVoiceChat(bot_state_t *bs, int toclient, char *voicechat) { +#ifdef MISSIONPACK + if (toclient == -1) + // voice only say team + trap_EA_Command(bs->client, va("vsay_team %s", voicechat)); + else + // voice only tell single player + trap_EA_Command(bs->client, va("vtell %d %s", toclient, voicechat)); +#endif +} + +/* +================== +BotVoiceChatOnly +================== +*/ +void BotVoiceChatOnly(bot_state_t *bs, int toclient, char *voicechat) { +#ifdef MISSIONPACK + if (toclient == -1) + // voice only say team + trap_EA_Command(bs->client, va("vosay_team %s", voicechat)); + else + // voice only tell single player + trap_EA_Command(bs->client, va("votell %d %s", toclient, voicechat)); +#endif +} + +/* +================== +BotSayVoiceTeamOrder +================== +*/ +void BotSayVoiceTeamOrder(bot_state_t *bs, int toclient, char *voicechat) { +#ifdef MISSIONPACK + BotVoiceChat(bs, toclient, voicechat); +#endif +} + +/* +================== +BotCTFOrders +================== +*/ +void BotCTFOrders_BothFlagsNotAtBase(bot_state_t *bs) { + int numteammates, defenders, attackers, i, other; + int teammates[MAX_CLIENTS]; + char name[MAX_NETNAME], carriername[MAX_NETNAME]; + + numteammates = BotSortTeamMatesByBaseTravelTime(bs, teammates, sizeof(teammates)); + BotSortTeamMatesByTaskPreference(bs, teammates, numteammates); + //different orders based on the number of team mates + switch(bs->numteammates) { + case 1: break; + case 2: + { + //tell the one not carrying the flag to attack the enemy base + if (teammates[0] != bs->flagcarrier) other = teammates[0]; + else other = teammates[1]; + ClientName(other, name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, other); + BotSayVoiceTeamOrder(bs, other, VOICECHAT_GETFLAG); + break; + } + case 3: + { + //tell the one closest to the base not carrying the flag to accompany the flag carrier + if (teammates[0] != bs->flagcarrier) other = teammates[0]; + else other = teammates[1]; + ClientName(other, name, sizeof(name)); + if ( bs->flagcarrier != -1 ) { + ClientName(bs->flagcarrier, carriername, sizeof(carriername)); + if (bs->flagcarrier == bs->client) { + BotAI_BotInitialChat(bs, "cmd_accompanyme", name, NULL); + BotSayVoiceTeamOrder(bs, other, VOICECHAT_FOLLOWME); + } + else { + BotAI_BotInitialChat(bs, "cmd_accompany", name, carriername, NULL); + BotSayVoiceTeamOrder(bs, other, VOICECHAT_FOLLOWFLAGCARRIER); + } + } + else { + // + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayVoiceTeamOrder(bs, other, VOICECHAT_GETFLAG); + } + BotSayTeamOrder(bs, other); + //tell the one furthest from the the base not carrying the flag to get the enemy flag + if (teammates[2] != bs->flagcarrier) other = teammates[2]; + else other = teammates[1]; + ClientName(other, name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, other); + BotSayVoiceTeamOrder(bs, other, VOICECHAT_RETURNFLAG); + break; + } + default: + { + defenders = (int) (float) numteammates * 0.4 + 0.5; + if (defenders > 4) defenders = 4; + attackers = (int) (float) numteammates * 0.5 + 0.5; + if (attackers > 5) attackers = 5; + if (bs->flagcarrier != -1) { + ClientName(bs->flagcarrier, carriername, sizeof(carriername)); + for (i = 0; i < defenders; i++) { + // + if (teammates[i] == bs->flagcarrier) { + continue; + } + // + ClientName(teammates[i], name, sizeof(name)); + if (bs->flagcarrier == bs->client) { + BotAI_BotInitialChat(bs, "cmd_accompanyme", name, NULL); + BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_FOLLOWME); + } + else { + BotAI_BotInitialChat(bs, "cmd_accompany", name, carriername, NULL); + BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_FOLLOWFLAGCARRIER); + } + BotSayTeamOrder(bs, teammates[i]); + } + } + else { + for (i = 0; i < defenders; i++) { + // + if (teammates[i] == bs->flagcarrier) { + continue; + } + // + ClientName(teammates[i], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_GETFLAG); + BotSayTeamOrder(bs, teammates[i]); + } + } + for (i = 0; i < attackers; i++) { + // + if (teammates[numteammates - i - 1] == bs->flagcarrier) { + continue; + } + // + ClientName(teammates[numteammates - i - 1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[numteammates - i - 1]); + BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_RETURNFLAG); + } + // + break; + } + } +} + +/* +================== +BotCTFOrders +================== +*/ +void BotCTFOrders_FlagNotAtBase(bot_state_t *bs) { + int numteammates, defenders, attackers, i; + int teammates[MAX_CLIENTS]; + char name[MAX_NETNAME]; + + numteammates = BotSortTeamMatesByBaseTravelTime(bs, teammates, sizeof(teammates)); + BotSortTeamMatesByTaskPreference(bs, teammates, numteammates); + //passive strategy + if (!(bs->ctfstrategy & CTFS_AGRESSIVE)) { + //different orders based on the number of team mates + switch(bs->numteammates) { + case 1: break; + case 2: + { + //both will go for the enemy flag + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_GETFLAG); + // + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_GETFLAG); + break; + } + case 3: + { + //keep one near the base for when the flag is returned + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the other two get the flag + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_GETFLAG); + // + ClientName(teammates[2], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[2]); + BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_GETFLAG); + break; + } + default: + { + //keep some people near the base for when the flag is returned + defenders = (int) (float) numteammates * 0.3 + 0.5; + if (defenders > 3) defenders = 3; + attackers = (int) (float) numteammates * 0.7 + 0.5; + if (attackers > 6) attackers = 6; + for (i = 0; i < defenders; i++) { + // + ClientName(teammates[i], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[i]); + BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); + } + for (i = 0; i < attackers; i++) { + // + ClientName(teammates[numteammates - i - 1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[numteammates - i - 1]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_GETFLAG); + } + // + break; + } + } + } + else { + //different orders based on the number of team mates + switch(bs->numteammates) { + case 1: break; + case 2: + { + //both will go for the enemy flag + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_GETFLAG); + // + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_GETFLAG); + break; + } + case 3: + { + //everyone go for the flag + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_GETFLAG); + // + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_GETFLAG); + // + ClientName(teammates[2], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[2]); + BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_GETFLAG); + break; + } + default: + { + //keep some people near the base for when the flag is returned + defenders = (int) (float) numteammates * 0.2 + 0.5; + if (defenders > 2) defenders = 2; + attackers = (int) (float) numteammates * 0.7 + 0.5; + if (attackers > 7) attackers = 7; + for (i = 0; i < defenders; i++) { + // + ClientName(teammates[i], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[i]); + BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); + } + for (i = 0; i < attackers; i++) { + // + ClientName(teammates[numteammates - i - 1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[numteammates - i - 1]); + BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_GETFLAG); + } + // + break; + } + } + } +} + +/* +================== +BotCTFOrders +================== +*/ +void BotCTFOrders_EnemyFlagNotAtBase(bot_state_t *bs) { + int numteammates, defenders, attackers, i, other; + int teammates[MAX_CLIENTS]; + char name[MAX_NETNAME], carriername[MAX_NETNAME]; + + numteammates = BotSortTeamMatesByBaseTravelTime(bs, teammates, sizeof(teammates)); + BotSortTeamMatesByTaskPreference(bs, teammates, numteammates); + //different orders based on the number of team mates + switch(numteammates) { + case 1: break; + case 2: + { + //tell the one not carrying the flag to defend the base + if (teammates[0] == bs->flagcarrier) other = teammates[1]; + else other = teammates[0]; + ClientName(other, name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, other); + BotSayVoiceTeamOrder(bs, other, VOICECHAT_DEFEND); + break; + } + case 3: + { + //tell the one closest to the base not carrying the flag to defend the base + if (teammates[0] != bs->flagcarrier) other = teammates[0]; + else other = teammates[1]; + ClientName(other, name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, other); + BotSayVoiceTeamOrder(bs, other, VOICECHAT_DEFEND); + //tell the other also to defend the base + if (teammates[2] != bs->flagcarrier) other = teammates[2]; + else other = teammates[1]; + ClientName(other, name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, other); + BotSayVoiceTeamOrder(bs, other, VOICECHAT_DEFEND); + break; + } + default: + { + //60% will defend the base + defenders = (int) (float) numteammates * 0.6 + 0.5; + if (defenders > 6) defenders = 6; + //30% accompanies the flag carrier + attackers = (int) (float) numteammates * 0.3 + 0.5; + if (attackers > 3) attackers = 3; + for (i = 0; i < defenders; i++) { + // + if (teammates[i] == bs->flagcarrier) { + continue; + } + ClientName(teammates[i], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[i]); + BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); + } + // if we have a flag carrier + if ( bs->flagcarrier != -1 ) { + ClientName(bs->flagcarrier, carriername, sizeof(carriername)); + for (i = 0; i < attackers; i++) { + // + if (teammates[numteammates - i - 1] == bs->flagcarrier) { + continue; + } + // + ClientName(teammates[numteammates - i - 1], name, sizeof(name)); + if (bs->flagcarrier == bs->client) { + BotAI_BotInitialChat(bs, "cmd_accompanyme", name, NULL); + BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_FOLLOWME); + } + else { + BotAI_BotInitialChat(bs, "cmd_accompany", name, carriername, NULL); + BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_FOLLOWFLAGCARRIER); + } + BotSayTeamOrder(bs, teammates[numteammates - i - 1]); + } + } + else { + for (i = 0; i < attackers; i++) { + // + if (teammates[numteammates - i - 1] == bs->flagcarrier) { + continue; + } + // + ClientName(teammates[numteammates - i - 1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_GETFLAG); + BotSayTeamOrder(bs, teammates[numteammates - i - 1]); + } + } + // + break; + } + } +} + + +/* +================== +BotCTFOrders +================== +*/ +void BotCTFOrders_BothFlagsAtBase(bot_state_t *bs) { + int numteammates, defenders, attackers, i; + int teammates[MAX_CLIENTS]; + char name[MAX_NETNAME]; + + //sort team mates by travel time to base + numteammates = BotSortTeamMatesByBaseTravelTime(bs, teammates, sizeof(teammates)); + //sort team mates by CTF preference + BotSortTeamMatesByTaskPreference(bs, teammates, numteammates); + //passive strategy + if (!(bs->ctfstrategy & CTFS_AGRESSIVE)) { + //different orders based on the number of team mates + switch(numteammates) { + case 1: break; + case 2: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the other will get the flag + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_GETFLAG); + break; + } + case 3: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the second one closest to the base will defend the base + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_DEFEND); + //the other will get the flag + ClientName(teammates[2], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[2]); + BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_GETFLAG); + break; + } + default: + { + defenders = (int) (float) numteammates * 0.5 + 0.5; + if (defenders > 5) defenders = 5; + attackers = (int) (float) numteammates * 0.4 + 0.5; + if (attackers > 4) attackers = 4; + for (i = 0; i < defenders; i++) { + // + ClientName(teammates[i], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[i]); + BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); + } + for (i = 0; i < attackers; i++) { + // + ClientName(teammates[numteammates - i - 1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[numteammates - i - 1]); + BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_GETFLAG); + } + // + break; + } + } + } + else { + //different orders based on the number of team mates + switch(numteammates) { + case 1: break; + case 2: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the other will get the flag + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_GETFLAG); + break; + } + case 3: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the others should go for the enemy flag + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_GETFLAG); + // + ClientName(teammates[2], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[2]); + BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_GETFLAG); + break; + } + default: + { + defenders = (int) (float) numteammates * 0.4 + 0.5; + if (defenders > 4) defenders = 4; + attackers = (int) (float) numteammates * 0.5 + 0.5; + if (attackers > 5) attackers = 5; + for (i = 0; i < defenders; i++) { + // + ClientName(teammates[i], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[i]); + BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); + } + for (i = 0; i < attackers; i++) { + // + ClientName(teammates[numteammates - i - 1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[numteammates - i - 1]); + BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_GETFLAG); + } + // + break; + } + } + } +} + +/* +================== +BotCTFOrders +================== +*/ +void BotCTFOrders(bot_state_t *bs) { + int flagstatus; + + // + if (BotTeam(bs) == TEAM_RED) flagstatus = bs->redflagstatus * 2 + bs->blueflagstatus; + else flagstatus = bs->blueflagstatus * 2 + bs->redflagstatus; + // + switch(flagstatus) { + case 0: BotCTFOrders_BothFlagsAtBase(bs); break; + case 1: BotCTFOrders_EnemyFlagNotAtBase(bs); break; + case 2: BotCTFOrders_FlagNotAtBase(bs); break; + case 3: BotCTFOrders_BothFlagsNotAtBase(bs); break; + } +} + + +/* +================== +BotCreateGroup +================== +*/ +void BotCreateGroup(bot_state_t *bs, int *teammates, int groupsize) { + char name[MAX_NETNAME], leadername[MAX_NETNAME]; + int i; + + // the others in the group will follow the teammates[0] + ClientName(teammates[0], leadername, sizeof(leadername)); + for (i = 1; i < groupsize; i++) + { + ClientName(teammates[i], name, sizeof(name)); + if (teammates[0] == bs->client) { + BotAI_BotInitialChat(bs, "cmd_accompanyme", name, NULL); + } + else { + BotAI_BotInitialChat(bs, "cmd_accompany", name, leadername, NULL); + } + BotSayTeamOrderAlways(bs, teammates[i]); + } +} + +/* +================== +BotTeamOrders + + FIXME: defend key areas? +================== +*/ +void BotTeamOrders(bot_state_t *bs) { + int teammates[MAX_CLIENTS]; + int numteammates, i; + char buf[MAX_INFO_STRING]; + static int maxclients; + + if (!maxclients) + maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); + + numteammates = 0; + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); + //if no config string or no name + if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue; + //skip spectators + if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue; + // + if (BotSameTeam(bs, i)) { + teammates[numteammates] = i; + numteammates++; + } + } + // + switch(numteammates) { + case 1: break; + case 2: + { + //nothing special + break; + } + case 3: + { + //have one follow another and one free roaming + BotCreateGroup(bs, teammates, 2); + break; + } + case 4: + { + BotCreateGroup(bs, teammates, 2); //a group of 2 + BotCreateGroup(bs, &teammates[2], 2); //a group of 2 + break; + } + case 5: + { + BotCreateGroup(bs, teammates, 2); //a group of 2 + BotCreateGroup(bs, &teammates[2], 3); //a group of 3 + break; + } + default: + { + if (numteammates <= 10) { + for (i = 0; i < numteammates / 2; i++) { + BotCreateGroup(bs, &teammates[i*2], 2); //groups of 2 + } + } + break; + } + } +} + +#ifdef MISSIONPACK + +/* +================== +Bot1FCTFOrders_FlagAtCenter + + X% defend the base, Y% get the flag +================== +*/ +void Bot1FCTFOrders_FlagAtCenter(bot_state_t *bs) { + int numteammates, defenders, attackers, i; + int teammates[MAX_CLIENTS]; + char name[MAX_NETNAME]; + + //sort team mates by travel time to base + numteammates = BotSortTeamMatesByBaseTravelTime(bs, teammates, sizeof(teammates)); + //sort team mates by CTF preference + BotSortTeamMatesByTaskPreference(bs, teammates, numteammates); + //passive strategy + if (!(bs->ctfstrategy & CTFS_AGRESSIVE)) { + //different orders based on the number of team mates + switch(numteammates) { + case 1: break; + case 2: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the other will get the flag + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_GETFLAG); + break; + } + case 3: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the second one closest to the base will defend the base + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the other will get the flag + ClientName(teammates[2], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[2]); + BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_GETFLAG); + break; + } + default: + { + //50% defend the base + defenders = (int) (float) numteammates * 0.5 + 0.5; + if (defenders > 5) defenders = 5; + //40% get the flag + attackers = (int) (float) numteammates * 0.4 + 0.5; + if (attackers > 4) attackers = 4; + for (i = 0; i < defenders; i++) { + // + ClientName(teammates[i], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[i]); + BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); + } + for (i = 0; i < attackers; i++) { + // + ClientName(teammates[numteammates - i - 1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[numteammates - i - 1]); + BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_GETFLAG); + } + // + break; + } + } + } + else { //agressive + //different orders based on the number of team mates + switch(numteammates) { + case 1: break; + case 2: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the other will get the flag + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_GETFLAG); + break; + } + case 3: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the others should go for the enemy flag + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_GETFLAG); + // + ClientName(teammates[2], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[2]); + BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_GETFLAG); + break; + } + default: + { + //30% defend the base + defenders = (int) (float) numteammates * 0.3 + 0.5; + if (defenders > 3) defenders = 3; + //60% get the flag + attackers = (int) (float) numteammates * 0.6 + 0.5; + if (attackers > 6) attackers = 6; + for (i = 0; i < defenders; i++) { + // + ClientName(teammates[i], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[i]); + BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); + } + for (i = 0; i < attackers; i++) { + // + ClientName(teammates[numteammates - i - 1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[numteammates - i - 1]); + BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_GETFLAG); + } + // + break; + } + } + } +} + +/* +================== +Bot1FCTFOrders_TeamHasFlag + + X% towards neutral flag, Y% go towards enemy base and accompany flag carrier if visible +================== +*/ +void Bot1FCTFOrders_TeamHasFlag(bot_state_t *bs) { + int numteammates, defenders, attackers, i, other; + int teammates[MAX_CLIENTS]; + char name[MAX_NETNAME], carriername[MAX_NETNAME]; + + //sort team mates by travel time to base + numteammates = BotSortTeamMatesByBaseTravelTime(bs, teammates, sizeof(teammates)); + //sort team mates by CTF preference + BotSortTeamMatesByTaskPreference(bs, teammates, numteammates); + //passive strategy + if (!(bs->ctfstrategy & CTFS_AGRESSIVE)) { + //different orders based on the number of team mates + switch(numteammates) { + case 1: break; + case 2: + { + //tell the one not carrying the flag to attack the enemy base + if (teammates[0] == bs->flagcarrier) other = teammates[1]; + else other = teammates[0]; + ClientName(other, name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_attackenemybase", name, NULL); + BotSayTeamOrder(bs, other); + BotSayVoiceTeamOrder(bs, other, VOICECHAT_OFFENSE); + break; + } + case 3: + { + //tell the one closest to the base not carrying the flag to defend the base + if (teammates[0] != bs->flagcarrier) other = teammates[0]; + else other = teammates[1]; + ClientName(other, name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, other); + BotSayVoiceTeamOrder(bs, other, VOICECHAT_DEFEND); + //tell the one furthest from the base not carrying the flag to accompany the flag carrier + if (teammates[2] != bs->flagcarrier) other = teammates[2]; + else other = teammates[1]; + ClientName(other, name, sizeof(name)); + if ( bs->flagcarrier != -1 ) { + ClientName(bs->flagcarrier, carriername, sizeof(carriername)); + if (bs->flagcarrier == bs->client) { + BotAI_BotInitialChat(bs, "cmd_accompanyme", name, NULL); + BotSayVoiceTeamOrder(bs, other, VOICECHAT_FOLLOWME); + } + else { + BotAI_BotInitialChat(bs, "cmd_accompany", name, carriername, NULL); + BotSayVoiceTeamOrder(bs, other, VOICECHAT_FOLLOWFLAGCARRIER); + } + } + else { + // + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayVoiceTeamOrder(bs, other, VOICECHAT_GETFLAG); + } + BotSayTeamOrder(bs, other); + break; + } + default: + { + //30% will defend the base + defenders = (int) (float) numteammates * 0.3 + 0.5; + if (defenders > 3) defenders = 3; + //70% accompanies the flag carrier + attackers = (int) (float) numteammates * 0.7 + 0.5; + if (attackers > 7) attackers = 7; + for (i = 0; i < defenders; i++) { + // + if (teammates[i] == bs->flagcarrier) { + continue; + } + ClientName(teammates[i], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[i]); + BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); + } + if (bs->flagcarrier != -1) { + ClientName(bs->flagcarrier, carriername, sizeof(carriername)); + for (i = 0; i < attackers; i++) { + // + if (teammates[numteammates - i - 1] == bs->flagcarrier) { + continue; + } + // + ClientName(teammates[numteammates - i - 1], name, sizeof(name)); + if (bs->flagcarrier == bs->client) { + BotAI_BotInitialChat(bs, "cmd_accompanyme", name, NULL); + BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_FOLLOWME); + } + else { + BotAI_BotInitialChat(bs, "cmd_accompany", name, carriername, NULL); + BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_FOLLOWFLAGCARRIER); + } + BotSayTeamOrder(bs, teammates[numteammates - i - 1]); + } + } + else { + for (i = 0; i < attackers; i++) { + // + if (teammates[numteammates - i - 1] == bs->flagcarrier) { + continue; + } + // + ClientName(teammates[numteammates - i - 1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[numteammates - i - 1]); + BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_GETFLAG); + } + } + // + break; + } + } + } + else { //agressive + //different orders based on the number of team mates + switch(numteammates) { + case 1: break; + case 2: + { + //tell the one not carrying the flag to defend the base + if (teammates[0] == bs->flagcarrier) other = teammates[1]; + else other = teammates[0]; + ClientName(other, name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, other); + BotSayVoiceTeamOrder(bs, other, VOICECHAT_DEFEND); + break; + } + case 3: + { + //tell the one closest to the base not carrying the flag to defend the base + if (teammates[0] != bs->flagcarrier) other = teammates[0]; + else other = teammates[1]; + ClientName(other, name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, other); + BotSayVoiceTeamOrder(bs, other, VOICECHAT_DEFEND); + //tell the one furthest from the base not carrying the flag to accompany the flag carrier + if (teammates[2] != bs->flagcarrier) other = teammates[2]; + else other = teammates[1]; + ClientName(other, name, sizeof(name)); + ClientName(bs->flagcarrier, carriername, sizeof(carriername)); + if (bs->flagcarrier == bs->client) { + BotAI_BotInitialChat(bs, "cmd_accompanyme", name, NULL); + BotSayVoiceTeamOrder(bs, other, VOICECHAT_FOLLOWME); + } + else { + BotAI_BotInitialChat(bs, "cmd_accompany", name, carriername, NULL); + BotSayVoiceTeamOrder(bs, other, VOICECHAT_FOLLOWFLAGCARRIER); + } + BotSayTeamOrder(bs, other); + break; + } + default: + { + //20% will defend the base + defenders = (int) (float) numteammates * 0.2 + 0.5; + if (defenders > 2) defenders = 2; + //80% accompanies the flag carrier + attackers = (int) (float) numteammates * 0.8 + 0.5; + if (attackers > 8) attackers = 8; + for (i = 0; i < defenders; i++) { + // + if (teammates[i] == bs->flagcarrier) { + continue; + } + ClientName(teammates[i], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[i]); + BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); + } + ClientName(bs->flagcarrier, carriername, sizeof(carriername)); + for (i = 0; i < attackers; i++) { + // + if (teammates[numteammates - i - 1] == bs->flagcarrier) { + continue; + } + // + ClientName(teammates[numteammates - i - 1], name, sizeof(name)); + if (bs->flagcarrier == bs->client) { + BotAI_BotInitialChat(bs, "cmd_accompanyme", name, NULL); + BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_FOLLOWME); + } + else { + BotAI_BotInitialChat(bs, "cmd_accompany", name, carriername, NULL); + BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_FOLLOWFLAGCARRIER); + } + BotSayTeamOrder(bs, teammates[numteammates - i - 1]); + } + // + break; + } + } + } +} + +/* +================== +Bot1FCTFOrders_EnemyHasFlag + + X% defend the base, Y% towards neutral flag +================== +*/ +void Bot1FCTFOrders_EnemyHasFlag(bot_state_t *bs) { + int numteammates, defenders, attackers, i; + int teammates[MAX_CLIENTS]; + char name[MAX_NETNAME]; + + //sort team mates by travel time to base + numteammates = BotSortTeamMatesByBaseTravelTime(bs, teammates, sizeof(teammates)); + //sort team mates by CTF preference + BotSortTeamMatesByTaskPreference(bs, teammates, numteammates); + //passive strategy + if (!(bs->ctfstrategy & CTFS_AGRESSIVE)) { + //different orders based on the number of team mates + switch(numteammates) { + case 1: break; + case 2: + { + //both defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + // + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_DEFEND); + break; + } + case 3: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the second one closest to the base will defend the base + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_DEFEND); + //the other will also defend the base + ClientName(teammates[2], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[2]); + BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_DEFEND); + break; + } + default: + { + //80% will defend the base + defenders = (int) (float) numteammates * 0.8 + 0.5; + if (defenders > 8) defenders = 8; + //10% will try to return the flag + attackers = (int) (float) numteammates * 0.1 + 0.5; + if (attackers > 2) attackers = 2; + for (i = 0; i < defenders; i++) { + // + ClientName(teammates[i], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[i]); + BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); + } + for (i = 0; i < attackers; i++) { + // + ClientName(teammates[numteammates - i - 1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_returnflag", name, NULL); + BotSayTeamOrder(bs, teammates[numteammates - i - 1]); + BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_GETFLAG); + } + // + break; + } + } + } + else { //agressive + //different orders based on the number of team mates + switch(numteammates) { + case 1: break; + case 2: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the other will get the flag + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_DEFEND); + break; + } + case 3: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the others should go for the enemy flag + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_DEFEND); + // + ClientName(teammates[2], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_returnflag", name, NULL); + BotSayTeamOrder(bs, teammates[2]); + BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_GETFLAG); + break; + } + default: + { + //70% defend the base + defenders = (int) (float) numteammates * 0.7 + 0.5; + if (defenders > 8) defenders = 8; + //20% try to return the flag + attackers = (int) (float) numteammates * 0.2 + 0.5; + if (attackers > 2) attackers = 2; + for (i = 0; i < defenders; i++) { + // + ClientName(teammates[i], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[i]); + BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); + } + for (i = 0; i < attackers; i++) { + // + ClientName(teammates[numteammates - i - 1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_returnflag", name, NULL); + BotSayTeamOrder(bs, teammates[numteammates - i - 1]); + BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_GETFLAG); + } + // + break; + } + } + } +} + +/* +================== +Bot1FCTFOrders_EnemyDroppedFlag + + X% defend the base, Y% get the flag +================== +*/ +void Bot1FCTFOrders_EnemyDroppedFlag(bot_state_t *bs) { + int numteammates, defenders, attackers, i; + int teammates[MAX_CLIENTS]; + char name[MAX_NETNAME]; + + //sort team mates by travel time to base + numteammates = BotSortTeamMatesByBaseTravelTime(bs, teammates, sizeof(teammates)); + //sort team mates by CTF preference + BotSortTeamMatesByTaskPreference(bs, teammates, numteammates); + //passive strategy + if (!(bs->ctfstrategy & CTFS_AGRESSIVE)) { + //different orders based on the number of team mates + switch(numteammates) { + case 1: break; + case 2: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the other will get the flag + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_GETFLAG); + break; + } + case 3: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the second one closest to the base will defend the base + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_DEFEND); + //the other will get the flag + ClientName(teammates[2], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[2]); + BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_GETFLAG); + break; + } + default: + { + //50% defend the base + defenders = (int) (float) numteammates * 0.5 + 0.5; + if (defenders > 5) defenders = 5; + //40% get the flag + attackers = (int) (float) numteammates * 0.4 + 0.5; + if (attackers > 4) attackers = 4; + for (i = 0; i < defenders; i++) { + // + ClientName(teammates[i], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[i]); + BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); + } + for (i = 0; i < attackers; i++) { + // + ClientName(teammates[numteammates - i - 1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[numteammates - i - 1]); + BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_GETFLAG); + } + // + break; + } + } + } + else { //agressive + //different orders based on the number of team mates + switch(numteammates) { + case 1: break; + case 2: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the other will get the flag + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_GETFLAG); + break; + } + case 3: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the others should go for the enemy flag + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_GETFLAG); + // + ClientName(teammates[2], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[2]); + BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_GETFLAG); + break; + } + default: + { + //30% defend the base + defenders = (int) (float) numteammates * 0.3 + 0.5; + if (defenders > 3) defenders = 3; + //60% get the flag + attackers = (int) (float) numteammates * 0.6 + 0.5; + if (attackers > 6) attackers = 6; + for (i = 0; i < defenders; i++) { + // + ClientName(teammates[i], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[i]); + BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); + } + for (i = 0; i < attackers; i++) { + // + ClientName(teammates[numteammates - i - 1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[numteammates - i - 1]); + BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_DEFEND); + } + // + break; + } + } + } +} + +/* +================== +Bot1FCTFOrders +================== +*/ +void Bot1FCTFOrders(bot_state_t *bs) { + switch(bs->neutralflagstatus) { + case 0: Bot1FCTFOrders_FlagAtCenter(bs); break; + case 1: Bot1FCTFOrders_TeamHasFlag(bs); break; + case 2: Bot1FCTFOrders_EnemyHasFlag(bs); break; + case 3: Bot1FCTFOrders_EnemyDroppedFlag(bs); break; + } +} + +/* +================== +BotObeliskOrders + + X% in defence Y% in offence +================== +*/ +void BotObeliskOrders(bot_state_t *bs) { + int numteammates, defenders, attackers, i; + int teammates[MAX_CLIENTS]; + char name[MAX_NETNAME]; + + //sort team mates by travel time to base + numteammates = BotSortTeamMatesByBaseTravelTime(bs, teammates, sizeof(teammates)); + //sort team mates by CTF preference + BotSortTeamMatesByTaskPreference(bs, teammates, numteammates); + //passive strategy + if (!(bs->ctfstrategy & CTFS_AGRESSIVE)) { + //different orders based on the number of team mates + switch(numteammates) { + case 1: break; + case 2: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the other will attack the enemy base + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_attackenemybase", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_OFFENSE); + break; + } + case 3: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the one second closest to the base also defends the base + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_DEFEND); + //the other one attacks the enemy base + ClientName(teammates[2], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_attackenemybase", name, NULL); + BotSayTeamOrder(bs, teammates[2]); + BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_OFFENSE); + break; + } + default: + { + //50% defend the base + defenders = (int) (float) numteammates * 0.5 + 0.5; + if (defenders > 5) defenders = 5; + //40% attack the enemy base + attackers = (int) (float) numteammates * 0.4 + 0.5; + if (attackers > 4) attackers = 4; + for (i = 0; i < defenders; i++) { + // + ClientName(teammates[i], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[i]); + BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); + } + for (i = 0; i < attackers; i++) { + // + ClientName(teammates[numteammates - i - 1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_attackenemybase", name, NULL); + BotSayTeamOrder(bs, teammates[numteammates - i - 1]); + BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_OFFENSE); + } + // + break; + } + } + } + else { + //different orders based on the number of team mates + switch(numteammates) { + case 1: break; + case 2: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the other will attack the enemy base + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_attackenemybase", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_OFFENSE); + break; + } + case 3: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the others attack the enemy base + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_attackenemybase", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_OFFENSE); + // + ClientName(teammates[2], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_attackenemybase", name, NULL); + BotSayTeamOrder(bs, teammates[2]); + BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_OFFENSE); + break; + } + default: + { + //30% defend the base + defenders = (int) (float) numteammates * 0.3 + 0.5; + if (defenders > 3) defenders = 3; + //70% attack the enemy base + attackers = (int) (float) numteammates * 0.7 + 0.5; + if (attackers > 7) attackers = 7; + for (i = 0; i < defenders; i++) { + // + ClientName(teammates[i], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[i]); + BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); + } + for (i = 0; i < attackers; i++) { + // + ClientName(teammates[numteammates - i - 1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_attackenemybase", name, NULL); + BotSayTeamOrder(bs, teammates[numteammates - i - 1]); + BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_OFFENSE); + } + // + break; + } + } + } +} + +/* +================== +BotHarvesterOrders + + X% defend the base, Y% harvest +================== +*/ +void BotHarvesterOrders(bot_state_t *bs) { + int numteammates, defenders, attackers, i; + int teammates[MAX_CLIENTS]; + char name[MAX_NETNAME]; + + //sort team mates by travel time to base + numteammates = BotSortTeamMatesByBaseTravelTime(bs, teammates, sizeof(teammates)); + //sort team mates by CTF preference + BotSortTeamMatesByTaskPreference(bs, teammates, numteammates); + //passive strategy + if (!(bs->ctfstrategy & CTFS_AGRESSIVE)) { + //different orders based on the number of team mates + switch(numteammates) { + case 1: break; + case 2: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the other will harvest + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_harvest", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_OFFENSE); + break; + } + case 3: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the one second closest to the base also defends the base + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_DEFEND); + //the other one goes harvesting + ClientName(teammates[2], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_harvest", name, NULL); + BotSayTeamOrder(bs, teammates[2]); + BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_OFFENSE); + break; + } + default: + { + //50% defend the base + defenders = (int) (float) numteammates * 0.5 + 0.5; + if (defenders > 5) defenders = 5; + //40% goes harvesting + attackers = (int) (float) numteammates * 0.4 + 0.5; + if (attackers > 4) attackers = 4; + for (i = 0; i < defenders; i++) { + // + ClientName(teammates[i], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[i]); + BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); + } + for (i = 0; i < attackers; i++) { + // + ClientName(teammates[numteammates - i - 1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_harvest", name, NULL); + BotSayTeamOrder(bs, teammates[numteammates - i - 1]); + BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_OFFENSE); + } + // + break; + } + } + } + else { + //different orders based on the number of team mates + switch(numteammates) { + case 1: break; + case 2: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the other will harvest + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_harvest", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_OFFENSE); + break; + } + case 3: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the others go harvesting + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_harvest", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_OFFENSE); + // + ClientName(teammates[2], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_harvest", name, NULL); + BotSayTeamOrder(bs, teammates[2]); + BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_OFFENSE); + break; + } + default: + { + //30% defend the base + defenders = (int) (float) numteammates * 0.3 + 0.5; + if (defenders > 3) defenders = 3; + //70% go harvesting + attackers = (int) (float) numteammates * 0.7 + 0.5; + if (attackers > 7) attackers = 7; + for (i = 0; i < defenders; i++) { + // + ClientName(teammates[i], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[i]); + BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); + } + for (i = 0; i < attackers; i++) { + // + ClientName(teammates[numteammates - i - 1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_harvest", name, NULL); + BotSayTeamOrder(bs, teammates[numteammates - i - 1]); + BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_OFFENSE); + } + // + break; + } + } + } +} +#endif + +/* +================== +FindHumanTeamLeader +================== +*/ +int FindHumanTeamLeader(bot_state_t *bs) { + int i; + + for (i = 0; i < MAX_CLIENTS; i++) { + if ( g_entities[i].inuse ) { + // if this player is not a bot + if ( !(g_entities[i].r.svFlags & SVF_BOT) ) { + // if this player is ok with being the leader + if (!notleader[i]) { + // if this player is on the same team + if ( BotSameTeam(bs, i) ) { + ClientName(i, bs->teamleader, sizeof(bs->teamleader)); + // if not yet ordered to do anything + if ( !BotSetLastOrderedTask(bs) ) { + // go on defense by default + BotVoiceChat_Defend(bs, i, SAY_TELL); + } + return qtrue; + } + } + } + } + } + return qfalse; +} + +/* +================== +BotTeamAI +================== +*/ +void BotTeamAI(bot_state_t *bs) { + int numteammates; + char netname[MAX_NETNAME]; + + // + if ( gametype < GT_TEAM ) + return; + // make sure we've got a valid team leader + if (!BotValidTeamLeader(bs)) { + // + if (!FindHumanTeamLeader(bs)) { + // + if (!bs->askteamleader_time && !bs->becometeamleader_time) { + if (bs->entergame_time + 10 > FloatTime()) { + bs->askteamleader_time = FloatTime() + 5 + random() * 10; + } + else { + bs->becometeamleader_time = FloatTime() + 5 + random() * 10; + } + } + if (bs->askteamleader_time && bs->askteamleader_time < FloatTime()) { + // if asked for a team leader and no response + BotAI_BotInitialChat(bs, "whoisteamleader", NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_TEAM); + bs->askteamleader_time = 0; + bs->becometeamleader_time = FloatTime() + 8 + random() * 10; + } + if (bs->becometeamleader_time && bs->becometeamleader_time < FloatTime()) { + BotAI_BotInitialChat(bs, "iamteamleader", NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_TEAM); + BotSayVoiceTeamOrder(bs, -1, VOICECHAT_STARTLEADER); + ClientName(bs->client, netname, sizeof(netname)); + strncpy(bs->teamleader, netname, sizeof(bs->teamleader)); + bs->teamleader[sizeof(bs->teamleader)] = '\0'; + bs->becometeamleader_time = 0; + } + return; + } + } + bs->askteamleader_time = 0; + bs->becometeamleader_time = 0; + + //return if this bot is NOT the team leader + ClientName(bs->client, netname, sizeof(netname)); + if (Q_stricmp(netname, bs->teamleader) != 0) return; + // + numteammates = BotNumTeamMates(bs); + //give orders + switch(gametype) { + case GT_TEAM: + { + if (bs->numteammates != numteammates || bs->forceorders) { + bs->teamgiveorders_time = FloatTime(); + bs->numteammates = numteammates; + bs->forceorders = qfalse; + } + //if it's time to give orders + if (bs->teamgiveorders_time && bs->teamgiveorders_time < FloatTime() - 5) { + BotTeamOrders(bs); + //give orders again after 120 seconds + bs->teamgiveorders_time = FloatTime() + 120; + } + break; + } + case GT_CTF: + { + //if the number of team mates changed or the flag status changed + //or someone wants to know what to do + if (bs->numteammates != numteammates || bs->flagstatuschanged || bs->forceorders) { + bs->teamgiveorders_time = FloatTime(); + bs->numteammates = numteammates; + bs->flagstatuschanged = qfalse; + bs->forceorders = qfalse; + } + //if there were no flag captures the last 3 minutes + if (bs->lastflagcapture_time < FloatTime() - 240) { + bs->lastflagcapture_time = FloatTime(); + //randomly change the CTF strategy + if (random() < 0.4) { + bs->ctfstrategy ^= CTFS_AGRESSIVE; + bs->teamgiveorders_time = FloatTime(); + } + } + //if it's time to give orders + if (bs->teamgiveorders_time && bs->teamgiveorders_time < FloatTime() - 3) { + BotCTFOrders(bs); + // + bs->teamgiveorders_time = 0; + } + break; + } +#ifdef MISSIONPACK + case GT_1FCTF: + { + if (bs->numteammates != numteammates || bs->flagstatuschanged || bs->forceorders) { + bs->teamgiveorders_time = FloatTime(); + bs->numteammates = numteammates; + bs->flagstatuschanged = qfalse; + bs->forceorders = qfalse; + } + //if there were no flag captures the last 4 minutes + if (bs->lastflagcapture_time < FloatTime() - 240) { + bs->lastflagcapture_time = FloatTime(); + //randomly change the CTF strategy + if (random() < 0.4) { + bs->ctfstrategy ^= CTFS_AGRESSIVE; + bs->teamgiveorders_time = FloatTime(); + } + } + //if it's time to give orders + if (bs->teamgiveorders_time && bs->teamgiveorders_time < FloatTime() - 2) { + Bot1FCTFOrders(bs); + // + bs->teamgiveorders_time = 0; + } + break; + } + case GT_OBELISK: + { + if (bs->numteammates != numteammates || bs->forceorders) { + bs->teamgiveorders_time = FloatTime(); + bs->numteammates = numteammates; + bs->forceorders = qfalse; + } + //if it's time to give orders + if (bs->teamgiveorders_time && bs->teamgiveorders_time < FloatTime() - 5) { + BotObeliskOrders(bs); + //give orders again after 30 seconds + bs->teamgiveorders_time = FloatTime() + 30; + } + break; + } + case GT_HARVESTER: + { + if (bs->numteammates != numteammates || bs->forceorders) { + bs->teamgiveorders_time = FloatTime(); + bs->numteammates = numteammates; + bs->forceorders = qfalse; + } + //if it's time to give orders + if (bs->teamgiveorders_time && bs->teamgiveorders_time < FloatTime() - 5) { + BotHarvesterOrders(bs); + //give orders again after 30 seconds + bs->teamgiveorders_time = FloatTime() + 30; + } + break; + } +#endif + } +} + diff --git a/code/game/ai_team.h b/code/game/ai_team.h index fbe4a9e..bb29414 100755 --- a/code/game/ai_team.h +++ b/code/game/ai_team.h @@ -1,39 +1,39 @@ -/* -=========================================================================== -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 Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// - -/***************************************************************************** - * name: ai_team.h - * - * desc: Quake3 bot AI - * - * $Archive: /source/code/botai/ai_chat.c $ - * - *****************************************************************************/ - -void BotTeamAI(bot_state_t *bs); -int BotGetTeamMateTaskPreference(bot_state_t *bs, int teammate); -void BotSetTeamMateTaskPreference(bot_state_t *bs, int teammate, int preference); -void BotVoiceChat(bot_state_t *bs, int toclient, char *voicechat); -void BotVoiceChatOnly(bot_state_t *bs, int toclient, char *voicechat); - - +/* +=========================================================================== +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 Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// + +/***************************************************************************** + * name: ai_team.h + * + * desc: Quake3 bot AI + * + * $Archive: /source/code/botai/ai_chat.c $ + * + *****************************************************************************/ + +void BotTeamAI(bot_state_t *bs); +int BotGetTeamMateTaskPreference(bot_state_t *bs, int teammate); +void BotSetTeamMateTaskPreference(bot_state_t *bs, int teammate, int preference); +void BotVoiceChat(bot_state_t *bs, int toclient, char *voicechat); +void BotVoiceChatOnly(bot_state_t *bs, int toclient, char *voicechat); + + diff --git a/code/game/ai_vcmd.c b/code/game/ai_vcmd.c index 812fe8f..026bbf0 100755 --- a/code/game/ai_vcmd.c +++ b/code/game/ai_vcmd.c @@ -1,550 +1,550 @@ -/* -=========================================================================== -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 Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// - -/***************************************************************************** - * name: ai_vcmd.c - * - * desc: Quake3 bot AI - * - * $Archive: /MissionPack/code/game/ai_vcmd.c $ - * - *****************************************************************************/ - -#include "g_local.h" -#include "botlib.h" -#include "be_aas.h" -#include "be_ea.h" -#include "be_ai_char.h" -#include "be_ai_chat.h" -#include "be_ai_gen.h" -#include "be_ai_goal.h" -#include "be_ai_move.h" -#include "be_ai_weap.h" -// -#include "ai_main.h" -#include "ai_dmq3.h" -#include "ai_chat.h" -#include "ai_cmd.h" -#include "ai_dmnet.h" -#include "ai_team.h" -#include "ai_vcmd.h" -// -#include "chars.h" //characteristics -#include "inv.h" //indexes into the inventory -#include "syn.h" //synonyms -#include "match.h" //string matching types and vars - -// for the voice chats -#include "../../ui/menudef.h" - - -typedef struct voiceCommand_s -{ - char *cmd; - void (*func)(bot_state_t *bs, int client, int mode); -} voiceCommand_t; - -/* -================== -BotVoiceChat_GetFlag -================== -*/ -void BotVoiceChat_GetFlag(bot_state_t *bs, int client, int mode) { - // - if (gametype == GT_CTF) { - if (!ctf_redflag.areanum || !ctf_blueflag.areanum) - return; - } -#ifdef MISSIONPACK - else if (gametype == GT_1FCTF) { - if (!ctf_neutralflag.areanum || !ctf_redflag.areanum || !ctf_blueflag.areanum) - return; - } -#endif - else { - return; - } - // - bs->decisionmaker = client; - bs->ordered = qtrue; - bs->order_time = FloatTime(); - //set the time to send a message to the team mates - bs->teammessage_time = FloatTime() + 2 * random(); - //set the ltg type - bs->ltgtype = LTG_GETFLAG; - //set the team goal time - bs->teamgoal_time = FloatTime() + CTF_GETFLAG_TIME; - // get an alternate route in ctf - if (gametype == GT_CTF) { - //get an alternative route goal towards the enemy base - BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs)); - } - // - BotSetTeamStatus(bs); - // remember last ordered task - BotRememberLastOrderedTask(bs); -#ifdef DEBUG - BotPrintTeamGoal(bs); -#endif //DEBUG -} - -/* -================== -BotVoiceChat_Offense -================== -*/ -void BotVoiceChat_Offense(bot_state_t *bs, int client, int mode) { - if ( gametype == GT_CTF -#ifdef MISSIONPACK - || gametype == GT_1FCTF -#endif - ) { - BotVoiceChat_GetFlag(bs, client, mode); - return; - } -#ifdef MISSIONPACK - if (gametype == GT_HARVESTER) { - // - bs->decisionmaker = client; - bs->ordered = qtrue; - bs->order_time = FloatTime(); - //set the time to send a message to the team mates - bs->teammessage_time = FloatTime() + 2 * random(); - //set the ltg type - bs->ltgtype = LTG_HARVEST; - //set the team goal time - bs->teamgoal_time = FloatTime() + TEAM_HARVEST_TIME; - bs->harvestaway_time = 0; - // - BotSetTeamStatus(bs); - // remember last ordered task - BotRememberLastOrderedTask(bs); - } - else -#endif - { - // - bs->decisionmaker = client; - bs->ordered = qtrue; - bs->order_time = FloatTime(); - //set the time to send a message to the team mates - bs->teammessage_time = FloatTime() + 2 * random(); - //set the ltg type - bs->ltgtype = LTG_ATTACKENEMYBASE; - //set the team goal time - bs->teamgoal_time = FloatTime() + TEAM_ATTACKENEMYBASE_TIME; - bs->attackaway_time = 0; - // - BotSetTeamStatus(bs); - // remember last ordered task - BotRememberLastOrderedTask(bs); - } -#ifdef DEBUG - BotPrintTeamGoal(bs); -#endif //DEBUG -} - -/* -================== -BotVoiceChat_Defend -================== -*/ -void BotVoiceChat_Defend(bot_state_t *bs, int client, int mode) { -#ifdef MISSIONPACK - if ( gametype == GT_OBELISK || gametype == GT_HARVESTER) { - // - switch(BotTeam(bs)) { - case TEAM_RED: memcpy(&bs->teamgoal, &redobelisk, sizeof(bot_goal_t)); break; - case TEAM_BLUE: memcpy(&bs->teamgoal, &blueobelisk, sizeof(bot_goal_t)); break; - default: return; - } - } - else -#endif - if (gametype == GT_CTF -#ifdef MISSIONPACK - || gametype == GT_1FCTF -#endif - ) { - // - switch(BotTeam(bs)) { - case TEAM_RED: memcpy(&bs->teamgoal, &ctf_redflag, sizeof(bot_goal_t)); break; - case TEAM_BLUE: memcpy(&bs->teamgoal, &ctf_blueflag, sizeof(bot_goal_t)); break; - default: return; - } - } - else { - return; - } - // - bs->decisionmaker = client; - bs->ordered = qtrue; - bs->order_time = FloatTime(); - //set the time to send a message to the team mates - bs->teammessage_time = FloatTime() + 2 * random(); - //set the ltg type - bs->ltgtype = LTG_DEFENDKEYAREA; - //get the team goal time - bs->teamgoal_time = FloatTime() + TEAM_DEFENDKEYAREA_TIME; - //away from defending - bs->defendaway_time = 0; - // - BotSetTeamStatus(bs); - // remember last ordered task - BotRememberLastOrderedTask(bs); -#ifdef DEBUG - BotPrintTeamGoal(bs); -#endif //DEBUG -} - -/* -================== -BotVoiceChat_DefendFlag -================== -*/ -void BotVoiceChat_DefendFlag(bot_state_t *bs, int client, int mode) { - BotVoiceChat_Defend(bs, client, mode); -} - -/* -================== -BotVoiceChat_Patrol -================== -*/ -void BotVoiceChat_Patrol(bot_state_t *bs, int client, int mode) { - // - bs->decisionmaker = client; - // - bs->ltgtype = 0; - bs->lead_time = 0; - bs->lastgoal_ltgtype = 0; - // - BotAI_BotInitialChat(bs, "dismissed", NULL); - trap_BotEnterChat(bs->cs, client, CHAT_TELL); - BotVoiceChatOnly(bs, -1, VOICECHAT_ONPATROL); - // - BotSetTeamStatus(bs); -#ifdef DEBUG - BotPrintTeamGoal(bs); -#endif //DEBUG -} - -/* -================== -BotVoiceChat_Camp -================== -*/ -void BotVoiceChat_Camp(bot_state_t *bs, int client, int mode) { - int areanum; - aas_entityinfo_t entinfo; - char netname[MAX_NETNAME]; - - // - bs->teamgoal.entitynum = -1; - BotEntityInfo(client, &entinfo); - //if info is valid (in PVS) - if (entinfo.valid) { - areanum = BotPointAreaNum(entinfo.origin); - if (areanum) { // && trap_AAS_AreaReachability(areanum)) { - //NOTE: just assume the bot knows where the person is - //if (BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, client)) { - bs->teamgoal.entitynum = client; - bs->teamgoal.areanum = areanum; - VectorCopy(entinfo.origin, bs->teamgoal.origin); - VectorSet(bs->teamgoal.mins, -8, -8, -8); - VectorSet(bs->teamgoal.maxs, 8, 8, 8); - //} - } - } - //if the other is not visible - if (bs->teamgoal.entitynum < 0) { - BotAI_BotInitialChat(bs, "whereareyou", EasyClientName(client, netname, sizeof(netname)), NULL); - trap_BotEnterChat(bs->cs, client, CHAT_TELL); - return; - } - // - bs->decisionmaker = client; - bs->ordered = qtrue; - bs->order_time = FloatTime(); - //set the time to send a message to the team mates - bs->teammessage_time = FloatTime() + 2 * random(); - //set the ltg type - bs->ltgtype = LTG_CAMPORDER; - //get the team goal time - bs->teamgoal_time = FloatTime() + TEAM_CAMP_TIME; - //the teammate that requested the camping - bs->teammate = client; - //not arrived yet - bs->arrive_time = 0; - // - BotSetTeamStatus(bs); - // remember last ordered task - BotRememberLastOrderedTask(bs); -#ifdef DEBUG - BotPrintTeamGoal(bs); -#endif //DEBUG -} - -/* -================== -BotVoiceChat_FollowMe -================== -*/ -void BotVoiceChat_FollowMe(bot_state_t *bs, int client, int mode) { - int areanum; - aas_entityinfo_t entinfo; - char netname[MAX_NETNAME]; - - bs->teamgoal.entitynum = -1; - BotEntityInfo(client, &entinfo); - //if info is valid (in PVS) - if (entinfo.valid) { - areanum = BotPointAreaNum(entinfo.origin); - if (areanum) { // && trap_AAS_AreaReachability(areanum)) { - bs->teamgoal.entitynum = client; - bs->teamgoal.areanum = areanum; - VectorCopy(entinfo.origin, bs->teamgoal.origin); - VectorSet(bs->teamgoal.mins, -8, -8, -8); - VectorSet(bs->teamgoal.maxs, 8, 8, 8); - } - } - //if the other is not visible - if (bs->teamgoal.entitynum < 0) { - BotAI_BotInitialChat(bs, "whereareyou", EasyClientName(client, netname, sizeof(netname)), NULL); - trap_BotEnterChat(bs->cs, client, CHAT_TELL); - return; - } - // - bs->decisionmaker = client; - bs->ordered = qtrue; - bs->order_time = FloatTime(); - //the team mate - bs->teammate = client; - //last time the team mate was assumed visible - bs->teammatevisible_time = FloatTime(); - //set the time to send a message to the team mates - bs->teammessage_time = FloatTime() + 2 * random(); - //get the team goal time - bs->teamgoal_time = FloatTime() + TEAM_ACCOMPANY_TIME; - //set the ltg type - bs->ltgtype = LTG_TEAMACCOMPANY; - bs->formation_dist = 3.5 * 32; //3.5 meter - bs->arrive_time = 0; - // - BotSetTeamStatus(bs); - // remember last ordered task - BotRememberLastOrderedTask(bs); -#ifdef DEBUG - BotPrintTeamGoal(bs); -#endif //DEBUG -} - -/* -================== -BotVoiceChat_FollowFlagCarrier -================== -*/ -void BotVoiceChat_FollowFlagCarrier(bot_state_t *bs, int client, int mode) { - int carrier; - - carrier = BotTeamFlagCarrier(bs); - if (carrier >= 0) - BotVoiceChat_FollowMe(bs, carrier, mode); -#ifdef DEBUG - BotPrintTeamGoal(bs); -#endif //DEBUG -} - -/* -================== -BotVoiceChat_ReturnFlag -================== -*/ -void BotVoiceChat_ReturnFlag(bot_state_t *bs, int client, int mode) { - //if not in CTF mode - if ( - gametype != GT_CTF -#ifdef MISSIONPACK - && gametype != GT_1FCTF -#endif - ) { - return; - } - // - bs->decisionmaker = client; - bs->ordered = qtrue; - bs->order_time = FloatTime(); - //set the time to send a message to the team mates - bs->teammessage_time = FloatTime() + 2 * random(); - //set the ltg type - bs->ltgtype = LTG_RETURNFLAG; - //set the team goal time - bs->teamgoal_time = FloatTime() + CTF_RETURNFLAG_TIME; - bs->rushbaseaway_time = 0; - BotSetTeamStatus(bs); -#ifdef DEBUG - BotPrintTeamGoal(bs); -#endif //DEBUG -} - -/* -================== -BotVoiceChat_StartLeader -================== -*/ -void BotVoiceChat_StartLeader(bot_state_t *bs, int client, int mode) { - ClientName(client, bs->teamleader, sizeof(bs->teamleader)); -} - -/* -================== -BotVoiceChat_StopLeader -================== -*/ -void BotVoiceChat_StopLeader(bot_state_t *bs, int client, int mode) { - char netname[MAX_MESSAGE_SIZE]; - - if (!Q_stricmp(bs->teamleader, ClientName(client, netname, sizeof(netname)))) { - bs->teamleader[0] = '\0'; - notleader[client] = qtrue; - } -} - -/* -================== -BotVoiceChat_WhoIsLeader -================== -*/ -void BotVoiceChat_WhoIsLeader(bot_state_t *bs, int client, int mode) { - char netname[MAX_MESSAGE_SIZE]; - - if (!TeamPlayIsOn()) return; - - ClientName(bs->client, netname, sizeof(netname)); - //if this bot IS the team leader - if (!Q_stricmp(netname, bs->teamleader)) { - BotAI_BotInitialChat(bs, "iamteamleader", NULL); - trap_BotEnterChat(bs->cs, 0, CHAT_TEAM); - BotVoiceChatOnly(bs, -1, VOICECHAT_STARTLEADER); - } -} - -/* -================== -BotVoiceChat_WantOnDefense -================== -*/ -void BotVoiceChat_WantOnDefense(bot_state_t *bs, int client, int mode) { - char netname[MAX_NETNAME]; - int preference; - - preference = BotGetTeamMateTaskPreference(bs, client); - preference &= ~TEAMTP_ATTACKER; - preference |= TEAMTP_DEFENDER; - BotSetTeamMateTaskPreference(bs, client, preference); - // - EasyClientName(client, netname, sizeof(netname)); - BotAI_BotInitialChat(bs, "keepinmind", netname, NULL); - trap_BotEnterChat(bs->cs, client, CHAT_TELL); - BotVoiceChatOnly(bs, client, VOICECHAT_YES); - trap_EA_Action(bs->client, ACTION_AFFIRMATIVE); -} - -/* -================== -BotVoiceChat_WantOnOffense -================== -*/ -void BotVoiceChat_WantOnOffense(bot_state_t *bs, int client, int mode) { - char netname[MAX_NETNAME]; - int preference; - - preference = BotGetTeamMateTaskPreference(bs, client); - preference &= ~TEAMTP_DEFENDER; - preference |= TEAMTP_ATTACKER; - BotSetTeamMateTaskPreference(bs, client, preference); - // - EasyClientName(client, netname, sizeof(netname)); - BotAI_BotInitialChat(bs, "keepinmind", netname, NULL); - trap_BotEnterChat(bs->cs, client, CHAT_TELL); - BotVoiceChatOnly(bs, client, VOICECHAT_YES); - trap_EA_Action(bs->client, ACTION_AFFIRMATIVE); -} - -void BotVoiceChat_Dummy(bot_state_t *bs, int client, int mode) { -} - -voiceCommand_t voiceCommands[] = { - {VOICECHAT_GETFLAG, BotVoiceChat_GetFlag}, - {VOICECHAT_OFFENSE, BotVoiceChat_Offense }, - {VOICECHAT_DEFEND, BotVoiceChat_Defend }, - {VOICECHAT_DEFENDFLAG, BotVoiceChat_DefendFlag }, - {VOICECHAT_PATROL, BotVoiceChat_Patrol }, - {VOICECHAT_CAMP, BotVoiceChat_Camp }, - {VOICECHAT_FOLLOWME, BotVoiceChat_FollowMe }, - {VOICECHAT_FOLLOWFLAGCARRIER, BotVoiceChat_FollowFlagCarrier }, - {VOICECHAT_RETURNFLAG, BotVoiceChat_ReturnFlag }, - {VOICECHAT_STARTLEADER, BotVoiceChat_StartLeader }, - {VOICECHAT_STOPLEADER, BotVoiceChat_StopLeader }, - {VOICECHAT_WHOISLEADER, BotVoiceChat_WhoIsLeader }, - {VOICECHAT_WANTONDEFENSE, BotVoiceChat_WantOnDefense }, - {VOICECHAT_WANTONOFFENSE, BotVoiceChat_WantOnOffense }, - {NULL, BotVoiceChat_Dummy} -}; - -int BotVoiceChatCommand(bot_state_t *bs, int mode, char *voiceChat) { - int i, voiceOnly, clientNum, color; - char *ptr, buf[MAX_MESSAGE_SIZE], *cmd; - - if (!TeamPlayIsOn()) { - return qfalse; - } - - if ( mode == SAY_ALL ) { - return qfalse; // don't do anything with voice chats to everyone - } - - Q_strncpyz(buf, voiceChat, sizeof(buf)); - cmd = buf; - for (ptr = cmd; *cmd && *cmd > ' '; cmd++); - while (*cmd && *cmd <= ' ') *cmd++ = '\0'; - voiceOnly = atoi(ptr); - for (ptr = cmd; *cmd && *cmd > ' '; cmd++); - while (*cmd && *cmd <= ' ') *cmd++ = '\0'; - clientNum = atoi(ptr); - for (ptr = cmd; *cmd && *cmd > ' '; cmd++); - while (*cmd && *cmd <= ' ') *cmd++ = '\0'; - color = atoi(ptr); - - if (!BotSameTeam(bs, clientNum)) { - return qfalse; - } - - for (i = 0; voiceCommands[i].cmd; i++) { - if (!Q_stricmp(cmd, voiceCommands[i].cmd)) { - voiceCommands[i].func(bs, clientNum, mode); - return qtrue; - } - } - return qfalse; -} +/* +=========================================================================== +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 Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// + +/***************************************************************************** + * name: ai_vcmd.c + * + * desc: Quake3 bot AI + * + * $Archive: /MissionPack/code/game/ai_vcmd.c $ + * + *****************************************************************************/ + +#include "g_local.h" +#include "botlib.h" +#include "be_aas.h" +#include "be_ea.h" +#include "be_ai_char.h" +#include "be_ai_chat.h" +#include "be_ai_gen.h" +#include "be_ai_goal.h" +#include "be_ai_move.h" +#include "be_ai_weap.h" +// +#include "ai_main.h" +#include "ai_dmq3.h" +#include "ai_chat.h" +#include "ai_cmd.h" +#include "ai_dmnet.h" +#include "ai_team.h" +#include "ai_vcmd.h" +// +#include "chars.h" //characteristics +#include "inv.h" //indexes into the inventory +#include "syn.h" //synonyms +#include "match.h" //string matching types and vars + +// for the voice chats +#include "../../ui/menudef.h" + + +typedef struct voiceCommand_s +{ + char *cmd; + void (*func)(bot_state_t *bs, int client, int mode); +} voiceCommand_t; + +/* +================== +BotVoiceChat_GetFlag +================== +*/ +void BotVoiceChat_GetFlag(bot_state_t *bs, int client, int mode) { + // + if (gametype == GT_CTF) { + if (!ctf_redflag.areanum || !ctf_blueflag.areanum) + return; + } +#ifdef MISSIONPACK + else if (gametype == GT_1FCTF) { + if (!ctf_neutralflag.areanum || !ctf_redflag.areanum || !ctf_blueflag.areanum) + return; + } +#endif + else { + return; + } + // + bs->decisionmaker = client; + bs->ordered = qtrue; + bs->order_time = FloatTime(); + //set the time to send a message to the team mates + bs->teammessage_time = FloatTime() + 2 * random(); + //set the ltg type + bs->ltgtype = LTG_GETFLAG; + //set the team goal time + bs->teamgoal_time = FloatTime() + CTF_GETFLAG_TIME; + // get an alternate route in ctf + if (gametype == GT_CTF) { + //get an alternative route goal towards the enemy base + BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs)); + } + // + BotSetTeamStatus(bs); + // remember last ordered task + BotRememberLastOrderedTask(bs); +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} + +/* +================== +BotVoiceChat_Offense +================== +*/ +void BotVoiceChat_Offense(bot_state_t *bs, int client, int mode) { + if ( gametype == GT_CTF +#ifdef MISSIONPACK + || gametype == GT_1FCTF +#endif + ) { + BotVoiceChat_GetFlag(bs, client, mode); + return; + } +#ifdef MISSIONPACK + if (gametype == GT_HARVESTER) { + // + bs->decisionmaker = client; + bs->ordered = qtrue; + bs->order_time = FloatTime(); + //set the time to send a message to the team mates + bs->teammessage_time = FloatTime() + 2 * random(); + //set the ltg type + bs->ltgtype = LTG_HARVEST; + //set the team goal time + bs->teamgoal_time = FloatTime() + TEAM_HARVEST_TIME; + bs->harvestaway_time = 0; + // + BotSetTeamStatus(bs); + // remember last ordered task + BotRememberLastOrderedTask(bs); + } + else +#endif + { + // + bs->decisionmaker = client; + bs->ordered = qtrue; + bs->order_time = FloatTime(); + //set the time to send a message to the team mates + bs->teammessage_time = FloatTime() + 2 * random(); + //set the ltg type + bs->ltgtype = LTG_ATTACKENEMYBASE; + //set the team goal time + bs->teamgoal_time = FloatTime() + TEAM_ATTACKENEMYBASE_TIME; + bs->attackaway_time = 0; + // + BotSetTeamStatus(bs); + // remember last ordered task + BotRememberLastOrderedTask(bs); + } +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} + +/* +================== +BotVoiceChat_Defend +================== +*/ +void BotVoiceChat_Defend(bot_state_t *bs, int client, int mode) { +#ifdef MISSIONPACK + if ( gametype == GT_OBELISK || gametype == GT_HARVESTER) { + // + switch(BotTeam(bs)) { + case TEAM_RED: memcpy(&bs->teamgoal, &redobelisk, sizeof(bot_goal_t)); break; + case TEAM_BLUE: memcpy(&bs->teamgoal, &blueobelisk, sizeof(bot_goal_t)); break; + default: return; + } + } + else +#endif + if (gametype == GT_CTF +#ifdef MISSIONPACK + || gametype == GT_1FCTF +#endif + ) { + // + switch(BotTeam(bs)) { + case TEAM_RED: memcpy(&bs->teamgoal, &ctf_redflag, sizeof(bot_goal_t)); break; + case TEAM_BLUE: memcpy(&bs->teamgoal, &ctf_blueflag, sizeof(bot_goal_t)); break; + default: return; + } + } + else { + return; + } + // + bs->decisionmaker = client; + bs->ordered = qtrue; + bs->order_time = FloatTime(); + //set the time to send a message to the team mates + bs->teammessage_time = FloatTime() + 2 * random(); + //set the ltg type + bs->ltgtype = LTG_DEFENDKEYAREA; + //get the team goal time + bs->teamgoal_time = FloatTime() + TEAM_DEFENDKEYAREA_TIME; + //away from defending + bs->defendaway_time = 0; + // + BotSetTeamStatus(bs); + // remember last ordered task + BotRememberLastOrderedTask(bs); +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} + +/* +================== +BotVoiceChat_DefendFlag +================== +*/ +void BotVoiceChat_DefendFlag(bot_state_t *bs, int client, int mode) { + BotVoiceChat_Defend(bs, client, mode); +} + +/* +================== +BotVoiceChat_Patrol +================== +*/ +void BotVoiceChat_Patrol(bot_state_t *bs, int client, int mode) { + // + bs->decisionmaker = client; + // + bs->ltgtype = 0; + bs->lead_time = 0; + bs->lastgoal_ltgtype = 0; + // + BotAI_BotInitialChat(bs, "dismissed", NULL); + trap_BotEnterChat(bs->cs, client, CHAT_TELL); + BotVoiceChatOnly(bs, -1, VOICECHAT_ONPATROL); + // + BotSetTeamStatus(bs); +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} + +/* +================== +BotVoiceChat_Camp +================== +*/ +void BotVoiceChat_Camp(bot_state_t *bs, int client, int mode) { + int areanum; + aas_entityinfo_t entinfo; + char netname[MAX_NETNAME]; + + // + bs->teamgoal.entitynum = -1; + BotEntityInfo(client, &entinfo); + //if info is valid (in PVS) + if (entinfo.valid) { + areanum = BotPointAreaNum(entinfo.origin); + if (areanum) { // && trap_AAS_AreaReachability(areanum)) { + //NOTE: just assume the bot knows where the person is + //if (BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, client)) { + bs->teamgoal.entitynum = client; + bs->teamgoal.areanum = areanum; + VectorCopy(entinfo.origin, bs->teamgoal.origin); + VectorSet(bs->teamgoal.mins, -8, -8, -8); + VectorSet(bs->teamgoal.maxs, 8, 8, 8); + //} + } + } + //if the other is not visible + if (bs->teamgoal.entitynum < 0) { + BotAI_BotInitialChat(bs, "whereareyou", EasyClientName(client, netname, sizeof(netname)), NULL); + trap_BotEnterChat(bs->cs, client, CHAT_TELL); + return; + } + // + bs->decisionmaker = client; + bs->ordered = qtrue; + bs->order_time = FloatTime(); + //set the time to send a message to the team mates + bs->teammessage_time = FloatTime() + 2 * random(); + //set the ltg type + bs->ltgtype = LTG_CAMPORDER; + //get the team goal time + bs->teamgoal_time = FloatTime() + TEAM_CAMP_TIME; + //the teammate that requested the camping + bs->teammate = client; + //not arrived yet + bs->arrive_time = 0; + // + BotSetTeamStatus(bs); + // remember last ordered task + BotRememberLastOrderedTask(bs); +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} + +/* +================== +BotVoiceChat_FollowMe +================== +*/ +void BotVoiceChat_FollowMe(bot_state_t *bs, int client, int mode) { + int areanum; + aas_entityinfo_t entinfo; + char netname[MAX_NETNAME]; + + bs->teamgoal.entitynum = -1; + BotEntityInfo(client, &entinfo); + //if info is valid (in PVS) + if (entinfo.valid) { + areanum = BotPointAreaNum(entinfo.origin); + if (areanum) { // && trap_AAS_AreaReachability(areanum)) { + bs->teamgoal.entitynum = client; + bs->teamgoal.areanum = areanum; + VectorCopy(entinfo.origin, bs->teamgoal.origin); + VectorSet(bs->teamgoal.mins, -8, -8, -8); + VectorSet(bs->teamgoal.maxs, 8, 8, 8); + } + } + //if the other is not visible + if (bs->teamgoal.entitynum < 0) { + BotAI_BotInitialChat(bs, "whereareyou", EasyClientName(client, netname, sizeof(netname)), NULL); + trap_BotEnterChat(bs->cs, client, CHAT_TELL); + return; + } + // + bs->decisionmaker = client; + bs->ordered = qtrue; + bs->order_time = FloatTime(); + //the team mate + bs->teammate = client; + //last time the team mate was assumed visible + bs->teammatevisible_time = FloatTime(); + //set the time to send a message to the team mates + bs->teammessage_time = FloatTime() + 2 * random(); + //get the team goal time + bs->teamgoal_time = FloatTime() + TEAM_ACCOMPANY_TIME; + //set the ltg type + bs->ltgtype = LTG_TEAMACCOMPANY; + bs->formation_dist = 3.5 * 32; //3.5 meter + bs->arrive_time = 0; + // + BotSetTeamStatus(bs); + // remember last ordered task + BotRememberLastOrderedTask(bs); +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} + +/* +================== +BotVoiceChat_FollowFlagCarrier +================== +*/ +void BotVoiceChat_FollowFlagCarrier(bot_state_t *bs, int client, int mode) { + int carrier; + + carrier = BotTeamFlagCarrier(bs); + if (carrier >= 0) + BotVoiceChat_FollowMe(bs, carrier, mode); +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} + +/* +================== +BotVoiceChat_ReturnFlag +================== +*/ +void BotVoiceChat_ReturnFlag(bot_state_t *bs, int client, int mode) { + //if not in CTF mode + if ( + gametype != GT_CTF +#ifdef MISSIONPACK + && gametype != GT_1FCTF +#endif + ) { + return; + } + // + bs->decisionmaker = client; + bs->ordered = qtrue; + bs->order_time = FloatTime(); + //set the time to send a message to the team mates + bs->teammessage_time = FloatTime() + 2 * random(); + //set the ltg type + bs->ltgtype = LTG_RETURNFLAG; + //set the team goal time + bs->teamgoal_time = FloatTime() + CTF_RETURNFLAG_TIME; + bs->rushbaseaway_time = 0; + BotSetTeamStatus(bs); +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} + +/* +================== +BotVoiceChat_StartLeader +================== +*/ +void BotVoiceChat_StartLeader(bot_state_t *bs, int client, int mode) { + ClientName(client, bs->teamleader, sizeof(bs->teamleader)); +} + +/* +================== +BotVoiceChat_StopLeader +================== +*/ +void BotVoiceChat_StopLeader(bot_state_t *bs, int client, int mode) { + char netname[MAX_MESSAGE_SIZE]; + + if (!Q_stricmp(bs->teamleader, ClientName(client, netname, sizeof(netname)))) { + bs->teamleader[0] = '\0'; + notleader[client] = qtrue; + } +} + +/* +================== +BotVoiceChat_WhoIsLeader +================== +*/ +void BotVoiceChat_WhoIsLeader(bot_state_t *bs, int client, int mode) { + char netname[MAX_MESSAGE_SIZE]; + + if (!TeamPlayIsOn()) return; + + ClientName(bs->client, netname, sizeof(netname)); + //if this bot IS the team leader + if (!Q_stricmp(netname, bs->teamleader)) { + BotAI_BotInitialChat(bs, "iamteamleader", NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_TEAM); + BotVoiceChatOnly(bs, -1, VOICECHAT_STARTLEADER); + } +} + +/* +================== +BotVoiceChat_WantOnDefense +================== +*/ +void BotVoiceChat_WantOnDefense(bot_state_t *bs, int client, int mode) { + char netname[MAX_NETNAME]; + int preference; + + preference = BotGetTeamMateTaskPreference(bs, client); + preference &= ~TEAMTP_ATTACKER; + preference |= TEAMTP_DEFENDER; + BotSetTeamMateTaskPreference(bs, client, preference); + // + EasyClientName(client, netname, sizeof(netname)); + BotAI_BotInitialChat(bs, "keepinmind", netname, NULL); + trap_BotEnterChat(bs->cs, client, CHAT_TELL); + BotVoiceChatOnly(bs, client, VOICECHAT_YES); + trap_EA_Action(bs->client, ACTION_AFFIRMATIVE); +} + +/* +================== +BotVoiceChat_WantOnOffense +================== +*/ +void BotVoiceChat_WantOnOffense(bot_state_t *bs, int client, int mode) { + char netname[MAX_NETNAME]; + int preference; + + preference = BotGetTeamMateTaskPreference(bs, client); + preference &= ~TEAMTP_DEFENDER; + preference |= TEAMTP_ATTACKER; + BotSetTeamMateTaskPreference(bs, client, preference); + // + EasyClientName(client, netname, sizeof(netname)); + BotAI_BotInitialChat(bs, "keepinmind", netname, NULL); + trap_BotEnterChat(bs->cs, client, CHAT_TELL); + BotVoiceChatOnly(bs, client, VOICECHAT_YES); + trap_EA_Action(bs->client, ACTION_AFFIRMATIVE); +} + +void BotVoiceChat_Dummy(bot_state_t *bs, int client, int mode) { +} + +voiceCommand_t voiceCommands[] = { + {VOICECHAT_GETFLAG, BotVoiceChat_GetFlag}, + {VOICECHAT_OFFENSE, BotVoiceChat_Offense }, + {VOICECHAT_DEFEND, BotVoiceChat_Defend }, + {VOICECHAT_DEFENDFLAG, BotVoiceChat_DefendFlag }, + {VOICECHAT_PATROL, BotVoiceChat_Patrol }, + {VOICECHAT_CAMP, BotVoiceChat_Camp }, + {VOICECHAT_FOLLOWME, BotVoiceChat_FollowMe }, + {VOICECHAT_FOLLOWFLAGCARRIER, BotVoiceChat_FollowFlagCarrier }, + {VOICECHAT_RETURNFLAG, BotVoiceChat_ReturnFlag }, + {VOICECHAT_STARTLEADER, BotVoiceChat_StartLeader }, + {VOICECHAT_STOPLEADER, BotVoiceChat_StopLeader }, + {VOICECHAT_WHOISLEADER, BotVoiceChat_WhoIsLeader }, + {VOICECHAT_WANTONDEFENSE, BotVoiceChat_WantOnDefense }, + {VOICECHAT_WANTONOFFENSE, BotVoiceChat_WantOnOffense }, + {NULL, BotVoiceChat_Dummy} +}; + +int BotVoiceChatCommand(bot_state_t *bs, int mode, char *voiceChat) { + int i, voiceOnly, clientNum, color; + char *ptr, buf[MAX_MESSAGE_SIZE], *cmd; + + if (!TeamPlayIsOn()) { + return qfalse; + } + + if ( mode == SAY_ALL ) { + return qfalse; // don't do anything with voice chats to everyone + } + + Q_strncpyz(buf, voiceChat, sizeof(buf)); + cmd = buf; + for (ptr = cmd; *cmd && *cmd > ' '; cmd++); + while (*cmd && *cmd <= ' ') *cmd++ = '\0'; + voiceOnly = atoi(ptr); + for (ptr = cmd; *cmd && *cmd > ' '; cmd++); + while (*cmd && *cmd <= ' ') *cmd++ = '\0'; + clientNum = atoi(ptr); + for (ptr = cmd; *cmd && *cmd > ' '; cmd++); + while (*cmd && *cmd <= ' ') *cmd++ = '\0'; + color = atoi(ptr); + + if (!BotSameTeam(bs, clientNum)) { + return qfalse; + } + + for (i = 0; voiceCommands[i].cmd; i++) { + if (!Q_stricmp(cmd, voiceCommands[i].cmd)) { + voiceCommands[i].func(bs, clientNum, mode); + return qtrue; + } + } + return qfalse; +} diff --git a/code/game/ai_vcmd.h b/code/game/ai_vcmd.h index 0eef6ac..f965f11 100755 --- a/code/game/ai_vcmd.h +++ b/code/game/ai_vcmd.h @@ -1,36 +1,36 @@ -/* -=========================================================================== -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 Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// - -/***************************************************************************** - * name: ai_vcmd.h - * - * desc: Quake3 bot AI - * - * $Archive: /source/code/botai/ai_vcmd.c $ - * - *****************************************************************************/ - -int BotVoiceChatCommand(bot_state_t *bs, int mode, char *voicechat); -void BotVoiceChat_Defend(bot_state_t *bs, int client, int mode); - - +/* +=========================================================================== +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 Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// + +/***************************************************************************** + * name: ai_vcmd.h + * + * desc: Quake3 bot AI + * + * $Archive: /source/code/botai/ai_vcmd.c $ + * + *****************************************************************************/ + +int BotVoiceChatCommand(bot_state_t *bs, int mode, char *voicechat); +void BotVoiceChat_Defend(bot_state_t *bs, int client, int mode); + + diff --git a/code/game/be_aas.h b/code/game/be_aas.h index 24f982c..4a98a8e 100755 --- a/code/game/be_aas.h +++ b/code/game/be_aas.h @@ -1,221 +1,221 @@ -/* -=========================================================================== -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 Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// - -/***************************************************************************** - * name: be_aas.h - * - * desc: Area Awareness System, stuff exported to the AI - * - * $Archive: /source/code/botlib/be_aas.h $ - * - *****************************************************************************/ - -#ifndef MAX_STRINGFIELD -#define MAX_STRINGFIELD 80 -#endif - -//travel flags -#define TFL_INVALID 0x00000001 //traveling temporary not possible -#define TFL_WALK 0x00000002 //walking -#define TFL_CROUCH 0x00000004 //crouching -#define TFL_BARRIERJUMP 0x00000008 //jumping onto a barrier -#define TFL_JUMP 0x00000010 //jumping -#define TFL_LADDER 0x00000020 //climbing a ladder -#define TFL_WALKOFFLEDGE 0x00000080 //walking of a ledge -#define TFL_SWIM 0x00000100 //swimming -#define TFL_WATERJUMP 0x00000200 //jumping out of the water -#define TFL_TELEPORT 0x00000400 //teleporting -#define TFL_ELEVATOR 0x00000800 //elevator -#define TFL_ROCKETJUMP 0x00001000 //rocket jumping -#define TFL_BFGJUMP 0x00002000 //bfg jumping -#define TFL_GRAPPLEHOOK 0x00004000 //grappling hook -#define TFL_DOUBLEJUMP 0x00008000 //double jump -#define TFL_RAMPJUMP 0x00010000 //ramp jump -#define TFL_STRAFEJUMP 0x00020000 //strafe jump -#define TFL_JUMPPAD 0x00040000 //jump pad -#define TFL_AIR 0x00080000 //travel through air -#define TFL_WATER 0x00100000 //travel through water -#define TFL_SLIME 0x00200000 //travel through slime -#define TFL_LAVA 0x00400000 //travel through lava -#define TFL_DONOTENTER 0x00800000 //travel through donotenter area -#define TFL_FUNCBOB 0x01000000 //func bobbing -#define TFL_FLIGHT 0x02000000 //flight -#define TFL_BRIDGE 0x04000000 //move over a bridge -// -#define TFL_NOTTEAM1 0x08000000 //not team 1 -#define TFL_NOTTEAM2 0x10000000 //not team 2 - -//default travel flags -#define TFL_DEFAULT TFL_WALK|TFL_CROUCH|TFL_BARRIERJUMP|\ - TFL_JUMP|TFL_LADDER|\ - TFL_WALKOFFLEDGE|TFL_SWIM|TFL_WATERJUMP|\ - TFL_TELEPORT|TFL_ELEVATOR|\ - TFL_AIR|TFL_WATER|TFL_JUMPPAD|TFL_FUNCBOB - -typedef enum -{ - SOLID_NOT, // no interaction with other objects - SOLID_TRIGGER, // only touch when inside, after moving - SOLID_BBOX, // touch on edge - SOLID_BSP // bsp clip, touch on edge -} solid_t; - -//a trace is returned when a box is swept through the AAS world -typedef struct aas_trace_s -{ - qboolean startsolid; // if true, the initial point was in a solid area - float fraction; // time completed, 1.0 = didn't hit anything - vec3_t endpos; // final position - int ent; // entity blocking the trace - int lastarea; // last area the trace was in (zero if none) - int area; // area blocking the trace (zero if none) - int planenum; // number of the plane that was hit -} aas_trace_t; - -/* Defined in botlib.h - -//bsp_trace_t hit surface -typedef struct bsp_surface_s -{ - char name[16]; - int flags; - int value; -} bsp_surface_t; - -//a trace is returned when a box is swept through the BSP world -typedef struct bsp_trace_s -{ - qboolean allsolid; // if true, plane is not valid - qboolean startsolid; // if true, the initial point was in a solid area - float fraction; // time completed, 1.0 = didn't hit anything - vec3_t endpos; // final position - cplane_t plane; // surface normal at impact - float exp_dist; // expanded plane distance - int sidenum; // number of the brush side hit - bsp_surface_t surface; // hit surface - int contents; // contents on other side of surface hit - int ent; // number of entity hit -} bsp_trace_t; -// -*/ - -//entity info -typedef struct aas_entityinfo_s -{ - int valid; // true if updated this frame - int type; // entity type - int flags; // entity flags - float ltime; // local time - float update_time; // time between last and current update - int number; // number of the entity - vec3_t origin; // origin of the entity - vec3_t angles; // angles of the model - vec3_t old_origin; // for lerping - vec3_t lastvisorigin; // last visible origin - vec3_t mins; // bounding box minimums - vec3_t maxs; // bounding box maximums - int groundent; // ground entity - int solid; // solid type - int modelindex; // model used - int modelindex2; // weapons, CTF flags, etc - int frame; // model frame number - int event; // impulse events -- muzzle flashes, footsteps, etc - int eventParm; // even parameter - int powerups; // bit flags - int weapon; // determines weapon and flash model, etc - int legsAnim; // mask off ANIM_TOGGLEBIT - int torsoAnim; // mask off ANIM_TOGGLEBIT -} aas_entityinfo_t; - -// area info -typedef struct aas_areainfo_s -{ - int contents; - int flags; - int presencetype; - int cluster; - vec3_t mins; - vec3_t maxs; - vec3_t center; -} aas_areainfo_t; - -// client movement prediction stop events, stop as soon as: -#define SE_NONE 0 -#define SE_HITGROUND 1 // the ground is hit -#define SE_LEAVEGROUND 2 // there's no ground -#define SE_ENTERWATER 4 // water is entered -#define SE_ENTERSLIME 8 // slime is entered -#define SE_ENTERLAVA 16 // lava is entered -#define SE_HITGROUNDDAMAGE 32 // the ground is hit with damage -#define SE_GAP 64 // there's a gap -#define SE_TOUCHJUMPPAD 128 // touching a jump pad area -#define SE_TOUCHTELEPORTER 256 // touching teleporter -#define SE_ENTERAREA 512 // the given stoparea is entered -#define SE_HITGROUNDAREA 1024 // a ground face in the area is hit -#define SE_HITBOUNDINGBOX 2048 // hit the specified bounding box -#define SE_TOUCHCLUSTERPORTAL 4096 // touching a cluster portal - -typedef struct aas_clientmove_s -{ - vec3_t endpos; //position at the end of movement prediction - int endarea; //area at end of movement prediction - vec3_t velocity; //velocity at the end of movement prediction - aas_trace_t trace; //last trace - int presencetype; //presence type at end of movement prediction - int stopevent; //event that made the prediction stop - int endcontents; //contents at the end of movement prediction - float time; //time predicted ahead - int frames; //number of frames predicted ahead -} aas_clientmove_t; - -// alternate route goals -#define ALTROUTEGOAL_ALL 1 -#define ALTROUTEGOAL_CLUSTERPORTALS 2 -#define ALTROUTEGOAL_VIEWPORTALS 4 - -typedef struct aas_altroutegoal_s -{ - vec3_t origin; - int areanum; - unsigned short starttraveltime; - unsigned short goaltraveltime; - unsigned short extratraveltime; -} aas_altroutegoal_t; - -// route prediction stop events -#define RSE_NONE 0 -#define RSE_NOROUTE 1 //no route to goal -#define RSE_USETRAVELTYPE 2 //stop as soon as on of the given travel types is used -#define RSE_ENTERCONTENTS 4 //stop when entering the given contents -#define RSE_ENTERAREA 8 //stop when entering the given area - -typedef struct aas_predictroute_s -{ - vec3_t endpos; //position at the end of movement prediction - int endarea; //area at end of movement prediction - int stopevent; //event that made the prediction stop - int endcontents; //contents at the end of movement prediction - int endtravelflags; //end travel flags - int numareas; //number of areas predicted ahead - int time; //time predicted ahead (in hundreth of a sec) -} aas_predictroute_t; +/* +=========================================================================== +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 Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// + +/***************************************************************************** + * name: be_aas.h + * + * desc: Area Awareness System, stuff exported to the AI + * + * $Archive: /source/code/botlib/be_aas.h $ + * + *****************************************************************************/ + +#ifndef MAX_STRINGFIELD +#define MAX_STRINGFIELD 80 +#endif + +//travel flags +#define TFL_INVALID 0x00000001 //traveling temporary not possible +#define TFL_WALK 0x00000002 //walking +#define TFL_CROUCH 0x00000004 //crouching +#define TFL_BARRIERJUMP 0x00000008 //jumping onto a barrier +#define TFL_JUMP 0x00000010 //jumping +#define TFL_LADDER 0x00000020 //climbing a ladder +#define TFL_WALKOFFLEDGE 0x00000080 //walking of a ledge +#define TFL_SWIM 0x00000100 //swimming +#define TFL_WATERJUMP 0x00000200 //jumping out of the water +#define TFL_TELEPORT 0x00000400 //teleporting +#define TFL_ELEVATOR 0x00000800 //elevator +#define TFL_ROCKETJUMP 0x00001000 //rocket jumping +#define TFL_BFGJUMP 0x00002000 //bfg jumping +#define TFL_GRAPPLEHOOK 0x00004000 //grappling hook +#define TFL_DOUBLEJUMP 0x00008000 //double jump +#define TFL_RAMPJUMP 0x00010000 //ramp jump +#define TFL_STRAFEJUMP 0x00020000 //strafe jump +#define TFL_JUMPPAD 0x00040000 //jump pad +#define TFL_AIR 0x00080000 //travel through air +#define TFL_WATER 0x00100000 //travel through water +#define TFL_SLIME 0x00200000 //travel through slime +#define TFL_LAVA 0x00400000 //travel through lava +#define TFL_DONOTENTER 0x00800000 //travel through donotenter area +#define TFL_FUNCBOB 0x01000000 //func bobbing +#define TFL_FLIGHT 0x02000000 //flight +#define TFL_BRIDGE 0x04000000 //move over a bridge +// +#define TFL_NOTTEAM1 0x08000000 //not team 1 +#define TFL_NOTTEAM2 0x10000000 //not team 2 + +//default travel flags +#define TFL_DEFAULT TFL_WALK|TFL_CROUCH|TFL_BARRIERJUMP|\ + TFL_JUMP|TFL_LADDER|\ + TFL_WALKOFFLEDGE|TFL_SWIM|TFL_WATERJUMP|\ + TFL_TELEPORT|TFL_ELEVATOR|\ + TFL_AIR|TFL_WATER|TFL_JUMPPAD|TFL_FUNCBOB + +typedef enum +{ + SOLID_NOT, // no interaction with other objects + SOLID_TRIGGER, // only touch when inside, after moving + SOLID_BBOX, // touch on edge + SOLID_BSP // bsp clip, touch on edge +} solid_t; + +//a trace is returned when a box is swept through the AAS world +typedef struct aas_trace_s +{ + qboolean startsolid; // if true, the initial point was in a solid area + float fraction; // time completed, 1.0 = didn't hit anything + vec3_t endpos; // final position + int ent; // entity blocking the trace + int lastarea; // last area the trace was in (zero if none) + int area; // area blocking the trace (zero if none) + int planenum; // number of the plane that was hit +} aas_trace_t; + +/* Defined in botlib.h + +//bsp_trace_t hit surface +typedef struct bsp_surface_s +{ + char name[16]; + int flags; + int value; +} bsp_surface_t; + +//a trace is returned when a box is swept through the BSP world +typedef struct bsp_trace_s +{ + qboolean allsolid; // if true, plane is not valid + qboolean startsolid; // if true, the initial point was in a solid area + float fraction; // time completed, 1.0 = didn't hit anything + vec3_t endpos; // final position + cplane_t plane; // surface normal at impact + float exp_dist; // expanded plane distance + int sidenum; // number of the brush side hit + bsp_surface_t surface; // hit surface + int contents; // contents on other side of surface hit + int ent; // number of entity hit +} bsp_trace_t; +// +*/ + +//entity info +typedef struct aas_entityinfo_s +{ + int valid; // true if updated this frame + int type; // entity type + int flags; // entity flags + float ltime; // local time + float update_time; // time between last and current update + int number; // number of the entity + vec3_t origin; // origin of the entity + vec3_t angles; // angles of the model + vec3_t old_origin; // for lerping + vec3_t lastvisorigin; // last visible origin + vec3_t mins; // bounding box minimums + vec3_t maxs; // bounding box maximums + int groundent; // ground entity + int solid; // solid type + int modelindex; // model used + int modelindex2; // weapons, CTF flags, etc + int frame; // model frame number + int event; // impulse events -- muzzle flashes, footsteps, etc + int eventParm; // even parameter + int powerups; // bit flags + int weapon; // determines weapon and flash model, etc + int legsAnim; // mask off ANIM_TOGGLEBIT + int torsoAnim; // mask off ANIM_TOGGLEBIT +} aas_entityinfo_t; + +// area info +typedef struct aas_areainfo_s +{ + int contents; + int flags; + int presencetype; + int cluster; + vec3_t mins; + vec3_t maxs; + vec3_t center; +} aas_areainfo_t; + +// client movement prediction stop events, stop as soon as: +#define SE_NONE 0 +#define SE_HITGROUND 1 // the ground is hit +#define SE_LEAVEGROUND 2 // there's no ground +#define SE_ENTERWATER 4 // water is entered +#define SE_ENTERSLIME 8 // slime is entered +#define SE_ENTERLAVA 16 // lava is entered +#define SE_HITGROUNDDAMAGE 32 // the ground is hit with damage +#define SE_GAP 64 // there's a gap +#define SE_TOUCHJUMPPAD 128 // touching a jump pad area +#define SE_TOUCHTELEPORTER 256 // touching teleporter +#define SE_ENTERAREA 512 // the given stoparea is entered +#define SE_HITGROUNDAREA 1024 // a ground face in the area is hit +#define SE_HITBOUNDINGBOX 2048 // hit the specified bounding box +#define SE_TOUCHCLUSTERPORTAL 4096 // touching a cluster portal + +typedef struct aas_clientmove_s +{ + vec3_t endpos; //position at the end of movement prediction + int endarea; //area at end of movement prediction + vec3_t velocity; //velocity at the end of movement prediction + aas_trace_t trace; //last trace + int presencetype; //presence type at end of movement prediction + int stopevent; //event that made the prediction stop + int endcontents; //contents at the end of movement prediction + float time; //time predicted ahead + int frames; //number of frames predicted ahead +} aas_clientmove_t; + +// alternate route goals +#define ALTROUTEGOAL_ALL 1 +#define ALTROUTEGOAL_CLUSTERPORTALS 2 +#define ALTROUTEGOAL_VIEWPORTALS 4 + +typedef struct aas_altroutegoal_s +{ + vec3_t origin; + int areanum; + unsigned short starttraveltime; + unsigned short goaltraveltime; + unsigned short extratraveltime; +} aas_altroutegoal_t; + +// route prediction stop events +#define RSE_NONE 0 +#define RSE_NOROUTE 1 //no route to goal +#define RSE_USETRAVELTYPE 2 //stop as soon as on of the given travel types is used +#define RSE_ENTERCONTENTS 4 //stop when entering the given contents +#define RSE_ENTERAREA 8 //stop when entering the given area + +typedef struct aas_predictroute_s +{ + vec3_t endpos; //position at the end of movement prediction + int endarea; //area at end of movement prediction + int stopevent; //event that made the prediction stop + int endcontents; //contents at the end of movement prediction + int endtravelflags; //end travel flags + int numareas; //number of areas predicted ahead + int time; //time predicted ahead (in hundreth of a sec) +} aas_predictroute_t; diff --git a/code/game/be_ai_char.h b/code/game/be_ai_char.h index 89f1e52..573fa1d 100755 --- a/code/game/be_ai_char.h +++ b/code/game/be_ai_char.h @@ -1,48 +1,48 @@ -/* -=========================================================================== -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 Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// - -/***************************************************************************** - * name: be_ai_char.h - * - * desc: bot characters - * - * $Archive: /source/code/botlib/be_ai_char.h $ - * - *****************************************************************************/ - -//loads a bot character from a file -int BotLoadCharacter(char *charfile, float skill); -//frees a bot character -void BotFreeCharacter(int character); -//returns a float characteristic -float Characteristic_Float(int character, int index); -//returns a bounded float characteristic -float Characteristic_BFloat(int character, int index, float min, float max); -//returns an integer characteristic -int Characteristic_Integer(int character, int index); -//returns a bounded integer characteristic -int Characteristic_BInteger(int character, int index, int min, int max); -//returns a string characteristic -void Characteristic_String(int character, int index, char *buf, int size); -//free cached bot characters -void BotShutdownCharacters(void); +/* +=========================================================================== +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 Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// + +/***************************************************************************** + * name: be_ai_char.h + * + * desc: bot characters + * + * $Archive: /source/code/botlib/be_ai_char.h $ + * + *****************************************************************************/ + +//loads a bot character from a file +int BotLoadCharacter(char *charfile, float skill); +//frees a bot character +void BotFreeCharacter(int character); +//returns a float characteristic +float Characteristic_Float(int character, int index); +//returns a bounded float characteristic +float Characteristic_BFloat(int character, int index, float min, float max); +//returns an integer characteristic +int Characteristic_Integer(int character, int index); +//returns a bounded integer characteristic +int Characteristic_BInteger(int character, int index, int min, int max); +//returns a string characteristic +void Characteristic_String(int character, int index, char *buf, int size); +//free cached bot characters +void BotShutdownCharacters(void); diff --git a/code/game/be_ai_chat.h b/code/game/be_ai_chat.h index 8d3d7a4..67a38ed 100755 --- a/code/game/be_ai_chat.h +++ b/code/game/be_ai_chat.h @@ -1,113 +1,113 @@ -/* -=========================================================================== -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 Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// -/***************************************************************************** - * name: be_ai_chat.h - * - * desc: char AI - * - * $Archive: /source/code/botlib/be_ai_chat.h $ - * - *****************************************************************************/ - -#define MAX_MESSAGE_SIZE 256 -#define MAX_CHATTYPE_NAME 32 -#define MAX_MATCHVARIABLES 8 - -#define CHAT_GENDERLESS 0 -#define CHAT_GENDERFEMALE 1 -#define CHAT_GENDERMALE 2 - -#define CHAT_ALL 0 -#define CHAT_TEAM 1 -#define CHAT_TELL 2 - -//a console message -typedef struct bot_consolemessage_s -{ - int handle; - float time; //message time - int type; //message type - char message[MAX_MESSAGE_SIZE]; //message - struct bot_consolemessage_s *prev, *next; //prev and next in list -} bot_consolemessage_t; - -//match variable -typedef struct bot_matchvariable_s -{ - char offset; - int length; -} bot_matchvariable_t; -//returned to AI when a match is found -typedef struct bot_match_s -{ - char string[MAX_MESSAGE_SIZE]; - int type; - int subtype; - bot_matchvariable_t variables[MAX_MATCHVARIABLES]; -} bot_match_t; - -//setup the chat AI -int BotSetupChatAI(void); -//shutdown the chat AI -void BotShutdownChatAI(void); -//returns the handle to a newly allocated chat state -int BotAllocChatState(void); -//frees the chatstate -void BotFreeChatState(int handle); -//adds a console message to the chat state -void BotQueueConsoleMessage(int chatstate, int type, char *message); -//removes the console message from the chat state -void BotRemoveConsoleMessage(int chatstate, int handle); -//returns the next console message from the state -int BotNextConsoleMessage(int chatstate, bot_consolemessage_t *cm); -//returns the number of console messages currently stored in the state -int BotNumConsoleMessages(int chatstate); -//selects a chat message of the given type -void BotInitialChat(int chatstate, char *type, int mcontext, char *var0, char *var1, char *var2, char *var3, char *var4, char *var5, char *var6, char *var7); -//returns the number of initial chat messages of the given type -int BotNumInitialChats(int chatstate, char *type); -//find and select a reply for the given message -int BotReplyChat(int chatstate, char *message, int mcontext, int vcontext, char *var0, char *var1, char *var2, char *var3, char *var4, char *var5, char *var6, char *var7); -//returns the length of the currently selected chat message -int BotChatLength(int chatstate); -//enters the selected chat message -void BotEnterChat(int chatstate, int clientto, int sendto); -//get the chat message ready to be output -void BotGetChatMessage(int chatstate, char *buf, int size); -//checks if the first string contains the second one, returns index into first string or -1 if not found -int StringContains(char *str1, char *str2, int casesensitive); -//finds a match for the given string using the match templates -int BotFindMatch(char *str, bot_match_t *match, unsigned long int context); -//returns a variable from a match -void BotMatchVariable(bot_match_t *match, int variable, char *buf, int size); -//unify all the white spaces in the string -void UnifyWhiteSpaces(char *string); -//replace all the context related synonyms in the string -void BotReplaceSynonyms(char *string, unsigned long int context); -//loads a chat file for the chat state -int BotLoadChatFile(int chatstate, char *chatfile, char *chatname); -//store the gender of the bot in the chat state -void BotSetChatGender(int chatstate, int gender); -//store the bot name in the chat state -void BotSetChatName(int chatstate, char *name, int client); - +/* +=========================================================================== +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 Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// +/***************************************************************************** + * name: be_ai_chat.h + * + * desc: char AI + * + * $Archive: /source/code/botlib/be_ai_chat.h $ + * + *****************************************************************************/ + +#define MAX_MESSAGE_SIZE 256 +#define MAX_CHATTYPE_NAME 32 +#define MAX_MATCHVARIABLES 8 + +#define CHAT_GENDERLESS 0 +#define CHAT_GENDERFEMALE 1 +#define CHAT_GENDERMALE 2 + +#define CHAT_ALL 0 +#define CHAT_TEAM 1 +#define CHAT_TELL 2 + +//a console message +typedef struct bot_consolemessage_s +{ + int handle; + float time; //message time + int type; //message type + char message[MAX_MESSAGE_SIZE]; //message + struct bot_consolemessage_s *prev, *next; //prev and next in list +} bot_consolemessage_t; + +//match variable +typedef struct bot_matchvariable_s +{ + char offset; + int length; +} bot_matchvariable_t; +//returned to AI when a match is found +typedef struct bot_match_s +{ + char string[MAX_MESSAGE_SIZE]; + int type; + int subtype; + bot_matchvariable_t variables[MAX_MATCHVARIABLES]; +} bot_match_t; + +//setup the chat AI +int BotSetupChatAI(void); +//shutdown the chat AI +void BotShutdownChatAI(void); +//returns the handle to a newly allocated chat state +int BotAllocChatState(void); +//frees the chatstate +void BotFreeChatState(int handle); +//adds a console message to the chat state +void BotQueueConsoleMessage(int chatstate, int type, char *message); +//removes the console message from the chat state +void BotRemoveConsoleMessage(int chatstate, int handle); +//returns the next console message from the state +int BotNextConsoleMessage(int chatstate, bot_consolemessage_t *cm); +//returns the number of console messages currently stored in the state +int BotNumConsoleMessages(int chatstate); +//selects a chat message of the given type +void BotInitialChat(int chatstate, char *type, int mcontext, char *var0, char *var1, char *var2, char *var3, char *var4, char *var5, char *var6, char *var7); +//returns the number of initial chat messages of the given type +int BotNumInitialChats(int chatstate, char *type); +//find and select a reply for the given message +int BotReplyChat(int chatstate, char *message, int mcontext, int vcontext, char *var0, char *var1, char *var2, char *var3, char *var4, char *var5, char *var6, char *var7); +//returns the length of the currently selected chat message +int BotChatLength(int chatstate); +//enters the selected chat message +void BotEnterChat(int chatstate, int clientto, int sendto); +//get the chat message ready to be output +void BotGetChatMessage(int chatstate, char *buf, int size); +//checks if the first string contains the second one, returns index into first string or -1 if not found +int StringContains(char *str1, char *str2, int casesensitive); +//finds a match for the given string using the match templates +int BotFindMatch(char *str, bot_match_t *match, unsigned long int context); +//returns a variable from a match +void BotMatchVariable(bot_match_t *match, int variable, char *buf, int size); +//unify all the white spaces in the string +void UnifyWhiteSpaces(char *string); +//replace all the context related synonyms in the string +void BotReplaceSynonyms(char *string, unsigned long int context); +//loads a chat file for the chat state +int BotLoadChatFile(int chatstate, char *chatfile, char *chatname); +//store the gender of the bot in the chat state +void BotSetChatGender(int chatstate, int gender); +//store the bot name in the chat state +void BotSetChatName(int chatstate, char *name, int client); + diff --git a/code/game/be_ai_gen.h b/code/game/be_ai_gen.h index 94528ed..2a4b53d 100755 --- a/code/game/be_ai_gen.h +++ b/code/game/be_ai_gen.h @@ -1,33 +1,33 @@ -/* -=========================================================================== -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 Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// - -/***************************************************************************** - * name: be_ai_gen.h - * - * desc: genetic selection - * - * $Archive: /source/code/botlib/be_ai_gen.h $ - * - *****************************************************************************/ - -int GeneticParentsAndChildSelection(int numranks, float *ranks, int *parent1, int *parent2, int *child); +/* +=========================================================================== +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 Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// + +/***************************************************************************** + * name: be_ai_gen.h + * + * desc: genetic selection + * + * $Archive: /source/code/botlib/be_ai_gen.h $ + * + *****************************************************************************/ + +int GeneticParentsAndChildSelection(int numranks, float *ranks, int *parent1, int *parent2, int *child); diff --git a/code/game/be_ai_goal.h b/code/game/be_ai_goal.h index 1fdddf3..354609d 100755 --- a/code/game/be_ai_goal.h +++ b/code/game/be_ai_goal.h @@ -1,118 +1,118 @@ -/* -=========================================================================== -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 Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// -/***************************************************************************** - * name: be_ai_goal.h - * - * desc: goal AI - * - * $Archive: /source/code/botlib/be_ai_goal.h $ - * - *****************************************************************************/ - -#define MAX_AVOIDGOALS 256 -#define MAX_GOALSTACK 8 - -#define GFL_NONE 0 -#define GFL_ITEM 1 -#define GFL_ROAM 2 -#define GFL_DROPPED 4 - -//a bot goal -typedef struct bot_goal_s -{ - vec3_t origin; //origin of the goal - int areanum; //area number of the goal - vec3_t mins, maxs; //mins and maxs of the goal - int entitynum; //number of the goal entity - int number; //goal number - int flags; //goal flags - int iteminfo; //item information -} bot_goal_t; - -//reset the whole goal state, but keep the item weights -void BotResetGoalState(int goalstate); -//reset avoid goals -void BotResetAvoidGoals(int goalstate); -//remove the goal with the given number from the avoid goals -void BotRemoveFromAvoidGoals(int goalstate, int number); -//push a goal onto the goal stack -void BotPushGoal(int goalstate, bot_goal_t *goal); -//pop a goal from the goal stack -void BotPopGoal(int goalstate); -//empty the bot's goal stack -void BotEmptyGoalStack(int goalstate); -//dump the avoid goals -void BotDumpAvoidGoals(int goalstate); -//dump the goal stack -void BotDumpGoalStack(int goalstate); -//get the name name of the goal with the given number -void BotGoalName(int number, char *name, int size); -//get the top goal from the stack -int BotGetTopGoal(int goalstate, bot_goal_t *goal); -//get the second goal on the stack -int BotGetSecondGoal(int goalstate, bot_goal_t *goal); -//choose the best long term goal item for the bot -int BotChooseLTGItem(int goalstate, vec3_t origin, int *inventory, int travelflags); -//choose the best nearby goal item for the bot -//the item may not be further away from the current bot position than maxtime -//also the travel time from the nearby goal towards the long term goal may not -//be larger than the travel time towards the long term goal from the current bot position -int BotChooseNBGItem(int goalstate, vec3_t origin, int *inventory, int travelflags, - bot_goal_t *ltg, float maxtime); -//returns true if the bot touches the goal -int BotTouchingGoal(vec3_t origin, bot_goal_t *goal); -//returns true if the goal should be visible but isn't -int BotItemGoalInVisButNotVisible(int viewer, vec3_t eye, vec3_t viewangles, bot_goal_t *goal); -//search for a goal for the given classname, the index can be used -//as a start point for the search when multiple goals are available with that same classname -int BotGetLevelItemGoal(int index, char *classname, bot_goal_t *goal); -//get the next camp spot in the map -int BotGetNextCampSpotGoal(int num, bot_goal_t *goal); -//get the map location with the given name -int BotGetMapLocationGoal(char *name, bot_goal_t *goal); -//returns the avoid goal time -float BotAvoidGoalTime(int goalstate, int number); -//set the avoid goal time -void BotSetAvoidGoalTime(int goalstate, int number, float avoidtime); -//initializes the items in the level -void BotInitLevelItems(void); -//regularly update dynamic entity items (dropped weapons, flags etc.) -void BotUpdateEntityItems(void); -//interbreed the goal fuzzy logic -void BotInterbreedGoalFuzzyLogic(int parent1, int parent2, int child); -//save the goal fuzzy logic to disk -void BotSaveGoalFuzzyLogic(int goalstate, char *filename); -//mutate the goal fuzzy logic -void BotMutateGoalFuzzyLogic(int goalstate, float range); -//loads item weights for the bot -int BotLoadItemWeights(int goalstate, char *filename); -//frees the item weights of the bot -void BotFreeItemWeights(int goalstate); -//returns the handle of a newly allocated goal state -int BotAllocGoalState(int client); -//free the given goal state -void BotFreeGoalState(int handle); -//setup the goal AI -int BotSetupGoalAI(void); -//shut down the goal AI -void BotShutdownGoalAI(void); +/* +=========================================================================== +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 Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// +/***************************************************************************** + * name: be_ai_goal.h + * + * desc: goal AI + * + * $Archive: /source/code/botlib/be_ai_goal.h $ + * + *****************************************************************************/ + +#define MAX_AVOIDGOALS 256 +#define MAX_GOALSTACK 8 + +#define GFL_NONE 0 +#define GFL_ITEM 1 +#define GFL_ROAM 2 +#define GFL_DROPPED 4 + +//a bot goal +typedef struct bot_goal_s +{ + vec3_t origin; //origin of the goal + int areanum; //area number of the goal + vec3_t mins, maxs; //mins and maxs of the goal + int entitynum; //number of the goal entity + int number; //goal number + int flags; //goal flags + int iteminfo; //item information +} bot_goal_t; + +//reset the whole goal state, but keep the item weights +void BotResetGoalState(int goalstate); +//reset avoid goals +void BotResetAvoidGoals(int goalstate); +//remove the goal with the given number from the avoid goals +void BotRemoveFromAvoidGoals(int goalstate, int number); +//push a goal onto the goal stack +void BotPushGoal(int goalstate, bot_goal_t *goal); +//pop a goal from the goal stack +void BotPopGoal(int goalstate); +//empty the bot's goal stack +void BotEmptyGoalStack(int goalstate); +//dump the avoid goals +void BotDumpAvoidGoals(int goalstate); +//dump the goal stack +void BotDumpGoalStack(int goalstate); +//get the name name of the goal with the given number +void BotGoalName(int number, char *name, int size); +//get the top goal from the stack +int BotGetTopGoal(int goalstate, bot_goal_t *goal); +//get the second goal on the stack +int BotGetSecondGoal(int goalstate, bot_goal_t *goal); +//choose the best long term goal item for the bot +int BotChooseLTGItem(int goalstate, vec3_t origin, int *inventory, int travelflags); +//choose the best nearby goal item for the bot +//the item may not be further away from the current bot position than maxtime +//also the travel time from the nearby goal towards the long term goal may not +//be larger than the travel time towards the long term goal from the current bot position +int BotChooseNBGItem(int goalstate, vec3_t origin, int *inventory, int travelflags, + bot_goal_t *ltg, float maxtime); +//returns true if the bot touches the goal +int BotTouchingGoal(vec3_t origin, bot_goal_t *goal); +//returns true if the goal should be visible but isn't +int BotItemGoalInVisButNotVisible(int viewer, vec3_t eye, vec3_t viewangles, bot_goal_t *goal); +//search for a goal for the given classname, the index can be used +//as a start point for the search when multiple goals are available with that same classname +int BotGetLevelItemGoal(int index, char *classname, bot_goal_t *goal); +//get the next camp spot in the map +int BotGetNextCampSpotGoal(int num, bot_goal_t *goal); +//get the map location with the given name +int BotGetMapLocationGoal(char *name, bot_goal_t *goal); +//returns the avoid goal time +float BotAvoidGoalTime(int goalstate, int number); +//set the avoid goal time +void BotSetAvoidGoalTime(int goalstate, int number, float avoidtime); +//initializes the items in the level +void BotInitLevelItems(void); +//regularly update dynamic entity items (dropped weapons, flags etc.) +void BotUpdateEntityItems(void); +//interbreed the goal fuzzy logic +void BotInterbreedGoalFuzzyLogic(int parent1, int parent2, int child); +//save the goal fuzzy logic to disk +void BotSaveGoalFuzzyLogic(int goalstate, char *filename); +//mutate the goal fuzzy logic +void BotMutateGoalFuzzyLogic(int goalstate, float range); +//loads item weights for the bot +int BotLoadItemWeights(int goalstate, char *filename); +//frees the item weights of the bot +void BotFreeItemWeights(int goalstate); +//returns the handle of a newly allocated goal state +int BotAllocGoalState(int client); +//free the given goal state +void BotFreeGoalState(int handle); +//setup the goal AI +int BotSetupGoalAI(void); +//shut down the goal AI +void BotShutdownGoalAI(void); diff --git a/code/game/be_ai_move.h b/code/game/be_ai_move.h index 1f0c756..fb80819 100755 --- a/code/game/be_ai_move.h +++ b/code/game/be_ai_move.h @@ -1,142 +1,142 @@ -/* -=========================================================================== -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 Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// - -/***************************************************************************** - * name: be_ai_move.h - * - * desc: movement AI - * - * $Archive: /source/code/botlib/be_ai_move.h $ - * - *****************************************************************************/ - -//movement types -#define MOVE_WALK 1 -#define MOVE_CROUCH 2 -#define MOVE_JUMP 4 -#define MOVE_GRAPPLE 8 -#define MOVE_ROCKETJUMP 16 -#define MOVE_BFGJUMP 32 -//move flags -#define MFL_BARRIERJUMP 1 //bot is performing a barrier jump -#define MFL_ONGROUND 2 //bot is in the ground -#define MFL_SWIMMING 4 //bot is swimming -#define MFL_AGAINSTLADDER 8 //bot is against a ladder -#define MFL_WATERJUMP 16 //bot is waterjumping -#define MFL_TELEPORTED 32 //bot is being teleported -#define MFL_GRAPPLEPULL 64 //bot is being pulled by the grapple -#define MFL_ACTIVEGRAPPLE 128 //bot is using the grapple hook -#define MFL_GRAPPLERESET 256 //bot has reset the grapple -#define MFL_WALK 512 //bot should walk slowly -// move result flags -#define MOVERESULT_MOVEMENTVIEW 1 //bot uses view for movement -#define MOVERESULT_SWIMVIEW 2 //bot uses view for swimming -#define MOVERESULT_WAITING 4 //bot is waiting for something -#define MOVERESULT_MOVEMENTVIEWSET 8 //bot has set the view in movement code -#define MOVERESULT_MOVEMENTWEAPON 16 //bot uses weapon for movement -#define MOVERESULT_ONTOPOFOBSTACLE 32 //bot is ontop of obstacle -#define MOVERESULT_ONTOPOF_FUNCBOB 64 //bot is ontop of a func_bobbing -#define MOVERESULT_ONTOPOF_ELEVATOR 128 //bot is ontop of an elevator (func_plat) -#define MOVERESULT_BLOCKEDBYAVOIDSPOT 256 //bot is blocked by an avoid spot -// -#define MAX_AVOIDREACH 1 -#define MAX_AVOIDSPOTS 32 -// avoid spot types -#define AVOID_CLEAR 0 //clear all avoid spots -#define AVOID_ALWAYS 1 //avoid always -#define AVOID_DONTBLOCK 2 //never totally block -// restult types -#define RESULTTYPE_ELEVATORUP 1 //elevator is up -#define RESULTTYPE_WAITFORFUNCBOBBING 2 //waiting for func bobbing to arrive -#define RESULTTYPE_BADGRAPPLEPATH 4 //grapple path is obstructed -#define RESULTTYPE_INSOLIDAREA 8 //stuck in solid area, this is bad - -//structure used to initialize the movement state -//the or_moveflags MFL_ONGROUND, MFL_TELEPORTED and MFL_WATERJUMP come from the playerstate -typedef struct bot_initmove_s -{ - vec3_t origin; //origin of the bot - vec3_t velocity; //velocity of the bot - vec3_t viewoffset; //view offset - int entitynum; //entity number of the bot - int client; //client number of the bot - float thinktime; //time the bot thinks - int presencetype; //presencetype of the bot - vec3_t viewangles; //view angles of the bot - int or_moveflags; //values ored to the movement flags -} bot_initmove_t; - -//NOTE: the ideal_viewangles are only valid if MFL_MOVEMENTVIEW is set -typedef struct bot_moveresult_s -{ - int failure; //true if movement failed all together - int type; //failure or blocked type - int blocked; //true if blocked by an entity - int blockentity; //entity blocking the bot - int traveltype; //last executed travel type - int flags; //result flags - int weapon; //weapon used for movement - vec3_t movedir; //movement direction - vec3_t ideal_viewangles; //ideal viewangles for the movement -} bot_moveresult_t; - -// bk001204: from code/botlib/be_ai_move.c -// TTimo 04/12/2001 was moved here to avoid dup defines -typedef struct bot_avoidspot_s -{ - vec3_t origin; - float radius; - int type; -} bot_avoidspot_t; - -//resets the whole move state -void BotResetMoveState(int movestate); -//moves the bot to the given goal -void BotMoveToGoal(bot_moveresult_t *result, int movestate, bot_goal_t *goal, int travelflags); -//moves the bot in the specified direction using the specified type of movement -int BotMoveInDirection(int movestate, vec3_t dir, float speed, int type); -//reset avoid reachability -void BotResetAvoidReach(int movestate); -//resets the last avoid reachability -void BotResetLastAvoidReach(int movestate); -//returns a reachability area if the origin is in one -int BotReachabilityArea(vec3_t origin, int client); -//view target based on movement -int BotMovementViewTarget(int movestate, bot_goal_t *goal, int travelflags, float lookahead, vec3_t target); -//predict the position of a player based on movement towards a goal -int BotPredictVisiblePosition(vec3_t origin, int areanum, bot_goal_t *goal, int travelflags, vec3_t target); -//returns the handle of a newly allocated movestate -int BotAllocMoveState(void); -//frees the movestate with the given handle -void BotFreeMoveState(int handle); -//initialize movement state before performing any movement -void BotInitMoveState(int handle, bot_initmove_t *initmove); -//add a spot to avoid (if type == AVOID_CLEAR all spots are removed) -void BotAddAvoidSpot(int movestate, vec3_t origin, float radius, int type); -//must be called every map change -void BotSetBrushModelTypes(void); -//setup movement AI -int BotSetupMoveAI(void); -//shutdown movement AI -void BotShutdownMoveAI(void); - +/* +=========================================================================== +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 Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// + +/***************************************************************************** + * name: be_ai_move.h + * + * desc: movement AI + * + * $Archive: /source/code/botlib/be_ai_move.h $ + * + *****************************************************************************/ + +//movement types +#define MOVE_WALK 1 +#define MOVE_CROUCH 2 +#define MOVE_JUMP 4 +#define MOVE_GRAPPLE 8 +#define MOVE_ROCKETJUMP 16 +#define MOVE_BFGJUMP 32 +//move flags +#define MFL_BARRIERJUMP 1 //bot is performing a barrier jump +#define MFL_ONGROUND 2 //bot is in the ground +#define MFL_SWIMMING 4 //bot is swimming +#define MFL_AGAINSTLADDER 8 //bot is against a ladder +#define MFL_WATERJUMP 16 //bot is waterjumping +#define MFL_TELEPORTED 32 //bot is being teleported +#define MFL_GRAPPLEPULL 64 //bot is being pulled by the grapple +#define MFL_ACTIVEGRAPPLE 128 //bot is using the grapple hook +#define MFL_GRAPPLERESET 256 //bot has reset the grapple +#define MFL_WALK 512 //bot should walk slowly +// move result flags +#define MOVERESULT_MOVEMENTVIEW 1 //bot uses view for movement +#define MOVERESULT_SWIMVIEW 2 //bot uses view for swimming +#define MOVERESULT_WAITING 4 //bot is waiting for something +#define MOVERESULT_MOVEMENTVIEWSET 8 //bot has set the view in movement code +#define MOVERESULT_MOVEMENTWEAPON 16 //bot uses weapon for movement +#define MOVERESULT_ONTOPOFOBSTACLE 32 //bot is ontop of obstacle +#define MOVERESULT_ONTOPOF_FUNCBOB 64 //bot is ontop of a func_bobbing +#define MOVERESULT_ONTOPOF_ELEVATOR 128 //bot is ontop of an elevator (func_plat) +#define MOVERESULT_BLOCKEDBYAVOIDSPOT 256 //bot is blocked by an avoid spot +// +#define MAX_AVOIDREACH 1 +#define MAX_AVOIDSPOTS 32 +// avoid spot types +#define AVOID_CLEAR 0 //clear all avoid spots +#define AVOID_ALWAYS 1 //avoid always +#define AVOID_DONTBLOCK 2 //never totally block +// restult types +#define RESULTTYPE_ELEVATORUP 1 //elevator is up +#define RESULTTYPE_WAITFORFUNCBOBBING 2 //waiting for func bobbing to arrive +#define RESULTTYPE_BADGRAPPLEPATH 4 //grapple path is obstructed +#define RESULTTYPE_INSOLIDAREA 8 //stuck in solid area, this is bad + +//structure used to initialize the movement state +//the or_moveflags MFL_ONGROUND, MFL_TELEPORTED and MFL_WATERJUMP come from the playerstate +typedef struct bot_initmove_s +{ + vec3_t origin; //origin of the bot + vec3_t velocity; //velocity of the bot + vec3_t viewoffset; //view offset + int entitynum; //entity number of the bot + int client; //client number of the bot + float thinktime; //time the bot thinks + int presencetype; //presencetype of the bot + vec3_t viewangles; //view angles of the bot + int or_moveflags; //values ored to the movement flags +} bot_initmove_t; + +//NOTE: the ideal_viewangles are only valid if MFL_MOVEMENTVIEW is set +typedef struct bot_moveresult_s +{ + int failure; //true if movement failed all together + int type; //failure or blocked type + int blocked; //true if blocked by an entity + int blockentity; //entity blocking the bot + int traveltype; //last executed travel type + int flags; //result flags + int weapon; //weapon used for movement + vec3_t movedir; //movement direction + vec3_t ideal_viewangles; //ideal viewangles for the movement +} bot_moveresult_t; + +// bk001204: from code/botlib/be_ai_move.c +// TTimo 04/12/2001 was moved here to avoid dup defines +typedef struct bot_avoidspot_s +{ + vec3_t origin; + float radius; + int type; +} bot_avoidspot_t; + +//resets the whole move state +void BotResetMoveState(int movestate); +//moves the bot to the given goal +void BotMoveToGoal(bot_moveresult_t *result, int movestate, bot_goal_t *goal, int travelflags); +//moves the bot in the specified direction using the specified type of movement +int BotMoveInDirection(int movestate, vec3_t dir, float speed, int type); +//reset avoid reachability +void BotResetAvoidReach(int movestate); +//resets the last avoid reachability +void BotResetLastAvoidReach(int movestate); +//returns a reachability area if the origin is in one +int BotReachabilityArea(vec3_t origin, int client); +//view target based on movement +int BotMovementViewTarget(int movestate, bot_goal_t *goal, int travelflags, float lookahead, vec3_t target); +//predict the position of a player based on movement towards a goal +int BotPredictVisiblePosition(vec3_t origin, int areanum, bot_goal_t *goal, int travelflags, vec3_t target); +//returns the handle of a newly allocated movestate +int BotAllocMoveState(void); +//frees the movestate with the given handle +void BotFreeMoveState(int handle); +//initialize movement state before performing any movement +void BotInitMoveState(int handle, bot_initmove_t *initmove); +//add a spot to avoid (if type == AVOID_CLEAR all spots are removed) +void BotAddAvoidSpot(int movestate, vec3_t origin, float radius, int type); +//must be called every map change +void BotSetBrushModelTypes(void); +//setup movement AI +int BotSetupMoveAI(void); +//shutdown movement AI +void BotShutdownMoveAI(void); + diff --git a/code/game/be_ai_weap.h b/code/game/be_ai_weap.h index 6dbb5a2..b78ad68 100755 --- a/code/game/be_ai_weap.h +++ b/code/game/be_ai_weap.h @@ -1,104 +1,104 @@ -/* -=========================================================================== -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 Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// - -/***************************************************************************** - * name: be_ai_weap.h - * - * desc: weapon AI - * - * $Archive: /source/code/botlib/be_ai_weap.h $ - * - *****************************************************************************/ - -//projectile flags -#define PFL_WINDOWDAMAGE 1 //projectile damages through window -#define PFL_RETURN 2 //set when projectile returns to owner -//weapon flags -#define WFL_FIRERELEASED 1 //set when projectile is fired with key-up event -//damage types -#define DAMAGETYPE_IMPACT 1 //damage on impact -#define DAMAGETYPE_RADIAL 2 //radial damage -#define DAMAGETYPE_VISIBLE 4 //damage to all entities visible to the projectile - -typedef struct projectileinfo_s -{ - char name[MAX_STRINGFIELD]; - char model[MAX_STRINGFIELD]; - int flags; - float gravity; - int damage; - float radius; - int visdamage; - int damagetype; - int healthinc; - float push; - float detonation; - float bounce; - float bouncefric; - float bouncestop; -} projectileinfo_t; - -typedef struct weaponinfo_s -{ - int valid; //true if the weapon info is valid - int number; //number of the weapon - char name[MAX_STRINGFIELD]; - char model[MAX_STRINGFIELD]; - int level; - int weaponindex; - int flags; - char projectile[MAX_STRINGFIELD]; - int numprojectiles; - float hspread; - float vspread; - float speed; - float acceleration; - vec3_t recoil; - vec3_t offset; - vec3_t angleoffset; - float extrazvelocity; - int ammoamount; - int ammoindex; - float activate; - float reload; - float spinup; - float spindown; - projectileinfo_t proj; //pointer to the used projectile -} weaponinfo_t; - -//setup the weapon AI -int BotSetupWeaponAI(void); -//shut down the weapon AI -void BotShutdownWeaponAI(void); -//returns the best weapon to fight with -int BotChooseBestFightWeapon(int weaponstate, int *inventory); -//returns the information of the current weapon -void BotGetWeaponInfo(int weaponstate, int weapon, weaponinfo_t *weaponinfo); -//loads the weapon weights -int BotLoadWeaponWeights(int weaponstate, char *filename); -//returns a handle to a newly allocated weapon state -int BotAllocWeaponState(void); -//frees the weapon state -void BotFreeWeaponState(int weaponstate); -//resets the whole weapon state -void BotResetWeaponState(int weaponstate); +/* +=========================================================================== +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 Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// + +/***************************************************************************** + * name: be_ai_weap.h + * + * desc: weapon AI + * + * $Archive: /source/code/botlib/be_ai_weap.h $ + * + *****************************************************************************/ + +//projectile flags +#define PFL_WINDOWDAMAGE 1 //projectile damages through window +#define PFL_RETURN 2 //set when projectile returns to owner +//weapon flags +#define WFL_FIRERELEASED 1 //set when projectile is fired with key-up event +//damage types +#define DAMAGETYPE_IMPACT 1 //damage on impact +#define DAMAGETYPE_RADIAL 2 //radial damage +#define DAMAGETYPE_VISIBLE 4 //damage to all entities visible to the projectile + +typedef struct projectileinfo_s +{ + char name[MAX_STRINGFIELD]; + char model[MAX_STRINGFIELD]; + int flags; + float gravity; + int damage; + float radius; + int visdamage; + int damagetype; + int healthinc; + float push; + float detonation; + float bounce; + float bouncefric; + float bouncestop; +} projectileinfo_t; + +typedef struct weaponinfo_s +{ + int valid; //true if the weapon info is valid + int number; //number of the weapon + char name[MAX_STRINGFIELD]; + char model[MAX_STRINGFIELD]; + int level; + int weaponindex; + int flags; + char projectile[MAX_STRINGFIELD]; + int numprojectiles; + float hspread; + float vspread; + float speed; + float acceleration; + vec3_t recoil; + vec3_t offset; + vec3_t angleoffset; + float extrazvelocity; + int ammoamount; + int ammoindex; + float activate; + float reload; + float spinup; + float spindown; + projectileinfo_t proj; //pointer to the used projectile +} weaponinfo_t; + +//setup the weapon AI +int BotSetupWeaponAI(void); +//shut down the weapon AI +void BotShutdownWeaponAI(void); +//returns the best weapon to fight with +int BotChooseBestFightWeapon(int weaponstate, int *inventory); +//returns the information of the current weapon +void BotGetWeaponInfo(int weaponstate, int weapon, weaponinfo_t *weaponinfo); +//loads the weapon weights +int BotLoadWeaponWeights(int weaponstate, char *filename); +//returns a handle to a newly allocated weapon state +int BotAllocWeaponState(void); +//frees the weapon state +void BotFreeWeaponState(int weaponstate); +//resets the whole weapon state +void BotResetWeaponState(int weaponstate); diff --git a/code/game/be_ea.h b/code/game/be_ea.h index 01cc7e5..1bc436b 100755 --- a/code/game/be_ea.h +++ b/code/game/be_ea.h @@ -1,66 +1,66 @@ -/* -=========================================================================== -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 Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// - -/***************************************************************************** - * name: be_ea.h - * - * desc: elementary actions - * - * $Archive: /source/code/botlib/be_ea.h $ - * - *****************************************************************************/ - -//ClientCommand elementary actions -void EA_Say(int client, char *str); -void EA_SayTeam(int client, char *str); -void EA_Command(int client, char *command ); - -void EA_Action(int client, int action); -void EA_Crouch(int client); -void EA_Walk(int client); -void EA_MoveUp(int client); -void EA_MoveDown(int client); -void EA_MoveForward(int client); -void EA_MoveBack(int client); -void EA_MoveLeft(int client); -void EA_MoveRight(int client); -void EA_Attack(int client); -void EA_Respawn(int client); -void EA_Talk(int client); -void EA_Gesture(int client); -void EA_Use(int client); - -//regular elementary actions -void EA_SelectWeapon(int client, int weapon); -void EA_Jump(int client); -void EA_DelayedJump(int client); -void EA_Move(int client, vec3_t dir, float speed); -void EA_View(int client, vec3_t viewangles); - -//send regular input to the server -void EA_EndRegular(int client, float thinktime); -void EA_GetInput(int client, float thinktime, bot_input_t *input); -void EA_ResetInput(int client); -//setup and shutdown routines -int EA_Setup(void); -void EA_Shutdown(void); +/* +=========================================================================== +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 Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// + +/***************************************************************************** + * name: be_ea.h + * + * desc: elementary actions + * + * $Archive: /source/code/botlib/be_ea.h $ + * + *****************************************************************************/ + +//ClientCommand elementary actions +void EA_Say(int client, char *str); +void EA_SayTeam(int client, char *str); +void EA_Command(int client, char *command ); + +void EA_Action(int client, int action); +void EA_Crouch(int client); +void EA_Walk(int client); +void EA_MoveUp(int client); +void EA_MoveDown(int client); +void EA_MoveForward(int client); +void EA_MoveBack(int client); +void EA_MoveLeft(int client); +void EA_MoveRight(int client); +void EA_Attack(int client); +void EA_Respawn(int client); +void EA_Talk(int client); +void EA_Gesture(int client); +void EA_Use(int client); + +//regular elementary actions +void EA_SelectWeapon(int client, int weapon); +void EA_Jump(int client); +void EA_DelayedJump(int client); +void EA_Move(int client, vec3_t dir, float speed); +void EA_View(int client, vec3_t viewangles); + +//send regular input to the server +void EA_EndRegular(int client, float thinktime); +void EA_GetInput(int client, float thinktime, bot_input_t *input); +void EA_ResetInput(int client); +//setup and shutdown routines +int EA_Setup(void); +void EA_Shutdown(void); diff --git a/code/game/bg_lib.c b/code/game/bg_lib.c index a5ee53e..bf1ac1e 100755 --- a/code/game/bg_lib.c +++ b/code/game/bg_lib.c @@ -1,1324 +1,1324 @@ -// -// -// bg_lib,c -- standard C library replacement routines used by code -// compiled for the virtual machine - -#include "q_shared.h" - -/*- - * Copyright (c) 1992, 1993 - * The Regents of the University of California. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. All advertising materials mentioning features or use of this software - * must display the following acknowledgement: - * This product includes software developed by the University of - * California, Berkeley and its contributors. - * 4. Neither the name of the University nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ - -#if defined(LIBC_SCCS) && !defined(lint) -#if 0 -static char sccsid[] = "@(#)qsort.c 8.1 (Berkeley) 6/4/93"; -#endif -static const char rcsid[] = -#endif /* LIBC_SCCS and not lint */ - -// bk001127 - needed for DLL's -#if !defined( Q3_VM ) -typedef int cmp_t(const void *, const void *); -#endif - -static char* med3(char *, char *, char *, cmp_t *); -static void swapfunc(char *, char *, int, int); - -#ifndef min -#define min(a, b) (a) < (b) ? a : b -#endif - -/* - * Qsort routine from Bentley & McIlroy's "Engineering a Sort Function". - */ -#define swapcode(TYPE, parmi, parmj, n) { \ - long i = (n) / sizeof (TYPE); \ - register TYPE *pi = (TYPE *) (parmi); \ - register TYPE *pj = (TYPE *) (parmj); \ - do { \ - register TYPE t = *pi; \ - *pi++ = *pj; \ - *pj++ = t; \ - } while (--i > 0); \ -} - -#define SWAPINIT(a, es) swaptype = ((char *)a - (char *)0) % sizeof(long) || \ - es % sizeof(long) ? 2 : es == sizeof(long)? 0 : 1; - -static void -swapfunc(a, b, n, swaptype) - char *a, *b; - int n, swaptype; -{ - if(swaptype <= 1) - swapcode(long, a, b, n) - else - swapcode(char, a, b, n) -} - -#define swap(a, b) \ - if (swaptype == 0) { \ - long t = *(long *)(a); \ - *(long *)(a) = *(long *)(b); \ - *(long *)(b) = t; \ - } else \ - swapfunc(a, b, es, swaptype) - -#define vecswap(a, b, n) if ((n) > 0) swapfunc(a, b, n, swaptype) - -static char * -med3(a, b, c, cmp) - char *a, *b, *c; - cmp_t *cmp; -{ - return cmp(a, b) < 0 ? - (cmp(b, c) < 0 ? b : (cmp(a, c) < 0 ? c : a )) - :(cmp(b, c) > 0 ? b : (cmp(a, c) < 0 ? a : c )); -} - -void -qsort(a, n, es, cmp) - void *a; - size_t n, es; - cmp_t *cmp; -{ - char *pa, *pb, *pc, *pd, *pl, *pm, *pn; - int d, r, swaptype, swap_cnt; - -loop: SWAPINIT(a, es); - swap_cnt = 0; - if (n < 7) { - for (pm = (char *)a + es; pm < (char *)a + n * es; pm += es) - for (pl = pm; pl > (char *)a && cmp(pl - es, pl) > 0; - pl -= es) - swap(pl, pl - es); - return; - } - pm = (char *)a + (n / 2) * es; - if (n > 7) { - pl = a; - pn = (char *)a + (n - 1) * es; - if (n > 40) { - d = (n / 8) * es; - pl = med3(pl, pl + d, pl + 2 * d, cmp); - pm = med3(pm - d, pm, pm + d, cmp); - pn = med3(pn - 2 * d, pn - d, pn, cmp); - } - pm = med3(pl, pm, pn, cmp); - } - swap(a, pm); - pa = pb = (char *)a + es; - - pc = pd = (char *)a + (n - 1) * es; - for (;;) { - while (pb <= pc && (r = cmp(pb, a)) <= 0) { - if (r == 0) { - swap_cnt = 1; - swap(pa, pb); - pa += es; - } - pb += es; - } - while (pb <= pc && (r = cmp(pc, a)) >= 0) { - if (r == 0) { - swap_cnt = 1; - swap(pc, pd); - pd -= es; - } - pc -= es; - } - if (pb > pc) - break; - swap(pb, pc); - swap_cnt = 1; - pb += es; - pc -= es; - } - if (swap_cnt == 0) { /* Switch to insertion sort */ - for (pm = (char *)a + es; pm < (char *)a + n * es; pm += es) - for (pl = pm; pl > (char *)a && cmp(pl - es, pl) > 0; - pl -= es) - swap(pl, pl - es); - return; - } - - pn = (char *)a + n * es; - r = min(pa - (char *)a, pb - pa); - vecswap(a, pb - r, r); - r = min(pd - pc, pn - pd - es); - vecswap(pb, pn - r, r); - if ((r = pb - pa) > es) - qsort(a, r / es, es, cmp); - if ((r = pd - pc) > es) { - /* Iterate rather than recurse to save stack space */ - a = pn - r; - n = r / es; - goto loop; - } -/* qsort(pn - r, r / es, es, cmp);*/ -} - -//================================================================================== - - -// this file is excluded from release builds because of intrinsics - -// bk001211 - gcc errors on compiling strcpy: parse error before `__extension__' -#if defined ( Q3_VM ) - -size_t strlen( const char *string ) { - const char *s; - - s = string; - while ( *s ) { - s++; - } - return s - string; -} - - -char *strcat( char *strDestination, const char *strSource ) { - char *s; - - s = strDestination; - while ( *s ) { - s++; - } - while ( *strSource ) { - *s++ = *strSource++; - } - *s = 0; - return strDestination; -} - -char *strcpy( char *strDestination, const char *strSource ) { - char *s; - - s = strDestination; - while ( *strSource ) { - *s++ = *strSource++; - } - *s = 0; - return strDestination; -} - - -int strcmp( const char *string1, const char *string2 ) { - while ( *string1 == *string2 && *string1 && *string2 ) { - string1++; - string2++; - } - return *string1 - *string2; -} - - -char *strchr( const char *string, int c ) { - while ( *string ) { - if ( *string == c ) { - return ( char * )string; - } - string++; - } - return (char *)0; -} - -char *strstr( const char *string, const char *strCharSet ) { - while ( *string ) { - int i; - - for ( i = 0 ; strCharSet[i] ; i++ ) { - if ( string[i] != strCharSet[i] ) { - break; - } - } - if ( !strCharSet[i] ) { - return (char *)string; - } - string++; - } - return (char *)0; -} -#endif // bk001211 - -// bk001120 - presumably needed for Mac -//#if !defined(_MSC_VER) && !defined(__linux__) -// bk001127 - undid undo -#if defined ( Q3_VM ) -int tolower( int c ) { - if ( c >= 'A' && c <= 'Z' ) { - c += 'a' - 'A'; - } - return c; -} - - -int toupper( int c ) { - if ( c >= 'a' && c <= 'z' ) { - c += 'A' - 'a'; - } - return c; -} - -#endif -//#ifndef _MSC_VER - -void *memmove( void *dest, const void *src, size_t count ) { - int i; - - if ( dest > src ) { - for ( i = count-1 ; i >= 0 ; i-- ) { - ((char *)dest)[i] = ((char *)src)[i]; - } - } else { - for ( i = 0 ; i < count ; i++ ) { - ((char *)dest)[i] = ((char *)src)[i]; - } - } - return dest; -} - - -#if 0 - -double floor( double x ) { - return (int)(x + 0x40000000) - 0x40000000; -} - -void *memset( void *dest, int c, size_t count ) { - while ( count-- ) { - ((char *)dest)[count] = c; - } - return dest; -} - -void *memcpy( void *dest, const void *src, size_t count ) { - while ( count-- ) { - ((char *)dest)[count] = ((char *)src)[count]; - } - return dest; -} - -char *strncpy( char *strDest, const char *strSource, size_t count ) { - char *s; - - s = strDest; - while ( *strSource && count ) { - *s++ = *strSource++; - count--; - } - while ( count-- ) { - *s++ = 0; - } - return strDest; -} - -double sqrt( double x ) { - float y; - float delta; - float maxError; - - if ( x <= 0 ) { - return 0; - } - - // initial guess - y = x / 2; - - // refine - maxError = x * 0.001; - - do { - delta = ( y * y ) - x; - y -= delta / ( 2 * y ); - } while ( delta > maxError || delta < -maxError ); - - return y; -} - - -float sintable[1024] = { -0.000000,0.001534,0.003068,0.004602,0.006136,0.007670,0.009204,0.010738, -0.012272,0.013805,0.015339,0.016873,0.018407,0.019940,0.021474,0.023008, -0.024541,0.026075,0.027608,0.029142,0.030675,0.032208,0.033741,0.035274, -0.036807,0.038340,0.039873,0.041406,0.042938,0.044471,0.046003,0.047535, -0.049068,0.050600,0.052132,0.053664,0.055195,0.056727,0.058258,0.059790, -0.061321,0.062852,0.064383,0.065913,0.067444,0.068974,0.070505,0.072035, -0.073565,0.075094,0.076624,0.078153,0.079682,0.081211,0.082740,0.084269, -0.085797,0.087326,0.088854,0.090381,0.091909,0.093436,0.094963,0.096490, -0.098017,0.099544,0.101070,0.102596,0.104122,0.105647,0.107172,0.108697, -0.110222,0.111747,0.113271,0.114795,0.116319,0.117842,0.119365,0.120888, -0.122411,0.123933,0.125455,0.126977,0.128498,0.130019,0.131540,0.133061, -0.134581,0.136101,0.137620,0.139139,0.140658,0.142177,0.143695,0.145213, -0.146730,0.148248,0.149765,0.151281,0.152797,0.154313,0.155828,0.157343, -0.158858,0.160372,0.161886,0.163400,0.164913,0.166426,0.167938,0.169450, -0.170962,0.172473,0.173984,0.175494,0.177004,0.178514,0.180023,0.181532, -0.183040,0.184548,0.186055,0.187562,0.189069,0.190575,0.192080,0.193586, -0.195090,0.196595,0.198098,0.199602,0.201105,0.202607,0.204109,0.205610, -0.207111,0.208612,0.210112,0.211611,0.213110,0.214609,0.216107,0.217604, -0.219101,0.220598,0.222094,0.223589,0.225084,0.226578,0.228072,0.229565, -0.231058,0.232550,0.234042,0.235533,0.237024,0.238514,0.240003,0.241492, -0.242980,0.244468,0.245955,0.247442,0.248928,0.250413,0.251898,0.253382, -0.254866,0.256349,0.257831,0.259313,0.260794,0.262275,0.263755,0.265234, -0.266713,0.268191,0.269668,0.271145,0.272621,0.274097,0.275572,0.277046, -0.278520,0.279993,0.281465,0.282937,0.284408,0.285878,0.287347,0.288816, -0.290285,0.291752,0.293219,0.294685,0.296151,0.297616,0.299080,0.300543, -0.302006,0.303468,0.304929,0.306390,0.307850,0.309309,0.310767,0.312225, -0.313682,0.315138,0.316593,0.318048,0.319502,0.320955,0.322408,0.323859, -0.325310,0.326760,0.328210,0.329658,0.331106,0.332553,0.334000,0.335445, -0.336890,0.338334,0.339777,0.341219,0.342661,0.344101,0.345541,0.346980, -0.348419,0.349856,0.351293,0.352729,0.354164,0.355598,0.357031,0.358463, -0.359895,0.361326,0.362756,0.364185,0.365613,0.367040,0.368467,0.369892, -0.371317,0.372741,0.374164,0.375586,0.377007,0.378428,0.379847,0.381266, -0.382683,0.384100,0.385516,0.386931,0.388345,0.389758,0.391170,0.392582, -0.393992,0.395401,0.396810,0.398218,0.399624,0.401030,0.402435,0.403838, -0.405241,0.406643,0.408044,0.409444,0.410843,0.412241,0.413638,0.415034, -0.416430,0.417824,0.419217,0.420609,0.422000,0.423390,0.424780,0.426168, -0.427555,0.428941,0.430326,0.431711,0.433094,0.434476,0.435857,0.437237, -0.438616,0.439994,0.441371,0.442747,0.444122,0.445496,0.446869,0.448241, -0.449611,0.450981,0.452350,0.453717,0.455084,0.456449,0.457813,0.459177, -0.460539,0.461900,0.463260,0.464619,0.465976,0.467333,0.468689,0.470043, -0.471397,0.472749,0.474100,0.475450,0.476799,0.478147,0.479494,0.480839, -0.482184,0.483527,0.484869,0.486210,0.487550,0.488889,0.490226,0.491563, -0.492898,0.494232,0.495565,0.496897,0.498228,0.499557,0.500885,0.502212, -0.503538,0.504863,0.506187,0.507509,0.508830,0.510150,0.511469,0.512786, -0.514103,0.515418,0.516732,0.518045,0.519356,0.520666,0.521975,0.523283, -0.524590,0.525895,0.527199,0.528502,0.529804,0.531104,0.532403,0.533701, -0.534998,0.536293,0.537587,0.538880,0.540171,0.541462,0.542751,0.544039, -0.545325,0.546610,0.547894,0.549177,0.550458,0.551738,0.553017,0.554294, -0.555570,0.556845,0.558119,0.559391,0.560662,0.561931,0.563199,0.564466, -0.565732,0.566996,0.568259,0.569521,0.570781,0.572040,0.573297,0.574553, -0.575808,0.577062,0.578314,0.579565,0.580814,0.582062,0.583309,0.584554, -0.585798,0.587040,0.588282,0.589521,0.590760,0.591997,0.593232,0.594466, -0.595699,0.596931,0.598161,0.599389,0.600616,0.601842,0.603067,0.604290, -0.605511,0.606731,0.607950,0.609167,0.610383,0.611597,0.612810,0.614022, -0.615232,0.616440,0.617647,0.618853,0.620057,0.621260,0.622461,0.623661, -0.624859,0.626056,0.627252,0.628446,0.629638,0.630829,0.632019,0.633207, -0.634393,0.635578,0.636762,0.637944,0.639124,0.640303,0.641481,0.642657, -0.643832,0.645005,0.646176,0.647346,0.648514,0.649681,0.650847,0.652011, -0.653173,0.654334,0.655493,0.656651,0.657807,0.658961,0.660114,0.661266, -0.662416,0.663564,0.664711,0.665856,0.667000,0.668142,0.669283,0.670422, -0.671559,0.672695,0.673829,0.674962,0.676093,0.677222,0.678350,0.679476, -0.680601,0.681724,0.682846,0.683965,0.685084,0.686200,0.687315,0.688429, -0.689541,0.690651,0.691759,0.692866,0.693971,0.695075,0.696177,0.697278, -0.698376,0.699473,0.700569,0.701663,0.702755,0.703845,0.704934,0.706021, -0.707107,0.708191,0.709273,0.710353,0.711432,0.712509,0.713585,0.714659, -0.715731,0.716801,0.717870,0.718937,0.720003,0.721066,0.722128,0.723188, -0.724247,0.725304,0.726359,0.727413,0.728464,0.729514,0.730563,0.731609, -0.732654,0.733697,0.734739,0.735779,0.736817,0.737853,0.738887,0.739920, -0.740951,0.741980,0.743008,0.744034,0.745058,0.746080,0.747101,0.748119, -0.749136,0.750152,0.751165,0.752177,0.753187,0.754195,0.755201,0.756206, -0.757209,0.758210,0.759209,0.760207,0.761202,0.762196,0.763188,0.764179, -0.765167,0.766154,0.767139,0.768122,0.769103,0.770083,0.771061,0.772036, -0.773010,0.773983,0.774953,0.775922,0.776888,0.777853,0.778817,0.779778, -0.780737,0.781695,0.782651,0.783605,0.784557,0.785507,0.786455,0.787402, -0.788346,0.789289,0.790230,0.791169,0.792107,0.793042,0.793975,0.794907, -0.795837,0.796765,0.797691,0.798615,0.799537,0.800458,0.801376,0.802293, -0.803208,0.804120,0.805031,0.805940,0.806848,0.807753,0.808656,0.809558, -0.810457,0.811355,0.812251,0.813144,0.814036,0.814926,0.815814,0.816701, -0.817585,0.818467,0.819348,0.820226,0.821103,0.821977,0.822850,0.823721, -0.824589,0.825456,0.826321,0.827184,0.828045,0.828904,0.829761,0.830616, -0.831470,0.832321,0.833170,0.834018,0.834863,0.835706,0.836548,0.837387, -0.838225,0.839060,0.839894,0.840725,0.841555,0.842383,0.843208,0.844032, -0.844854,0.845673,0.846491,0.847307,0.848120,0.848932,0.849742,0.850549, -0.851355,0.852159,0.852961,0.853760,0.854558,0.855354,0.856147,0.856939, -0.857729,0.858516,0.859302,0.860085,0.860867,0.861646,0.862424,0.863199, -0.863973,0.864744,0.865514,0.866281,0.867046,0.867809,0.868571,0.869330, -0.870087,0.870842,0.871595,0.872346,0.873095,0.873842,0.874587,0.875329, -0.876070,0.876809,0.877545,0.878280,0.879012,0.879743,0.880471,0.881197, -0.881921,0.882643,0.883363,0.884081,0.884797,0.885511,0.886223,0.886932, -0.887640,0.888345,0.889048,0.889750,0.890449,0.891146,0.891841,0.892534, -0.893224,0.893913,0.894599,0.895284,0.895966,0.896646,0.897325,0.898001, -0.898674,0.899346,0.900016,0.900683,0.901349,0.902012,0.902673,0.903332, -0.903989,0.904644,0.905297,0.905947,0.906596,0.907242,0.907886,0.908528, -0.909168,0.909806,0.910441,0.911075,0.911706,0.912335,0.912962,0.913587, -0.914210,0.914830,0.915449,0.916065,0.916679,0.917291,0.917901,0.918508, -0.919114,0.919717,0.920318,0.920917,0.921514,0.922109,0.922701,0.923291, -0.923880,0.924465,0.925049,0.925631,0.926210,0.926787,0.927363,0.927935, -0.928506,0.929075,0.929641,0.930205,0.930767,0.931327,0.931884,0.932440, -0.932993,0.933544,0.934093,0.934639,0.935184,0.935726,0.936266,0.936803, -0.937339,0.937872,0.938404,0.938932,0.939459,0.939984,0.940506,0.941026, -0.941544,0.942060,0.942573,0.943084,0.943593,0.944100,0.944605,0.945107, -0.945607,0.946105,0.946601,0.947094,0.947586,0.948075,0.948561,0.949046, -0.949528,0.950008,0.950486,0.950962,0.951435,0.951906,0.952375,0.952842, -0.953306,0.953768,0.954228,0.954686,0.955141,0.955594,0.956045,0.956494, -0.956940,0.957385,0.957826,0.958266,0.958703,0.959139,0.959572,0.960002, -0.960431,0.960857,0.961280,0.961702,0.962121,0.962538,0.962953,0.963366, -0.963776,0.964184,0.964590,0.964993,0.965394,0.965793,0.966190,0.966584, -0.966976,0.967366,0.967754,0.968139,0.968522,0.968903,0.969281,0.969657, -0.970031,0.970403,0.970772,0.971139,0.971504,0.971866,0.972226,0.972584, -0.972940,0.973293,0.973644,0.973993,0.974339,0.974684,0.975025,0.975365, -0.975702,0.976037,0.976370,0.976700,0.977028,0.977354,0.977677,0.977999, -0.978317,0.978634,0.978948,0.979260,0.979570,0.979877,0.980182,0.980485, -0.980785,0.981083,0.981379,0.981673,0.981964,0.982253,0.982539,0.982824, -0.983105,0.983385,0.983662,0.983937,0.984210,0.984480,0.984749,0.985014, -0.985278,0.985539,0.985798,0.986054,0.986308,0.986560,0.986809,0.987057, -0.987301,0.987544,0.987784,0.988022,0.988258,0.988491,0.988722,0.988950, -0.989177,0.989400,0.989622,0.989841,0.990058,0.990273,0.990485,0.990695, -0.990903,0.991108,0.991311,0.991511,0.991710,0.991906,0.992099,0.992291, -0.992480,0.992666,0.992850,0.993032,0.993212,0.993389,0.993564,0.993737, -0.993907,0.994075,0.994240,0.994404,0.994565,0.994723,0.994879,0.995033, -0.995185,0.995334,0.995481,0.995625,0.995767,0.995907,0.996045,0.996180, -0.996313,0.996443,0.996571,0.996697,0.996820,0.996941,0.997060,0.997176, -0.997290,0.997402,0.997511,0.997618,0.997723,0.997825,0.997925,0.998023, -0.998118,0.998211,0.998302,0.998390,0.998476,0.998559,0.998640,0.998719, -0.998795,0.998870,0.998941,0.999011,0.999078,0.999142,0.999205,0.999265, -0.999322,0.999378,0.999431,0.999481,0.999529,0.999575,0.999619,0.999660, -0.999699,0.999735,0.999769,0.999801,0.999831,0.999858,0.999882,0.999905, -0.999925,0.999942,0.999958,0.999971,0.999981,0.999989,0.999995,0.999999 -}; - -double sin( double x ) { - int index; - int quad; - - index = 1024 * x / (M_PI * 0.5); - quad = ( index >> 10 ) & 3; - index &= 1023; - switch ( quad ) { - case 0: - return sintable[index]; - case 1: - return sintable[1023-index]; - case 2: - return -sintable[index]; - case 3: - return -sintable[1023-index]; - } - return 0; -} - - -double cos( double x ) { - int index; - int quad; - - index = 1024 * x / (M_PI * 0.5); - quad = ( index >> 10 ) & 3; - index &= 1023; - switch ( quad ) { - case 3: - return sintable[index]; - case 0: - return sintable[1023-index]; - case 1: - return -sintable[index]; - case 2: - return -sintable[1023-index]; - } - return 0; -} - - -/* -void create_acostable( void ) { - int i; - FILE *fp; - float a; - - fp = fopen("c:\\acostable.txt", "w"); - fprintf(fp, "float acostable[] = {"); - for (i = 0; i < 1024; i++) { - if (!(i & 7)) - fprintf(fp, "\n"); - a = acos( (float) -1 + i / 512 ); - fprintf(fp, "%1.8f,", a); - } - fprintf(fp, "\n}\n"); - fclose(fp); -} -*/ - -float acostable[] = { -3.14159265,3.07908248,3.05317551,3.03328655,3.01651113,3.00172442,2.98834964,2.97604422, -2.96458497,2.95381690,2.94362719,2.93393068,2.92466119,2.91576615,2.90720289,2.89893629, -2.89093699,2.88318015,2.87564455,2.86831188,2.86116621,2.85419358,2.84738169,2.84071962, -2.83419760,2.82780691,2.82153967,2.81538876,2.80934770,2.80341062,2.79757211,2.79182724, -2.78617145,2.78060056,2.77511069,2.76969824,2.76435988,2.75909250,2.75389319,2.74875926, -2.74368816,2.73867752,2.73372510,2.72882880,2.72398665,2.71919677,2.71445741,2.70976688, -2.70512362,2.70052613,2.69597298,2.69146283,2.68699438,2.68256642,2.67817778,2.67382735, -2.66951407,2.66523692,2.66099493,2.65678719,2.65261279,2.64847088,2.64436066,2.64028133, -2.63623214,2.63221238,2.62822133,2.62425835,2.62032277,2.61641398,2.61253138,2.60867440, -2.60484248,2.60103507,2.59725167,2.59349176,2.58975488,2.58604053,2.58234828,2.57867769, -2.57502832,2.57139977,2.56779164,2.56420354,2.56063509,2.55708594,2.55355572,2.55004409, -2.54655073,2.54307530,2.53961750,2.53617701,2.53275354,2.52934680,2.52595650,2.52258238, -2.51922417,2.51588159,2.51255441,2.50924238,2.50594525,2.50266278,2.49939476,2.49614096, -2.49290115,2.48967513,2.48646269,2.48326362,2.48007773,2.47690482,2.47374472,2.47059722, -2.46746215,2.46433933,2.46122860,2.45812977,2.45504269,2.45196720,2.44890314,2.44585034, -2.44280867,2.43977797,2.43675809,2.43374890,2.43075025,2.42776201,2.42478404,2.42181622, -2.41885841,2.41591048,2.41297232,2.41004380,2.40712480,2.40421521,2.40131491,2.39842379, -2.39554173,2.39266863,2.38980439,2.38694889,2.38410204,2.38126374,2.37843388,2.37561237, -2.37279910,2.36999400,2.36719697,2.36440790,2.36162673,2.35885335,2.35608768,2.35332964, -2.35057914,2.34783610,2.34510044,2.34237208,2.33965094,2.33693695,2.33423003,2.33153010, -2.32883709,2.32615093,2.32347155,2.32079888,2.31813284,2.31547337,2.31282041,2.31017388, -2.30753373,2.30489988,2.30227228,2.29965086,2.29703556,2.29442632,2.29182309,2.28922580, -2.28663439,2.28404881,2.28146900,2.27889490,2.27632647,2.27376364,2.27120637,2.26865460, -2.26610827,2.26356735,2.26103177,2.25850149,2.25597646,2.25345663,2.25094195,2.24843238, -2.24592786,2.24342836,2.24093382,2.23844420,2.23595946,2.23347956,2.23100444,2.22853408, -2.22606842,2.22360742,2.22115104,2.21869925,2.21625199,2.21380924,2.21137096,2.20893709, -2.20650761,2.20408248,2.20166166,2.19924511,2.19683280,2.19442469,2.19202074,2.18962092, -2.18722520,2.18483354,2.18244590,2.18006225,2.17768257,2.17530680,2.17293493,2.17056692, -2.16820274,2.16584236,2.16348574,2.16113285,2.15878367,2.15643816,2.15409630,2.15175805, -2.14942338,2.14709226,2.14476468,2.14244059,2.14011997,2.13780279,2.13548903,2.13317865, -2.13087163,2.12856795,2.12626757,2.12397047,2.12167662,2.11938600,2.11709859,2.11481435, -2.11253326,2.11025530,2.10798044,2.10570867,2.10343994,2.10117424,2.09891156,2.09665185, -2.09439510,2.09214129,2.08989040,2.08764239,2.08539725,2.08315496,2.08091550,2.07867884, -2.07644495,2.07421383,2.07198545,2.06975978,2.06753681,2.06531651,2.06309887,2.06088387, -2.05867147,2.05646168,2.05425445,2.05204979,2.04984765,2.04764804,2.04545092,2.04325628, -2.04106409,2.03887435,2.03668703,2.03450211,2.03231957,2.03013941,2.02796159,2.02578610, -2.02361292,2.02144204,2.01927344,2.01710710,2.01494300,2.01278113,2.01062146,2.00846399, -2.00630870,2.00415556,2.00200457,1.99985570,1.99770895,1.99556429,1.99342171,1.99128119, -1.98914271,1.98700627,1.98487185,1.98273942,1.98060898,1.97848051,1.97635399,1.97422942, -1.97210676,1.96998602,1.96786718,1.96575021,1.96363511,1.96152187,1.95941046,1.95730088, -1.95519310,1.95308712,1.95098292,1.94888050,1.94677982,1.94468089,1.94258368,1.94048818, -1.93839439,1.93630228,1.93421185,1.93212308,1.93003595,1.92795046,1.92586659,1.92378433, -1.92170367,1.91962459,1.91754708,1.91547113,1.91339673,1.91132385,1.90925250,1.90718266, -1.90511432,1.90304746,1.90098208,1.89891815,1.89685568,1.89479464,1.89273503,1.89067683, -1.88862003,1.88656463,1.88451060,1.88245794,1.88040664,1.87835668,1.87630806,1.87426076, -1.87221477,1.87017008,1.86812668,1.86608457,1.86404371,1.86200412,1.85996577,1.85792866, -1.85589277,1.85385809,1.85182462,1.84979234,1.84776125,1.84573132,1.84370256,1.84167495, -1.83964848,1.83762314,1.83559892,1.83357582,1.83155381,1.82953289,1.82751305,1.82549429, -1.82347658,1.82145993,1.81944431,1.81742973,1.81541617,1.81340362,1.81139207,1.80938151, -1.80737194,1.80536334,1.80335570,1.80134902,1.79934328,1.79733848,1.79533460,1.79333164, -1.79132959,1.78932843,1.78732817,1.78532878,1.78333027,1.78133261,1.77933581,1.77733985, -1.77534473,1.77335043,1.77135695,1.76936428,1.76737240,1.76538132,1.76339101,1.76140148, -1.75941271,1.75742470,1.75543743,1.75345090,1.75146510,1.74948002,1.74749565,1.74551198, -1.74352900,1.74154672,1.73956511,1.73758417,1.73560389,1.73362426,1.73164527,1.72966692, -1.72768920,1.72571209,1.72373560,1.72175971,1.71978441,1.71780969,1.71583556,1.71386199, -1.71188899,1.70991653,1.70794462,1.70597325,1.70400241,1.70203209,1.70006228,1.69809297, -1.69612416,1.69415584,1.69218799,1.69022062,1.68825372,1.68628727,1.68432127,1.68235571, -1.68039058,1.67842588,1.67646160,1.67449772,1.67253424,1.67057116,1.66860847,1.66664615, -1.66468420,1.66272262,1.66076139,1.65880050,1.65683996,1.65487975,1.65291986,1.65096028, -1.64900102,1.64704205,1.64508338,1.64312500,1.64116689,1.63920905,1.63725148,1.63529416, -1.63333709,1.63138026,1.62942366,1.62746728,1.62551112,1.62355517,1.62159943,1.61964388, -1.61768851,1.61573332,1.61377831,1.61182346,1.60986877,1.60791422,1.60595982,1.60400556, -1.60205142,1.60009739,1.59814349,1.59618968,1.59423597,1.59228235,1.59032882,1.58837536, -1.58642196,1.58446863,1.58251535,1.58056211,1.57860891,1.57665574,1.57470259,1.57274945, -1.57079633,1.56884320,1.56689007,1.56493692,1.56298375,1.56103055,1.55907731,1.55712403, -1.55517069,1.55321730,1.55126383,1.54931030,1.54735668,1.54540297,1.54344917,1.54149526, -1.53954124,1.53758710,1.53563283,1.53367843,1.53172389,1.52976919,1.52781434,1.52585933, -1.52390414,1.52194878,1.51999323,1.51803748,1.51608153,1.51412537,1.51216900,1.51021240, -1.50825556,1.50629849,1.50434117,1.50238360,1.50042576,1.49846765,1.49650927,1.49455060, -1.49259163,1.49063237,1.48867280,1.48671291,1.48475270,1.48279215,1.48083127,1.47887004, -1.47690845,1.47494650,1.47298419,1.47102149,1.46905841,1.46709493,1.46513106,1.46316677, -1.46120207,1.45923694,1.45727138,1.45530538,1.45333893,1.45137203,1.44940466,1.44743682, -1.44546850,1.44349969,1.44153038,1.43956057,1.43759024,1.43561940,1.43364803,1.43167612, -1.42970367,1.42773066,1.42575709,1.42378296,1.42180825,1.41983295,1.41785705,1.41588056, -1.41390346,1.41192573,1.40994738,1.40796840,1.40598877,1.40400849,1.40202755,1.40004594, -1.39806365,1.39608068,1.39409701,1.39211264,1.39012756,1.38814175,1.38615522,1.38416795, -1.38217994,1.38019117,1.37820164,1.37621134,1.37422025,1.37222837,1.37023570,1.36824222, -1.36624792,1.36425280,1.36225684,1.36026004,1.35826239,1.35626387,1.35426449,1.35226422, -1.35026307,1.34826101,1.34625805,1.34425418,1.34224937,1.34024364,1.33823695,1.33622932, -1.33422072,1.33221114,1.33020059,1.32818904,1.32617649,1.32416292,1.32214834,1.32013273, -1.31811607,1.31609837,1.31407960,1.31205976,1.31003885,1.30801684,1.30599373,1.30396951, -1.30194417,1.29991770,1.29789009,1.29586133,1.29383141,1.29180031,1.28976803,1.28773456, -1.28569989,1.28366400,1.28162688,1.27958854,1.27754894,1.27550809,1.27346597,1.27142257, -1.26937788,1.26733189,1.26528459,1.26323597,1.26118602,1.25913471,1.25708205,1.25502803, -1.25297262,1.25091583,1.24885763,1.24679802,1.24473698,1.24267450,1.24061058,1.23854519, -1.23647833,1.23440999,1.23234015,1.23026880,1.22819593,1.22612152,1.22404557,1.22196806, -1.21988898,1.21780832,1.21572606,1.21364219,1.21155670,1.20946958,1.20738080,1.20529037, -1.20319826,1.20110447,1.19900898,1.19691177,1.19481283,1.19271216,1.19060973,1.18850553, -1.18639955,1.18429178,1.18218219,1.18007079,1.17795754,1.17584244,1.17372548,1.17160663, -1.16948589,1.16736324,1.16523866,1.16311215,1.16098368,1.15885323,1.15672081,1.15458638, -1.15244994,1.15031147,1.14817095,1.14602836,1.14388370,1.14173695,1.13958808,1.13743709, -1.13528396,1.13312866,1.13097119,1.12881153,1.12664966,1.12448556,1.12231921,1.12015061, -1.11797973,1.11580656,1.11363107,1.11145325,1.10927308,1.10709055,1.10490563,1.10271831, -1.10052856,1.09833638,1.09614174,1.09394462,1.09174500,1.08954287,1.08733820,1.08513098, -1.08292118,1.08070879,1.07849378,1.07627614,1.07405585,1.07183287,1.06960721,1.06737882, -1.06514770,1.06291382,1.06067715,1.05843769,1.05619540,1.05395026,1.05170226,1.04945136, -1.04719755,1.04494080,1.04268110,1.04041841,1.03815271,1.03588399,1.03361221,1.03133735, -1.02905939,1.02677830,1.02449407,1.02220665,1.01991603,1.01762219,1.01532509,1.01302471, -1.01072102,1.00841400,1.00610363,1.00378986,1.00147268,0.99915206,0.99682798,0.99450039, -0.99216928,0.98983461,0.98749636,0.98515449,0.98280898,0.98045980,0.97810691,0.97575030, -0.97338991,0.97102573,0.96865772,0.96628585,0.96391009,0.96153040,0.95914675,0.95675912, -0.95436745,0.95197173,0.94957191,0.94716796,0.94475985,0.94234754,0.93993099,0.93751017, -0.93508504,0.93265556,0.93022170,0.92778341,0.92534066,0.92289341,0.92044161,0.91798524, -0.91552424,0.91305858,0.91058821,0.90811309,0.90563319,0.90314845,0.90065884,0.89816430, -0.89566479,0.89316028,0.89065070,0.88813602,0.88561619,0.88309116,0.88056088,0.87802531, -0.87548438,0.87293806,0.87038629,0.86782901,0.86526619,0.86269775,0.86012366,0.85754385, -0.85495827,0.85236686,0.84976956,0.84716633,0.84455709,0.84194179,0.83932037,0.83669277, -0.83405893,0.83141877,0.82877225,0.82611928,0.82345981,0.82079378,0.81812110,0.81544172, -0.81275556,0.81006255,0.80736262,0.80465570,0.80194171,0.79922057,0.79649221,0.79375655, -0.79101352,0.78826302,0.78550497,0.78273931,0.77996593,0.77718475,0.77439569,0.77159865, -0.76879355,0.76598029,0.76315878,0.76032891,0.75749061,0.75464376,0.75178826,0.74892402, -0.74605092,0.74316887,0.74027775,0.73737744,0.73446785,0.73154885,0.72862033,0.72568217, -0.72273425,0.71977644,0.71680861,0.71383064,0.71084240,0.70784376,0.70483456,0.70181469, -0.69878398,0.69574231,0.69268952,0.68962545,0.68654996,0.68346288,0.68036406,0.67725332, -0.67413051,0.67099544,0.66784794,0.66468783,0.66151492,0.65832903,0.65512997,0.65191753, -0.64869151,0.64545170,0.64219789,0.63892987,0.63564741,0.63235028,0.62903824,0.62571106, -0.62236849,0.61901027,0.61563615,0.61224585,0.60883911,0.60541564,0.60197515,0.59851735, -0.59504192,0.59154856,0.58803694,0.58450672,0.58095756,0.57738911,0.57380101,0.57019288, -0.56656433,0.56291496,0.55924437,0.55555212,0.55183778,0.54810089,0.54434099,0.54055758, -0.53675018,0.53291825,0.52906127,0.52517867,0.52126988,0.51733431,0.51337132,0.50938028, -0.50536051,0.50131132,0.49723200,0.49312177,0.48897987,0.48480547,0.48059772,0.47635573, -0.47207859,0.46776530,0.46341487,0.45902623,0.45459827,0.45012983,0.44561967,0.44106652, -0.43646903,0.43182577,0.42713525,0.42239588,0.41760600,0.41276385,0.40786755,0.40291513, -0.39790449,0.39283339,0.38769946,0.38250016,0.37723277,0.37189441,0.36648196,0.36099209, -0.35542120,0.34976542,0.34402054,0.33818204,0.33224495,0.32620390,0.32005298,0.31378574, -0.30739505,0.30087304,0.29421096,0.28739907,0.28042645,0.27328078,0.26594810,0.25841250, -0.25065566,0.24265636,0.23438976,0.22582651,0.21693146,0.20766198,0.19796546,0.18777575, -0.17700769,0.16554844,0.15324301,0.13986823,0.12508152,0.10830610,0.08841715,0.06251018, -} - -double acos( double x ) { - int index; - - if (x < -1) - x = -1; - if (x > 1) - x = 1; - index = (float) (1.0 + x) * 511.9; - return acostable[index]; -} - -double atan2( double y, double x ) { - float base; - float temp; - float dir; - float test; - int i; - - if ( x < 0 ) { - if ( y >= 0 ) { - // quad 1 - base = M_PI / 2; - temp = x; - x = y; - y = -temp; - } else { - // quad 2 - base = M_PI; - x = -x; - y = -y; - } - } else { - if ( y < 0 ) { - // quad 3 - base = 3 * M_PI / 2; - temp = x; - x = -y; - y = temp; - } - } - - if ( y > x ) { - base += M_PI/2; - temp = x; - x = y; - y = temp; - dir = -1; - } else { - dir = 1; - } - - // calcualte angle in octant 0 - if ( x == 0 ) { - return base; - } - y /= x; - - for ( i = 0 ; i < 512 ; i++ ) { - test = sintable[i] / sintable[1023-i]; - if ( test > y ) { - break; - } - } - - return base + dir * i * ( M_PI/2048); -} - - -#endif - -#ifdef Q3_VM -// bk001127 - guarded this tan replacement -// ld: undefined versioned symbol name tan@@GLIBC_2.0 -double tan( double x ) { - return sin(x) / cos(x); -} -#endif - - -static int randSeed = 0; - -void srand( unsigned seed ) { - randSeed = seed; -} - -int rand( void ) { - randSeed = (69069 * randSeed + 1); - return randSeed & 0x7fff; -} - -double atof( const char *string ) { - float sign; - float value; - int c; - - - // skip whitespace - while ( *string <= ' ' ) { - if ( !*string ) { - return 0; - } - string++; - } - - // check sign - switch ( *string ) { - case '+': - string++; - sign = 1; - break; - case '-': - string++; - sign = -1; - break; - default: - sign = 1; - break; - } - - // read digits - value = 0; - c = string[0]; - if ( c != '.' ) { - do { - c = *string++; - if ( c < '0' || c > '9' ) { - break; - } - c -= '0'; - value = value * 10 + c; - } while ( 1 ); - } else { - string++; - } - - // check for decimal point - if ( c == '.' ) { - double fraction; - - fraction = 0.1; - do { - c = *string++; - if ( c < '0' || c > '9' ) { - break; - } - c -= '0'; - value += c * fraction; - fraction *= 0.1; - } while ( 1 ); - - } - - // not handling 10e10 notation... - - return value * sign; -} - -double _atof( const char **stringPtr ) { - const char *string; - float sign; - float value; - int c = '0'; // bk001211 - uninitialized use possible - - string = *stringPtr; - - // skip whitespace - while ( *string <= ' ' ) { - if ( !*string ) { - *stringPtr = string; - return 0; - } - string++; - } - - // check sign - switch ( *string ) { - case '+': - string++; - sign = 1; - break; - case '-': - string++; - sign = -1; - break; - default: - sign = 1; - break; - } - - // read digits - value = 0; - if ( string[0] != '.' ) { - do { - c = *string++; - if ( c < '0' || c > '9' ) { - break; - } - c -= '0'; - value = value * 10 + c; - } while ( 1 ); - } - - // check for decimal point - if ( c == '.' ) { - double fraction; - - fraction = 0.1; - do { - c = *string++; - if ( c < '0' || c > '9' ) { - break; - } - c -= '0'; - value += c * fraction; - fraction *= 0.1; - } while ( 1 ); - - } - - // not handling 10e10 notation... - *stringPtr = string; - - return value * sign; -} - - -// bk001120 - presumably needed for Mac -//#if !defined ( _MSC_VER ) && ! defined ( __linux__ ) - -// bk001127 - undid undo -#if defined ( Q3_VM ) -int atoi( const char *string ) { - int sign; - int value; - int c; - - - // skip whitespace - while ( *string <= ' ' ) { - if ( !*string ) { - return 0; - } - string++; - } - - // check sign - switch ( *string ) { - case '+': - string++; - sign = 1; - break; - case '-': - string++; - sign = -1; - break; - default: - sign = 1; - break; - } - - // read digits - value = 0; - do { - c = *string++; - if ( c < '0' || c > '9' ) { - break; - } - c -= '0'; - value = value * 10 + c; - } while ( 1 ); - - // not handling 10e10 notation... - - return value * sign; -} - - -int _atoi( const char **stringPtr ) { - int sign; - int value; - int c; - const char *string; - - string = *stringPtr; - - // skip whitespace - while ( *string <= ' ' ) { - if ( !*string ) { - return 0; - } - string++; - } - - // check sign - switch ( *string ) { - case '+': - string++; - sign = 1; - break; - case '-': - string++; - sign = -1; - break; - default: - sign = 1; - break; - } - - // read digits - value = 0; - do { - c = *string++; - if ( c < '0' || c > '9' ) { - break; - } - c -= '0'; - value = value * 10 + c; - } while ( 1 ); - - // not handling 10e10 notation... - - *stringPtr = string; - - return value * sign; -} - -int abs( int n ) { - return n < 0 ? -n : n; -} - -double fabs( double x ) { - return x < 0 ? -x : x; -} - - - -//========================================================= - - -#define ALT 0x00000001 /* alternate form */ -#define HEXPREFIX 0x00000002 /* add 0x or 0X prefix */ -#define LADJUST 0x00000004 /* left adjustment */ -#define LONGDBL 0x00000008 /* long double */ -#define LONGINT 0x00000010 /* long integer */ -#define QUADINT 0x00000020 /* quad integer */ -#define SHORTINT 0x00000040 /* short integer */ -#define ZEROPAD 0x00000080 /* zero (as opposed to blank) pad */ -#define FPT 0x00000100 /* floating point number */ - -#define to_digit(c) ((c) - '0') -#define is_digit(c) ((unsigned)to_digit(c) <= 9) -#define to_char(n) ((n) + '0') - -void AddInt( char **buf_p, int val, int width, int flags ) { - char text[32]; - int digits; - int signedVal; - char *buf; - - digits = 0; - signedVal = val; - if ( val < 0 ) { - val = -val; - } - do { - text[digits++] = '0' + val % 10; - val /= 10; - } while ( val ); - - if ( signedVal < 0 ) { - text[digits++] = '-'; - } - - buf = *buf_p; - - if( !( flags & LADJUST ) ) { - while ( digits < width ) { - *buf++ = ( flags & ZEROPAD ) ? '0' : ' '; - width--; - } - } - - while ( digits-- ) { - *buf++ = text[digits]; - width--; - } - - if( flags & LADJUST ) { - while ( width-- ) { - *buf++ = ( flags & ZEROPAD ) ? '0' : ' '; - } - } - - *buf_p = buf; -} - -void AddFloat( char **buf_p, float fval, int width, int prec ) { - char text[32]; - int digits; - float signedVal; - char *buf; - int val; - - // get the sign - signedVal = fval; - if ( fval < 0 ) { - fval = -fval; - } - - // write the float number - digits = 0; - val = (int)fval; - do { - text[digits++] = '0' + val % 10; - val /= 10; - } while ( val ); - - if ( signedVal < 0 ) { - text[digits++] = '-'; - } - - buf = *buf_p; - - while ( digits < width ) { - *buf++ = ' '; - width--; - } - - while ( digits-- ) { - *buf++ = text[digits]; - } - - *buf_p = buf; - - if (prec < 0) - prec = 6; - // write the fraction - digits = 0; - while (digits < prec) { - fval -= (int) fval; - fval *= 10.0; - val = (int) fval; - text[digits++] = '0' + val % 10; - } - - if (digits > 0) { - buf = *buf_p; - *buf++ = '.'; - for (prec = 0; prec < digits; prec++) { - *buf++ = text[prec]; - } - *buf_p = buf; - } -} - - -void AddString( char **buf_p, char *string, int width, int prec ) { - int size; - char *buf; - - buf = *buf_p; - - if ( string == NULL ) { - string = "(null)"; - prec = -1; - } - - if ( prec >= 0 ) { - for( size = 0; size < prec; size++ ) { - if( string[size] == '\0' ) { - break; - } - } - } - else { - size = strlen( string ); - } - - width -= size; - - while( size-- ) { - *buf++ = *string++; - } - - while( width-- > 0 ) { - *buf++ = ' '; - } - - *buf_p = buf; -} - -/* -vsprintf - -I'm not going to support a bunch of the more arcane stuff in here -just to keep it simpler. For example, the '*' and '$' are not -currently supported. I've tried to make it so that it will just -parse and ignore formats we don't support. -*/ -int vsprintf( char *buffer, const char *fmt, va_list argptr ) { - int *arg; - char *buf_p; - char ch; - int flags; - int width; - int prec; - int n; - char sign; - - buf_p = buffer; - arg = (int *)argptr; - - while( qtrue ) { - // run through the format string until we hit a '%' or '\0' - for ( ch = *fmt; (ch = *fmt) != '\0' && ch != '%'; fmt++ ) { - *buf_p++ = ch; - } - if ( ch == '\0' ) { - goto done; - } - - // skip over the '%' - fmt++; - - // reset formatting state - flags = 0; - width = 0; - prec = -1; - sign = '\0'; - -rflag: - ch = *fmt++; -reswitch: - switch( ch ) { - case '-': - flags |= LADJUST; - goto rflag; - case '.': - n = 0; - while( is_digit( ( ch = *fmt++ ) ) ) { - n = 10 * n + ( ch - '0' ); - } - prec = n < 0 ? -1 : n; - goto reswitch; - case '0': - flags |= ZEROPAD; - goto rflag; - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - n = 0; - do { - n = 10 * n + ( ch - '0' ); - ch = *fmt++; - } while( is_digit( ch ) ); - width = n; - goto reswitch; - case 'c': - *buf_p++ = (char)*arg; - arg++; - break; - case 'd': - case 'i': - AddInt( &buf_p, *arg, width, flags ); - arg++; - break; - case 'f': - AddFloat( &buf_p, *(double *)arg, width, prec ); -#ifdef __LCC__ - arg += 1; // everything is 32 bit in my compiler -#else - arg += 2; -#endif - break; - case 's': - AddString( &buf_p, (char *)*arg, width, prec ); - arg++; - break; - case '%': - *buf_p++ = ch; - break; - default: - *buf_p++ = (char)*arg; - arg++; - break; - } - } - -done: - *buf_p = 0; - return buf_p - buffer; -} - -/* this is really crappy */ -int sscanf( const char *buffer, const char *fmt, ... ) { - int cmd; - int **arg; - int count; - - arg = (int **)&fmt + 1; - count = 0; - - while ( *fmt ) { - if ( fmt[0] != '%' ) { - fmt++; - continue; - } - - cmd = fmt[1]; - fmt += 2; - - switch ( cmd ) { - case 'i': - case 'd': - case 'u': - **arg = _atoi( &buffer ); - break; - case 'f': - *(float *)*arg = _atof( &buffer ); - break; - } - arg++; - } - - return count; -} - -#endif +// +// +// bg_lib,c -- standard C library replacement routines used by code +// compiled for the virtual machine + +#include "q_shared.h" + +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#if defined(LIBC_SCCS) && !defined(lint) +#if 0 +static char sccsid[] = "@(#)qsort.c 8.1 (Berkeley) 6/4/93"; +#endif +static const char rcsid[] = +#endif /* LIBC_SCCS and not lint */ + +// bk001127 - needed for DLL's +#if !defined( Q3_VM ) +typedef int cmp_t(const void *, const void *); +#endif + +static char* med3(char *, char *, char *, cmp_t *); +static void swapfunc(char *, char *, int, int); + +#ifndef min +#define min(a, b) (a) < (b) ? a : b +#endif + +/* + * Qsort routine from Bentley & McIlroy's "Engineering a Sort Function". + */ +#define swapcode(TYPE, parmi, parmj, n) { \ + long i = (n) / sizeof (TYPE); \ + register TYPE *pi = (TYPE *) (parmi); \ + register TYPE *pj = (TYPE *) (parmj); \ + do { \ + register TYPE t = *pi; \ + *pi++ = *pj; \ + *pj++ = t; \ + } while (--i > 0); \ +} + +#define SWAPINIT(a, es) swaptype = ((char *)a - (char *)0) % sizeof(long) || \ + es % sizeof(long) ? 2 : es == sizeof(long)? 0 : 1; + +static void +swapfunc(a, b, n, swaptype) + char *a, *b; + int n, swaptype; +{ + if(swaptype <= 1) + swapcode(long, a, b, n) + else + swapcode(char, a, b, n) +} + +#define swap(a, b) \ + if (swaptype == 0) { \ + long t = *(long *)(a); \ + *(long *)(a) = *(long *)(b); \ + *(long *)(b) = t; \ + } else \ + swapfunc(a, b, es, swaptype) + +#define vecswap(a, b, n) if ((n) > 0) swapfunc(a, b, n, swaptype) + +static char * +med3(a, b, c, cmp) + char *a, *b, *c; + cmp_t *cmp; +{ + return cmp(a, b) < 0 ? + (cmp(b, c) < 0 ? b : (cmp(a, c) < 0 ? c : a )) + :(cmp(b, c) > 0 ? b : (cmp(a, c) < 0 ? a : c )); +} + +void +qsort(a, n, es, cmp) + void *a; + size_t n, es; + cmp_t *cmp; +{ + char *pa, *pb, *pc, *pd, *pl, *pm, *pn; + int d, r, swaptype, swap_cnt; + +loop: SWAPINIT(a, es); + swap_cnt = 0; + if (n < 7) { + for (pm = (char *)a + es; pm < (char *)a + n * es; pm += es) + for (pl = pm; pl > (char *)a && cmp(pl - es, pl) > 0; + pl -= es) + swap(pl, pl - es); + return; + } + pm = (char *)a + (n / 2) * es; + if (n > 7) { + pl = a; + pn = (char *)a + (n - 1) * es; + if (n > 40) { + d = (n / 8) * es; + pl = med3(pl, pl + d, pl + 2 * d, cmp); + pm = med3(pm - d, pm, pm + d, cmp); + pn = med3(pn - 2 * d, pn - d, pn, cmp); + } + pm = med3(pl, pm, pn, cmp); + } + swap(a, pm); + pa = pb = (char *)a + es; + + pc = pd = (char *)a + (n - 1) * es; + for (;;) { + while (pb <= pc && (r = cmp(pb, a)) <= 0) { + if (r == 0) { + swap_cnt = 1; + swap(pa, pb); + pa += es; + } + pb += es; + } + while (pb <= pc && (r = cmp(pc, a)) >= 0) { + if (r == 0) { + swap_cnt = 1; + swap(pc, pd); + pd -= es; + } + pc -= es; + } + if (pb > pc) + break; + swap(pb, pc); + swap_cnt = 1; + pb += es; + pc -= es; + } + if (swap_cnt == 0) { /* Switch to insertion sort */ + for (pm = (char *)a + es; pm < (char *)a + n * es; pm += es) + for (pl = pm; pl > (char *)a && cmp(pl - es, pl) > 0; + pl -= es) + swap(pl, pl - es); + return; + } + + pn = (char *)a + n * es; + r = min(pa - (char *)a, pb - pa); + vecswap(a, pb - r, r); + r = min(pd - pc, pn - pd - es); + vecswap(pb, pn - r, r); + if ((r = pb - pa) > es) + qsort(a, r / es, es, cmp); + if ((r = pd - pc) > es) { + /* Iterate rather than recurse to save stack space */ + a = pn - r; + n = r / es; + goto loop; + } +/* qsort(pn - r, r / es, es, cmp);*/ +} + +//================================================================================== + + +// this file is excluded from release builds because of intrinsics + +// bk001211 - gcc errors on compiling strcpy: parse error before `__extension__' +#if defined ( Q3_VM ) + +size_t strlen( const char *string ) { + const char *s; + + s = string; + while ( *s ) { + s++; + } + return s - string; +} + + +char *strcat( char *strDestination, const char *strSource ) { + char *s; + + s = strDestination; + while ( *s ) { + s++; + } + while ( *strSource ) { + *s++ = *strSource++; + } + *s = 0; + return strDestination; +} + +char *strcpy( char *strDestination, const char *strSource ) { + char *s; + + s = strDestination; + while ( *strSource ) { + *s++ = *strSource++; + } + *s = 0; + return strDestination; +} + + +int strcmp( const char *string1, const char *string2 ) { + while ( *string1 == *string2 && *string1 && *string2 ) { + string1++; + string2++; + } + return *string1 - *string2; +} + + +char *strchr( const char *string, int c ) { + while ( *string ) { + if ( *string == c ) { + return ( char * )string; + } + string++; + } + return (char *)0; +} + +char *strstr( const char *string, const char *strCharSet ) { + while ( *string ) { + int i; + + for ( i = 0 ; strCharSet[i] ; i++ ) { + if ( string[i] != strCharSet[i] ) { + break; + } + } + if ( !strCharSet[i] ) { + return (char *)string; + } + string++; + } + return (char *)0; +} +#endif // bk001211 + +// bk001120 - presumably needed for Mac +//#if !defined(_MSC_VER) && !defined(__linux__) +// bk001127 - undid undo +#if defined ( Q3_VM ) +int tolower( int c ) { + if ( c >= 'A' && c <= 'Z' ) { + c += 'a' - 'A'; + } + return c; +} + + +int toupper( int c ) { + if ( c >= 'a' && c <= 'z' ) { + c += 'A' - 'a'; + } + return c; +} + +#endif +//#ifndef _MSC_VER + +void *memmove( void *dest, const void *src, size_t count ) { + int i; + + if ( dest > src ) { + for ( i = count-1 ; i >= 0 ; i-- ) { + ((char *)dest)[i] = ((char *)src)[i]; + } + } else { + for ( i = 0 ; i < count ; i++ ) { + ((char *)dest)[i] = ((char *)src)[i]; + } + } + return dest; +} + + +#if 0 + +double floor( double x ) { + return (int)(x + 0x40000000) - 0x40000000; +} + +void *memset( void *dest, int c, size_t count ) { + while ( count-- ) { + ((char *)dest)[count] = c; + } + return dest; +} + +void *memcpy( void *dest, const void *src, size_t count ) { + while ( count-- ) { + ((char *)dest)[count] = ((char *)src)[count]; + } + return dest; +} + +char *strncpy( char *strDest, const char *strSource, size_t count ) { + char *s; + + s = strDest; + while ( *strSource && count ) { + *s++ = *strSource++; + count--; + } + while ( count-- ) { + *s++ = 0; + } + return strDest; +} + +double sqrt( double x ) { + float y; + float delta; + float maxError; + + if ( x <= 0 ) { + return 0; + } + + // initial guess + y = x / 2; + + // refine + maxError = x * 0.001; + + do { + delta = ( y * y ) - x; + y -= delta / ( 2 * y ); + } while ( delta > maxError || delta < -maxError ); + + return y; +} + + +float sintable[1024] = { +0.000000,0.001534,0.003068,0.004602,0.006136,0.007670,0.009204,0.010738, +0.012272,0.013805,0.015339,0.016873,0.018407,0.019940,0.021474,0.023008, +0.024541,0.026075,0.027608,0.029142,0.030675,0.032208,0.033741,0.035274, +0.036807,0.038340,0.039873,0.041406,0.042938,0.044471,0.046003,0.047535, +0.049068,0.050600,0.052132,0.053664,0.055195,0.056727,0.058258,0.059790, +0.061321,0.062852,0.064383,0.065913,0.067444,0.068974,0.070505,0.072035, +0.073565,0.075094,0.076624,0.078153,0.079682,0.081211,0.082740,0.084269, +0.085797,0.087326,0.088854,0.090381,0.091909,0.093436,0.094963,0.096490, +0.098017,0.099544,0.101070,0.102596,0.104122,0.105647,0.107172,0.108697, +0.110222,0.111747,0.113271,0.114795,0.116319,0.117842,0.119365,0.120888, +0.122411,0.123933,0.125455,0.126977,0.128498,0.130019,0.131540,0.133061, +0.134581,0.136101,0.137620,0.139139,0.140658,0.142177,0.143695,0.145213, +0.146730,0.148248,0.149765,0.151281,0.152797,0.154313,0.155828,0.157343, +0.158858,0.160372,0.161886,0.163400,0.164913,0.166426,0.167938,0.169450, +0.170962,0.172473,0.173984,0.175494,0.177004,0.178514,0.180023,0.181532, +0.183040,0.184548,0.186055,0.187562,0.189069,0.190575,0.192080,0.193586, +0.195090,0.196595,0.198098,0.199602,0.201105,0.202607,0.204109,0.205610, +0.207111,0.208612,0.210112,0.211611,0.213110,0.214609,0.216107,0.217604, +0.219101,0.220598,0.222094,0.223589,0.225084,0.226578,0.228072,0.229565, +0.231058,0.232550,0.234042,0.235533,0.237024,0.238514,0.240003,0.241492, +0.242980,0.244468,0.245955,0.247442,0.248928,0.250413,0.251898,0.253382, +0.254866,0.256349,0.257831,0.259313,0.260794,0.262275,0.263755,0.265234, +0.266713,0.268191,0.269668,0.271145,0.272621,0.274097,0.275572,0.277046, +0.278520,0.279993,0.281465,0.282937,0.284408,0.285878,0.287347,0.288816, +0.290285,0.291752,0.293219,0.294685,0.296151,0.297616,0.299080,0.300543, +0.302006,0.303468,0.304929,0.306390,0.307850,0.309309,0.310767,0.312225, +0.313682,0.315138,0.316593,0.318048,0.319502,0.320955,0.322408,0.323859, +0.325310,0.326760,0.328210,0.329658,0.331106,0.332553,0.334000,0.335445, +0.336890,0.338334,0.339777,0.341219,0.342661,0.344101,0.345541,0.346980, +0.348419,0.349856,0.351293,0.352729,0.354164,0.355598,0.357031,0.358463, +0.359895,0.361326,0.362756,0.364185,0.365613,0.367040,0.368467,0.369892, +0.371317,0.372741,0.374164,0.375586,0.377007,0.378428,0.379847,0.381266, +0.382683,0.384100,0.385516,0.386931,0.388345,0.389758,0.391170,0.392582, +0.393992,0.395401,0.396810,0.398218,0.399624,0.401030,0.402435,0.403838, +0.405241,0.406643,0.408044,0.409444,0.410843,0.412241,0.413638,0.415034, +0.416430,0.417824,0.419217,0.420609,0.422000,0.423390,0.424780,0.426168, +0.427555,0.428941,0.430326,0.431711,0.433094,0.434476,0.435857,0.437237, +0.438616,0.439994,0.441371,0.442747,0.444122,0.445496,0.446869,0.448241, +0.449611,0.450981,0.452350,0.453717,0.455084,0.456449,0.457813,0.459177, +0.460539,0.461900,0.463260,0.464619,0.465976,0.467333,0.468689,0.470043, +0.471397,0.472749,0.474100,0.475450,0.476799,0.478147,0.479494,0.480839, +0.482184,0.483527,0.484869,0.486210,0.487550,0.488889,0.490226,0.491563, +0.492898,0.494232,0.495565,0.496897,0.498228,0.499557,0.500885,0.502212, +0.503538,0.504863,0.506187,0.507509,0.508830,0.510150,0.511469,0.512786, +0.514103,0.515418,0.516732,0.518045,0.519356,0.520666,0.521975,0.523283, +0.524590,0.525895,0.527199,0.528502,0.529804,0.531104,0.532403,0.533701, +0.534998,0.536293,0.537587,0.538880,0.540171,0.541462,0.542751,0.544039, +0.545325,0.546610,0.547894,0.549177,0.550458,0.551738,0.553017,0.554294, +0.555570,0.556845,0.558119,0.559391,0.560662,0.561931,0.563199,0.564466, +0.565732,0.566996,0.568259,0.569521,0.570781,0.572040,0.573297,0.574553, +0.575808,0.577062,0.578314,0.579565,0.580814,0.582062,0.583309,0.584554, +0.585798,0.587040,0.588282,0.589521,0.590760,0.591997,0.593232,0.594466, +0.595699,0.596931,0.598161,0.599389,0.600616,0.601842,0.603067,0.604290, +0.605511,0.606731,0.607950,0.609167,0.610383,0.611597,0.612810,0.614022, +0.615232,0.616440,0.617647,0.618853,0.620057,0.621260,0.622461,0.623661, +0.624859,0.626056,0.627252,0.628446,0.629638,0.630829,0.632019,0.633207, +0.634393,0.635578,0.636762,0.637944,0.639124,0.640303,0.641481,0.642657, +0.643832,0.645005,0.646176,0.647346,0.648514,0.649681,0.650847,0.652011, +0.653173,0.654334,0.655493,0.656651,0.657807,0.658961,0.660114,0.661266, +0.662416,0.663564,0.664711,0.665856,0.667000,0.668142,0.669283,0.670422, +0.671559,0.672695,0.673829,0.674962,0.676093,0.677222,0.678350,0.679476, +0.680601,0.681724,0.682846,0.683965,0.685084,0.686200,0.687315,0.688429, +0.689541,0.690651,0.691759,0.692866,0.693971,0.695075,0.696177,0.697278, +0.698376,0.699473,0.700569,0.701663,0.702755,0.703845,0.704934,0.706021, +0.707107,0.708191,0.709273,0.710353,0.711432,0.712509,0.713585,0.714659, +0.715731,0.716801,0.717870,0.718937,0.720003,0.721066,0.722128,0.723188, +0.724247,0.725304,0.726359,0.727413,0.728464,0.729514,0.730563,0.731609, +0.732654,0.733697,0.734739,0.735779,0.736817,0.737853,0.738887,0.739920, +0.740951,0.741980,0.743008,0.744034,0.745058,0.746080,0.747101,0.748119, +0.749136,0.750152,0.751165,0.752177,0.753187,0.754195,0.755201,0.756206, +0.757209,0.758210,0.759209,0.760207,0.761202,0.762196,0.763188,0.764179, +0.765167,0.766154,0.767139,0.768122,0.769103,0.770083,0.771061,0.772036, +0.773010,0.773983,0.774953,0.775922,0.776888,0.777853,0.778817,0.779778, +0.780737,0.781695,0.782651,0.783605,0.784557,0.785507,0.786455,0.787402, +0.788346,0.789289,0.790230,0.791169,0.792107,0.793042,0.793975,0.794907, +0.795837,0.796765,0.797691,0.798615,0.799537,0.800458,0.801376,0.802293, +0.803208,0.804120,0.805031,0.805940,0.806848,0.807753,0.808656,0.809558, +0.810457,0.811355,0.812251,0.813144,0.814036,0.814926,0.815814,0.816701, +0.817585,0.818467,0.819348,0.820226,0.821103,0.821977,0.822850,0.823721, +0.824589,0.825456,0.826321,0.827184,0.828045,0.828904,0.829761,0.830616, +0.831470,0.832321,0.833170,0.834018,0.834863,0.835706,0.836548,0.837387, +0.838225,0.839060,0.839894,0.840725,0.841555,0.842383,0.843208,0.844032, +0.844854,0.845673,0.846491,0.847307,0.848120,0.848932,0.849742,0.850549, +0.851355,0.852159,0.852961,0.853760,0.854558,0.855354,0.856147,0.856939, +0.857729,0.858516,0.859302,0.860085,0.860867,0.861646,0.862424,0.863199, +0.863973,0.864744,0.865514,0.866281,0.867046,0.867809,0.868571,0.869330, +0.870087,0.870842,0.871595,0.872346,0.873095,0.873842,0.874587,0.875329, +0.876070,0.876809,0.877545,0.878280,0.879012,0.879743,0.880471,0.881197, +0.881921,0.882643,0.883363,0.884081,0.884797,0.885511,0.886223,0.886932, +0.887640,0.888345,0.889048,0.889750,0.890449,0.891146,0.891841,0.892534, +0.893224,0.893913,0.894599,0.895284,0.895966,0.896646,0.897325,0.898001, +0.898674,0.899346,0.900016,0.900683,0.901349,0.902012,0.902673,0.903332, +0.903989,0.904644,0.905297,0.905947,0.906596,0.907242,0.907886,0.908528, +0.909168,0.909806,0.910441,0.911075,0.911706,0.912335,0.912962,0.913587, +0.914210,0.914830,0.915449,0.916065,0.916679,0.917291,0.917901,0.918508, +0.919114,0.919717,0.920318,0.920917,0.921514,0.922109,0.922701,0.923291, +0.923880,0.924465,0.925049,0.925631,0.926210,0.926787,0.927363,0.927935, +0.928506,0.929075,0.929641,0.930205,0.930767,0.931327,0.931884,0.932440, +0.932993,0.933544,0.934093,0.934639,0.935184,0.935726,0.936266,0.936803, +0.937339,0.937872,0.938404,0.938932,0.939459,0.939984,0.940506,0.941026, +0.941544,0.942060,0.942573,0.943084,0.943593,0.944100,0.944605,0.945107, +0.945607,0.946105,0.946601,0.947094,0.947586,0.948075,0.948561,0.949046, +0.949528,0.950008,0.950486,0.950962,0.951435,0.951906,0.952375,0.952842, +0.953306,0.953768,0.954228,0.954686,0.955141,0.955594,0.956045,0.956494, +0.956940,0.957385,0.957826,0.958266,0.958703,0.959139,0.959572,0.960002, +0.960431,0.960857,0.961280,0.961702,0.962121,0.962538,0.962953,0.963366, +0.963776,0.964184,0.964590,0.964993,0.965394,0.965793,0.966190,0.966584, +0.966976,0.967366,0.967754,0.968139,0.968522,0.968903,0.969281,0.969657, +0.970031,0.970403,0.970772,0.971139,0.971504,0.971866,0.972226,0.972584, +0.972940,0.973293,0.973644,0.973993,0.974339,0.974684,0.975025,0.975365, +0.975702,0.976037,0.976370,0.976700,0.977028,0.977354,0.977677,0.977999, +0.978317,0.978634,0.978948,0.979260,0.979570,0.979877,0.980182,0.980485, +0.980785,0.981083,0.981379,0.981673,0.981964,0.982253,0.982539,0.982824, +0.983105,0.983385,0.983662,0.983937,0.984210,0.984480,0.984749,0.985014, +0.985278,0.985539,0.985798,0.986054,0.986308,0.986560,0.986809,0.987057, +0.987301,0.987544,0.987784,0.988022,0.988258,0.988491,0.988722,0.988950, +0.989177,0.989400,0.989622,0.989841,0.990058,0.990273,0.990485,0.990695, +0.990903,0.991108,0.991311,0.991511,0.991710,0.991906,0.992099,0.992291, +0.992480,0.992666,0.992850,0.993032,0.993212,0.993389,0.993564,0.993737, +0.993907,0.994075,0.994240,0.994404,0.994565,0.994723,0.994879,0.995033, +0.995185,0.995334,0.995481,0.995625,0.995767,0.995907,0.996045,0.996180, +0.996313,0.996443,0.996571,0.996697,0.996820,0.996941,0.997060,0.997176, +0.997290,0.997402,0.997511,0.997618,0.997723,0.997825,0.997925,0.998023, +0.998118,0.998211,0.998302,0.998390,0.998476,0.998559,0.998640,0.998719, +0.998795,0.998870,0.998941,0.999011,0.999078,0.999142,0.999205,0.999265, +0.999322,0.999378,0.999431,0.999481,0.999529,0.999575,0.999619,0.999660, +0.999699,0.999735,0.999769,0.999801,0.999831,0.999858,0.999882,0.999905, +0.999925,0.999942,0.999958,0.999971,0.999981,0.999989,0.999995,0.999999 +}; + +double sin( double x ) { + int index; + int quad; + + index = 1024 * x / (M_PI * 0.5); + quad = ( index >> 10 ) & 3; + index &= 1023; + switch ( quad ) { + case 0: + return sintable[index]; + case 1: + return sintable[1023-index]; + case 2: + return -sintable[index]; + case 3: + return -sintable[1023-index]; + } + return 0; +} + + +double cos( double x ) { + int index; + int quad; + + index = 1024 * x / (M_PI * 0.5); + quad = ( index >> 10 ) & 3; + index &= 1023; + switch ( quad ) { + case 3: + return sintable[index]; + case 0: + return sintable[1023-index]; + case 1: + return -sintable[index]; + case 2: + return -sintable[1023-index]; + } + return 0; +} + + +/* +void create_acostable( void ) { + int i; + FILE *fp; + float a; + + fp = fopen("c:\\acostable.txt", "w"); + fprintf(fp, "float acostable[] = {"); + for (i = 0; i < 1024; i++) { + if (!(i & 7)) + fprintf(fp, "\n"); + a = acos( (float) -1 + i / 512 ); + fprintf(fp, "%1.8f,", a); + } + fprintf(fp, "\n}\n"); + fclose(fp); +} +*/ + +float acostable[] = { +3.14159265,3.07908248,3.05317551,3.03328655,3.01651113,3.00172442,2.98834964,2.97604422, +2.96458497,2.95381690,2.94362719,2.93393068,2.92466119,2.91576615,2.90720289,2.89893629, +2.89093699,2.88318015,2.87564455,2.86831188,2.86116621,2.85419358,2.84738169,2.84071962, +2.83419760,2.82780691,2.82153967,2.81538876,2.80934770,2.80341062,2.79757211,2.79182724, +2.78617145,2.78060056,2.77511069,2.76969824,2.76435988,2.75909250,2.75389319,2.74875926, +2.74368816,2.73867752,2.73372510,2.72882880,2.72398665,2.71919677,2.71445741,2.70976688, +2.70512362,2.70052613,2.69597298,2.69146283,2.68699438,2.68256642,2.67817778,2.67382735, +2.66951407,2.66523692,2.66099493,2.65678719,2.65261279,2.64847088,2.64436066,2.64028133, +2.63623214,2.63221238,2.62822133,2.62425835,2.62032277,2.61641398,2.61253138,2.60867440, +2.60484248,2.60103507,2.59725167,2.59349176,2.58975488,2.58604053,2.58234828,2.57867769, +2.57502832,2.57139977,2.56779164,2.56420354,2.56063509,2.55708594,2.55355572,2.55004409, +2.54655073,2.54307530,2.53961750,2.53617701,2.53275354,2.52934680,2.52595650,2.52258238, +2.51922417,2.51588159,2.51255441,2.50924238,2.50594525,2.50266278,2.49939476,2.49614096, +2.49290115,2.48967513,2.48646269,2.48326362,2.48007773,2.47690482,2.47374472,2.47059722, +2.46746215,2.46433933,2.46122860,2.45812977,2.45504269,2.45196720,2.44890314,2.44585034, +2.44280867,2.43977797,2.43675809,2.43374890,2.43075025,2.42776201,2.42478404,2.42181622, +2.41885841,2.41591048,2.41297232,2.41004380,2.40712480,2.40421521,2.40131491,2.39842379, +2.39554173,2.39266863,2.38980439,2.38694889,2.38410204,2.38126374,2.37843388,2.37561237, +2.37279910,2.36999400,2.36719697,2.36440790,2.36162673,2.35885335,2.35608768,2.35332964, +2.35057914,2.34783610,2.34510044,2.34237208,2.33965094,2.33693695,2.33423003,2.33153010, +2.32883709,2.32615093,2.32347155,2.32079888,2.31813284,2.31547337,2.31282041,2.31017388, +2.30753373,2.30489988,2.30227228,2.29965086,2.29703556,2.29442632,2.29182309,2.28922580, +2.28663439,2.28404881,2.28146900,2.27889490,2.27632647,2.27376364,2.27120637,2.26865460, +2.26610827,2.26356735,2.26103177,2.25850149,2.25597646,2.25345663,2.25094195,2.24843238, +2.24592786,2.24342836,2.24093382,2.23844420,2.23595946,2.23347956,2.23100444,2.22853408, +2.22606842,2.22360742,2.22115104,2.21869925,2.21625199,2.21380924,2.21137096,2.20893709, +2.20650761,2.20408248,2.20166166,2.19924511,2.19683280,2.19442469,2.19202074,2.18962092, +2.18722520,2.18483354,2.18244590,2.18006225,2.17768257,2.17530680,2.17293493,2.17056692, +2.16820274,2.16584236,2.16348574,2.16113285,2.15878367,2.15643816,2.15409630,2.15175805, +2.14942338,2.14709226,2.14476468,2.14244059,2.14011997,2.13780279,2.13548903,2.13317865, +2.13087163,2.12856795,2.12626757,2.12397047,2.12167662,2.11938600,2.11709859,2.11481435, +2.11253326,2.11025530,2.10798044,2.10570867,2.10343994,2.10117424,2.09891156,2.09665185, +2.09439510,2.09214129,2.08989040,2.08764239,2.08539725,2.08315496,2.08091550,2.07867884, +2.07644495,2.07421383,2.07198545,2.06975978,2.06753681,2.06531651,2.06309887,2.06088387, +2.05867147,2.05646168,2.05425445,2.05204979,2.04984765,2.04764804,2.04545092,2.04325628, +2.04106409,2.03887435,2.03668703,2.03450211,2.03231957,2.03013941,2.02796159,2.02578610, +2.02361292,2.02144204,2.01927344,2.01710710,2.01494300,2.01278113,2.01062146,2.00846399, +2.00630870,2.00415556,2.00200457,1.99985570,1.99770895,1.99556429,1.99342171,1.99128119, +1.98914271,1.98700627,1.98487185,1.98273942,1.98060898,1.97848051,1.97635399,1.97422942, +1.97210676,1.96998602,1.96786718,1.96575021,1.96363511,1.96152187,1.95941046,1.95730088, +1.95519310,1.95308712,1.95098292,1.94888050,1.94677982,1.94468089,1.94258368,1.94048818, +1.93839439,1.93630228,1.93421185,1.93212308,1.93003595,1.92795046,1.92586659,1.92378433, +1.92170367,1.91962459,1.91754708,1.91547113,1.91339673,1.91132385,1.90925250,1.90718266, +1.90511432,1.90304746,1.90098208,1.89891815,1.89685568,1.89479464,1.89273503,1.89067683, +1.88862003,1.88656463,1.88451060,1.88245794,1.88040664,1.87835668,1.87630806,1.87426076, +1.87221477,1.87017008,1.86812668,1.86608457,1.86404371,1.86200412,1.85996577,1.85792866, +1.85589277,1.85385809,1.85182462,1.84979234,1.84776125,1.84573132,1.84370256,1.84167495, +1.83964848,1.83762314,1.83559892,1.83357582,1.83155381,1.82953289,1.82751305,1.82549429, +1.82347658,1.82145993,1.81944431,1.81742973,1.81541617,1.81340362,1.81139207,1.80938151, +1.80737194,1.80536334,1.80335570,1.80134902,1.79934328,1.79733848,1.79533460,1.79333164, +1.79132959,1.78932843,1.78732817,1.78532878,1.78333027,1.78133261,1.77933581,1.77733985, +1.77534473,1.77335043,1.77135695,1.76936428,1.76737240,1.76538132,1.76339101,1.76140148, +1.75941271,1.75742470,1.75543743,1.75345090,1.75146510,1.74948002,1.74749565,1.74551198, +1.74352900,1.74154672,1.73956511,1.73758417,1.73560389,1.73362426,1.73164527,1.72966692, +1.72768920,1.72571209,1.72373560,1.72175971,1.71978441,1.71780969,1.71583556,1.71386199, +1.71188899,1.70991653,1.70794462,1.70597325,1.70400241,1.70203209,1.70006228,1.69809297, +1.69612416,1.69415584,1.69218799,1.69022062,1.68825372,1.68628727,1.68432127,1.68235571, +1.68039058,1.67842588,1.67646160,1.67449772,1.67253424,1.67057116,1.66860847,1.66664615, +1.66468420,1.66272262,1.66076139,1.65880050,1.65683996,1.65487975,1.65291986,1.65096028, +1.64900102,1.64704205,1.64508338,1.64312500,1.64116689,1.63920905,1.63725148,1.63529416, +1.63333709,1.63138026,1.62942366,1.62746728,1.62551112,1.62355517,1.62159943,1.61964388, +1.61768851,1.61573332,1.61377831,1.61182346,1.60986877,1.60791422,1.60595982,1.60400556, +1.60205142,1.60009739,1.59814349,1.59618968,1.59423597,1.59228235,1.59032882,1.58837536, +1.58642196,1.58446863,1.58251535,1.58056211,1.57860891,1.57665574,1.57470259,1.57274945, +1.57079633,1.56884320,1.56689007,1.56493692,1.56298375,1.56103055,1.55907731,1.55712403, +1.55517069,1.55321730,1.55126383,1.54931030,1.54735668,1.54540297,1.54344917,1.54149526, +1.53954124,1.53758710,1.53563283,1.53367843,1.53172389,1.52976919,1.52781434,1.52585933, +1.52390414,1.52194878,1.51999323,1.51803748,1.51608153,1.51412537,1.51216900,1.51021240, +1.50825556,1.50629849,1.50434117,1.50238360,1.50042576,1.49846765,1.49650927,1.49455060, +1.49259163,1.49063237,1.48867280,1.48671291,1.48475270,1.48279215,1.48083127,1.47887004, +1.47690845,1.47494650,1.47298419,1.47102149,1.46905841,1.46709493,1.46513106,1.46316677, +1.46120207,1.45923694,1.45727138,1.45530538,1.45333893,1.45137203,1.44940466,1.44743682, +1.44546850,1.44349969,1.44153038,1.43956057,1.43759024,1.43561940,1.43364803,1.43167612, +1.42970367,1.42773066,1.42575709,1.42378296,1.42180825,1.41983295,1.41785705,1.41588056, +1.41390346,1.41192573,1.40994738,1.40796840,1.40598877,1.40400849,1.40202755,1.40004594, +1.39806365,1.39608068,1.39409701,1.39211264,1.39012756,1.38814175,1.38615522,1.38416795, +1.38217994,1.38019117,1.37820164,1.37621134,1.37422025,1.37222837,1.37023570,1.36824222, +1.36624792,1.36425280,1.36225684,1.36026004,1.35826239,1.35626387,1.35426449,1.35226422, +1.35026307,1.34826101,1.34625805,1.34425418,1.34224937,1.34024364,1.33823695,1.33622932, +1.33422072,1.33221114,1.33020059,1.32818904,1.32617649,1.32416292,1.32214834,1.32013273, +1.31811607,1.31609837,1.31407960,1.31205976,1.31003885,1.30801684,1.30599373,1.30396951, +1.30194417,1.29991770,1.29789009,1.29586133,1.29383141,1.29180031,1.28976803,1.28773456, +1.28569989,1.28366400,1.28162688,1.27958854,1.27754894,1.27550809,1.27346597,1.27142257, +1.26937788,1.26733189,1.26528459,1.26323597,1.26118602,1.25913471,1.25708205,1.25502803, +1.25297262,1.25091583,1.24885763,1.24679802,1.24473698,1.24267450,1.24061058,1.23854519, +1.23647833,1.23440999,1.23234015,1.23026880,1.22819593,1.22612152,1.22404557,1.22196806, +1.21988898,1.21780832,1.21572606,1.21364219,1.21155670,1.20946958,1.20738080,1.20529037, +1.20319826,1.20110447,1.19900898,1.19691177,1.19481283,1.19271216,1.19060973,1.18850553, +1.18639955,1.18429178,1.18218219,1.18007079,1.17795754,1.17584244,1.17372548,1.17160663, +1.16948589,1.16736324,1.16523866,1.16311215,1.16098368,1.15885323,1.15672081,1.15458638, +1.15244994,1.15031147,1.14817095,1.14602836,1.14388370,1.14173695,1.13958808,1.13743709, +1.13528396,1.13312866,1.13097119,1.12881153,1.12664966,1.12448556,1.12231921,1.12015061, +1.11797973,1.11580656,1.11363107,1.11145325,1.10927308,1.10709055,1.10490563,1.10271831, +1.10052856,1.09833638,1.09614174,1.09394462,1.09174500,1.08954287,1.08733820,1.08513098, +1.08292118,1.08070879,1.07849378,1.07627614,1.07405585,1.07183287,1.06960721,1.06737882, +1.06514770,1.06291382,1.06067715,1.05843769,1.05619540,1.05395026,1.05170226,1.04945136, +1.04719755,1.04494080,1.04268110,1.04041841,1.03815271,1.03588399,1.03361221,1.03133735, +1.02905939,1.02677830,1.02449407,1.02220665,1.01991603,1.01762219,1.01532509,1.01302471, +1.01072102,1.00841400,1.00610363,1.00378986,1.00147268,0.99915206,0.99682798,0.99450039, +0.99216928,0.98983461,0.98749636,0.98515449,0.98280898,0.98045980,0.97810691,0.97575030, +0.97338991,0.97102573,0.96865772,0.96628585,0.96391009,0.96153040,0.95914675,0.95675912, +0.95436745,0.95197173,0.94957191,0.94716796,0.94475985,0.94234754,0.93993099,0.93751017, +0.93508504,0.93265556,0.93022170,0.92778341,0.92534066,0.92289341,0.92044161,0.91798524, +0.91552424,0.91305858,0.91058821,0.90811309,0.90563319,0.90314845,0.90065884,0.89816430, +0.89566479,0.89316028,0.89065070,0.88813602,0.88561619,0.88309116,0.88056088,0.87802531, +0.87548438,0.87293806,0.87038629,0.86782901,0.86526619,0.86269775,0.86012366,0.85754385, +0.85495827,0.85236686,0.84976956,0.84716633,0.84455709,0.84194179,0.83932037,0.83669277, +0.83405893,0.83141877,0.82877225,0.82611928,0.82345981,0.82079378,0.81812110,0.81544172, +0.81275556,0.81006255,0.80736262,0.80465570,0.80194171,0.79922057,0.79649221,0.79375655, +0.79101352,0.78826302,0.78550497,0.78273931,0.77996593,0.77718475,0.77439569,0.77159865, +0.76879355,0.76598029,0.76315878,0.76032891,0.75749061,0.75464376,0.75178826,0.74892402, +0.74605092,0.74316887,0.74027775,0.73737744,0.73446785,0.73154885,0.72862033,0.72568217, +0.72273425,0.71977644,0.71680861,0.71383064,0.71084240,0.70784376,0.70483456,0.70181469, +0.69878398,0.69574231,0.69268952,0.68962545,0.68654996,0.68346288,0.68036406,0.67725332, +0.67413051,0.67099544,0.66784794,0.66468783,0.66151492,0.65832903,0.65512997,0.65191753, +0.64869151,0.64545170,0.64219789,0.63892987,0.63564741,0.63235028,0.62903824,0.62571106, +0.62236849,0.61901027,0.61563615,0.61224585,0.60883911,0.60541564,0.60197515,0.59851735, +0.59504192,0.59154856,0.58803694,0.58450672,0.58095756,0.57738911,0.57380101,0.57019288, +0.56656433,0.56291496,0.55924437,0.55555212,0.55183778,0.54810089,0.54434099,0.54055758, +0.53675018,0.53291825,0.52906127,0.52517867,0.52126988,0.51733431,0.51337132,0.50938028, +0.50536051,0.50131132,0.49723200,0.49312177,0.48897987,0.48480547,0.48059772,0.47635573, +0.47207859,0.46776530,0.46341487,0.45902623,0.45459827,0.45012983,0.44561967,0.44106652, +0.43646903,0.43182577,0.42713525,0.42239588,0.41760600,0.41276385,0.40786755,0.40291513, +0.39790449,0.39283339,0.38769946,0.38250016,0.37723277,0.37189441,0.36648196,0.36099209, +0.35542120,0.34976542,0.34402054,0.33818204,0.33224495,0.32620390,0.32005298,0.31378574, +0.30739505,0.30087304,0.29421096,0.28739907,0.28042645,0.27328078,0.26594810,0.25841250, +0.25065566,0.24265636,0.23438976,0.22582651,0.21693146,0.20766198,0.19796546,0.18777575, +0.17700769,0.16554844,0.15324301,0.13986823,0.12508152,0.10830610,0.08841715,0.06251018, +} + +double acos( double x ) { + int index; + + if (x < -1) + x = -1; + if (x > 1) + x = 1; + index = (float) (1.0 + x) * 511.9; + return acostable[index]; +} + +double atan2( double y, double x ) { + float base; + float temp; + float dir; + float test; + int i; + + if ( x < 0 ) { + if ( y >= 0 ) { + // quad 1 + base = M_PI / 2; + temp = x; + x = y; + y = -temp; + } else { + // quad 2 + base = M_PI; + x = -x; + y = -y; + } + } else { + if ( y < 0 ) { + // quad 3 + base = 3 * M_PI / 2; + temp = x; + x = -y; + y = temp; + } + } + + if ( y > x ) { + base += M_PI/2; + temp = x; + x = y; + y = temp; + dir = -1; + } else { + dir = 1; + } + + // calcualte angle in octant 0 + if ( x == 0 ) { + return base; + } + y /= x; + + for ( i = 0 ; i < 512 ; i++ ) { + test = sintable[i] / sintable[1023-i]; + if ( test > y ) { + break; + } + } + + return base + dir * i * ( M_PI/2048); +} + + +#endif + +#ifdef Q3_VM +// bk001127 - guarded this tan replacement +// ld: undefined versioned symbol name tan@@GLIBC_2.0 +double tan( double x ) { + return sin(x) / cos(x); +} +#endif + + +static int randSeed = 0; + +void srand( unsigned seed ) { + randSeed = seed; +} + +int rand( void ) { + randSeed = (69069 * randSeed + 1); + return randSeed & 0x7fff; +} + +double atof( const char *string ) { + float sign; + float value; + int c; + + + // skip whitespace + while ( *string <= ' ' ) { + if ( !*string ) { + return 0; + } + string++; + } + + // check sign + switch ( *string ) { + case '+': + string++; + sign = 1; + break; + case '-': + string++; + sign = -1; + break; + default: + sign = 1; + break; + } + + // read digits + value = 0; + c = string[0]; + if ( c != '.' ) { + do { + c = *string++; + if ( c < '0' || c > '9' ) { + break; + } + c -= '0'; + value = value * 10 + c; + } while ( 1 ); + } else { + string++; + } + + // check for decimal point + if ( c == '.' ) { + double fraction; + + fraction = 0.1; + do { + c = *string++; + if ( c < '0' || c > '9' ) { + break; + } + c -= '0'; + value += c * fraction; + fraction *= 0.1; + } while ( 1 ); + + } + + // not handling 10e10 notation... + + return value * sign; +} + +double _atof( const char **stringPtr ) { + const char *string; + float sign; + float value; + int c = '0'; // bk001211 - uninitialized use possible + + string = *stringPtr; + + // skip whitespace + while ( *string <= ' ' ) { + if ( !*string ) { + *stringPtr = string; + return 0; + } + string++; + } + + // check sign + switch ( *string ) { + case '+': + string++; + sign = 1; + break; + case '-': + string++; + sign = -1; + break; + default: + sign = 1; + break; + } + + // read digits + value = 0; + if ( string[0] != '.' ) { + do { + c = *string++; + if ( c < '0' || c > '9' ) { + break; + } + c -= '0'; + value = value * 10 + c; + } while ( 1 ); + } + + // check for decimal point + if ( c == '.' ) { + double fraction; + + fraction = 0.1; + do { + c = *string++; + if ( c < '0' || c > '9' ) { + break; + } + c -= '0'; + value += c * fraction; + fraction *= 0.1; + } while ( 1 ); + + } + + // not handling 10e10 notation... + *stringPtr = string; + + return value * sign; +} + + +// bk001120 - presumably needed for Mac +//#if !defined ( _MSC_VER ) && ! defined ( __linux__ ) + +// bk001127 - undid undo +#if defined ( Q3_VM ) +int atoi( const char *string ) { + int sign; + int value; + int c; + + + // skip whitespace + while ( *string <= ' ' ) { + if ( !*string ) { + return 0; + } + string++; + } + + // check sign + switch ( *string ) { + case '+': + string++; + sign = 1; + break; + case '-': + string++; + sign = -1; + break; + default: + sign = 1; + break; + } + + // read digits + value = 0; + do { + c = *string++; + if ( c < '0' || c > '9' ) { + break; + } + c -= '0'; + value = value * 10 + c; + } while ( 1 ); + + // not handling 10e10 notation... + + return value * sign; +} + + +int _atoi( const char **stringPtr ) { + int sign; + int value; + int c; + const char *string; + + string = *stringPtr; + + // skip whitespace + while ( *string <= ' ' ) { + if ( !*string ) { + return 0; + } + string++; + } + + // check sign + switch ( *string ) { + case '+': + string++; + sign = 1; + break; + case '-': + string++; + sign = -1; + break; + default: + sign = 1; + break; + } + + // read digits + value = 0; + do { + c = *string++; + if ( c < '0' || c > '9' ) { + break; + } + c -= '0'; + value = value * 10 + c; + } while ( 1 ); + + // not handling 10e10 notation... + + *stringPtr = string; + + return value * sign; +} + +int abs( int n ) { + return n < 0 ? -n : n; +} + +double fabs( double x ) { + return x < 0 ? -x : x; +} + + + +//========================================================= + + +#define ALT 0x00000001 /* alternate form */ +#define HEXPREFIX 0x00000002 /* add 0x or 0X prefix */ +#define LADJUST 0x00000004 /* left adjustment */ +#define LONGDBL 0x00000008 /* long double */ +#define LONGINT 0x00000010 /* long integer */ +#define QUADINT 0x00000020 /* quad integer */ +#define SHORTINT 0x00000040 /* short integer */ +#define ZEROPAD 0x00000080 /* zero (as opposed to blank) pad */ +#define FPT 0x00000100 /* floating point number */ + +#define to_digit(c) ((c) - '0') +#define is_digit(c) ((unsigned)to_digit(c) <= 9) +#define to_char(n) ((n) + '0') + +void AddInt( char **buf_p, int val, int width, int flags ) { + char text[32]; + int digits; + int signedVal; + char *buf; + + digits = 0; + signedVal = val; + if ( val < 0 ) { + val = -val; + } + do { + text[digits++] = '0' + val % 10; + val /= 10; + } while ( val ); + + if ( signedVal < 0 ) { + text[digits++] = '-'; + } + + buf = *buf_p; + + if( !( flags & LADJUST ) ) { + while ( digits < width ) { + *buf++ = ( flags & ZEROPAD ) ? '0' : ' '; + width--; + } + } + + while ( digits-- ) { + *buf++ = text[digits]; + width--; + } + + if( flags & LADJUST ) { + while ( width-- ) { + *buf++ = ( flags & ZEROPAD ) ? '0' : ' '; + } + } + + *buf_p = buf; +} + +void AddFloat( char **buf_p, float fval, int width, int prec ) { + char text[32]; + int digits; + float signedVal; + char *buf; + int val; + + // get the sign + signedVal = fval; + if ( fval < 0 ) { + fval = -fval; + } + + // write the float number + digits = 0; + val = (int)fval; + do { + text[digits++] = '0' + val % 10; + val /= 10; + } while ( val ); + + if ( signedVal < 0 ) { + text[digits++] = '-'; + } + + buf = *buf_p; + + while ( digits < width ) { + *buf++ = ' '; + width--; + } + + while ( digits-- ) { + *buf++ = text[digits]; + } + + *buf_p = buf; + + if (prec < 0) + prec = 6; + // write the fraction + digits = 0; + while (digits < prec) { + fval -= (int) fval; + fval *= 10.0; + val = (int) fval; + text[digits++] = '0' + val % 10; + } + + if (digits > 0) { + buf = *buf_p; + *buf++ = '.'; + for (prec = 0; prec < digits; prec++) { + *buf++ = text[prec]; + } + *buf_p = buf; + } +} + + +void AddString( char **buf_p, char *string, int width, int prec ) { + int size; + char *buf; + + buf = *buf_p; + + if ( string == NULL ) { + string = "(null)"; + prec = -1; + } + + if ( prec >= 0 ) { + for( size = 0; size < prec; size++ ) { + if( string[size] == '\0' ) { + break; + } + } + } + else { + size = strlen( string ); + } + + width -= size; + + while( size-- ) { + *buf++ = *string++; + } + + while( width-- > 0 ) { + *buf++ = ' '; + } + + *buf_p = buf; +} + +/* +vsprintf + +I'm not going to support a bunch of the more arcane stuff in here +just to keep it simpler. For example, the '*' and '$' are not +currently supported. I've tried to make it so that it will just +parse and ignore formats we don't support. +*/ +int vsprintf( char *buffer, const char *fmt, va_list argptr ) { + int *arg; + char *buf_p; + char ch; + int flags; + int width; + int prec; + int n; + char sign; + + buf_p = buffer; + arg = (int *)argptr; + + while( qtrue ) { + // run through the format string until we hit a '%' or '\0' + for ( ch = *fmt; (ch = *fmt) != '\0' && ch != '%'; fmt++ ) { + *buf_p++ = ch; + } + if ( ch == '\0' ) { + goto done; + } + + // skip over the '%' + fmt++; + + // reset formatting state + flags = 0; + width = 0; + prec = -1; + sign = '\0'; + +rflag: + ch = *fmt++; +reswitch: + switch( ch ) { + case '-': + flags |= LADJUST; + goto rflag; + case '.': + n = 0; + while( is_digit( ( ch = *fmt++ ) ) ) { + n = 10 * n + ( ch - '0' ); + } + prec = n < 0 ? -1 : n; + goto reswitch; + case '0': + flags |= ZEROPAD; + goto rflag; + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + n = 0; + do { + n = 10 * n + ( ch - '0' ); + ch = *fmt++; + } while( is_digit( ch ) ); + width = n; + goto reswitch; + case 'c': + *buf_p++ = (char)*arg; + arg++; + break; + case 'd': + case 'i': + AddInt( &buf_p, *arg, width, flags ); + arg++; + break; + case 'f': + AddFloat( &buf_p, *(double *)arg, width, prec ); +#ifdef __LCC__ + arg += 1; // everything is 32 bit in my compiler +#else + arg += 2; +#endif + break; + case 's': + AddString( &buf_p, (char *)*arg, width, prec ); + arg++; + break; + case '%': + *buf_p++ = ch; + break; + default: + *buf_p++ = (char)*arg; + arg++; + break; + } + } + +done: + *buf_p = 0; + return buf_p - buffer; +} + +/* this is really crappy */ +int sscanf( const char *buffer, const char *fmt, ... ) { + int cmd; + int **arg; + int count; + + arg = (int **)&fmt + 1; + count = 0; + + while ( *fmt ) { + if ( fmt[0] != '%' ) { + fmt++; + continue; + } + + cmd = fmt[1]; + fmt += 2; + + switch ( cmd ) { + case 'i': + case 'd': + case 'u': + **arg = _atoi( &buffer ); + break; + case 'f': + *(float *)*arg = _atof( &buffer ); + break; + } + arg++; + } + + return count; +} + +#endif diff --git a/code/game/bg_lib.h b/code/game/bg_lib.h index 57333c0..d6deb60 100755 --- a/code/game/bg_lib.h +++ b/code/game/bg_lib.h @@ -1,91 +1,91 @@ -/* -=========================================================================== -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 Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// bg_lib.h -- standard C library replacement routines used by code -// compiled for the virtual machine - -// This file is NOT included on native builds - -typedef int size_t; - -typedef char * va_list; -#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) ) -#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) ) -#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) ) -#define va_end(ap) ( ap = (va_list)0 ) - -#define CHAR_BIT 8 /* number of bits in a char */ -#define SCHAR_MIN (-128) /* minimum signed char value */ -#define SCHAR_MAX 127 /* maximum signed char value */ -#define UCHAR_MAX 0xff /* maximum unsigned char value */ - -#define SHRT_MIN (-32768) /* minimum (signed) short value */ -#define SHRT_MAX 32767 /* maximum (signed) short value */ -#define USHRT_MAX 0xffff /* maximum unsigned short value */ -#define INT_MIN (-2147483647 - 1) /* minimum (signed) int value */ -#define INT_MAX 2147483647 /* maximum (signed) int value */ -#define UINT_MAX 0xffffffff /* maximum unsigned int value */ -#define LONG_MIN (-2147483647L - 1) /* minimum (signed) long value */ -#define LONG_MAX 2147483647L /* maximum (signed) long value */ -#define ULONG_MAX 0xffffffffUL /* maximum unsigned long value */ - -// Misc functions -typedef int cmp_t(const void *, const void *); -void qsort(void *a, size_t n, size_t es, cmp_t *cmp); -void srand( unsigned seed ); -int rand( void ); - -// String functions -size_t strlen( const char *string ); -char *strcat( char *strDestination, const char *strSource ); -char *strcpy( char *strDestination, const char *strSource ); -int strcmp( const char *string1, const char *string2 ); -char *strchr( const char *string, int c ); -char *strstr( const char *string, const char *strCharSet ); -char *strncpy( char *strDest, const char *strSource, size_t count ); -int tolower( int c ); -int toupper( int c ); - -double atof( const char *string ); -double _atof( const char **stringPtr ); -int atoi( const char *string ); -int _atoi( const char **stringPtr ); - -int vsprintf( char *buffer, const char *fmt, va_list argptr ); -int sscanf( const char *buffer, const char *fmt, ... ); - -// Memory functions -void *memmove( void *dest, const void *src, size_t count ); -void *memset( void *dest, int c, size_t count ); -void *memcpy( void *dest, const void *src, size_t count ); - -// Math functions -double ceil( double x ); -double floor( double x ); -double sqrt( double x ); -double sin( double x ); -double cos( double x ); -double atan2( double y, double x ); -double tan( double x ); -int abs( int n ); -double fabs( double x ); -double acos( double x ); - +/* +=========================================================================== +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 Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// bg_lib.h -- standard C library replacement routines used by code +// compiled for the virtual machine + +// This file is NOT included on native builds + +typedef int size_t; + +typedef char * va_list; +#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) ) +#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) ) +#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) ) +#define va_end(ap) ( ap = (va_list)0 ) + +#define CHAR_BIT 8 /* number of bits in a char */ +#define SCHAR_MIN (-128) /* minimum signed char value */ +#define SCHAR_MAX 127 /* maximum signed char value */ +#define UCHAR_MAX 0xff /* maximum unsigned char value */ + +#define SHRT_MIN (-32768) /* minimum (signed) short value */ +#define SHRT_MAX 32767 /* maximum (signed) short value */ +#define USHRT_MAX 0xffff /* maximum unsigned short value */ +#define INT_MIN (-2147483647 - 1) /* minimum (signed) int value */ +#define INT_MAX 2147483647 /* maximum (signed) int value */ +#define UINT_MAX 0xffffffff /* maximum unsigned int value */ +#define LONG_MIN (-2147483647L - 1) /* minimum (signed) long value */ +#define LONG_MAX 2147483647L /* maximum (signed) long value */ +#define ULONG_MAX 0xffffffffUL /* maximum unsigned long value */ + +// Misc functions +typedef int cmp_t(const void *, const void *); +void qsort(void *a, size_t n, size_t es, cmp_t *cmp); +void srand( unsigned seed ); +int rand( void ); + +// String functions +size_t strlen( const char *string ); +char *strcat( char *strDestination, const char *strSource ); +char *strcpy( char *strDestination, const char *strSource ); +int strcmp( const char *string1, const char *string2 ); +char *strchr( const char *string, int c ); +char *strstr( const char *string, const char *strCharSet ); +char *strncpy( char *strDest, const char *strSource, size_t count ); +int tolower( int c ); +int toupper( int c ); + +double atof( const char *string ); +double _atof( const char **stringPtr ); +int atoi( const char *string ); +int _atoi( const char **stringPtr ); + +int vsprintf( char *buffer, const char *fmt, va_list argptr ); +int sscanf( const char *buffer, const char *fmt, ... ); + +// Memory functions +void *memmove( void *dest, const void *src, size_t count ); +void *memset( void *dest, int c, size_t count ); +void *memcpy( void *dest, const void *src, size_t count ); + +// Math functions +double ceil( double x ); +double floor( double x ); +double sqrt( double x ); +double sin( double x ); +double cos( double x ); +double atan2( double y, double x ); +double tan( double x ); +int abs( int n ); +double fabs( double x ); +double acos( double x ); + diff --git a/code/game/bg_local.h b/code/game/bg_local.h index 223c688..389683d 100755 --- a/code/game/bg_local.h +++ b/code/game/bg_local.h @@ -1,83 +1,83 @@ -/* -=========================================================================== -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 Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// -// bg_local.h -- local definitions for the bg (both games) files - -#define MIN_WALK_NORMAL 0.7f // can't walk on very steep slopes - -#define STEPSIZE 18 - -#define JUMP_VELOCITY 270 - -#define TIMER_LAND 130 -#define TIMER_GESTURE (34*66+50) - -#define OVERCLIP 1.001f - -// all of the locals will be zeroed before each -// pmove, just to make damn sure we don't have -// any differences when running on client or server -typedef struct { - vec3_t forward, right, up; - float frametime; - - int msec; - - qboolean walking; - qboolean groundPlane; - trace_t groundTrace; - - float impactSpeed; - - vec3_t previous_origin; - vec3_t previous_velocity; - int previous_waterlevel; -} pml_t; - -extern pmove_t *pm; -extern pml_t pml; - -// movement parameters -extern float pm_stopspeed; -extern float pm_duckScale; -extern float pm_swimScale; -extern float pm_wadeScale; - -extern float pm_accelerate; -extern float pm_airaccelerate; -extern float pm_wateraccelerate; -extern float pm_flyaccelerate; - -extern float pm_friction; -extern float pm_waterfriction; -extern float pm_flightfriction; - -extern int c_pmove; - -void PM_ClipVelocity( vec3_t in, vec3_t normal, vec3_t out, float overbounce ); -void PM_AddTouchEnt( int entityNum ); -void PM_AddEvent( int newEvent ); - -qboolean PM_SlideMove( qboolean gravity ); -void PM_StepSlideMove( qboolean gravity ); - - +/* +=========================================================================== +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 Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// +// bg_local.h -- local definitions for the bg (both games) files + +#define MIN_WALK_NORMAL 0.7f // can't walk on very steep slopes + +#define STEPSIZE 18 + +#define JUMP_VELOCITY 270 + +#define TIMER_LAND 130 +#define TIMER_GESTURE (34*66+50) + +#define OVERCLIP 1.001f + +// all of the locals will be zeroed before each +// pmove, just to make damn sure we don't have +// any differences when running on client or server +typedef struct { + vec3_t forward, right, up; + float frametime; + + int msec; + + qboolean walking; + qboolean groundPlane; + trace_t groundTrace; + + float impactSpeed; + + vec3_t previous_origin; + vec3_t previous_velocity; + int previous_waterlevel; +} pml_t; + +extern pmove_t *pm; +extern pml_t pml; + +// movement parameters +extern float pm_stopspeed; +extern float pm_duckScale; +extern float pm_swimScale; +extern float pm_wadeScale; + +extern float pm_accelerate; +extern float pm_airaccelerate; +extern float pm_wateraccelerate; +extern float pm_flyaccelerate; + +extern float pm_friction; +extern float pm_waterfriction; +extern float pm_flightfriction; + +extern int c_pmove; + +void PM_ClipVelocity( vec3_t in, vec3_t normal, vec3_t out, float overbounce ); +void PM_AddTouchEnt( int entityNum ); +void PM_AddEvent( int newEvent ); + +qboolean PM_SlideMove( qboolean gravity ); +void PM_StepSlideMove( qboolean gravity ); + + diff --git a/code/game/bg_misc.c b/code/game/bg_misc.c index f729f06..1e5a27e 100755 --- a/code/game/bg_misc.c +++ b/code/game/bg_misc.c @@ -1,1604 +1,1604 @@ -/* -=========================================================================== -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 Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// -// bg_misc.c -- both games misc functions, all completely stateless - -#include "q_shared.h" -#include "bg_public.h" - -/*QUAKED item_***** ( 0 0 0 ) (-16 -16 -16) (16 16 16) suspended -DO NOT USE THIS CLASS, IT JUST HOLDS GENERAL INFORMATION. -The suspended flag will allow items to hang in the air, otherwise they are dropped to the next surface. - -If an item is the target of another entity, it will not spawn in until fired. - -An item fires all of its targets when it is picked up. If the toucher can't carry it, the targets won't be fired. - -"notfree" if set to 1, don't spawn in free for all games -"notteam" if set to 1, don't spawn in team games -"notsingle" if set to 1, don't spawn in single player games -"wait" override the default wait before respawning. -1 = never respawn automatically, which can be used with targeted spawning. -"random" random number of plus or minus seconds varied from the respawn time -"count" override quantity or duration on most items. -*/ - -gitem_t bg_itemlist[] = -{ - { - NULL, - NULL, - { NULL, - NULL, - 0, 0} , -/* icon */ NULL, -/* pickup */ NULL, - 0, - 0, - 0, -/* precache */ "", -/* sounds */ "" - }, // leave index 0 alone - - // - // ARMOR - // - -/*QUAKED item_armor_shard (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "item_armor_shard", - "sound/misc/ar1_pkup.wav", - { "models/powerups/armor/shard.md3", - "models/powerups/armor/shard_sphere.md3", - 0, 0} , -/* icon */ "icons/iconr_shard", -/* pickup */ "Armor Shard", - 5, - IT_ARMOR, - 0, -/* precache */ "", -/* sounds */ "" - }, - -/*QUAKED item_armor_combat (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "item_armor_combat", - "sound/misc/ar2_pkup.wav", - { "models/powerups/armor/armor_yel.md3", - 0, 0, 0}, -/* icon */ "icons/iconr_yellow", -/* pickup */ "Armor", - 50, - IT_ARMOR, - 0, -/* precache */ "", -/* sounds */ "" - }, - -/*QUAKED item_armor_body (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "item_armor_body", - "sound/misc/ar2_pkup.wav", - { "models/powerups/armor/armor_red.md3", - 0, 0, 0}, -/* icon */ "icons/iconr_red", -/* pickup */ "Heavy Armor", - 100, - IT_ARMOR, - 0, -/* precache */ "", -/* sounds */ "" - }, - - // - // health - // -/*QUAKED item_health_small (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "item_health_small", - "sound/items/s_health.wav", - { "models/powerups/health/small_cross.md3", - "models/powerups/health/small_sphere.md3", - 0, 0 }, -/* icon */ "icons/iconh_green", -/* pickup */ "5 Health", - 5, - IT_HEALTH, - 0, -/* precache */ "", -/* sounds */ "" - }, - -/*QUAKED item_health (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "item_health", - "sound/items/n_health.wav", - { "models/powerups/health/medium_cross.md3", - "models/powerups/health/medium_sphere.md3", - 0, 0 }, -/* icon */ "icons/iconh_yellow", -/* pickup */ "25 Health", - 25, - IT_HEALTH, - 0, -/* precache */ "", -/* sounds */ "" - }, - -/*QUAKED item_health_large (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "item_health_large", - "sound/items/l_health.wav", - { "models/powerups/health/large_cross.md3", - "models/powerups/health/large_sphere.md3", - 0, 0 }, -/* icon */ "icons/iconh_red", -/* pickup */ "50 Health", - 50, - IT_HEALTH, - 0, -/* precache */ "", -/* sounds */ "" - }, - -/*QUAKED item_health_mega (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "item_health_mega", - "sound/items/m_health.wav", - { "models/powerups/health/mega_cross.md3", - "models/powerups/health/mega_sphere.md3", - 0, 0 }, -/* icon */ "icons/iconh_mega", -/* pickup */ "Mega Health", - 100, - IT_HEALTH, - 0, -/* precache */ "", -/* sounds */ "" - }, - - - // - // WEAPONS - // - -/*QUAKED weapon_gauntlet (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "weapon_gauntlet", - "sound/misc/w_pkup.wav", - { "models/weapons2/gauntlet/gauntlet.md3", - 0, 0, 0}, -/* icon */ "icons/iconw_gauntlet", -/* pickup */ "Gauntlet", - 0, - IT_WEAPON, - WP_GAUNTLET, -/* precache */ "", -/* sounds */ "" - }, - -/*QUAKED weapon_shotgun (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "weapon_shotgun", - "sound/misc/w_pkup.wav", - { "models/weapons2/shotgun/shotgun.md3", - 0, 0, 0}, -/* icon */ "icons/iconw_shotgun", -/* pickup */ "Shotgun", - 10, - IT_WEAPON, - WP_SHOTGUN, -/* precache */ "", -/* sounds */ "" - }, - -/*QUAKED weapon_machinegun (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "weapon_machinegun", - "sound/misc/w_pkup.wav", - { "models/weapons2/machinegun/machinegun.md3", - 0, 0, 0}, -/* icon */ "icons/iconw_machinegun", -/* pickup */ "Machinegun", - 40, - IT_WEAPON, - WP_MACHINEGUN, -/* precache */ "", -/* sounds */ "" - }, - -/*QUAKED weapon_grenadelauncher (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "weapon_grenadelauncher", - "sound/misc/w_pkup.wav", - { "models/weapons2/grenadel/grenadel.md3", - 0, 0, 0}, -/* icon */ "icons/iconw_grenade", -/* pickup */ "Grenade Launcher", - 10, - IT_WEAPON, - WP_GRENADE_LAUNCHER, -/* precache */ "", -/* sounds */ "sound/weapons/grenade/hgrenb1a.wav sound/weapons/grenade/hgrenb2a.wav" - }, - -/*QUAKED weapon_rocketlauncher (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "weapon_rocketlauncher", - "sound/misc/w_pkup.wav", - { "models/weapons2/rocketl/rocketl.md3", - 0, 0, 0}, -/* icon */ "icons/iconw_rocket", -/* pickup */ "Rocket Launcher", - 10, - IT_WEAPON, - WP_ROCKET_LAUNCHER, -/* precache */ "", -/* sounds */ "" - }, - -/*QUAKED weapon_lightning (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "weapon_lightning", - "sound/misc/w_pkup.wav", - { "models/weapons2/lightning/lightning.md3", - 0, 0, 0}, -/* icon */ "icons/iconw_lightning", -/* pickup */ "Lightning Gun", - 100, - IT_WEAPON, - WP_LIGHTNING, -/* precache */ "", -/* sounds */ "" - }, - -/*QUAKED weapon_railgun (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "weapon_railgun", - "sound/misc/w_pkup.wav", - { "models/weapons2/railgun/railgun.md3", - 0, 0, 0}, -/* icon */ "icons/iconw_railgun", -/* pickup */ "Railgun", - 10, - IT_WEAPON, - WP_RAILGUN, -/* precache */ "", -/* sounds */ "" - }, - -/*QUAKED weapon_plasmagun (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "weapon_plasmagun", - "sound/misc/w_pkup.wav", - { "models/weapons2/plasma/plasma.md3", - 0, 0, 0}, -/* icon */ "icons/iconw_plasma", -/* pickup */ "Plasma Gun", - 50, - IT_WEAPON, - WP_PLASMAGUN, -/* precache */ "", -/* sounds */ "" - }, - -/*QUAKED weapon_bfg (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "weapon_bfg", - "sound/misc/w_pkup.wav", - { "models/weapons2/bfg/bfg.md3", - 0, 0, 0}, -/* icon */ "icons/iconw_bfg", -/* pickup */ "BFG10K", - 20, - IT_WEAPON, - WP_BFG, -/* precache */ "", -/* sounds */ "" - }, - -/*QUAKED weapon_grapplinghook (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "weapon_grapplinghook", - "sound/misc/w_pkup.wav", - { "models/weapons2/grapple/grapple.md3", - 0, 0, 0}, -/* icon */ "icons/iconw_grapple", -/* pickup */ "Grappling Hook", - 0, - IT_WEAPON, - WP_GRAPPLING_HOOK, -/* precache */ "", -/* sounds */ "" - }, - - // - // AMMO ITEMS - // - -/*QUAKED ammo_shells (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "ammo_shells", - "sound/misc/am_pkup.wav", - { "models/powerups/ammo/shotgunam.md3", - 0, 0, 0}, -/* icon */ "icons/icona_shotgun", -/* pickup */ "Shells", - 10, - IT_AMMO, - WP_SHOTGUN, -/* precache */ "", -/* sounds */ "" - }, - -/*QUAKED ammo_bullets (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "ammo_bullets", - "sound/misc/am_pkup.wav", - { "models/powerups/ammo/machinegunam.md3", - 0, 0, 0}, -/* icon */ "icons/icona_machinegun", -/* pickup */ "Bullets", - 50, - IT_AMMO, - WP_MACHINEGUN, -/* precache */ "", -/* sounds */ "" - }, - -/*QUAKED ammo_grenades (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "ammo_grenades", - "sound/misc/am_pkup.wav", - { "models/powerups/ammo/grenadeam.md3", - 0, 0, 0}, -/* icon */ "icons/icona_grenade", -/* pickup */ "Grenades", - 5, - IT_AMMO, - WP_GRENADE_LAUNCHER, -/* precache */ "", -/* sounds */ "" - }, - -/*QUAKED ammo_cells (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "ammo_cells", - "sound/misc/am_pkup.wav", - { "models/powerups/ammo/plasmaam.md3", - 0, 0, 0}, -/* icon */ "icons/icona_plasma", -/* pickup */ "Cells", - 30, - IT_AMMO, - WP_PLASMAGUN, -/* precache */ "", -/* sounds */ "" - }, - -/*QUAKED ammo_lightning (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "ammo_lightning", - "sound/misc/am_pkup.wav", - { "models/powerups/ammo/lightningam.md3", - 0, 0, 0}, -/* icon */ "icons/icona_lightning", -/* pickup */ "Lightning", - 60, - IT_AMMO, - WP_LIGHTNING, -/* precache */ "", -/* sounds */ "" - }, - -/*QUAKED ammo_rockets (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "ammo_rockets", - "sound/misc/am_pkup.wav", - { "models/powerups/ammo/rocketam.md3", - 0, 0, 0}, -/* icon */ "icons/icona_rocket", -/* pickup */ "Rockets", - 5, - IT_AMMO, - WP_ROCKET_LAUNCHER, -/* precache */ "", -/* sounds */ "" - }, - -/*QUAKED ammo_slugs (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "ammo_slugs", - "sound/misc/am_pkup.wav", - { "models/powerups/ammo/railgunam.md3", - 0, 0, 0}, -/* icon */ "icons/icona_railgun", -/* pickup */ "Slugs", - 10, - IT_AMMO, - WP_RAILGUN, -/* precache */ "", -/* sounds */ "" - }, - -/*QUAKED ammo_bfg (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "ammo_bfg", - "sound/misc/am_pkup.wav", - { "models/powerups/ammo/bfgam.md3", - 0, 0, 0}, -/* icon */ "icons/icona_bfg", -/* pickup */ "Bfg Ammo", - 15, - IT_AMMO, - WP_BFG, -/* precache */ "", -/* sounds */ "" - }, - - // - // HOLDABLE ITEMS - // -/*QUAKED holdable_teleporter (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "holdable_teleporter", - "sound/items/holdable.wav", - { "models/powerups/holdable/teleporter.md3", - 0, 0, 0}, -/* icon */ "icons/teleporter", -/* pickup */ "Personal Teleporter", - 60, - IT_HOLDABLE, - HI_TELEPORTER, -/* precache */ "", -/* sounds */ "" - }, -/*QUAKED holdable_medkit (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "holdable_medkit", - "sound/items/holdable.wav", - { - "models/powerups/holdable/medkit.md3", - "models/powerups/holdable/medkit_sphere.md3", - 0, 0}, -/* icon */ "icons/medkit", -/* pickup */ "Medkit", - 60, - IT_HOLDABLE, - HI_MEDKIT, -/* precache */ "", -/* sounds */ "sound/items/use_medkit.wav" - }, - - // - // POWERUP ITEMS - // -/*QUAKED item_quad (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "item_quad", - "sound/items/quaddamage.wav", - { "models/powerups/instant/quad.md3", - "models/powerups/instant/quad_ring.md3", - 0, 0 }, -/* icon */ "icons/quad", -/* pickup */ "Quad Damage", - 30, - IT_POWERUP, - PW_QUAD, -/* precache */ "", -/* sounds */ "sound/items/damage2.wav sound/items/damage3.wav" - }, - -/*QUAKED item_enviro (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "item_enviro", - "sound/items/protect.wav", - { "models/powerups/instant/enviro.md3", - "models/powerups/instant/enviro_ring.md3", - 0, 0 }, -/* icon */ "icons/envirosuit", -/* pickup */ "Battle Suit", - 30, - IT_POWERUP, - PW_BATTLESUIT, -/* precache */ "", -/* sounds */ "sound/items/airout.wav sound/items/protect3.wav" - }, - -/*QUAKED item_haste (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "item_haste", - "sound/items/haste.wav", - { "models/powerups/instant/haste.md3", - "models/powerups/instant/haste_ring.md3", - 0, 0 }, -/* icon */ "icons/haste", -/* pickup */ "Speed", - 30, - IT_POWERUP, - PW_HASTE, -/* precache */ "", -/* sounds */ "" - }, - -/*QUAKED item_invis (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "item_invis", - "sound/items/invisibility.wav", - { "models/powerups/instant/invis.md3", - "models/powerups/instant/invis_ring.md3", - 0, 0 }, -/* icon */ "icons/invis", -/* pickup */ "Invisibility", - 30, - IT_POWERUP, - PW_INVIS, -/* precache */ "", -/* sounds */ "" - }, - -/*QUAKED item_regen (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "item_regen", - "sound/items/regeneration.wav", - { "models/powerups/instant/regen.md3", - "models/powerups/instant/regen_ring.md3", - 0, 0 }, -/* icon */ "icons/regen", -/* pickup */ "Regeneration", - 30, - IT_POWERUP, - PW_REGEN, -/* precache */ "", -/* sounds */ "sound/items/regen.wav" - }, - -/*QUAKED item_flight (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "item_flight", - "sound/items/flight.wav", - { "models/powerups/instant/flight.md3", - "models/powerups/instant/flight_ring.md3", - 0, 0 }, -/* icon */ "icons/flight", -/* pickup */ "Flight", - 60, - IT_POWERUP, - PW_FLIGHT, -/* precache */ "", -/* sounds */ "sound/items/flight.wav" - }, - -/*QUAKED team_CTF_redflag (1 0 0) (-16 -16 -16) (16 16 16) -Only in CTF games -*/ - { - "team_CTF_redflag", - NULL, - { "models/flags/r_flag.md3", - 0, 0, 0 }, -/* icon */ "icons/iconf_red1", -/* pickup */ "Red Flag", - 0, - IT_TEAM, - PW_REDFLAG, -/* precache */ "", -/* sounds */ "" - }, - -/*QUAKED team_CTF_blueflag (0 0 1) (-16 -16 -16) (16 16 16) -Only in CTF games -*/ - { - "team_CTF_blueflag", - NULL, - { "models/flags/b_flag.md3", - 0, 0, 0 }, -/* icon */ "icons/iconf_blu1", -/* pickup */ "Blue Flag", - 0, - IT_TEAM, - PW_BLUEFLAG, -/* precache */ "", -/* sounds */ "" - }, - -#ifdef MISSIONPACK -/*QUAKED holdable_kamikaze (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "holdable_kamikaze", - "sound/items/holdable.wav", - { "models/powerups/kamikazi.md3", - 0, 0, 0}, -/* icon */ "icons/kamikaze", -/* pickup */ "Kamikaze", - 60, - IT_HOLDABLE, - HI_KAMIKAZE, -/* precache */ "", -/* sounds */ "sound/items/kamikazerespawn.wav" - }, - -/*QUAKED holdable_portal (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "holdable_portal", - "sound/items/holdable.wav", - { "models/powerups/holdable/porter.md3", - 0, 0, 0}, -/* icon */ "icons/portal", -/* pickup */ "Portal", - 60, - IT_HOLDABLE, - HI_PORTAL, -/* precache */ "", -/* sounds */ "" - }, - -/*QUAKED holdable_invulnerability (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "holdable_invulnerability", - "sound/items/holdable.wav", - { "models/powerups/holdable/invulnerability.md3", - 0, 0, 0}, -/* icon */ "icons/invulnerability", -/* pickup */ "Invulnerability", - 60, - IT_HOLDABLE, - HI_INVULNERABILITY, -/* precache */ "", -/* sounds */ "" - }, - -/*QUAKED ammo_nails (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "ammo_nails", - "sound/misc/am_pkup.wav", - { "models/powerups/ammo/nailgunam.md3", - 0, 0, 0}, -/* icon */ "icons/icona_nailgun", -/* pickup */ "Nails", - 20, - IT_AMMO, - WP_NAILGUN, -/* precache */ "", -/* sounds */ "" - }, - -/*QUAKED ammo_mines (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "ammo_mines", - "sound/misc/am_pkup.wav", - { "models/powerups/ammo/proxmineam.md3", - 0, 0, 0}, -/* icon */ "icons/icona_proxlauncher", -/* pickup */ "Proximity Mines", - 10, - IT_AMMO, - WP_PROX_LAUNCHER, -/* precache */ "", -/* sounds */ "" - }, - -/*QUAKED ammo_belt (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "ammo_belt", - "sound/misc/am_pkup.wav", - { "models/powerups/ammo/chaingunam.md3", - 0, 0, 0}, -/* icon */ "icons/icona_chaingun", -/* pickup */ "Chaingun Belt", - 100, - IT_AMMO, - WP_CHAINGUN, -/* precache */ "", -/* sounds */ "" - }, - - // - // PERSISTANT POWERUP ITEMS - // -/*QUAKED item_scout (.3 .3 1) (-16 -16 -16) (16 16 16) suspended redTeam blueTeam -*/ - { - "item_scout", - "sound/items/scout.wav", - { "models/powerups/scout.md3", - 0, 0, 0 }, -/* icon */ "icons/scout", -/* pickup */ "Scout", - 30, - IT_PERSISTANT_POWERUP, - PW_SCOUT, -/* precache */ "", -/* sounds */ "" - }, - -/*QUAKED item_guard (.3 .3 1) (-16 -16 -16) (16 16 16) suspended redTeam blueTeam -*/ - { - "item_guard", - "sound/items/guard.wav", - { "models/powerups/guard.md3", - 0, 0, 0 }, -/* icon */ "icons/guard", -/* pickup */ "Guard", - 30, - IT_PERSISTANT_POWERUP, - PW_GUARD, -/* precache */ "", -/* sounds */ "" - }, - -/*QUAKED item_doubler (.3 .3 1) (-16 -16 -16) (16 16 16) suspended redTeam blueTeam -*/ - { - "item_doubler", - "sound/items/doubler.wav", - { "models/powerups/doubler.md3", - 0, 0, 0 }, -/* icon */ "icons/doubler", -/* pickup */ "Doubler", - 30, - IT_PERSISTANT_POWERUP, - PW_DOUBLER, -/* precache */ "", -/* sounds */ "" - }, - -/*QUAKED item_doubler (.3 .3 1) (-16 -16 -16) (16 16 16) suspended redTeam blueTeam -*/ - { - "item_ammoregen", - "sound/items/ammoregen.wav", - { "models/powerups/ammo.md3", - 0, 0, 0 }, -/* icon */ "icons/ammo_regen", -/* pickup */ "Ammo Regen", - 30, - IT_PERSISTANT_POWERUP, - PW_AMMOREGEN, -/* precache */ "", -/* sounds */ "" - }, - - /*QUAKED team_CTF_neutralflag (0 0 1) (-16 -16 -16) (16 16 16) -Only in One Flag CTF games -*/ - { - "team_CTF_neutralflag", - NULL, - { "models/flags/n_flag.md3", - 0, 0, 0 }, -/* icon */ "icons/iconf_neutral1", -/* pickup */ "Neutral Flag", - 0, - IT_TEAM, - PW_NEUTRALFLAG, -/* precache */ "", -/* sounds */ "" - }, - - { - "item_redcube", - "sound/misc/am_pkup.wav", - { "models/powerups/orb/r_orb.md3", - 0, 0, 0 }, -/* icon */ "icons/iconh_rorb", -/* pickup */ "Red Cube", - 0, - IT_TEAM, - 0, -/* precache */ "", -/* sounds */ "" - }, - - { - "item_bluecube", - "sound/misc/am_pkup.wav", - { "models/powerups/orb/b_orb.md3", - 0, 0, 0 }, -/* icon */ "icons/iconh_borb", -/* pickup */ "Blue Cube", - 0, - IT_TEAM, - 0, -/* precache */ "", -/* sounds */ "" - }, -/*QUAKED weapon_nailgun (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "weapon_nailgun", - "sound/misc/w_pkup.wav", - { "models/weapons/nailgun/nailgun.md3", - 0, 0, 0}, -/* icon */ "icons/iconw_nailgun", -/* pickup */ "Nailgun", - 10, - IT_WEAPON, - WP_NAILGUN, -/* precache */ "", -/* sounds */ "" - }, - -/*QUAKED weapon_prox_launcher (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "weapon_prox_launcher", - "sound/misc/w_pkup.wav", - { "models/weapons/proxmine/proxmine.md3", - 0, 0, 0}, -/* icon */ "icons/iconw_proxlauncher", -/* pickup */ "Prox Launcher", - 5, - IT_WEAPON, - WP_PROX_LAUNCHER, -/* precache */ "", -/* sounds */ "sound/weapons/proxmine/wstbtick.wav " - "sound/weapons/proxmine/wstbactv.wav " - "sound/weapons/proxmine/wstbimpl.wav " - "sound/weapons/proxmine/wstbimpm.wav " - "sound/weapons/proxmine/wstbimpd.wav " - "sound/weapons/proxmine/wstbactv.wav" - }, - -/*QUAKED weapon_chaingun (.3 .3 1) (-16 -16 -16) (16 16 16) suspended -*/ - { - "weapon_chaingun", - "sound/misc/w_pkup.wav", - { "models/weapons/vulcan/vulcan.md3", - 0, 0, 0}, -/* icon */ "icons/iconw_chaingun", -/* pickup */ "Chaingun", - 80, - IT_WEAPON, - WP_CHAINGUN, -/* precache */ "", -/* sounds */ "sound/weapons/vulcan/wvulwind.wav" - }, -#endif - - // end of list marker - {NULL} -}; - -int bg_numItems = sizeof(bg_itemlist) / sizeof(bg_itemlist[0]) - 1; - - -/* -============== -BG_FindItemForPowerup -============== -*/ -gitem_t *BG_FindItemForPowerup( powerup_t pw ) { - int i; - - for ( i = 0 ; i < bg_numItems ; i++ ) { - if ( (bg_itemlist[i].giType == IT_POWERUP || - bg_itemlist[i].giType == IT_TEAM || - bg_itemlist[i].giType == IT_PERSISTANT_POWERUP) && - bg_itemlist[i].giTag == pw ) { - return &bg_itemlist[i]; - } - } - - return NULL; -} - - -/* -============== -BG_FindItemForHoldable -============== -*/ -gitem_t *BG_FindItemForHoldable( holdable_t pw ) { - int i; - - for ( i = 0 ; i < bg_numItems ; i++ ) { - if ( bg_itemlist[i].giType == IT_HOLDABLE && bg_itemlist[i].giTag == pw ) { - return &bg_itemlist[i]; - } - } - - Com_Error( ERR_DROP, "HoldableItem not found" ); - - return NULL; -} - - -/* -=============== -BG_FindItemForWeapon - -=============== -*/ -gitem_t *BG_FindItemForWeapon( weapon_t weapon ) { - gitem_t *it; - - for ( it = bg_itemlist + 1 ; it->classname ; it++) { - if ( it->giType == IT_WEAPON && it->giTag == weapon ) { - return it; - } - } - - Com_Error( ERR_DROP, "Couldn't find item for weapon %i", weapon); - return NULL; -} - -/* -=============== -BG_FindItem - -=============== -*/ -gitem_t *BG_FindItem( const char *pickupName ) { - gitem_t *it; - - for ( it = bg_itemlist + 1 ; it->classname ; it++ ) { - if ( !Q_stricmp( it->pickup_name, pickupName ) ) - return it; - } - - return NULL; -} - -/* -============ -BG_PlayerTouchesItem - -Items can be picked up without actually touching their physical bounds to make -grabbing them easier -============ -*/ -qboolean BG_PlayerTouchesItem( playerState_t *ps, entityState_t *item, int atTime ) { - vec3_t origin; - - BG_EvaluateTrajectory( &item->pos, atTime, origin ); - - // we are ignoring ducked differences here - if ( ps->origin[0] - origin[0] > 44 - || ps->origin[0] - origin[0] < -50 - || ps->origin[1] - origin[1] > 36 - || ps->origin[1] - origin[1] < -36 - || ps->origin[2] - origin[2] > 36 - || ps->origin[2] - origin[2] < -36 ) { - return qfalse; - } - - return qtrue; -} - - - -/* -================ -BG_CanItemBeGrabbed - -Returns false if the item should not be picked up. -This needs to be the same for client side prediction and server use. -================ -*/ -qboolean BG_CanItemBeGrabbed( int gametype, const entityState_t *ent, const playerState_t *ps ) { - gitem_t *item; -#ifdef MISSIONPACK - int upperBound; -#endif - - if ( ent->modelindex < 1 || ent->modelindex >= bg_numItems ) { - Com_Error( ERR_DROP, "BG_CanItemBeGrabbed: index out of range" ); - } - - item = &bg_itemlist[ent->modelindex]; - - switch( item->giType ) { - case IT_WEAPON: - return qtrue; // weapons are always picked up - - case IT_AMMO: - if ( ps->ammo[ item->giTag ] >= 200 ) { - return qfalse; // can't hold any more - } - return qtrue; - - case IT_ARMOR: -#ifdef MISSIONPACK - if( bg_itemlist[ps->stats[STAT_PERSISTANT_POWERUP]].giTag == PW_SCOUT ) { - return qfalse; - } - - // we also clamp armor to the maxhealth for handicapping - if( bg_itemlist[ps->stats[STAT_PERSISTANT_POWERUP]].giTag == PW_GUARD ) { - upperBound = ps->stats[STAT_MAX_HEALTH]; - } - else { - upperBound = ps->stats[STAT_MAX_HEALTH] * 2; - } - - if ( ps->stats[STAT_ARMOR] >= upperBound ) { - return qfalse; - } -#else - if ( ps->stats[STAT_ARMOR] >= ps->stats[STAT_MAX_HEALTH] * 2 ) { - return qfalse; - } -#endif - return qtrue; - - case IT_HEALTH: - // small and mega healths will go over the max, otherwise - // don't pick up if already at max -#ifdef MISSIONPACK - if( bg_itemlist[ps->stats[STAT_PERSISTANT_POWERUP]].giTag == PW_GUARD ) { - upperBound = ps->stats[STAT_MAX_HEALTH]; - } - else -#endif - if ( item->quantity == 5 || item->quantity == 100 ) { - if ( ps->stats[STAT_HEALTH] >= ps->stats[STAT_MAX_HEALTH] * 2 ) { - return qfalse; - } - return qtrue; - } - - if ( ps->stats[STAT_HEALTH] >= ps->stats[STAT_MAX_HEALTH] ) { - return qfalse; - } - return qtrue; - - case IT_POWERUP: - return qtrue; // powerups are always picked up - -#ifdef MISSIONPACK - case IT_PERSISTANT_POWERUP: - // can only hold one item at a time - if ( ps->stats[STAT_PERSISTANT_POWERUP] ) { - return qfalse; - } - - // check team only - if( ( ent->generic1 & 2 ) && ( ps->persistant[PERS_TEAM] != TEAM_RED ) ) { - return qfalse; - } - if( ( ent->generic1 & 4 ) && ( ps->persistant[PERS_TEAM] != TEAM_BLUE ) ) { - return qfalse; - } - - return qtrue; -#endif - - case IT_TEAM: // team items, such as flags -#ifdef MISSIONPACK - if( gametype == GT_1FCTF ) { - // neutral flag can always be picked up - if( item->giTag == PW_NEUTRALFLAG ) { - return qtrue; - } - if (ps->persistant[PERS_TEAM] == TEAM_RED) { - if (item->giTag == PW_BLUEFLAG && ps->powerups[PW_NEUTRALFLAG] ) { - return qtrue; - } - } else if (ps->persistant[PERS_TEAM] == TEAM_BLUE) { - if (item->giTag == PW_REDFLAG && ps->powerups[PW_NEUTRALFLAG] ) { - return qtrue; - } - } - } -#endif - if( gametype == GT_CTF ) { - // ent->modelindex2 is non-zero on items if they are dropped - // we need to know this because we can pick up our dropped flag (and return it) - // but we can't pick up our flag at base - if (ps->persistant[PERS_TEAM] == TEAM_RED) { - if (item->giTag == PW_BLUEFLAG || - (item->giTag == PW_REDFLAG && ent->modelindex2) || - (item->giTag == PW_REDFLAG && ps->powerups[PW_BLUEFLAG]) ) - return qtrue; - } else if (ps->persistant[PERS_TEAM] == TEAM_BLUE) { - if (item->giTag == PW_REDFLAG || - (item->giTag == PW_BLUEFLAG && ent->modelindex2) || - (item->giTag == PW_BLUEFLAG && ps->powerups[PW_REDFLAG]) ) - return qtrue; - } - } - -#ifdef MISSIONPACK - if( gametype == GT_HARVESTER ) { - return qtrue; - } -#endif - return qfalse; - - case IT_HOLDABLE: - // can only hold one item at a time - if ( ps->stats[STAT_HOLDABLE_ITEM] ) { - return qfalse; - } - return qtrue; - - case IT_BAD: - Com_Error( ERR_DROP, "BG_CanItemBeGrabbed: IT_BAD" ); - default: -#ifndef Q3_VM -#ifndef NDEBUG // bk0001204 - Com_Printf("BG_CanItemBeGrabbed: unknown enum %d\n", item->giType ); -#endif -#endif - break; - } - - return qfalse; -} - -//====================================================================== - -/* -================ -BG_EvaluateTrajectory - -================ -*/ -void BG_EvaluateTrajectory( const trajectory_t *tr, int atTime, vec3_t result ) { - float deltaTime; - float phase; - - switch( tr->trType ) { - case TR_STATIONARY: - case TR_INTERPOLATE: - VectorCopy( tr->trBase, result ); - break; - case TR_LINEAR: - deltaTime = ( atTime - tr->trTime ) * 0.001; // milliseconds to seconds - VectorMA( tr->trBase, deltaTime, tr->trDelta, result ); - break; - case TR_SINE: - deltaTime = ( atTime - tr->trTime ) / (float) tr->trDuration; - phase = sin( deltaTime * M_PI * 2 ); - VectorMA( tr->trBase, phase, tr->trDelta, result ); - break; - case TR_LINEAR_STOP: - if ( atTime > tr->trTime + tr->trDuration ) { - atTime = tr->trTime + tr->trDuration; - } - deltaTime = ( atTime - tr->trTime ) * 0.001; // milliseconds to seconds - if ( deltaTime < 0 ) { - deltaTime = 0; - } - VectorMA( tr->trBase, deltaTime, tr->trDelta, result ); - break; - case TR_GRAVITY: - deltaTime = ( atTime - tr->trTime ) * 0.001; // milliseconds to seconds - VectorMA( tr->trBase, deltaTime, tr->trDelta, result ); - result[2] -= 0.5 * DEFAULT_GRAVITY * deltaTime * deltaTime; // FIXME: local gravity... - break; - default: - Com_Error( ERR_DROP, "BG_EvaluateTrajectory: unknown trType: %i", tr->trTime ); - break; - } -} - -/* -================ -BG_EvaluateTrajectoryDelta - -For determining velocity at a given time -================ -*/ -void BG_EvaluateTrajectoryDelta( const trajectory_t *tr, int atTime, vec3_t result ) { - float deltaTime; - float phase; - - switch( tr->trType ) { - case TR_STATIONARY: - case TR_INTERPOLATE: - VectorClear( result ); - break; - case TR_LINEAR: - VectorCopy( tr->trDelta, result ); - break; - case TR_SINE: - deltaTime = ( atTime - tr->trTime ) / (float) tr->trDuration; - phase = cos( deltaTime * M_PI * 2 ); // derivative of sin = cos - phase *= 0.5; - VectorScale( tr->trDelta, phase, result ); - break; - case TR_LINEAR_STOP: - if ( atTime > tr->trTime + tr->trDuration ) { - VectorClear( result ); - return; - } - VectorCopy( tr->trDelta, result ); - break; - case TR_GRAVITY: - deltaTime = ( atTime - tr->trTime ) * 0.001; // milliseconds to seconds - VectorCopy( tr->trDelta, result ); - result[2] -= DEFAULT_GRAVITY * deltaTime; // FIXME: local gravity... - break; - default: - Com_Error( ERR_DROP, "BG_EvaluateTrajectoryDelta: unknown trType: %i", tr->trTime ); - break; - } -} - -char *eventnames[] = { - "EV_NONE", - - "EV_FOOTSTEP", - "EV_FOOTSTEP_METAL", - "EV_FOOTSPLASH", - "EV_FOOTWADE", - "EV_SWIM", - - "EV_STEP_4", - "EV_STEP_8", - "EV_STEP_12", - "EV_STEP_16", - - "EV_FALL_SHORT", - "EV_FALL_MEDIUM", - "EV_FALL_FAR", - - "EV_JUMP_PAD", // boing sound at origin", jump sound on player - - "EV_JUMP", - "EV_WATER_TOUCH", // foot touches - "EV_WATER_LEAVE", // foot leaves - "EV_WATER_UNDER", // head touches - "EV_WATER_CLEAR", // head leaves - - "EV_ITEM_PICKUP", // normal item pickups are predictable - "EV_GLOBAL_ITEM_PICKUP", // powerup / team sounds are broadcast to everyone - - "EV_NOAMMO", - "EV_CHANGE_WEAPON", - "EV_FIRE_WEAPON", - - "EV_USE_ITEM0", - "EV_USE_ITEM1", - "EV_USE_ITEM2", - "EV_USE_ITEM3", - "EV_USE_ITEM4", - "EV_USE_ITEM5", - "EV_USE_ITEM6", - "EV_USE_ITEM7", - "EV_USE_ITEM8", - "EV_USE_ITEM9", - "EV_USE_ITEM10", - "EV_USE_ITEM11", - "EV_USE_ITEM12", - "EV_USE_ITEM13", - "EV_USE_ITEM14", - "EV_USE_ITEM15", - - "EV_ITEM_RESPAWN", - "EV_ITEM_POP", - "EV_PLAYER_TELEPORT_IN", - "EV_PLAYER_TELEPORT_OUT", - - "EV_GRENADE_BOUNCE", // eventParm will be the soundindex - - "EV_GENERAL_SOUND", - "EV_GLOBAL_SOUND", // no attenuation - "EV_GLOBAL_TEAM_SOUND", - - "EV_BULLET_HIT_FLESH", - "EV_BULLET_HIT_WALL", - - "EV_MISSILE_HIT", - "EV_MISSILE_MISS", - "EV_MISSILE_MISS_METAL", - "EV_RAILTRAIL", - "EV_SHOTGUN", - "EV_BULLET", // otherEntity is the shooter - - "EV_PAIN", - "EV_DEATH1", - "EV_DEATH2", - "EV_DEATH3", - "EV_OBITUARY", - - "EV_POWERUP_QUAD", - "EV_POWERUP_BATTLESUIT", - "EV_POWERUP_REGEN", - - "EV_GIB_PLAYER", // gib a previously living player - "EV_SCOREPLUM", // score plum - -//#ifdef MISSIONPACK - "EV_PROXIMITY_MINE_STICK", - "EV_PROXIMITY_MINE_TRIGGER", - "EV_KAMIKAZE", // kamikaze explodes - "EV_OBELISKEXPLODE", // obelisk explodes - "EV_INVUL_IMPACT", // invulnerability sphere impact - "EV_JUICED", // invulnerability juiced effect - "EV_LIGHTNINGBOLT", // lightning bolt bounced of invulnerability sphere -//#endif - - "EV_DEBUG_LINE", - "EV_STOPLOOPINGSOUND", - "EV_TAUNT" - -}; - -/* -=============== -BG_AddPredictableEventToPlayerstate - -Handles the sequence numbers -=============== -*/ - -void trap_Cvar_VariableStringBuffer( const char *var_name, char *buffer, int bufsize ); - -void BG_AddPredictableEventToPlayerstate( int newEvent, int eventParm, playerState_t *ps ) { - -#ifdef _DEBUG - { - char buf[256]; - trap_Cvar_VariableStringBuffer("showevents", buf, sizeof(buf)); - if ( atof(buf) != 0 ) { -#ifdef QAGAME - Com_Printf(" game event svt %5d -> %5d: num = %20s parm %d\n", ps->pmove_framecount/*ps->commandTime*/, ps->eventSequence, eventnames[newEvent], eventParm); -#else - Com_Printf("Cgame event svt %5d -> %5d: num = %20s parm %d\n", ps->pmove_framecount/*ps->commandTime*/, ps->eventSequence, eventnames[newEvent], eventParm); -#endif - } - } -#endif - ps->events[ps->eventSequence & (MAX_PS_EVENTS-1)] = newEvent; - ps->eventParms[ps->eventSequence & (MAX_PS_EVENTS-1)] = eventParm; - ps->eventSequence++; -} - -/* -======================== -BG_TouchJumpPad -======================== -*/ -void BG_TouchJumpPad( playerState_t *ps, entityState_t *jumppad ) { - vec3_t angles; - float p; - int effectNum; - - // spectators don't use jump pads - if ( ps->pm_type != PM_NORMAL ) { - return; - } - - // flying characters don't hit bounce pads - if ( ps->powerups[PW_FLIGHT] ) { - return; - } - - // if we didn't hit this same jumppad the previous frame - // then don't play the event sound again if we are in a fat trigger - if ( ps->jumppad_ent != jumppad->number ) { - - vectoangles( jumppad->origin2, angles); - p = fabs( AngleNormalize180( angles[PITCH] ) ); - if( p < 45 ) { - effectNum = 0; - } else { - effectNum = 1; - } - BG_AddPredictableEventToPlayerstate( EV_JUMP_PAD, effectNum, ps ); - } - // remember hitting this jumppad this frame - ps->jumppad_ent = jumppad->number; - ps->jumppad_frame = ps->pmove_framecount; - // give the player the velocity from the jumppad - VectorCopy( jumppad->origin2, ps->velocity ); -} - -/* -======================== -BG_PlayerStateToEntityState - -This is done after each set of usercmd_t on the server, -and after local prediction on the client -======================== -*/ -void BG_PlayerStateToEntityState( playerState_t *ps, entityState_t *s, qboolean snap ) { - int i; - - if ( ps->pm_type == PM_INTERMISSION || ps->pm_type == PM_SPECTATOR ) { - s->eType = ET_INVISIBLE; - } else if ( ps->stats[STAT_HEALTH] <= GIB_HEALTH ) { - s->eType = ET_INVISIBLE; - } else { - s->eType = ET_PLAYER; - } - - s->number = ps->clientNum; - - s->pos.trType = TR_INTERPOLATE; - VectorCopy( ps->origin, s->pos.trBase ); - if ( snap ) { - SnapVector( s->pos.trBase ); - } - // set the trDelta for flag direction - VectorCopy( ps->velocity, s->pos.trDelta ); - - s->apos.trType = TR_INTERPOLATE; - VectorCopy( ps->viewangles, s->apos.trBase ); - if ( snap ) { - SnapVector( s->apos.trBase ); - } - - s->angles2[YAW] = ps->movementDir; - s->legsAnim = ps->legsAnim; - s->torsoAnim = ps->torsoAnim; - s->clientNum = ps->clientNum; // ET_PLAYER looks here instead of at number - // so corpses can also reference the proper config - s->eFlags = ps->eFlags; - if ( ps->stats[STAT_HEALTH] <= 0 ) { - s->eFlags |= EF_DEAD; - } else { - s->eFlags &= ~EF_DEAD; - } - - if ( ps->externalEvent ) { - s->event = ps->externalEvent; - s->eventParm = ps->externalEventParm; - } else if ( ps->entityEventSequence < ps->eventSequence ) { - int seq; - - if ( ps->entityEventSequence < ps->eventSequence - MAX_PS_EVENTS) { - ps->entityEventSequence = ps->eventSequence - MAX_PS_EVENTS; - } - seq = ps->entityEventSequence & (MAX_PS_EVENTS-1); - s->event = ps->events[ seq ] | ( ( ps->entityEventSequence & 3 ) << 8 ); - s->eventParm = ps->eventParms[ seq ]; - ps->entityEventSequence++; - } - - s->weapon = ps->weapon; - s->groundEntityNum = ps->groundEntityNum; - - s->powerups = 0; - for ( i = 0 ; i < MAX_POWERUPS ; i++ ) { - if ( ps->powerups[ i ] ) { - s->powerups |= 1 << i; - } - } - - s->loopSound = ps->loopSound; - s->generic1 = ps->generic1; -} - -/* -======================== -BG_PlayerStateToEntityStateExtraPolate - -This is done after each set of usercmd_t on the server, -and after local prediction on the client -======================== -*/ -void BG_PlayerStateToEntityStateExtraPolate( playerState_t *ps, entityState_t *s, int time, qboolean snap ) { - int i; - - if ( ps->pm_type == PM_INTERMISSION || ps->pm_type == PM_SPECTATOR ) { - s->eType = ET_INVISIBLE; - } else if ( ps->stats[STAT_HEALTH] <= GIB_HEALTH ) { - s->eType = ET_INVISIBLE; - } else { - s->eType = ET_PLAYER; - } - - s->number = ps->clientNum; - - s->pos.trType = TR_LINEAR_STOP; - VectorCopy( ps->origin, s->pos.trBase ); - if ( snap ) { - SnapVector( s->pos.trBase ); - } - // set the trDelta for flag direction and linear prediction - VectorCopy( ps->velocity, s->pos.trDelta ); - // set the time for linear prediction - s->pos.trTime = time; - // set maximum extra polation time - s->pos.trDuration = 50; // 1000 / sv_fps (default = 20) - - s->apos.trType = TR_INTERPOLATE; - VectorCopy( ps->viewangles, s->apos.trBase ); - if ( snap ) { - SnapVector( s->apos.trBase ); - } - - s->angles2[YAW] = ps->movementDir; - s->legsAnim = ps->legsAnim; - s->torsoAnim = ps->torsoAnim; - s->clientNum = ps->clientNum; // ET_PLAYER looks here instead of at number - // so corpses can also reference the proper config - s->eFlags = ps->eFlags; - if ( ps->stats[STAT_HEALTH] <= 0 ) { - s->eFlags |= EF_DEAD; - } else { - s->eFlags &= ~EF_DEAD; - } - - if ( ps->externalEvent ) { - s->event = ps->externalEvent; - s->eventParm = ps->externalEventParm; - } else if ( ps->entityEventSequence < ps->eventSequence ) { - int seq; - - if ( ps->entityEventSequence < ps->eventSequence - MAX_PS_EVENTS) { - ps->entityEventSequence = ps->eventSequence - MAX_PS_EVENTS; - } - seq = ps->entityEventSequence & (MAX_PS_EVENTS-1); - s->event = ps->events[ seq ] | ( ( ps->entityEventSequence & 3 ) << 8 ); - s->eventParm = ps->eventParms[ seq ]; - ps->entityEventSequence++; - } - - s->weapon = ps->weapon; - s->groundEntityNum = ps->groundEntityNum; - - s->powerups = 0; - for ( i = 0 ; i < MAX_POWERUPS ; i++ ) { - if ( ps->powerups[ i ] ) { - s->powerups |= 1 << i; - } - } - - s->loopSound = ps->loopSound; - s->generic1 = ps->generic1; -} +/* +=========================================================================== +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 Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// +// bg_misc.c -- both games misc functions, all completely stateless + +#include "q_shared.h" +#include "bg_public.h" + +/*QUAKED item_***** ( 0 0 0 ) (-16 -16 -16) (16 16 16) suspended +DO NOT USE THIS CLASS, IT JUST HOLDS GENERAL INFORMATION. +The suspended flag will allow items to hang in the air, otherwise they are dropped to the next surface. + +If an item is the target of another entity, it will not spawn in until fired. + +An item fires all of its targets when it is picked up. If the toucher can't carry it, the targets won't be fired. + +"notfree" if set to 1, don't spawn in free for all games +"notteam" if set to 1, don't spawn in team games +"notsingle" if set to 1, don't spawn in single player games +"wait" override the default wait before respawning. -1 = never respawn automatically, which can be used with targeted spawning. +"random" random number of plus or minus seconds varied from the respawn time +"count" override quantity or duration on most items. +*/ + +gitem_t bg_itemlist[] = +{ + { + NULL, + NULL, + { NULL, + NULL, + 0, 0} , +/* icon */ NULL, +/* pickup */ NULL, + 0, + 0, + 0, +/* precache */ "", +/* sounds */ "" + }, // leave index 0 alone + + // + // ARMOR + // + +/*QUAKED item_armor_shard (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "item_armor_shard", + "sound/misc/ar1_pkup.wav", + { "models/powerups/armor/shard.md3", + "models/powerups/armor/shard_sphere.md3", + 0, 0} , +/* icon */ "icons/iconr_shard", +/* pickup */ "Armor Shard", + 5, + IT_ARMOR, + 0, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED item_armor_combat (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "item_armor_combat", + "sound/misc/ar2_pkup.wav", + { "models/powerups/armor/armor_yel.md3", + 0, 0, 0}, +/* icon */ "icons/iconr_yellow", +/* pickup */ "Armor", + 50, + IT_ARMOR, + 0, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED item_armor_body (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "item_armor_body", + "sound/misc/ar2_pkup.wav", + { "models/powerups/armor/armor_red.md3", + 0, 0, 0}, +/* icon */ "icons/iconr_red", +/* pickup */ "Heavy Armor", + 100, + IT_ARMOR, + 0, +/* precache */ "", +/* sounds */ "" + }, + + // + // health + // +/*QUAKED item_health_small (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "item_health_small", + "sound/items/s_health.wav", + { "models/powerups/health/small_cross.md3", + "models/powerups/health/small_sphere.md3", + 0, 0 }, +/* icon */ "icons/iconh_green", +/* pickup */ "5 Health", + 5, + IT_HEALTH, + 0, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED item_health (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "item_health", + "sound/items/n_health.wav", + { "models/powerups/health/medium_cross.md3", + "models/powerups/health/medium_sphere.md3", + 0, 0 }, +/* icon */ "icons/iconh_yellow", +/* pickup */ "25 Health", + 25, + IT_HEALTH, + 0, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED item_health_large (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "item_health_large", + "sound/items/l_health.wav", + { "models/powerups/health/large_cross.md3", + "models/powerups/health/large_sphere.md3", + 0, 0 }, +/* icon */ "icons/iconh_red", +/* pickup */ "50 Health", + 50, + IT_HEALTH, + 0, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED item_health_mega (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "item_health_mega", + "sound/items/m_health.wav", + { "models/powerups/health/mega_cross.md3", + "models/powerups/health/mega_sphere.md3", + 0, 0 }, +/* icon */ "icons/iconh_mega", +/* pickup */ "Mega Health", + 100, + IT_HEALTH, + 0, +/* precache */ "", +/* sounds */ "" + }, + + + // + // WEAPONS + // + +/*QUAKED weapon_gauntlet (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "weapon_gauntlet", + "sound/misc/w_pkup.wav", + { "models/weapons2/gauntlet/gauntlet.md3", + 0, 0, 0}, +/* icon */ "icons/iconw_gauntlet", +/* pickup */ "Gauntlet", + 0, + IT_WEAPON, + WP_GAUNTLET, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED weapon_shotgun (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "weapon_shotgun", + "sound/misc/w_pkup.wav", + { "models/weapons2/shotgun/shotgun.md3", + 0, 0, 0}, +/* icon */ "icons/iconw_shotgun", +/* pickup */ "Shotgun", + 10, + IT_WEAPON, + WP_SHOTGUN, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED weapon_machinegun (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "weapon_machinegun", + "sound/misc/w_pkup.wav", + { "models/weapons2/machinegun/machinegun.md3", + 0, 0, 0}, +/* icon */ "icons/iconw_machinegun", +/* pickup */ "Machinegun", + 40, + IT_WEAPON, + WP_MACHINEGUN, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED weapon_grenadelauncher (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "weapon_grenadelauncher", + "sound/misc/w_pkup.wav", + { "models/weapons2/grenadel/grenadel.md3", + 0, 0, 0}, +/* icon */ "icons/iconw_grenade", +/* pickup */ "Grenade Launcher", + 10, + IT_WEAPON, + WP_GRENADE_LAUNCHER, +/* precache */ "", +/* sounds */ "sound/weapons/grenade/hgrenb1a.wav sound/weapons/grenade/hgrenb2a.wav" + }, + +/*QUAKED weapon_rocketlauncher (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "weapon_rocketlauncher", + "sound/misc/w_pkup.wav", + { "models/weapons2/rocketl/rocketl.md3", + 0, 0, 0}, +/* icon */ "icons/iconw_rocket", +/* pickup */ "Rocket Launcher", + 10, + IT_WEAPON, + WP_ROCKET_LAUNCHER, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED weapon_lightning (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "weapon_lightning", + "sound/misc/w_pkup.wav", + { "models/weapons2/lightning/lightning.md3", + 0, 0, 0}, +/* icon */ "icons/iconw_lightning", +/* pickup */ "Lightning Gun", + 100, + IT_WEAPON, + WP_LIGHTNING, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED weapon_railgun (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "weapon_railgun", + "sound/misc/w_pkup.wav", + { "models/weapons2/railgun/railgun.md3", + 0, 0, 0}, +/* icon */ "icons/iconw_railgun", +/* pickup */ "Railgun", + 10, + IT_WEAPON, + WP_RAILGUN, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED weapon_plasmagun (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "weapon_plasmagun", + "sound/misc/w_pkup.wav", + { "models/weapons2/plasma/plasma.md3", + 0, 0, 0}, +/* icon */ "icons/iconw_plasma", +/* pickup */ "Plasma Gun", + 50, + IT_WEAPON, + WP_PLASMAGUN, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED weapon_bfg (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "weapon_bfg", + "sound/misc/w_pkup.wav", + { "models/weapons2/bfg/bfg.md3", + 0, 0, 0}, +/* icon */ "icons/iconw_bfg", +/* pickup */ "BFG10K", + 20, + IT_WEAPON, + WP_BFG, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED weapon_grapplinghook (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "weapon_grapplinghook", + "sound/misc/w_pkup.wav", + { "models/weapons2/grapple/grapple.md3", + 0, 0, 0}, +/* icon */ "icons/iconw_grapple", +/* pickup */ "Grappling Hook", + 0, + IT_WEAPON, + WP_GRAPPLING_HOOK, +/* precache */ "", +/* sounds */ "" + }, + + // + // AMMO ITEMS + // + +/*QUAKED ammo_shells (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "ammo_shells", + "sound/misc/am_pkup.wav", + { "models/powerups/ammo/shotgunam.md3", + 0, 0, 0}, +/* icon */ "icons/icona_shotgun", +/* pickup */ "Shells", + 10, + IT_AMMO, + WP_SHOTGUN, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED ammo_bullets (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "ammo_bullets", + "sound/misc/am_pkup.wav", + { "models/powerups/ammo/machinegunam.md3", + 0, 0, 0}, +/* icon */ "icons/icona_machinegun", +/* pickup */ "Bullets", + 50, + IT_AMMO, + WP_MACHINEGUN, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED ammo_grenades (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "ammo_grenades", + "sound/misc/am_pkup.wav", + { "models/powerups/ammo/grenadeam.md3", + 0, 0, 0}, +/* icon */ "icons/icona_grenade", +/* pickup */ "Grenades", + 5, + IT_AMMO, + WP_GRENADE_LAUNCHER, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED ammo_cells (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "ammo_cells", + "sound/misc/am_pkup.wav", + { "models/powerups/ammo/plasmaam.md3", + 0, 0, 0}, +/* icon */ "icons/icona_plasma", +/* pickup */ "Cells", + 30, + IT_AMMO, + WP_PLASMAGUN, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED ammo_lightning (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "ammo_lightning", + "sound/misc/am_pkup.wav", + { "models/powerups/ammo/lightningam.md3", + 0, 0, 0}, +/* icon */ "icons/icona_lightning", +/* pickup */ "Lightning", + 60, + IT_AMMO, + WP_LIGHTNING, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED ammo_rockets (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "ammo_rockets", + "sound/misc/am_pkup.wav", + { "models/powerups/ammo/rocketam.md3", + 0, 0, 0}, +/* icon */ "icons/icona_rocket", +/* pickup */ "Rockets", + 5, + IT_AMMO, + WP_ROCKET_LAUNCHER, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED ammo_slugs (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "ammo_slugs", + "sound/misc/am_pkup.wav", + { "models/powerups/ammo/railgunam.md3", + 0, 0, 0}, +/* icon */ "icons/icona_railgun", +/* pickup */ "Slugs", + 10, + IT_AMMO, + WP_RAILGUN, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED ammo_bfg (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "ammo_bfg", + "sound/misc/am_pkup.wav", + { "models/powerups/ammo/bfgam.md3", + 0, 0, 0}, +/* icon */ "icons/icona_bfg", +/* pickup */ "Bfg Ammo", + 15, + IT_AMMO, + WP_BFG, +/* precache */ "", +/* sounds */ "" + }, + + // + // HOLDABLE ITEMS + // +/*QUAKED holdable_teleporter (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "holdable_teleporter", + "sound/items/holdable.wav", + { "models/powerups/holdable/teleporter.md3", + 0, 0, 0}, +/* icon */ "icons/teleporter", +/* pickup */ "Personal Teleporter", + 60, + IT_HOLDABLE, + HI_TELEPORTER, +/* precache */ "", +/* sounds */ "" + }, +/*QUAKED holdable_medkit (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "holdable_medkit", + "sound/items/holdable.wav", + { + "models/powerups/holdable/medkit.md3", + "models/powerups/holdable/medkit_sphere.md3", + 0, 0}, +/* icon */ "icons/medkit", +/* pickup */ "Medkit", + 60, + IT_HOLDABLE, + HI_MEDKIT, +/* precache */ "", +/* sounds */ "sound/items/use_medkit.wav" + }, + + // + // POWERUP ITEMS + // +/*QUAKED item_quad (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "item_quad", + "sound/items/quaddamage.wav", + { "models/powerups/instant/quad.md3", + "models/powerups/instant/quad_ring.md3", + 0, 0 }, +/* icon */ "icons/quad", +/* pickup */ "Quad Damage", + 30, + IT_POWERUP, + PW_QUAD, +/* precache */ "", +/* sounds */ "sound/items/damage2.wav sound/items/damage3.wav" + }, + +/*QUAKED item_enviro (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "item_enviro", + "sound/items/protect.wav", + { "models/powerups/instant/enviro.md3", + "models/powerups/instant/enviro_ring.md3", + 0, 0 }, +/* icon */ "icons/envirosuit", +/* pickup */ "Battle Suit", + 30, + IT_POWERUP, + PW_BATTLESUIT, +/* precache */ "", +/* sounds */ "sound/items/airout.wav sound/items/protect3.wav" + }, + +/*QUAKED item_haste (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "item_haste", + "sound/items/haste.wav", + { "models/powerups/instant/haste.md3", + "models/powerups/instant/haste_ring.md3", + 0, 0 }, +/* icon */ "icons/haste", +/* pickup */ "Speed", + 30, + IT_POWERUP, + PW_HASTE, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED item_invis (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "item_invis", + "sound/items/invisibility.wav", + { "models/powerups/instant/invis.md3", + "models/powerups/instant/invis_ring.md3", + 0, 0 }, +/* icon */ "icons/invis", +/* pickup */ "Invisibility", + 30, + IT_POWERUP, + PW_INVIS, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED item_regen (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "item_regen", + "sound/items/regeneration.wav", + { "models/powerups/instant/regen.md3", + "models/powerups/instant/regen_ring.md3", + 0, 0 }, +/* icon */ "icons/regen", +/* pickup */ "Regeneration", + 30, + IT_POWERUP, + PW_REGEN, +/* precache */ "", +/* sounds */ "sound/items/regen.wav" + }, + +/*QUAKED item_flight (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "item_flight", + "sound/items/flight.wav", + { "models/powerups/instant/flight.md3", + "models/powerups/instant/flight_ring.md3", + 0, 0 }, +/* icon */ "icons/flight", +/* pickup */ "Flight", + 60, + IT_POWERUP, + PW_FLIGHT, +/* precache */ "", +/* sounds */ "sound/items/flight.wav" + }, + +/*QUAKED team_CTF_redflag (1 0 0) (-16 -16 -16) (16 16 16) +Only in CTF games +*/ + { + "team_CTF_redflag", + NULL, + { "models/flags/r_flag.md3", + 0, 0, 0 }, +/* icon */ "icons/iconf_red1", +/* pickup */ "Red Flag", + 0, + IT_TEAM, + PW_REDFLAG, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED team_CTF_blueflag (0 0 1) (-16 -16 -16) (16 16 16) +Only in CTF games +*/ + { + "team_CTF_blueflag", + NULL, + { "models/flags/b_flag.md3", + 0, 0, 0 }, +/* icon */ "icons/iconf_blu1", +/* pickup */ "Blue Flag", + 0, + IT_TEAM, + PW_BLUEFLAG, +/* precache */ "", +/* sounds */ "" + }, + +#ifdef MISSIONPACK +/*QUAKED holdable_kamikaze (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "holdable_kamikaze", + "sound/items/holdable.wav", + { "models/powerups/kamikazi.md3", + 0, 0, 0}, +/* icon */ "icons/kamikaze", +/* pickup */ "Kamikaze", + 60, + IT_HOLDABLE, + HI_KAMIKAZE, +/* precache */ "", +/* sounds */ "sound/items/kamikazerespawn.wav" + }, + +/*QUAKED holdable_portal (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "holdable_portal", + "sound/items/holdable.wav", + { "models/powerups/holdable/porter.md3", + 0, 0, 0}, +/* icon */ "icons/portal", +/* pickup */ "Portal", + 60, + IT_HOLDABLE, + HI_PORTAL, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED holdable_invulnerability (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "holdable_invulnerability", + "sound/items/holdable.wav", + { "models/powerups/holdable/invulnerability.md3", + 0, 0, 0}, +/* icon */ "icons/invulnerability", +/* pickup */ "Invulnerability", + 60, + IT_HOLDABLE, + HI_INVULNERABILITY, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED ammo_nails (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "ammo_nails", + "sound/misc/am_pkup.wav", + { "models/powerups/ammo/nailgunam.md3", + 0, 0, 0}, +/* icon */ "icons/icona_nailgun", +/* pickup */ "Nails", + 20, + IT_AMMO, + WP_NAILGUN, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED ammo_mines (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "ammo_mines", + "sound/misc/am_pkup.wav", + { "models/powerups/ammo/proxmineam.md3", + 0, 0, 0}, +/* icon */ "icons/icona_proxlauncher", +/* pickup */ "Proximity Mines", + 10, + IT_AMMO, + WP_PROX_LAUNCHER, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED ammo_belt (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "ammo_belt", + "sound/misc/am_pkup.wav", + { "models/powerups/ammo/chaingunam.md3", + 0, 0, 0}, +/* icon */ "icons/icona_chaingun", +/* pickup */ "Chaingun Belt", + 100, + IT_AMMO, + WP_CHAINGUN, +/* precache */ "", +/* sounds */ "" + }, + + // + // PERSISTANT POWERUP ITEMS + // +/*QUAKED item_scout (.3 .3 1) (-16 -16 -16) (16 16 16) suspended redTeam blueTeam +*/ + { + "item_scout", + "sound/items/scout.wav", + { "models/powerups/scout.md3", + 0, 0, 0 }, +/* icon */ "icons/scout", +/* pickup */ "Scout", + 30, + IT_PERSISTANT_POWERUP, + PW_SCOUT, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED item_guard (.3 .3 1) (-16 -16 -16) (16 16 16) suspended redTeam blueTeam +*/ + { + "item_guard", + "sound/items/guard.wav", + { "models/powerups/guard.md3", + 0, 0, 0 }, +/* icon */ "icons/guard", +/* pickup */ "Guard", + 30, + IT_PERSISTANT_POWERUP, + PW_GUARD, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED item_doubler (.3 .3 1) (-16 -16 -16) (16 16 16) suspended redTeam blueTeam +*/ + { + "item_doubler", + "sound/items/doubler.wav", + { "models/powerups/doubler.md3", + 0, 0, 0 }, +/* icon */ "icons/doubler", +/* pickup */ "Doubler", + 30, + IT_PERSISTANT_POWERUP, + PW_DOUBLER, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED item_doubler (.3 .3 1) (-16 -16 -16) (16 16 16) suspended redTeam blueTeam +*/ + { + "item_ammoregen", + "sound/items/ammoregen.wav", + { "models/powerups/ammo.md3", + 0, 0, 0 }, +/* icon */ "icons/ammo_regen", +/* pickup */ "Ammo Regen", + 30, + IT_PERSISTANT_POWERUP, + PW_AMMOREGEN, +/* precache */ "", +/* sounds */ "" + }, + + /*QUAKED team_CTF_neutralflag (0 0 1) (-16 -16 -16) (16 16 16) +Only in One Flag CTF games +*/ + { + "team_CTF_neutralflag", + NULL, + { "models/flags/n_flag.md3", + 0, 0, 0 }, +/* icon */ "icons/iconf_neutral1", +/* pickup */ "Neutral Flag", + 0, + IT_TEAM, + PW_NEUTRALFLAG, +/* precache */ "", +/* sounds */ "" + }, + + { + "item_redcube", + "sound/misc/am_pkup.wav", + { "models/powerups/orb/r_orb.md3", + 0, 0, 0 }, +/* icon */ "icons/iconh_rorb", +/* pickup */ "Red Cube", + 0, + IT_TEAM, + 0, +/* precache */ "", +/* sounds */ "" + }, + + { + "item_bluecube", + "sound/misc/am_pkup.wav", + { "models/powerups/orb/b_orb.md3", + 0, 0, 0 }, +/* icon */ "icons/iconh_borb", +/* pickup */ "Blue Cube", + 0, + IT_TEAM, + 0, +/* precache */ "", +/* sounds */ "" + }, +/*QUAKED weapon_nailgun (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "weapon_nailgun", + "sound/misc/w_pkup.wav", + { "models/weapons/nailgun/nailgun.md3", + 0, 0, 0}, +/* icon */ "icons/iconw_nailgun", +/* pickup */ "Nailgun", + 10, + IT_WEAPON, + WP_NAILGUN, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED weapon_prox_launcher (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "weapon_prox_launcher", + "sound/misc/w_pkup.wav", + { "models/weapons/proxmine/proxmine.md3", + 0, 0, 0}, +/* icon */ "icons/iconw_proxlauncher", +/* pickup */ "Prox Launcher", + 5, + IT_WEAPON, + WP_PROX_LAUNCHER, +/* precache */ "", +/* sounds */ "sound/weapons/proxmine/wstbtick.wav " + "sound/weapons/proxmine/wstbactv.wav " + "sound/weapons/proxmine/wstbimpl.wav " + "sound/weapons/proxmine/wstbimpm.wav " + "sound/weapons/proxmine/wstbimpd.wav " + "sound/weapons/proxmine/wstbactv.wav" + }, + +/*QUAKED weapon_chaingun (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "weapon_chaingun", + "sound/misc/w_pkup.wav", + { "models/weapons/vulcan/vulcan.md3", + 0, 0, 0}, +/* icon */ "icons/iconw_chaingun", +/* pickup */ "Chaingun", + 80, + IT_WEAPON, + WP_CHAINGUN, +/* precache */ "", +/* sounds */ "sound/weapons/vulcan/wvulwind.wav" + }, +#endif + + // end of list marker + {NULL} +}; + +int bg_numItems = sizeof(bg_itemlist) / sizeof(bg_itemlist[0]) - 1; + + +/* +============== +BG_FindItemForPowerup +============== +*/ +gitem_t *BG_FindItemForPowerup( powerup_t pw ) { + int i; + + for ( i = 0 ; i < bg_numItems ; i++ ) { + if ( (bg_itemlist[i].giType == IT_POWERUP || + bg_itemlist[i].giType == IT_TEAM || + bg_itemlist[i].giType == IT_PERSISTANT_POWERUP) && + bg_itemlist[i].giTag == pw ) { + return &bg_itemlist[i]; + } + } + + return NULL; +} + + +/* +============== +BG_FindItemForHoldable +============== +*/ +gitem_t *BG_FindItemForHoldable( holdable_t pw ) { + int i; + + for ( i = 0 ; i < bg_numItems ; i++ ) { + if ( bg_itemlist[i].giType == IT_HOLDABLE && bg_itemlist[i].giTag == pw ) { + return &bg_itemlist[i]; + } + } + + Com_Error( ERR_DROP, "HoldableItem not found" ); + + return NULL; +} + + +/* +=============== +BG_FindItemForWeapon + +=============== +*/ +gitem_t *BG_FindItemForWeapon( weapon_t weapon ) { + gitem_t *it; + + for ( it = bg_itemlist + 1 ; it->classname ; it++) { + if ( it->giType == IT_WEAPON && it->giTag == weapon ) { + return it; + } + } + + Com_Error( ERR_DROP, "Couldn't find item for weapon %i", weapon); + return NULL; +} + +/* +=============== +BG_FindItem + +=============== +*/ +gitem_t *BG_FindItem( const char *pickupName ) { + gitem_t *it; + + for ( it = bg_itemlist + 1 ; it->classname ; it++ ) { + if ( !Q_stricmp( it->pickup_name, pickupName ) ) + return it; + } + + return NULL; +} + +/* +============ +BG_PlayerTouchesItem + +Items can be picked up without actually touching their physical bounds to make +grabbing them easier +============ +*/ +qboolean BG_PlayerTouchesItem( playerState_t *ps, entityState_t *item, int atTime ) { + vec3_t origin; + + BG_EvaluateTrajectory( &item->pos, atTime, origin ); + + // we are ignoring ducked differences here + if ( ps->origin[0] - origin[0] > 44 + || ps->origin[0] - origin[0] < -50 + || ps->origin[1] - origin[1] > 36 + || ps->origin[1] - origin[1] < -36 + || ps->origin[2] - origin[2] > 36 + || ps->origin[2] - origin[2] < -36 ) { + return qfalse; + } + + return qtrue; +} + + + +/* +================ +BG_CanItemBeGrabbed + +Returns false if the item should not be picked up. +This needs to be the same for client side prediction and server use. +================ +*/ +qboolean BG_CanItemBeGrabbed( int gametype, const entityState_t *ent, const playerState_t *ps ) { + gitem_t *item; +#ifdef MISSIONPACK + int upperBound; +#endif + + if ( ent->modelindex < 1 || ent->modelindex >= bg_numItems ) { + Com_Error( ERR_DROP, "BG_CanItemBeGrabbed: index out of range" ); + } + + item = &bg_itemlist[ent->modelindex]; + + switch( item->giType ) { + case IT_WEAPON: + return qtrue; // weapons are always picked up + + case IT_AMMO: + if ( ps->ammo[ item->giTag ] >= 200 ) { + return qfalse; // can't hold any more + } + return qtrue; + + case IT_ARMOR: +#ifdef MISSIONPACK + if( bg_itemlist[ps->stats[STAT_PERSISTANT_POWERUP]].giTag == PW_SCOUT ) { + return qfalse; + } + + // we also clamp armor to the maxhealth for handicapping + if( bg_itemlist[ps->stats[STAT_PERSISTANT_POWERUP]].giTag == PW_GUARD ) { + upperBound = ps->stats[STAT_MAX_HEALTH]; + } + else { + upperBound = ps->stats[STAT_MAX_HEALTH] * 2; + } + + if ( ps->stats[STAT_ARMOR] >= upperBound ) { + return qfalse; + } +#else + if ( ps->stats[STAT_ARMOR] >= ps->stats[STAT_MAX_HEALTH] * 2 ) { + return qfalse; + } +#endif + return qtrue; + + case IT_HEALTH: + // small and mega healths will go over the max, otherwise + // don't pick up if already at max +#ifdef MISSIONPACK + if( bg_itemlist[ps->stats[STAT_PERSISTANT_POWERUP]].giTag == PW_GUARD ) { + upperBound = ps->stats[STAT_MAX_HEALTH]; + } + else +#endif + if ( item->quantity == 5 || item->quantity == 100 ) { + if ( ps->stats[STAT_HEALTH] >= ps->stats[STAT_MAX_HEALTH] * 2 ) { + return qfalse; + } + return qtrue; + } + + if ( ps->stats[STAT_HEALTH] >= ps->stats[STAT_MAX_HEALTH] ) { + return qfalse; + } + return qtrue; + + case IT_POWERUP: + return qtrue; // powerups are always picked up + +#ifdef MISSIONPACK + case IT_PERSISTANT_POWERUP: + // can only hold one item at a time + if ( ps->stats[STAT_PERSISTANT_POWERUP] ) { + return qfalse; + } + + // check team only + if( ( ent->generic1 & 2 ) && ( ps->persistant[PERS_TEAM] != TEAM_RED ) ) { + return qfalse; + } + if( ( ent->generic1 & 4 ) && ( ps->persistant[PERS_TEAM] != TEAM_BLUE ) ) { + return qfalse; + } + + return qtrue; +#endif + + case IT_TEAM: // team items, such as flags +#ifdef MISSIONPACK + if( gametype == GT_1FCTF ) { + // neutral flag can always be picked up + if( item->giTag == PW_NEUTRALFLAG ) { + return qtrue; + } + if (ps->persistant[PERS_TEAM] == TEAM_RED) { + if (item->giTag == PW_BLUEFLAG && ps->powerups[PW_NEUTRALFLAG] ) { + return qtrue; + } + } else if (ps->persistant[PERS_TEAM] == TEAM_BLUE) { + if (item->giTag == PW_REDFLAG && ps->powerups[PW_NEUTRALFLAG] ) { + return qtrue; + } + } + } +#endif + if( gametype == GT_CTF ) { + // ent->modelindex2 is non-zero on items if they are dropped + // we need to know this because we can pick up our dropped flag (and return it) + // but we can't pick up our flag at base + if (ps->persistant[PERS_TEAM] == TEAM_RED) { + if (item->giTag == PW_BLUEFLAG || + (item->giTag == PW_REDFLAG && ent->modelindex2) || + (item->giTag == PW_REDFLAG && ps->powerups[PW_BLUEFLAG]) ) + return qtrue; + } else if (ps->persistant[PERS_TEAM] == TEAM_BLUE) { + if (item->giTag == PW_REDFLAG || + (item->giTag == PW_BLUEFLAG && ent->modelindex2) || + (item->giTag == PW_BLUEFLAG && ps->powerups[PW_REDFLAG]) ) + return qtrue; + } + } + +#ifdef MISSIONPACK + if( gametype == GT_HARVESTER ) { + return qtrue; + } +#endif + return qfalse; + + case IT_HOLDABLE: + // can only hold one item at a time + if ( ps->stats[STAT_HOLDABLE_ITEM] ) { + return qfalse; + } + return qtrue; + + case IT_BAD: + Com_Error( ERR_DROP, "BG_CanItemBeGrabbed: IT_BAD" ); + default: +#ifndef Q3_VM +#ifndef NDEBUG // bk0001204 + Com_Printf("BG_CanItemBeGrabbed: unknown enum %d\n", item->giType ); +#endif +#endif + break; + } + + return qfalse; +} + +//====================================================================== + +/* +================ +BG_EvaluateTrajectory + +================ +*/ +void BG_EvaluateTrajectory( const trajectory_t *tr, int atTime, vec3_t result ) { + float deltaTime; + float phase; + + switch( tr->trType ) { + case TR_STATIONARY: + case TR_INTERPOLATE: + VectorCopy( tr->trBase, result ); + break; + case TR_LINEAR: + deltaTime = ( atTime - tr->trTime ) * 0.001; // milliseconds to seconds + VectorMA( tr->trBase, deltaTime, tr->trDelta, result ); + break; + case TR_SINE: + deltaTime = ( atTime - tr->trTime ) / (float) tr->trDuration; + phase = sin( deltaTime * M_PI * 2 ); + VectorMA( tr->trBase, phase, tr->trDelta, result ); + break; + case TR_LINEAR_STOP: + if ( atTime > tr->trTime + tr->trDuration ) { + atTime = tr->trTime + tr->trDuration; + } + deltaTime = ( atTime - tr->trTime ) * 0.001; // milliseconds to seconds + if ( deltaTime < 0 ) { + deltaTime = 0; + } + VectorMA( tr->trBase, deltaTime, tr->trDelta, result ); + break; + case TR_GRAVITY: + deltaTime = ( atTime - tr->trTime ) * 0.001; // milliseconds to seconds + VectorMA( tr->trBase, deltaTime, tr->trDelta, result ); + result[2] -= 0.5 * DEFAULT_GRAVITY * deltaTime * deltaTime; // FIXME: local gravity... + break; + default: + Com_Error( ERR_DROP, "BG_EvaluateTrajectory: unknown trType: %i", tr->trTime ); + break; + } +} + +/* +================ +BG_EvaluateTrajectoryDelta + +For determining velocity at a given time +================ +*/ +void BG_EvaluateTrajectoryDelta( const trajectory_t *tr, int atTime, vec3_t result ) { + float deltaTime; + float phase; + + switch( tr->trType ) { + case TR_STATIONARY: + case TR_INTERPOLATE: + VectorClear( result ); + break; + case TR_LINEAR: + VectorCopy( tr->trDelta, result ); + break; + case TR_SINE: + deltaTime = ( atTime - tr->trTime ) / (float) tr->trDuration; + phase = cos( deltaTime * M_PI * 2 ); // derivative of sin = cos + phase *= 0.5; + VectorScale( tr->trDelta, phase, result ); + break; + case TR_LINEAR_STOP: + if ( atTime > tr->trTime + tr->trDuration ) { + VectorClear( result ); + return; + } + VectorCopy( tr->trDelta, result ); + break; + case TR_GRAVITY: + deltaTime = ( atTime - tr->trTime ) * 0.001; // milliseconds to seconds + VectorCopy( tr->trDelta, result ); + result[2] -= DEFAULT_GRAVITY * deltaTime; // FIXME: local gravity... + break; + default: + Com_Error( ERR_DROP, "BG_EvaluateTrajectoryDelta: unknown trType: %i", tr->trTime ); + break; + } +} + +char *eventnames[] = { + "EV_NONE", + + "EV_FOOTSTEP", + "EV_FOOTSTEP_METAL", + "EV_FOOTSPLASH", + "EV_FOOTWADE", + "EV_SWIM", + + "EV_STEP_4", + "EV_STEP_8", + "EV_STEP_12", + "EV_STEP_16", + + "EV_FALL_SHORT", + "EV_FALL_MEDIUM", + "EV_FALL_FAR", + + "EV_JUMP_PAD", // boing sound at origin", jump sound on player + + "EV_JUMP", + "EV_WATER_TOUCH", // foot touches + "EV_WATER_LEAVE", // foot leaves + "EV_WATER_UNDER", // head touches + "EV_WATER_CLEAR", // head leaves + + "EV_ITEM_PICKUP", // normal item pickups are predictable + "EV_GLOBAL_ITEM_PICKUP", // powerup / team sounds are broadcast to everyone + + "EV_NOAMMO", + "EV_CHANGE_WEAPON", + "EV_FIRE_WEAPON", + + "EV_USE_ITEM0", + "EV_USE_ITEM1", + "EV_USE_ITEM2", + "EV_USE_ITEM3", + "EV_USE_ITEM4", + "EV_USE_ITEM5", + "EV_USE_ITEM6", + "EV_USE_ITEM7", + "EV_USE_ITEM8", + "EV_USE_ITEM9", + "EV_USE_ITEM10", + "EV_USE_ITEM11", + "EV_USE_ITEM12", + "EV_USE_ITEM13", + "EV_USE_ITEM14", + "EV_USE_ITEM15", + + "EV_ITEM_RESPAWN", + "EV_ITEM_POP", + "EV_PLAYER_TELEPORT_IN", + "EV_PLAYER_TELEPORT_OUT", + + "EV_GRENADE_BOUNCE", // eventParm will be the soundindex + + "EV_GENERAL_SOUND", + "EV_GLOBAL_SOUND", // no attenuation + "EV_GLOBAL_TEAM_SOUND", + + "EV_BULLET_HIT_FLESH", + "EV_BULLET_HIT_WALL", + + "EV_MISSILE_HIT", + "EV_MISSILE_MISS", + "EV_MISSILE_MISS_METAL", + "EV_RAILTRAIL", + "EV_SHOTGUN", + "EV_BULLET", // otherEntity is the shooter + + "EV_PAIN", + "EV_DEATH1", + "EV_DEATH2", + "EV_DEATH3", + "EV_OBITUARY", + + "EV_POWERUP_QUAD", + "EV_POWERUP_BATTLESUIT", + "EV_POWERUP_REGEN", + + "EV_GIB_PLAYER", // gib a previously living player + "EV_SCOREPLUM", // score plum + +//#ifdef MISSIONPACK + "EV_PROXIMITY_MINE_STICK", + "EV_PROXIMITY_MINE_TRIGGER", + "EV_KAMIKAZE", // kamikaze explodes + "EV_OBELISKEXPLODE", // obelisk explodes + "EV_INVUL_IMPACT", // invulnerability sphere impact + "EV_JUICED", // invulnerability juiced effect + "EV_LIGHTNINGBOLT", // lightning bolt bounced of invulnerability sphere +//#endif + + "EV_DEBUG_LINE", + "EV_STOPLOOPINGSOUND", + "EV_TAUNT" + +}; + +/* +=============== +BG_AddPredictableEventToPlayerstate + +Handles the sequence numbers +=============== +*/ + +void trap_Cvar_VariableStringBuffer( const char *var_name, char *buffer, int bufsize ); + +void BG_AddPredictableEventToPlayerstate( int newEvent, int eventParm, playerState_t *ps ) { + +#ifdef _DEBUG + { + char buf[256]; + trap_Cvar_VariableStringBuffer("showevents", buf, sizeof(buf)); + if ( atof(buf) != 0 ) { +#ifdef QAGAME + Com_Printf(" game event svt %5d -> %5d: num = %20s parm %d\n", ps->pmove_framecount/*ps->commandTime*/, ps->eventSequence, eventnames[newEvent], eventParm); +#else + Com_Printf("Cgame event svt %5d -> %5d: num = %20s parm %d\n", ps->pmove_framecount/*ps->commandTime*/, ps->eventSequence, eventnames[newEvent], eventParm); +#endif + } + } +#endif + ps->events[ps->eventSequence & (MAX_PS_EVENTS-1)] = newEvent; + ps->eventParms[ps->eventSequence & (MAX_PS_EVENTS-1)] = eventParm; + ps->eventSequence++; +} + +/* +======================== +BG_TouchJumpPad +======================== +*/ +void BG_TouchJumpPad( playerState_t *ps, entityState_t *jumppad ) { + vec3_t angles; + float p; + int effectNum; + + // spectators don't use jump pads + if ( ps->pm_type != PM_NORMAL ) { + return; + } + + // flying characters don't hit bounce pads + if ( ps->powerups[PW_FLIGHT] ) { + return; + } + + // if we didn't hit this same jumppad the previous frame + // then don't play the event sound again if we are in a fat trigger + if ( ps->jumppad_ent != jumppad->number ) { + + vectoangles( jumppad->origin2, angles); + p = fabs( AngleNormalize180( angles[PITCH] ) ); + if( p < 45 ) { + effectNum = 0; + } else { + effectNum = 1; + } + BG_AddPredictableEventToPlayerstate( EV_JUMP_PAD, effectNum, ps ); + } + // remember hitting this jumppad this frame + ps->jumppad_ent = jumppad->number; + ps->jumppad_frame = ps->pmove_framecount; + // give the player the velocity from the jumppad + VectorCopy( jumppad->origin2, ps->velocity ); +} + +/* +======================== +BG_PlayerStateToEntityState + +This is done after each set of usercmd_t on the server, +and after local prediction on the client +======================== +*/ +void BG_PlayerStateToEntityState( playerState_t *ps, entityState_t *s, qboolean snap ) { + int i; + + if ( ps->pm_type == PM_INTERMISSION || ps->pm_type == PM_SPECTATOR ) { + s->eType = ET_INVISIBLE; + } else if ( ps->stats[STAT_HEALTH] <= GIB_HEALTH ) { + s->eType = ET_INVISIBLE; + } else { + s->eType = ET_PLAYER; + } + + s->number = ps->clientNum; + + s->pos.trType = TR_INTERPOLATE; + VectorCopy( ps->origin, s->pos.trBase ); + if ( snap ) { + SnapVector( s->pos.trBase ); + } + // set the trDelta for flag direction + VectorCopy( ps->velocity, s->pos.trDelta ); + + s->apos.trType = TR_INTERPOLATE; + VectorCopy( ps->viewangles, s->apos.trBase ); + if ( snap ) { + SnapVector( s->apos.trBase ); + } + + s->angles2[YAW] = ps->movementDir; + s->legsAnim = ps->legsAnim; + s->torsoAnim = ps->torsoAnim; + s->clientNum = ps->clientNum; // ET_PLAYER looks here instead of at number + // so corpses can also reference the proper config + s->eFlags = ps->eFlags; + if ( ps->stats[STAT_HEALTH] <= 0 ) { + s->eFlags |= EF_DEAD; + } else { + s->eFlags &= ~EF_DEAD; + } + + if ( ps->externalEvent ) { + s->event = ps->externalEvent; + s->eventParm = ps->externalEventParm; + } else if ( ps->entityEventSequence < ps->eventSequence ) { + int seq; + + if ( ps->entityEventSequence < ps->eventSequence - MAX_PS_EVENTS) { + ps->entityEventSequence = ps->eventSequence - MAX_PS_EVENTS; + } + seq = ps->entityEventSequence & (MAX_PS_EVENTS-1); + s->event = ps->events[ seq ] | ( ( ps->entityEventSequence & 3 ) << 8 ); + s->eventParm = ps->eventParms[ seq ]; + ps->entityEventSequence++; + } + + s->weapon = ps->weapon; + s->groundEntityNum = ps->groundEntityNum; + + s->powerups = 0; + for ( i = 0 ; i < MAX_POWERUPS ; i++ ) { + if ( ps->powerups[ i ] ) { + s->powerups |= 1 << i; + } + } + + s->loopSound = ps->loopSound; + s->generic1 = ps->generic1; +} + +/* +======================== +BG_PlayerStateToEntityStateExtraPolate + +This is done after each set of usercmd_t on the server, +and after local prediction on the client +======================== +*/ +void BG_PlayerStateToEntityStateExtraPolate( playerState_t *ps, entityState_t *s, int time, qboolean snap ) { + int i; + + if ( ps->pm_type == PM_INTERMISSION || ps->pm_type == PM_SPECTATOR ) { + s->eType = ET_INVISIBLE; + } else if ( ps->stats[STAT_HEALTH] <= GIB_HEALTH ) { + s->eType = ET_INVISIBLE; + } else { + s->eType = ET_PLAYER; + } + + s->number = ps->clientNum; + + s->pos.trType = TR_LINEAR_STOP; + VectorCopy( ps->origin, s->pos.trBase ); + if ( snap ) { + SnapVector( s->pos.trBase ); + } + // set the trDelta for flag direction and linear prediction + VectorCopy( ps->velocity, s->pos.trDelta ); + // set the time for linear prediction + s->pos.trTime = time; + // set maximum extra polation time + s->pos.trDuration = 50; // 1000 / sv_fps (default = 20) + + s->apos.trType = TR_INTERPOLATE; + VectorCopy( ps->viewangles, s->apos.trBase ); + if ( snap ) { + SnapVector( s->apos.trBase ); + } + + s->angles2[YAW] = ps->movementDir; + s->legsAnim = ps->legsAnim; + s->torsoAnim = ps->torsoAnim; + s->clientNum = ps->clientNum; // ET_PLAYER looks here instead of at number + // so corpses can also reference the proper config + s->eFlags = ps->eFlags; + if ( ps->stats[STAT_HEALTH] <= 0 ) { + s->eFlags |= EF_DEAD; + } else { + s->eFlags &= ~EF_DEAD; + } + + if ( ps->externalEvent ) { + s->event = ps->externalEvent; + s->eventParm = ps->externalEventParm; + } else if ( ps->entityEventSequence < ps->eventSequence ) { + int seq; + + if ( ps->entityEventSequence < ps->eventSequence - MAX_PS_EVENTS) { + ps->entityEventSequence = ps->eventSequence - MAX_PS_EVENTS; + } + seq = ps->entityEventSequence & (MAX_PS_EVENTS-1); + s->event = ps->events[ seq ] | ( ( ps->entityEventSequence & 3 ) << 8 ); + s->eventParm = ps->eventParms[ seq ]; + ps->entityEventSequence++; + } + + s->weapon = ps->weapon; + s->groundEntityNum = ps->groundEntityNum; + + s->powerups = 0; + for ( i = 0 ; i < MAX_POWERUPS ; i++ ) { + if ( ps->powerups[ i ] ) { + s->powerups |= 1 << i; + } + } + + s->loopSound = ps->loopSound; + s->generic1 = ps->generic1; +} diff --git a/code/game/bg_pmove.c b/code/game/bg_pmove.c index 3711869..f5f5c68 100755 --- a/code/game/bg_pmove.c +++ b/code/game/bg_pmove.c @@ -1,2069 +1,2069 @@ -/* -=========================================================================== -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 Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// -// bg_pmove.c -- both games player movement code -// takes a playerstate and a usercmd as input and returns a modifed playerstate - -#include "q_shared.h" -#include "bg_public.h" -#include "bg_local.h" - -pmove_t *pm; -pml_t pml; - -// movement parameters -float pm_stopspeed = 100.0f; -float pm_duckScale = 0.25f; -float pm_swimScale = 0.50f; -float pm_wadeScale = 0.70f; - -float pm_accelerate = 10.0f; -float pm_airaccelerate = 1.0f; -float pm_wateraccelerate = 4.0f; -float pm_flyaccelerate = 8.0f; - -float pm_friction = 6.0f; -float pm_waterfriction = 1.0f; -float pm_flightfriction = 3.0f; -float pm_spectatorfriction = 5.0f; - -int c_pmove = 0; - - -/* -=============== -PM_AddEvent - -=============== -*/ -void PM_AddEvent( int newEvent ) { - BG_AddPredictableEventToPlayerstate( newEvent, 0, pm->ps ); -} - -/* -=============== -PM_AddTouchEnt -=============== -*/ -void PM_AddTouchEnt( int entityNum ) { - int i; - - if ( entityNum == ENTITYNUM_WORLD ) { - return; - } - if ( pm->numtouch == MAXTOUCH ) { - return; - } - - // see if it is already added - for ( i = 0 ; i < pm->numtouch ; i++ ) { - if ( pm->touchents[ i ] == entityNum ) { - return; - } - } - - // add it - pm->touchents[pm->numtouch] = entityNum; - pm->numtouch++; -} - -/* -=================== -PM_StartTorsoAnim -=================== -*/ -static void PM_StartTorsoAnim( int anim ) { - if ( pm->ps->pm_type >= PM_DEAD ) { - return; - } - pm->ps->torsoAnim = ( ( pm->ps->torsoAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) - | anim; -} -static void PM_StartLegsAnim( int anim ) { - if ( pm->ps->pm_type >= PM_DEAD ) { - return; - } - if ( pm->ps->legsTimer > 0 ) { - return; // a high priority animation is running - } - pm->ps->legsAnim = ( ( pm->ps->legsAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) - | anim; -} - -static void PM_ContinueLegsAnim( int anim ) { - if ( ( pm->ps->legsAnim & ~ANIM_TOGGLEBIT ) == anim ) { - return; - } - if ( pm->ps->legsTimer > 0 ) { - return; // a high priority animation is running - } - PM_StartLegsAnim( anim ); -} - -static void PM_ContinueTorsoAnim( int anim ) { - if ( ( pm->ps->torsoAnim & ~ANIM_TOGGLEBIT ) == anim ) { - return; - } - if ( pm->ps->torsoTimer > 0 ) { - return; // a high priority animation is running - } - PM_StartTorsoAnim( anim ); -} - -static void PM_ForceLegsAnim( int anim ) { - pm->ps->legsTimer = 0; - PM_StartLegsAnim( anim ); -} - - -/* -================== -PM_ClipVelocity - -Slide off of the impacting surface -================== -*/ -void PM_ClipVelocity( vec3_t in, vec3_t normal, vec3_t out, float overbounce ) { - float backoff; - float change; - int i; - - backoff = DotProduct (in, normal); - - if ( backoff < 0 ) { - backoff *= overbounce; - } else { - backoff /= overbounce; - } - - for ( i=0 ; i<3 ; i++ ) { - change = normal[i]*backoff; - out[i] = in[i] - change; - } -} - - -/* -================== -PM_Friction - -Handles both ground friction and water friction -================== -*/ -static void PM_Friction( void ) { - vec3_t vec; - float *vel; - float speed, newspeed, control; - float drop; - - vel = pm->ps->velocity; - - VectorCopy( vel, vec ); - if ( pml.walking ) { - vec[2] = 0; // ignore slope movement - } - - speed = VectorLength(vec); - if (speed < 1) { - vel[0] = 0; - vel[1] = 0; // allow sinking underwater - // FIXME: still have z friction underwater? - return; - } - - drop = 0; - - // apply ground friction - if ( pm->waterlevel <= 1 ) { - if ( pml.walking && !(pml.groundTrace.surfaceFlags & SURF_SLICK) ) { - // if getting knocked back, no friction - if ( ! (pm->ps->pm_flags & PMF_TIME_KNOCKBACK) ) { - control = speed < pm_stopspeed ? pm_stopspeed : speed; - drop += control*pm_friction*pml.frametime; - } - } - } - - // apply water friction even if just wading - if ( pm->waterlevel ) { - drop += speed*pm_waterfriction*pm->waterlevel*pml.frametime; - } - - // apply flying friction - if ( pm->ps->powerups[PW_FLIGHT]) { - drop += speed*pm_flightfriction*pml.frametime; - } - - if ( pm->ps->pm_type == PM_SPECTATOR) { - drop += speed*pm_spectatorfriction*pml.frametime; - } - - // scale the velocity - newspeed = speed - drop; - if (newspeed < 0) { - newspeed = 0; - } - newspeed /= speed; - - vel[0] = vel[0] * newspeed; - vel[1] = vel[1] * newspeed; - vel[2] = vel[2] * newspeed; -} - - -/* -============== -PM_Accelerate - -Handles user intended acceleration -============== -*/ -static void PM_Accelerate( vec3_t wishdir, float wishspeed, float accel ) { -#if 1 - // q2 style - int i; - float addspeed, accelspeed, currentspeed; - - currentspeed = DotProduct (pm->ps->velocity, wishdir); - addspeed = wishspeed - currentspeed; - if (addspeed <= 0) { - return; - } - accelspeed = accel*pml.frametime*wishspeed; - if (accelspeed > addspeed) { - accelspeed = addspeed; - } - - for (i=0 ; i<3 ; i++) { - pm->ps->velocity[i] += accelspeed*wishdir[i]; - } -#else - // proper way (avoids strafe jump maxspeed bug), but feels bad - vec3_t wishVelocity; - vec3_t pushDir; - float pushLen; - float canPush; - - VectorScale( wishdir, wishspeed, wishVelocity ); - VectorSubtract( wishVelocity, pm->ps->velocity, pushDir ); - pushLen = VectorNormalize( pushDir ); - - canPush = accel*pml.frametime*wishspeed; - if (canPush > pushLen) { - canPush = pushLen; - } - - VectorMA( pm->ps->velocity, canPush, pushDir, pm->ps->velocity ); -#endif -} - - - -/* -============ -PM_CmdScale - -Returns the scale factor to apply to cmd movements -This allows the clients to use axial -127 to 127 values for all directions -without getting a sqrt(2) distortion in speed. -============ -*/ -static float PM_CmdScale( usercmd_t *cmd ) { - int max; - float total; - float scale; - - max = abs( cmd->forwardmove ); - if ( abs( cmd->rightmove ) > max ) { - max = abs( cmd->rightmove ); - } - if ( abs( cmd->upmove ) > max ) { - max = abs( cmd->upmove ); - } - if ( !max ) { - return 0; - } - - total = sqrt( cmd->forwardmove * cmd->forwardmove - + cmd->rightmove * cmd->rightmove + cmd->upmove * cmd->upmove ); - scale = (float)pm->ps->speed * max / ( 127.0 * total ); - - return scale; -} - - -/* -================ -PM_SetMovementDir - -Determine the rotation of the legs reletive -to the facing dir -================ -*/ -static void PM_SetMovementDir( void ) { - if ( pm->cmd.forwardmove || pm->cmd.rightmove ) { - if ( pm->cmd.rightmove == 0 && pm->cmd.forwardmove > 0 ) { - pm->ps->movementDir = 0; - } else if ( pm->cmd.rightmove < 0 && pm->cmd.forwardmove > 0 ) { - pm->ps->movementDir = 1; - } else if ( pm->cmd.rightmove < 0 && pm->cmd.forwardmove == 0 ) { - pm->ps->movementDir = 2; - } else if ( pm->cmd.rightmove < 0 && pm->cmd.forwardmove < 0 ) { - pm->ps->movementDir = 3; - } else if ( pm->cmd.rightmove == 0 && pm->cmd.forwardmove < 0 ) { - pm->ps->movementDir = 4; - } else if ( pm->cmd.rightmove > 0 && pm->cmd.forwardmove < 0 ) { - pm->ps->movementDir = 5; - } else if ( pm->cmd.rightmove > 0 && pm->cmd.forwardmove == 0 ) { - pm->ps->movementDir = 6; - } else if ( pm->cmd.rightmove > 0 && pm->cmd.forwardmove > 0 ) { - pm->ps->movementDir = 7; - } - } else { - // if they aren't actively going directly sideways, - // change the animation to the diagonal so they - // don't stop too crooked - if ( pm->ps->movementDir == 2 ) { - pm->ps->movementDir = 1; - } else if ( pm->ps->movementDir == 6 ) { - pm->ps->movementDir = 7; - } - } -} - - -/* -============= -PM_CheckJump -============= -*/ -static qboolean PM_CheckJump( void ) { - if ( pm->ps->pm_flags & PMF_RESPAWNED ) { - return qfalse; // don't allow jump until all buttons are up - } - - if ( pm->cmd.upmove < 10 ) { - // not holding jump - return qfalse; - } - - // must wait for jump to be released - if ( pm->ps->pm_flags & PMF_JUMP_HELD ) { - // clear upmove so cmdscale doesn't lower running speed - pm->cmd.upmove = 0; - return qfalse; - } - - pml.groundPlane = qfalse; // jumping away - pml.walking = qfalse; - pm->ps->pm_flags |= PMF_JUMP_HELD; - - pm->ps->groundEntityNum = ENTITYNUM_NONE; - pm->ps->velocity[2] = JUMP_VELOCITY; - PM_AddEvent( EV_JUMP ); - - if ( pm->cmd.forwardmove >= 0 ) { - PM_ForceLegsAnim( LEGS_JUMP ); - pm->ps->pm_flags &= ~PMF_BACKWARDS_JUMP; - } else { - PM_ForceLegsAnim( LEGS_JUMPB ); - pm->ps->pm_flags |= PMF_BACKWARDS_JUMP; - } - - return qtrue; -} - -/* -============= -PM_CheckWaterJump -============= -*/ -static qboolean PM_CheckWaterJump( void ) { - vec3_t spot; - int cont; - vec3_t flatforward; - - if (pm->ps->pm_time) { - return qfalse; - } - - // check for water jump - if ( pm->waterlevel != 2 ) { - return qfalse; - } - - flatforward[0] = pml.forward[0]; - flatforward[1] = pml.forward[1]; - flatforward[2] = 0; - VectorNormalize (flatforward); - - VectorMA (pm->ps->origin, 30, flatforward, spot); - spot[2] += 4; - cont = pm->pointcontents (spot, pm->ps->clientNum ); - if ( !(cont & CONTENTS_SOLID) ) { - return qfalse; - } - - spot[2] += 16; - cont = pm->pointcontents (spot, pm->ps->clientNum ); - if ( cont ) { - return qfalse; - } - - // jump out of water - VectorScale (pml.forward, 200, pm->ps->velocity); - pm->ps->velocity[2] = 350; - - pm->ps->pm_flags |= PMF_TIME_WATERJUMP; - pm->ps->pm_time = 2000; - - return qtrue; -} - -//============================================================================ - - -/* -=================== -PM_WaterJumpMove - -Flying out of the water -=================== -*/ -static void PM_WaterJumpMove( void ) { - // waterjump has no control, but falls - - PM_StepSlideMove( qtrue ); - - pm->ps->velocity[2] -= pm->ps->gravity * pml.frametime; - if (pm->ps->velocity[2] < 0) { - // cancel as soon as we are falling down again - pm->ps->pm_flags &= ~PMF_ALL_TIMES; - pm->ps->pm_time = 0; - } -} - -/* -=================== -PM_WaterMove - -=================== -*/ -static void PM_WaterMove( void ) { - int i; - vec3_t wishvel; - float wishspeed; - vec3_t wishdir; - float scale; - float vel; - - if ( PM_CheckWaterJump() ) { - PM_WaterJumpMove(); - return; - } -#if 0 - // jump = head for surface - if ( pm->cmd.upmove >= 10 ) { - if (pm->ps->velocity[2] > -300) { - if ( pm->watertype == CONTENTS_WATER ) { - pm->ps->velocity[2] = 100; - } else if (pm->watertype == CONTENTS_SLIME) { - pm->ps->velocity[2] = 80; - } else { - pm->ps->velocity[2] = 50; - } - } - } -#endif - PM_Friction (); - - scale = PM_CmdScale( &pm->cmd ); - // - // user intentions - // - if ( !scale ) { - wishvel[0] = 0; - wishvel[1] = 0; - wishvel[2] = -60; // sink towards bottom - } else { - for (i=0 ; i<3 ; i++) - wishvel[i] = scale * pml.forward[i]*pm->cmd.forwardmove + scale * pml.right[i]*pm->cmd.rightmove; - - wishvel[2] += scale * pm->cmd.upmove; - } - - VectorCopy (wishvel, wishdir); - wishspeed = VectorNormalize(wishdir); - - if ( wishspeed > pm->ps->speed * pm_swimScale ) { - wishspeed = pm->ps->speed * pm_swimScale; - } - - PM_Accelerate (wishdir, wishspeed, pm_wateraccelerate); - - // make sure we can go up slopes easily under water - if ( pml.groundPlane && DotProduct( pm->ps->velocity, pml.groundTrace.plane.normal ) < 0 ) { - vel = VectorLength(pm->ps->velocity); - // slide along the ground plane - PM_ClipVelocity (pm->ps->velocity, pml.groundTrace.plane.normal, - pm->ps->velocity, OVERCLIP ); - - VectorNormalize(pm->ps->velocity); - VectorScale(pm->ps->velocity, vel, pm->ps->velocity); - } - - PM_SlideMove( qfalse ); -} - -#ifdef MISSIONPACK -/* -=================== -PM_InvulnerabilityMove - -Only with the invulnerability powerup -=================== -*/ -static void PM_InvulnerabilityMove( void ) { - pm->cmd.forwardmove = 0; - pm->cmd.rightmove = 0; - pm->cmd.upmove = 0; - VectorClear(pm->ps->velocity); -} -#endif - -/* -=================== -PM_FlyMove - -Only with the flight powerup -=================== -*/ -static void PM_FlyMove( void ) { - int i; - vec3_t wishvel; - float wishspeed; - vec3_t wishdir; - float scale; - - // normal slowdown - PM_Friction (); - - scale = PM_CmdScale( &pm->cmd ); - // - // user intentions - // - if ( !scale ) { - wishvel[0] = 0; - wishvel[1] = 0; - wishvel[2] = 0; - } else { - for (i=0 ; i<3 ; i++) { - wishvel[i] = scale * pml.forward[i]*pm->cmd.forwardmove + scale * pml.right[i]*pm->cmd.rightmove; - } - - wishvel[2] += scale * pm->cmd.upmove; - } - - VectorCopy (wishvel, wishdir); - wishspeed = VectorNormalize(wishdir); - - PM_Accelerate (wishdir, wishspeed, pm_flyaccelerate); - - PM_StepSlideMove( qfalse ); -} - - -/* -=================== -PM_AirMove - -=================== -*/ -static void PM_AirMove( void ) { - int i; - vec3_t wishvel; - float fmove, smove; - vec3_t wishdir; - float wishspeed; - float scale; - usercmd_t cmd; - - PM_Friction(); - - fmove = pm->cmd.forwardmove; - smove = pm->cmd.rightmove; - - cmd = pm->cmd; - scale = PM_CmdScale( &cmd ); - - // set the movementDir so clients can rotate the legs for strafing - PM_SetMovementDir(); - - // project moves down to flat plane - pml.forward[2] = 0; - pml.right[2] = 0; - VectorNormalize (pml.forward); - VectorNormalize (pml.right); - - for ( i = 0 ; i < 2 ; i++ ) { - wishvel[i] = pml.forward[i]*fmove + pml.right[i]*smove; - } - wishvel[2] = 0; - - VectorCopy (wishvel, wishdir); - wishspeed = VectorNormalize(wishdir); - wishspeed *= scale; - - // not on ground, so little effect on velocity - PM_Accelerate (wishdir, wishspeed, pm_airaccelerate); - - // we may have a ground plane that is very steep, even - // though we don't have a groundentity - // slide along the steep plane - if ( pml.groundPlane ) { - PM_ClipVelocity (pm->ps->velocity, pml.groundTrace.plane.normal, - pm->ps->velocity, OVERCLIP ); - } - -#if 0 - //ZOID: If we are on the grapple, try stair-stepping - //this allows a player to use the grapple to pull himself - //over a ledge - if (pm->ps->pm_flags & PMF_GRAPPLE_PULL) - PM_StepSlideMove ( qtrue ); - else - PM_SlideMove ( qtrue ); -#endif - - PM_StepSlideMove ( qtrue ); -} - -/* -=================== -PM_GrappleMove - -=================== -*/ -static void PM_GrappleMove( void ) { - vec3_t vel, v; - float vlen; - - VectorScale(pml.forward, -16, v); - VectorAdd(pm->ps->grapplePoint, v, v); - VectorSubtract(v, pm->ps->origin, vel); - vlen = VectorLength(vel); - VectorNormalize( vel ); - - if (vlen <= 100) - VectorScale(vel, 10 * vlen, vel); - else - VectorScale(vel, 800, vel); - - VectorCopy(vel, pm->ps->velocity); - - pml.groundPlane = qfalse; -} - -/* -=================== -PM_WalkMove - -=================== -*/ -static void PM_WalkMove( void ) { - int i; - vec3_t wishvel; - float fmove, smove; - vec3_t wishdir; - float wishspeed; - float scale; - usercmd_t cmd; - float accelerate; - float vel; - - if ( pm->waterlevel > 2 && DotProduct( pml.forward, pml.groundTrace.plane.normal ) > 0 ) { - // begin swimming - PM_WaterMove(); - return; - } - - - if ( PM_CheckJump () ) { - // jumped away - if ( pm->waterlevel > 1 ) { - PM_WaterMove(); - } else { - PM_AirMove(); - } - return; - } - - PM_Friction (); - - fmove = pm->cmd.forwardmove; - smove = pm->cmd.rightmove; - - cmd = pm->cmd; - scale = PM_CmdScale( &cmd ); - - // set the movementDir so clients can rotate the legs for strafing - PM_SetMovementDir(); - - // project moves down to flat plane - pml.forward[2] = 0; - pml.right[2] = 0; - - // project the forward and right directions onto the ground plane - PM_ClipVelocity (pml.forward, pml.groundTrace.plane.normal, pml.forward, OVERCLIP ); - PM_ClipVelocity (pml.right, pml.groundTrace.plane.normal, pml.right, OVERCLIP ); - // - VectorNormalize (pml.forward); - VectorNormalize (pml.right); - - for ( i = 0 ; i < 3 ; i++ ) { - wishvel[i] = pml.forward[i]*fmove + pml.right[i]*smove; - } - // when going up or down slopes the wish velocity should Not be zero -// wishvel[2] = 0; - - VectorCopy (wishvel, wishdir); - wishspeed = VectorNormalize(wishdir); - wishspeed *= scale; - - // clamp the speed lower if ducking - if ( pm->ps->pm_flags & PMF_DUCKED ) { - if ( wishspeed > pm->ps->speed * pm_duckScale ) { - wishspeed = pm->ps->speed * pm_duckScale; - } - } - - // clamp the speed lower if wading or walking on the bottom - if ( pm->waterlevel ) { - float waterScale; - - waterScale = pm->waterlevel / 3.0; - waterScale = 1.0 - ( 1.0 - pm_swimScale ) * waterScale; - if ( wishspeed > pm->ps->speed * waterScale ) { - wishspeed = pm->ps->speed * waterScale; - } - } - - // when a player gets hit, they temporarily lose - // full control, which allows them to be moved a bit - if ( ( pml.groundTrace.surfaceFlags & SURF_SLICK ) || pm->ps->pm_flags & PMF_TIME_KNOCKBACK ) { - accelerate = pm_airaccelerate; - } else { - accelerate = pm_accelerate; - } - - PM_Accelerate (wishdir, wishspeed, accelerate); - - //Com_Printf("velocity = %1.1f %1.1f %1.1f\n", pm->ps->velocity[0], pm->ps->velocity[1], pm->ps->velocity[2]); - //Com_Printf("velocity1 = %1.1f\n", VectorLength(pm->ps->velocity)); - - if ( ( pml.groundTrace.surfaceFlags & SURF_SLICK ) || pm->ps->pm_flags & PMF_TIME_KNOCKBACK ) { - pm->ps->velocity[2] -= pm->ps->gravity * pml.frametime; - } else { - // don't reset the z velocity for slopes -// pm->ps->velocity[2] = 0; - } - - vel = VectorLength(pm->ps->velocity); - - // slide along the ground plane - PM_ClipVelocity (pm->ps->velocity, pml.groundTrace.plane.normal, - pm->ps->velocity, OVERCLIP ); - - // don't decrease velocity when going up or down a slope - VectorNormalize(pm->ps->velocity); - VectorScale(pm->ps->velocity, vel, pm->ps->velocity); - - // don't do anything if standing still - if (!pm->ps->velocity[0] && !pm->ps->velocity[1]) { - return; - } - - PM_StepSlideMove( qfalse ); - - //Com_Printf("velocity2 = %1.1f\n", VectorLength(pm->ps->velocity)); - -} - - -/* -============== -PM_DeadMove -============== -*/ -static void PM_DeadMove( void ) { - float forward; - - if ( !pml.walking ) { - return; - } - - // extra friction - - forward = VectorLength (pm->ps->velocity); - forward -= 20; - if ( forward <= 0 ) { - VectorClear (pm->ps->velocity); - } else { - VectorNormalize (pm->ps->velocity); - VectorScale (pm->ps->velocity, forward, pm->ps->velocity); - } -} - - -/* -=============== -PM_NoclipMove -=============== -*/ -static void PM_NoclipMove( void ) { - float speed, drop, friction, control, newspeed; - int i; - vec3_t wishvel; - float fmove, smove; - vec3_t wishdir; - float wishspeed; - float scale; - - pm->ps->viewheight = DEFAULT_VIEWHEIGHT; - - // friction - - speed = VectorLength (pm->ps->velocity); - if (speed < 1) - { - VectorCopy (vec3_origin, pm->ps->velocity); - } - else - { - drop = 0; - - friction = pm_friction*1.5; // extra friction - control = speed < pm_stopspeed ? pm_stopspeed : speed; - drop += control*friction*pml.frametime; - - // scale the velocity - newspeed = speed - drop; - if (newspeed < 0) - newspeed = 0; - newspeed /= speed; - - VectorScale (pm->ps->velocity, newspeed, pm->ps->velocity); - } - - // accelerate - scale = PM_CmdScale( &pm->cmd ); - - fmove = pm->cmd.forwardmove; - smove = pm->cmd.rightmove; - - for (i=0 ; i<3 ; i++) - wishvel[i] = pml.forward[i]*fmove + pml.right[i]*smove; - wishvel[2] += pm->cmd.upmove; - - VectorCopy (wishvel, wishdir); - wishspeed = VectorNormalize(wishdir); - wishspeed *= scale; - - PM_Accelerate( wishdir, wishspeed, pm_accelerate ); - - // move - VectorMA (pm->ps->origin, pml.frametime, pm->ps->velocity, pm->ps->origin); -} - -//============================================================================ - -/* -================ -PM_FootstepForSurface - -Returns an event number apropriate for the groundsurface -================ -*/ -static int PM_FootstepForSurface( void ) { - if ( pml.groundTrace.surfaceFlags & SURF_NOSTEPS ) { - return 0; - } - if ( pml.groundTrace.surfaceFlags & SURF_METALSTEPS ) { - return EV_FOOTSTEP_METAL; - } - return EV_FOOTSTEP; -} - - -/* -================= -PM_CrashLand - -Check for hard landings that generate sound events -================= -*/ -static void PM_CrashLand( void ) { - float delta; - float dist; - float vel, acc; - float t; - float a, b, c, den; - - // decide which landing animation to use - if ( pm->ps->pm_flags & PMF_BACKWARDS_JUMP ) { - PM_ForceLegsAnim( LEGS_LANDB ); - } else { - PM_ForceLegsAnim( LEGS_LAND ); - } - - pm->ps->legsTimer = TIMER_LAND; - - // calculate the exact velocity on landing - dist = pm->ps->origin[2] - pml.previous_origin[2]; - vel = pml.previous_velocity[2]; - acc = -pm->ps->gravity; - - a = acc / 2; - b = vel; - c = -dist; - - den = b * b - 4 * a * c; - if ( den < 0 ) { - return; - } - t = (-b - sqrt( den ) ) / ( 2 * a ); - - delta = vel + t * acc; - delta = delta*delta * 0.0001; - - // ducking while falling doubles damage - if ( pm->ps->pm_flags & PMF_DUCKED ) { - delta *= 2; - } - - // never take falling damage if completely underwater - if ( pm->waterlevel == 3 ) { - return; - } - - // reduce falling damage if there is standing water - if ( pm->waterlevel == 2 ) { - delta *= 0.25; - } - if ( pm->waterlevel == 1 ) { - delta *= 0.5; - } - - if ( delta < 1 ) { - return; - } - - // create a local entity event to play the sound - - // SURF_NODAMAGE is used for bounce pads where you don't ever - // want to take damage or play a crunch sound - if ( !(pml.groundTrace.surfaceFlags & SURF_NODAMAGE) ) { - if ( delta > 60 ) { - PM_AddEvent( EV_FALL_FAR ); - } else if ( delta > 40 ) { - // this is a pain grunt, so don't play it if dead - if ( pm->ps->stats[STAT_HEALTH] > 0 ) { - PM_AddEvent( EV_FALL_MEDIUM ); - } - } else if ( delta > 7 ) { - PM_AddEvent( EV_FALL_SHORT ); - } else { - PM_AddEvent( PM_FootstepForSurface() ); - } - } - - // start footstep cycle over - pm->ps->bobCycle = 0; -} - -/* -============= -PM_CheckStuck -============= -*/ -/* -void PM_CheckStuck(void) { - trace_t trace; - - pm->trace (&trace, pm->ps->origin, pm->mins, pm->maxs, pm->ps->origin, pm->ps->clientNum, pm->tracemask); - if (trace.allsolid) { - //int shit = qtrue; - } -} -*/ - -/* -============= -PM_CorrectAllSolid -============= -*/ -static int PM_CorrectAllSolid( trace_t *trace ) { - int i, j, k; - vec3_t point; - - if ( pm->debugLevel ) { - Com_Printf("%i:allsolid\n", c_pmove); - } - - // jitter around - for (i = -1; i <= 1; i++) { - for (j = -1; j <= 1; j++) { - for (k = -1; k <= 1; k++) { - VectorCopy(pm->ps->origin, point); - point[0] += (float) i; - point[1] += (float) j; - point[2] += (float) k; - pm->trace (trace, point, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask); - if ( !trace->allsolid ) { - point[0] = pm->ps->origin[0]; - point[1] = pm->ps->origin[1]; - point[2] = pm->ps->origin[2] - 0.25; - - pm->trace (trace, pm->ps->origin, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask); - pml.groundTrace = *trace; - return qtrue; - } - } - } - } - - pm->ps->groundEntityNum = ENTITYNUM_NONE; - pml.groundPlane = qfalse; - pml.walking = qfalse; - - return qfalse; -} - - -/* -============= -PM_GroundTraceMissed - -The ground trace didn't hit a surface, so we are in freefall -============= -*/ -static void PM_GroundTraceMissed( void ) { - trace_t trace; - vec3_t point; - - if ( pm->ps->groundEntityNum != ENTITYNUM_NONE ) { - // we just transitioned into freefall - if ( pm->debugLevel ) { - Com_Printf("%i:lift\n", c_pmove); - } - - // if they aren't in a jumping animation and the ground is a ways away, force into it - // if we didn't do the trace, the player would be backflipping down staircases - VectorCopy( pm->ps->origin, point ); - point[2] -= 64; - - pm->trace (&trace, pm->ps->origin, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask); - if ( trace.fraction == 1.0 ) { - if ( pm->cmd.forwardmove >= 0 ) { - PM_ForceLegsAnim( LEGS_JUMP ); - pm->ps->pm_flags &= ~PMF_BACKWARDS_JUMP; - } else { - PM_ForceLegsAnim( LEGS_JUMPB ); - pm->ps->pm_flags |= PMF_BACKWARDS_JUMP; - } - } - } - - pm->ps->groundEntityNum = ENTITYNUM_NONE; - pml.groundPlane = qfalse; - pml.walking = qfalse; -} - - -/* -============= -PM_GroundTrace -============= -*/ -static void PM_GroundTrace( void ) { - vec3_t point; - trace_t trace; - - point[0] = pm->ps->origin[0]; - point[1] = pm->ps->origin[1]; - point[2] = pm->ps->origin[2] - 0.25; - - pm->trace (&trace, pm->ps->origin, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask); - pml.groundTrace = trace; - - // do something corrective if the trace starts in a solid... - if ( trace.allsolid ) { - if ( !PM_CorrectAllSolid(&trace) ) - return; - } - - // if the trace didn't hit anything, we are in free fall - if ( trace.fraction == 1.0 ) { - PM_GroundTraceMissed(); - pml.groundPlane = qfalse; - pml.walking = qfalse; - return; - } - - // check if getting thrown off the ground - if ( pm->ps->velocity[2] > 0 && DotProduct( pm->ps->velocity, trace.plane.normal ) > 10 ) { - if ( pm->debugLevel ) { - Com_Printf("%i:kickoff\n", c_pmove); - } - // go into jump animation - if ( pm->cmd.forwardmove >= 0 ) { - PM_ForceLegsAnim( LEGS_JUMP ); - pm->ps->pm_flags &= ~PMF_BACKWARDS_JUMP; - } else { - PM_ForceLegsAnim( LEGS_JUMPB ); - pm->ps->pm_flags |= PMF_BACKWARDS_JUMP; - } - - pm->ps->groundEntityNum = ENTITYNUM_NONE; - pml.groundPlane = qfalse; - pml.walking = qfalse; - return; - } - - // slopes that are too steep will not be considered onground - if ( trace.plane.normal[2] < MIN_WALK_NORMAL ) { - if ( pm->debugLevel ) { - Com_Printf("%i:steep\n", c_pmove); - } - // FIXME: if they can't slide down the slope, let them - // walk (sharp crevices) - pm->ps->groundEntityNum = ENTITYNUM_NONE; - pml.groundPlane = qtrue; - pml.walking = qfalse; - return; - } - - pml.groundPlane = qtrue; - pml.walking = qtrue; - - // hitting solid ground will end a waterjump - if (pm->ps->pm_flags & PMF_TIME_WATERJUMP) - { - pm->ps->pm_flags &= ~(PMF_TIME_WATERJUMP | PMF_TIME_LAND); - pm->ps->pm_time = 0; - } - - if ( pm->ps->groundEntityNum == ENTITYNUM_NONE ) { - // just hit the ground - if ( pm->debugLevel ) { - Com_Printf("%i:Land\n", c_pmove); - } - - PM_CrashLand(); - - // don't do landing time if we were just going down a slope - if ( pml.previous_velocity[2] < -200 ) { - // don't allow another jump for a little while - pm->ps->pm_flags |= PMF_TIME_LAND; - pm->ps->pm_time = 250; - } - } - - pm->ps->groundEntityNum = trace.entityNum; - - // don't reset the z velocity for slopes -// pm->ps->velocity[2] = 0; - - PM_AddTouchEnt( trace.entityNum ); -} - - -/* -============= -PM_SetWaterLevel FIXME: avoid this twice? certainly if not moving -============= -*/ -static void PM_SetWaterLevel( void ) { - vec3_t point; - int cont; - int sample1; - int sample2; - - // - // get waterlevel, accounting for ducking - // - pm->waterlevel = 0; - pm->watertype = 0; - - point[0] = pm->ps->origin[0]; - point[1] = pm->ps->origin[1]; - point[2] = pm->ps->origin[2] + MINS_Z + 1; - cont = pm->pointcontents( point, pm->ps->clientNum ); - - if ( cont & MASK_WATER ) { - sample2 = pm->ps->viewheight - MINS_Z; - sample1 = sample2 / 2; - - pm->watertype = cont; - pm->waterlevel = 1; - point[2] = pm->ps->origin[2] + MINS_Z + sample1; - cont = pm->pointcontents (point, pm->ps->clientNum ); - if ( cont & MASK_WATER ) { - pm->waterlevel = 2; - point[2] = pm->ps->origin[2] + MINS_Z + sample2; - cont = pm->pointcontents (point, pm->ps->clientNum ); - if ( cont & MASK_WATER ){ - pm->waterlevel = 3; - } - } - } - -} - -/* -============== -PM_CheckDuck - -Sets mins, maxs, and pm->ps->viewheight -============== -*/ -static void PM_CheckDuck (void) -{ - trace_t trace; - - if ( pm->ps->powerups[PW_INVULNERABILITY] ) { - if ( pm->ps->pm_flags & PMF_INVULEXPAND ) { - // invulnerability sphere has a 42 units radius - VectorSet( pm->mins, -42, -42, -42 ); - VectorSet( pm->maxs, 42, 42, 42 ); - } - else { - VectorSet( pm->mins, -15, -15, MINS_Z ); - VectorSet( pm->maxs, 15, 15, 16 ); - } - pm->ps->pm_flags |= PMF_DUCKED; - pm->ps->viewheight = CROUCH_VIEWHEIGHT; - return; - } - pm->ps->pm_flags &= ~PMF_INVULEXPAND; - - pm->mins[0] = -15; - pm->mins[1] = -15; - - pm->maxs[0] = 15; - pm->maxs[1] = 15; - - pm->mins[2] = MINS_Z; - - if (pm->ps->pm_type == PM_DEAD) - { - pm->maxs[2] = -8; - pm->ps->viewheight = DEAD_VIEWHEIGHT; - return; - } - - if (pm->cmd.upmove < 0) - { // duck - pm->ps->pm_flags |= PMF_DUCKED; - } - else - { // stand up if possible - if (pm->ps->pm_flags & PMF_DUCKED) - { - // try to stand up - pm->maxs[2] = 32; - pm->trace (&trace, pm->ps->origin, pm->mins, pm->maxs, pm->ps->origin, pm->ps->clientNum, pm->tracemask ); - if (!trace.allsolid) - pm->ps->pm_flags &= ~PMF_DUCKED; - } - } - - if (pm->ps->pm_flags & PMF_DUCKED) - { - pm->maxs[2] = 16; - pm->ps->viewheight = CROUCH_VIEWHEIGHT; - } - else - { - pm->maxs[2] = 32; - pm->ps->viewheight = DEFAULT_VIEWHEIGHT; - } -} - - - -//=================================================================== - - -/* -=============== -PM_Footsteps -=============== -*/ -static void PM_Footsteps( void ) { - float bobmove; - int old; - qboolean footstep; - - // - // calculate speed and cycle to be used for - // all cyclic walking effects - // - pm->xyspeed = sqrt( pm->ps->velocity[0] * pm->ps->velocity[0] - + pm->ps->velocity[1] * pm->ps->velocity[1] ); - - if ( pm->ps->groundEntityNum == ENTITYNUM_NONE ) { - - if ( pm->ps->powerups[PW_INVULNERABILITY] ) { - PM_ContinueLegsAnim( LEGS_IDLECR ); - } - // airborne leaves position in cycle intact, but doesn't advance - if ( pm->waterlevel > 1 ) { - PM_ContinueLegsAnim( LEGS_SWIM ); - } - return; - } - - // if not trying to move - if ( !pm->cmd.forwardmove && !pm->cmd.rightmove ) { - if ( pm->xyspeed < 5 ) { - pm->ps->bobCycle = 0; // start at beginning of cycle again - if ( pm->ps->pm_flags & PMF_DUCKED ) { - PM_ContinueLegsAnim( LEGS_IDLECR ); - } else { - PM_ContinueLegsAnim( LEGS_IDLE ); - } - } - return; - } - - - footstep = qfalse; - - if ( pm->ps->pm_flags & PMF_DUCKED ) { - bobmove = 0.5; // ducked characters bob much faster - if ( pm->ps->pm_flags & PMF_BACKWARDS_RUN ) { - PM_ContinueLegsAnim( LEGS_BACKCR ); - } - else { - PM_ContinueLegsAnim( LEGS_WALKCR ); - } - // ducked characters never play footsteps - /* - } else if ( pm->ps->pm_flags & PMF_BACKWARDS_RUN ) { - if ( !( pm->cmd.buttons & BUTTON_WALKING ) ) { - bobmove = 0.4; // faster speeds bob faster - footstep = qtrue; - } else { - bobmove = 0.3; - } - PM_ContinueLegsAnim( LEGS_BACK ); - */ - } else { - if ( !( pm->cmd.buttons & BUTTON_WALKING ) ) { - bobmove = 0.4f; // faster speeds bob faster - if ( pm->ps->pm_flags & PMF_BACKWARDS_RUN ) { - PM_ContinueLegsAnim( LEGS_BACK ); - } - else { - PM_ContinueLegsAnim( LEGS_RUN ); - } - footstep = qtrue; - } else { - bobmove = 0.3f; // walking bobs slow - if ( pm->ps->pm_flags & PMF_BACKWARDS_RUN ) { - PM_ContinueLegsAnim( LEGS_BACKWALK ); - } - else { - PM_ContinueLegsAnim( LEGS_WALK ); - } - } - } - - // check for footstep / splash sounds - old = pm->ps->bobCycle; - pm->ps->bobCycle = (int)( old + bobmove * pml.msec ) & 255; - - // if we just crossed a cycle boundary, play an apropriate footstep event - if ( ( ( old + 64 ) ^ ( pm->ps->bobCycle + 64 ) ) & 128 ) { - if ( pm->waterlevel == 0 ) { - // on ground will only play sounds if running - if ( footstep && !pm->noFootsteps ) { - PM_AddEvent( PM_FootstepForSurface() ); - } - } else if ( pm->waterlevel == 1 ) { - // splashing - PM_AddEvent( EV_FOOTSPLASH ); - } else if ( pm->waterlevel == 2 ) { - // wading / swimming at surface - PM_AddEvent( EV_SWIM ); - } else if ( pm->waterlevel == 3 ) { - // no sound when completely underwater - - } - } -} - -/* -============== -PM_WaterEvents - -Generate sound events for entering and leaving water -============== -*/ -static void PM_WaterEvents( void ) { // FIXME? - // - // if just entered a water volume, play a sound - // - if (!pml.previous_waterlevel && pm->waterlevel) { - PM_AddEvent( EV_WATER_TOUCH ); - } - - // - // if just completely exited a water volume, play a sound - // - if (pml.previous_waterlevel && !pm->waterlevel) { - PM_AddEvent( EV_WATER_LEAVE ); - } - - // - // check for head just going under water - // - if (pml.previous_waterlevel != 3 && pm->waterlevel == 3) { - PM_AddEvent( EV_WATER_UNDER ); - } - - // - // check for head just coming out of water - // - if (pml.previous_waterlevel == 3 && pm->waterlevel != 3) { - PM_AddEvent( EV_WATER_CLEAR ); - } -} - - -/* -=============== -PM_BeginWeaponChange -=============== -*/ -static void PM_BeginWeaponChange( int weapon ) { - if ( weapon <= WP_NONE || weapon >= WP_NUM_WEAPONS ) { - return; - } - - if ( !( pm->ps->stats[STAT_WEAPONS] & ( 1 << weapon ) ) ) { - return; - } - - if ( pm->ps->weaponstate == WEAPON_DROPPING ) { - return; - } - - PM_AddEvent( EV_CHANGE_WEAPON ); - pm->ps->weaponstate = WEAPON_DROPPING; - pm->ps->weaponTime += 200; - PM_StartTorsoAnim( TORSO_DROP ); -} - - -/* -=============== -PM_FinishWeaponChange -=============== -*/ -static void PM_FinishWeaponChange( void ) { - int weapon; - - weapon = pm->cmd.weapon; - if ( weapon < WP_NONE || weapon >= WP_NUM_WEAPONS ) { - weapon = WP_NONE; - } - - if ( !( pm->ps->stats[STAT_WEAPONS] & ( 1 << weapon ) ) ) { - weapon = WP_NONE; - } - - pm->ps->weapon = weapon; - pm->ps->weaponstate = WEAPON_RAISING; - pm->ps->weaponTime += 250; - PM_StartTorsoAnim( TORSO_RAISE ); -} - - -/* -============== -PM_TorsoAnimation - -============== -*/ -static void PM_TorsoAnimation( void ) { - if ( pm->ps->weaponstate == WEAPON_READY ) { - if ( pm->ps->weapon == WP_GAUNTLET ) { - PM_ContinueTorsoAnim( TORSO_STAND2 ); - } else { - PM_ContinueTorsoAnim( TORSO_STAND ); - } - return; - } -} - - -/* -============== -PM_Weapon - -Generates weapon events and modifes the weapon counter -============== -*/ -static void PM_Weapon( void ) { - int addTime; - - // don't allow attack until all buttons are up - if ( pm->ps->pm_flags & PMF_RESPAWNED ) { - return; - } - - // ignore if spectator - if ( pm->ps->persistant[PERS_TEAM] == TEAM_SPECTATOR ) { - return; - } - - // check for dead player - if ( pm->ps->stats[STAT_HEALTH] <= 0 ) { - pm->ps->weapon = WP_NONE; - return; - } - - // check for item using - if ( pm->cmd.buttons & BUTTON_USE_HOLDABLE ) { - if ( ! ( pm->ps->pm_flags & PMF_USE_ITEM_HELD ) ) { - if ( bg_itemlist[pm->ps->stats[STAT_HOLDABLE_ITEM]].giTag == HI_MEDKIT - && pm->ps->stats[STAT_HEALTH] >= (pm->ps->stats[STAT_MAX_HEALTH] + 25) ) { - // don't use medkit if at max health - } else { - pm->ps->pm_flags |= PMF_USE_ITEM_HELD; - PM_AddEvent( EV_USE_ITEM0 + bg_itemlist[pm->ps->stats[STAT_HOLDABLE_ITEM]].giTag ); - pm->ps->stats[STAT_HOLDABLE_ITEM] = 0; - } - return; - } - } else { - pm->ps->pm_flags &= ~PMF_USE_ITEM_HELD; - } - - - // make weapon function - if ( pm->ps->weaponTime > 0 ) { - pm->ps->weaponTime -= pml.msec; - } - - // check for weapon change - // can't change if weapon is firing, but can change - // again if lowering or raising - if ( pm->ps->weaponTime <= 0 || pm->ps->weaponstate != WEAPON_FIRING ) { - if ( pm->ps->weapon != pm->cmd.weapon ) { - PM_BeginWeaponChange( pm->cmd.weapon ); - } - } - - if ( pm->ps->weaponTime > 0 ) { - return; - } - - // change weapon if time - if ( pm->ps->weaponstate == WEAPON_DROPPING ) { - PM_FinishWeaponChange(); - return; - } - - if ( pm->ps->weaponstate == WEAPON_RAISING ) { - pm->ps->weaponstate = WEAPON_READY; - if ( pm->ps->weapon == WP_GAUNTLET ) { - PM_StartTorsoAnim( TORSO_STAND2 ); - } else { - PM_StartTorsoAnim( TORSO_STAND ); - } - return; - } - - // check for fire - if ( ! (pm->cmd.buttons & BUTTON_ATTACK) ) { - pm->ps->weaponTime = 0; - pm->ps->weaponstate = WEAPON_READY; - return; - } - - // start the animation even if out of ammo - if ( pm->ps->weapon == WP_GAUNTLET ) { - // the guantlet only "fires" when it actually hits something - if ( !pm->gauntletHit ) { - pm->ps->weaponTime = 0; - pm->ps->weaponstate = WEAPON_READY; - return; - } - PM_StartTorsoAnim( TORSO_ATTACK2 ); - } else { - PM_StartTorsoAnim( TORSO_ATTACK ); - } - - pm->ps->weaponstate = WEAPON_FIRING; - - // check for out of ammo - if ( ! pm->ps->ammo[ pm->ps->weapon ] ) { - PM_AddEvent( EV_NOAMMO ); - pm->ps->weaponTime += 500; - return; - } - - // take an ammo away if not infinite - if ( pm->ps->ammo[ pm->ps->weapon ] != -1 ) { - pm->ps->ammo[ pm->ps->weapon ]--; - } - - // fire weapon - PM_AddEvent( EV_FIRE_WEAPON ); - - switch( pm->ps->weapon ) { - default: - case WP_GAUNTLET: - addTime = 400; - break; - case WP_LIGHTNING: - addTime = 50; - break; - case WP_SHOTGUN: - addTime = 1000; - break; - case WP_MACHINEGUN: - addTime = 100; - break; - case WP_GRENADE_LAUNCHER: - addTime = 800; - break; - case WP_ROCKET_LAUNCHER: - addTime = 800; - break; - case WP_PLASMAGUN: - addTime = 100; - break; - case WP_RAILGUN: - addTime = 1500; - break; - case WP_BFG: - addTime = 200; - break; - case WP_GRAPPLING_HOOK: - addTime = 400; - break; -#ifdef MISSIONPACK - case WP_NAILGUN: - addTime = 1000; - break; - case WP_PROX_LAUNCHER: - addTime = 800; - break; - case WP_CHAINGUN: - addTime = 30; - break; -#endif - } - -#ifdef MISSIONPACK - if( bg_itemlist[pm->ps->stats[STAT_PERSISTANT_POWERUP]].giTag == PW_SCOUT ) { - addTime /= 1.5; - } - else - if( bg_itemlist[pm->ps->stats[STAT_PERSISTANT_POWERUP]].giTag == PW_AMMOREGEN ) { - addTime /= 1.3; - } - else -#endif - if ( pm->ps->powerups[PW_HASTE] ) { - addTime /= 1.3; - } - - pm->ps->weaponTime += addTime; -} - -/* -================ -PM_Animate -================ -*/ - -static void PM_Animate( void ) { - if ( pm->cmd.buttons & BUTTON_GESTURE ) { - if ( pm->ps->torsoTimer == 0 ) { - PM_StartTorsoAnim( TORSO_GESTURE ); - pm->ps->torsoTimer = TIMER_GESTURE; - PM_AddEvent( EV_TAUNT ); - } -#ifdef MISSIONPACK - } else if ( pm->cmd.buttons & BUTTON_GETFLAG ) { - if ( pm->ps->torsoTimer == 0 ) { - PM_StartTorsoAnim( TORSO_GETFLAG ); - pm->ps->torsoTimer = 600; //TIMER_GESTURE; - } - } else if ( pm->cmd.buttons & BUTTON_GUARDBASE ) { - if ( pm->ps->torsoTimer == 0 ) { - PM_StartTorsoAnim( TORSO_GUARDBASE ); - pm->ps->torsoTimer = 600; //TIMER_GESTURE; - } - } else if ( pm->cmd.buttons & BUTTON_PATROL ) { - if ( pm->ps->torsoTimer == 0 ) { - PM_StartTorsoAnim( TORSO_PATROL ); - pm->ps->torsoTimer = 600; //TIMER_GESTURE; - } - } else if ( pm->cmd.buttons & BUTTON_FOLLOWME ) { - if ( pm->ps->torsoTimer == 0 ) { - PM_StartTorsoAnim( TORSO_FOLLOWME ); - pm->ps->torsoTimer = 600; //TIMER_GESTURE; - } - } else if ( pm->cmd.buttons & BUTTON_AFFIRMATIVE ) { - if ( pm->ps->torsoTimer == 0 ) { - PM_StartTorsoAnim( TORSO_AFFIRMATIVE); - pm->ps->torsoTimer = 600; //TIMER_GESTURE; - } - } else if ( pm->cmd.buttons & BUTTON_NEGATIVE ) { - if ( pm->ps->torsoTimer == 0 ) { - PM_StartTorsoAnim( TORSO_NEGATIVE ); - pm->ps->torsoTimer = 600; //TIMER_GESTURE; - } -#endif - } -} - - -/* -================ -PM_DropTimers -================ -*/ -static void PM_DropTimers( void ) { - // drop misc timing counter - if ( pm->ps->pm_time ) { - if ( pml.msec >= pm->ps->pm_time ) { - pm->ps->pm_flags &= ~PMF_ALL_TIMES; - pm->ps->pm_time = 0; - } else { - pm->ps->pm_time -= pml.msec; - } - } - - // drop animation counter - if ( pm->ps->legsTimer > 0 ) { - pm->ps->legsTimer -= pml.msec; - if ( pm->ps->legsTimer < 0 ) { - pm->ps->legsTimer = 0; - } - } - - if ( pm->ps->torsoTimer > 0 ) { - pm->ps->torsoTimer -= pml.msec; - if ( pm->ps->torsoTimer < 0 ) { - pm->ps->torsoTimer = 0; - } - } -} - -/* -================ -PM_UpdateViewAngles - -This can be used as another entry point when only the viewangles -are being updated isntead of a full move -================ -*/ -void PM_UpdateViewAngles( playerState_t *ps, const usercmd_t *cmd ) { - short temp; - int i; - - if ( ps->pm_type == PM_INTERMISSION || ps->pm_type == PM_SPINTERMISSION) { - return; // no view changes at all - } - - if ( ps->pm_type != PM_SPECTATOR && ps->stats[STAT_HEALTH] <= 0 ) { - return; // no view changes at all - } - - // circularly clamp the angles with deltas - for (i=0 ; i<3 ; i++) { - temp = cmd->angles[i] + ps->delta_angles[i]; - if ( i == PITCH ) { - // don't let the player look up or down more than 90 degrees - if ( temp > 16000 ) { - ps->delta_angles[i] = 16000 - cmd->angles[i]; - temp = 16000; - } else if ( temp < -16000 ) { - ps->delta_angles[i] = -16000 - cmd->angles[i]; - temp = -16000; - } - } - ps->viewangles[i] = SHORT2ANGLE(temp); - } - -} - - -/* -================ -PmoveSingle - -================ -*/ -void trap_SnapVector( float *v ); - -void PmoveSingle (pmove_t *pmove) { - pm = pmove; - - // this counter lets us debug movement problems with a journal - // by setting a conditional breakpoint fot the previous frame - c_pmove++; - - // clear results - pm->numtouch = 0; - pm->watertype = 0; - pm->waterlevel = 0; - - if ( pm->ps->stats[STAT_HEALTH] <= 0 ) { - pm->tracemask &= ~CONTENTS_BODY; // corpses can fly through bodies - } - - // make sure walking button is clear if they are running, to avoid - // proxy no-footsteps cheats - if ( abs( pm->cmd.forwardmove ) > 64 || abs( pm->cmd.rightmove ) > 64 ) { - pm->cmd.buttons &= ~BUTTON_WALKING; - } - - // set the talk balloon flag - if ( pm->cmd.buttons & BUTTON_TALK ) { - pm->ps->eFlags |= EF_TALK; - } else { - pm->ps->eFlags &= ~EF_TALK; - } - - // set the firing flag for continuous beam weapons - if ( !(pm->ps->pm_flags & PMF_RESPAWNED) && pm->ps->pm_type != PM_INTERMISSION - && ( pm->cmd.buttons & BUTTON_ATTACK ) && pm->ps->ammo[ pm->ps->weapon ] ) { - pm->ps->eFlags |= EF_FIRING; - } else { - pm->ps->eFlags &= ~EF_FIRING; - } - - // clear the respawned flag if attack and use are cleared - if ( pm->ps->stats[STAT_HEALTH] > 0 && - !( pm->cmd.buttons & (BUTTON_ATTACK | BUTTON_USE_HOLDABLE) ) ) { - pm->ps->pm_flags &= ~PMF_RESPAWNED; - } - - // if talk button is down, dissallow all other input - // this is to prevent any possible intercept proxy from - // adding fake talk balloons - if ( pmove->cmd.buttons & BUTTON_TALK ) { - // keep the talk button set tho for when the cmd.serverTime > 66 msec - // and the same cmd is used multiple times in Pmove - pmove->cmd.buttons = BUTTON_TALK; - pmove->cmd.forwardmove = 0; - pmove->cmd.rightmove = 0; - pmove->cmd.upmove = 0; - } - - // clear all pmove local vars - memset (&pml, 0, sizeof(pml)); - - // determine the time - pml.msec = pmove->cmd.serverTime - pm->ps->commandTime; - if ( pml.msec < 1 ) { - pml.msec = 1; - } else if ( pml.msec > 200 ) { - pml.msec = 200; - } - pm->ps->commandTime = pmove->cmd.serverTime; - - // save old org in case we get stuck - VectorCopy (pm->ps->origin, pml.previous_origin); - - // save old velocity for crashlanding - VectorCopy (pm->ps->velocity, pml.previous_velocity); - - pml.frametime = pml.msec * 0.001; - - // update the viewangles - PM_UpdateViewAngles( pm->ps, &pm->cmd ); - - AngleVectors (pm->ps->viewangles, pml.forward, pml.right, pml.up); - - if ( pm->cmd.upmove < 10 ) { - // not holding jump - pm->ps->pm_flags &= ~PMF_JUMP_HELD; - } - - // decide if backpedaling animations should be used - if ( pm->cmd.forwardmove < 0 ) { - pm->ps->pm_flags |= PMF_BACKWARDS_RUN; - } else if ( pm->cmd.forwardmove > 0 || ( pm->cmd.forwardmove == 0 && pm->cmd.rightmove ) ) { - pm->ps->pm_flags &= ~PMF_BACKWARDS_RUN; - } - - if ( pm->ps->pm_type >= PM_DEAD ) { - pm->cmd.forwardmove = 0; - pm->cmd.rightmove = 0; - pm->cmd.upmove = 0; - } - - if ( pm->ps->pm_type == PM_SPECTATOR ) { - PM_CheckDuck (); - PM_FlyMove (); - PM_DropTimers (); - return; - } - - if ( pm->ps->pm_type == PM_NOCLIP ) { - PM_NoclipMove (); - PM_DropTimers (); - return; - } - - if (pm->ps->pm_type == PM_FREEZE) { - return; // no movement at all - } - - if ( pm->ps->pm_type == PM_INTERMISSION || pm->ps->pm_type == PM_SPINTERMISSION) { - return; // no movement at all - } - - // set watertype, and waterlevel - PM_SetWaterLevel(); - pml.previous_waterlevel = pmove->waterlevel; - - // set mins, maxs, and viewheight - PM_CheckDuck (); - - // set groundentity - PM_GroundTrace(); - - if ( pm->ps->pm_type == PM_DEAD ) { - PM_DeadMove (); - } - - PM_DropTimers(); - -#ifdef MISSIONPACK - if ( pm->ps->powerups[PW_INVULNERABILITY] ) { - PM_InvulnerabilityMove(); - } else -#endif - if ( pm->ps->powerups[PW_FLIGHT] ) { - // flight powerup doesn't allow jump and has different friction - PM_FlyMove(); - } else if (pm->ps->pm_flags & PMF_GRAPPLE_PULL) { - PM_GrappleMove(); - // We can wiggle a bit - PM_AirMove(); - } else if (pm->ps->pm_flags & PMF_TIME_WATERJUMP) { - PM_WaterJumpMove(); - } else if ( pm->waterlevel > 1 ) { - // swimming - PM_WaterMove(); - } else if ( pml.walking ) { - // walking on ground - PM_WalkMove(); - } else { - // airborne - PM_AirMove(); - } - - PM_Animate(); - - // set groundentity, watertype, and waterlevel - PM_GroundTrace(); - PM_SetWaterLevel(); - - // weapons - PM_Weapon(); - - // torso animation - PM_TorsoAnimation(); - - // footstep events / legs animations - PM_Footsteps(); - - // entering / leaving water splashes - PM_WaterEvents(); - - // snap some parts of playerstate to save network bandwidth - trap_SnapVector( pm->ps->velocity ); -} - - -/* -================ -Pmove - -Can be called by either the server or the client -================ -*/ -void Pmove (pmove_t *pmove) { - int finalTime; - - finalTime = pmove->cmd.serverTime; - - if ( finalTime < pmove->ps->commandTime ) { - return; // should not happen - } - - if ( finalTime > pmove->ps->commandTime + 1000 ) { - pmove->ps->commandTime = finalTime - 1000; - } - - pmove->ps->pmove_framecount = (pmove->ps->pmove_framecount+1) & ((1<ps->commandTime != finalTime ) { - int msec; - - msec = finalTime - pmove->ps->commandTime; - - if ( pmove->pmove_fixed ) { - if ( msec > pmove->pmove_msec ) { - msec = pmove->pmove_msec; - } - } - else { - if ( msec > 66 ) { - msec = 66; - } - } - pmove->cmd.serverTime = pmove->ps->commandTime + msec; - PmoveSingle( pmove ); - - if ( pmove->ps->pm_flags & PMF_JUMP_HELD ) { - pmove->cmd.upmove = 20; - } - } - - //PM_CheckStuck(); - -} - +/* +=========================================================================== +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 Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// +// bg_pmove.c -- both games player movement code +// takes a playerstate and a usercmd as input and returns a modifed playerstate + +#include "q_shared.h" +#include "bg_public.h" +#include "bg_local.h" + +pmove_t *pm; +pml_t pml; + +// movement parameters +float pm_stopspeed = 100.0f; +float pm_duckScale = 0.25f; +float pm_swimScale = 0.50f; +float pm_wadeScale = 0.70f; + +float pm_accelerate = 10.0f; +float pm_airaccelerate = 1.0f; +float pm_wateraccelerate = 4.0f; +float pm_flyaccelerate = 8.0f; + +float pm_friction = 6.0f; +float pm_waterfriction = 1.0f; +float pm_flightfriction = 3.0f; +float pm_spectatorfriction = 5.0f; + +int c_pmove = 0; + + +/* +=============== +PM_AddEvent + +=============== +*/ +void PM_AddEvent( int newEvent ) { + BG_AddPredictableEventToPlayerstate( newEvent, 0, pm->ps ); +} + +/* +=============== +PM_AddTouchEnt +=============== +*/ +void PM_AddTouchEnt( int entityNum ) { + int i; + + if ( entityNum == ENTITYNUM_WORLD ) { + return; + } + if ( pm->numtouch == MAXTOUCH ) { + return; + } + + // see if it is already added + for ( i = 0 ; i < pm->numtouch ; i++ ) { + if ( pm->touchents[ i ] == entityNum ) { + return; + } + } + + // add it + pm->touchents[pm->numtouch] = entityNum; + pm->numtouch++; +} + +/* +=================== +PM_StartTorsoAnim +=================== +*/ +static void PM_StartTorsoAnim( int anim ) { + if ( pm->ps->pm_type >= PM_DEAD ) { + return; + } + pm->ps->torsoAnim = ( ( pm->ps->torsoAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) + | anim; +} +static void PM_StartLegsAnim( int anim ) { + if ( pm->ps->pm_type >= PM_DEAD ) { + return; + } + if ( pm->ps->legsTimer > 0 ) { + return; // a high priority animation is running + } + pm->ps->legsAnim = ( ( pm->ps->legsAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) + | anim; +} + +static void PM_ContinueLegsAnim( int anim ) { + if ( ( pm->ps->legsAnim & ~ANIM_TOGGLEBIT ) == anim ) { + return; + } + if ( pm->ps->legsTimer > 0 ) { + return; // a high priority animation is running + } + PM_StartLegsAnim( anim ); +} + +static void PM_ContinueTorsoAnim( int anim ) { + if ( ( pm->ps->torsoAnim & ~ANIM_TOGGLEBIT ) == anim ) { + return; + } + if ( pm->ps->torsoTimer > 0 ) { + return; // a high priority animation is running + } + PM_StartTorsoAnim( anim ); +} + +static void PM_ForceLegsAnim( int anim ) { + pm->ps->legsTimer = 0; + PM_StartLegsAnim( anim ); +} + + +/* +================== +PM_ClipVelocity + +Slide off of the impacting surface +================== +*/ +void PM_ClipVelocity( vec3_t in, vec3_t normal, vec3_t out, float overbounce ) { + float backoff; + float change; + int i; + + backoff = DotProduct (in, normal); + + if ( backoff < 0 ) { + backoff *= overbounce; + } else { + backoff /= overbounce; + } + + for ( i=0 ; i<3 ; i++ ) { + change = normal[i]*backoff; + out[i] = in[i] - change; + } +} + + +/* +================== +PM_Friction + +Handles both ground friction and water friction +================== +*/ +static void PM_Friction( void ) { + vec3_t vec; + float *vel; + float speed, newspeed, control; + float drop; + + vel = pm->ps->velocity; + + VectorCopy( vel, vec ); + if ( pml.walking ) { + vec[2] = 0; // ignore slope movement + } + + speed = VectorLength(vec); + if (speed < 1) { + vel[0] = 0; + vel[1] = 0; // allow sinking underwater + // FIXME: still have z friction underwater? + return; + } + + drop = 0; + + // apply ground friction + if ( pm->waterlevel <= 1 ) { + if ( pml.walking && !(pml.groundTrace.surfaceFlags & SURF_SLICK) ) { + // if getting knocked back, no friction + if ( ! (pm->ps->pm_flags & PMF_TIME_KNOCKBACK) ) { + control = speed < pm_stopspeed ? pm_stopspeed : speed; + drop += control*pm_friction*pml.frametime; + } + } + } + + // apply water friction even if just wading + if ( pm->waterlevel ) { + drop += speed*pm_waterfriction*pm->waterlevel*pml.frametime; + } + + // apply flying friction + if ( pm->ps->powerups[PW_FLIGHT]) { + drop += speed*pm_flightfriction*pml.frametime; + } + + if ( pm->ps->pm_type == PM_SPECTATOR) { + drop += speed*pm_spectatorfriction*pml.frametime; + } + + // scale the velocity + newspeed = speed - drop; + if (newspeed < 0) { + newspeed = 0; + } + newspeed /= speed; + + vel[0] = vel[0] * newspeed; + vel[1] = vel[1] * newspeed; + vel[2] = vel[2] * newspeed; +} + + +/* +============== +PM_Accelerate + +Handles user intended acceleration +============== +*/ +static void PM_Accelerate( vec3_t wishdir, float wishspeed, float accel ) { +#if 1 + // q2 style + int i; + float addspeed, accelspeed, currentspeed; + + currentspeed = DotProduct (pm->ps->velocity, wishdir); + addspeed = wishspeed - currentspeed; + if (addspeed <= 0) { + return; + } + accelspeed = accel*pml.frametime*wishspeed; + if (accelspeed > addspeed) { + accelspeed = addspeed; + } + + for (i=0 ; i<3 ; i++) { + pm->ps->velocity[i] += accelspeed*wishdir[i]; + } +#else + // proper way (avoids strafe jump maxspeed bug), but feels bad + vec3_t wishVelocity; + vec3_t pushDir; + float pushLen; + float canPush; + + VectorScale( wishdir, wishspeed, wishVelocity ); + VectorSubtract( wishVelocity, pm->ps->velocity, pushDir ); + pushLen = VectorNormalize( pushDir ); + + canPush = accel*pml.frametime*wishspeed; + if (canPush > pushLen) { + canPush = pushLen; + } + + VectorMA( pm->ps->velocity, canPush, pushDir, pm->ps->velocity ); +#endif +} + + + +/* +============ +PM_CmdScale + +Returns the scale factor to apply to cmd movements +This allows the clients to use axial -127 to 127 values for all directions +without getting a sqrt(2) distortion in speed. +============ +*/ +static float PM_CmdScale( usercmd_t *cmd ) { + int max; + float total; + float scale; + + max = abs( cmd->forwardmove ); + if ( abs( cmd->rightmove ) > max ) { + max = abs( cmd->rightmove ); + } + if ( abs( cmd->upmove ) > max ) { + max = abs( cmd->upmove ); + } + if ( !max ) { + return 0; + } + + total = sqrt( cmd->forwardmove * cmd->forwardmove + + cmd->rightmove * cmd->rightmove + cmd->upmove * cmd->upmove ); + scale = (float)pm->ps->speed * max / ( 127.0 * total ); + + return scale; +} + + +/* +================ +PM_SetMovementDir + +Determine the rotation of the legs reletive +to the facing dir +================ +*/ +static void PM_SetMovementDir( void ) { + if ( pm->cmd.forwardmove || pm->cmd.rightmove ) { + if ( pm->cmd.rightmove == 0 && pm->cmd.forwardmove > 0 ) { + pm->ps->movementDir = 0; + } else if ( pm->cmd.rightmove < 0 && pm->cmd.forwardmove > 0 ) { + pm->ps->movementDir = 1; + } else if ( pm->cmd.rightmove < 0 && pm->cmd.forwardmove == 0 ) { + pm->ps->movementDir = 2; + } else if ( pm->cmd.rightmove < 0 && pm->cmd.forwardmove < 0 ) { + pm->ps->movementDir = 3; + } else if ( pm->cmd.rightmove == 0 && pm->cmd.forwardmove < 0 ) { + pm->ps->movementDir = 4; + } else if ( pm->cmd.rightmove > 0 && pm->cmd.forwardmove < 0 ) { + pm->ps->movementDir = 5; + } else if ( pm->cmd.rightmove > 0 && pm->cmd.forwardmove == 0 ) { + pm->ps->movementDir = 6; + } else if ( pm->cmd.rightmove > 0 && pm->cmd.forwardmove > 0 ) { + pm->ps->movementDir = 7; + } + } else { + // if they aren't actively going directly sideways, + // change the animation to the diagonal so they + // don't stop too crooked + if ( pm->ps->movementDir == 2 ) { + pm->ps->movementDir = 1; + } else if ( pm->ps->movementDir == 6 ) { + pm->ps->movementDir = 7; + } + } +} + + +/* +============= +PM_CheckJump +============= +*/ +static qboolean PM_CheckJump( void ) { + if ( pm->ps->pm_flags & PMF_RESPAWNED ) { + return qfalse; // don't allow jump until all buttons are up + } + + if ( pm->cmd.upmove < 10 ) { + // not holding jump + return qfalse; + } + + // must wait for jump to be released + if ( pm->ps->pm_flags & PMF_JUMP_HELD ) { + // clear upmove so cmdscale doesn't lower running speed + pm->cmd.upmove = 0; + return qfalse; + } + + pml.groundPlane = qfalse; // jumping away + pml.walking = qfalse; + pm->ps->pm_flags |= PMF_JUMP_HELD; + + pm->ps->groundEntityNum = ENTITYNUM_NONE; + pm->ps->velocity[2] = JUMP_VELOCITY; + PM_AddEvent( EV_JUMP ); + + if ( pm->cmd.forwardmove >= 0 ) { + PM_ForceLegsAnim( LEGS_JUMP ); + pm->ps->pm_flags &= ~PMF_BACKWARDS_JUMP; + } else { + PM_ForceLegsAnim( LEGS_JUMPB ); + pm->ps->pm_flags |= PMF_BACKWARDS_JUMP; + } + + return qtrue; +} + +/* +============= +PM_CheckWaterJump +============= +*/ +static qboolean PM_CheckWaterJump( void ) { + vec3_t spot; + int cont; + vec3_t flatforward; + + if (pm->ps->pm_time) { + return qfalse; + } + + // check for water jump + if ( pm->waterlevel != 2 ) { + return qfalse; + } + + flatforward[0] = pml.forward[0]; + flatforward[1] = pml.forward[1]; + flatforward[2] = 0; + VectorNormalize (flatforward); + + VectorMA (pm->ps->origin, 30, flatforward, spot); + spot[2] += 4; + cont = pm->pointcontents (spot, pm->ps->clientNum ); + if ( !(cont & CONTENTS_SOLID) ) { + return qfalse; + } + + spot[2] += 16; + cont = pm->pointcontents (spot, pm->ps->clientNum ); + if ( cont ) { + return qfalse; + } + + // jump out of water + VectorScale (pml.forward, 200, pm->ps->velocity); + pm->ps->velocity[2] = 350; + + pm->ps->pm_flags |= PMF_TIME_WATERJUMP; + pm->ps->pm_time = 2000; + + return qtrue; +} + +//============================================================================ + + +/* +=================== +PM_WaterJumpMove + +Flying out of the water +=================== +*/ +static void PM_WaterJumpMove( void ) { + // waterjump has no control, but falls + + PM_StepSlideMove( qtrue ); + + pm->ps->velocity[2] -= pm->ps->gravity * pml.frametime; + if (pm->ps->velocity[2] < 0) { + // cancel as soon as we are falling down again + pm->ps->pm_flags &= ~PMF_ALL_TIMES; + pm->ps->pm_time = 0; + } +} + +/* +=================== +PM_WaterMove + +=================== +*/ +static void PM_WaterMove( void ) { + int i; + vec3_t wishvel; + float wishspeed; + vec3_t wishdir; + float scale; + float vel; + + if ( PM_CheckWaterJump() ) { + PM_WaterJumpMove(); + return; + } +#if 0 + // jump = head for surface + if ( pm->cmd.upmove >= 10 ) { + if (pm->ps->velocity[2] > -300) { + if ( pm->watertype == CONTENTS_WATER ) { + pm->ps->velocity[2] = 100; + } else if (pm->watertype == CONTENTS_SLIME) { + pm->ps->velocity[2] = 80; + } else { + pm->ps->velocity[2] = 50; + } + } + } +#endif + PM_Friction (); + + scale = PM_CmdScale( &pm->cmd ); + // + // user intentions + // + if ( !scale ) { + wishvel[0] = 0; + wishvel[1] = 0; + wishvel[2] = -60; // sink towards bottom + } else { + for (i=0 ; i<3 ; i++) + wishvel[i] = scale * pml.forward[i]*pm->cmd.forwardmove + scale * pml.right[i]*pm->cmd.rightmove; + + wishvel[2] += scale * pm->cmd.upmove; + } + + VectorCopy (wishvel, wishdir); + wishspeed = VectorNormalize(wishdir); + + if ( wishspeed > pm->ps->speed * pm_swimScale ) { + wishspeed = pm->ps->speed * pm_swimScale; + } + + PM_Accelerate (wishdir, wishspeed, pm_wateraccelerate); + + // make sure we can go up slopes easily under water + if ( pml.groundPlane && DotProduct( pm->ps->velocity, pml.groundTrace.plane.normal ) < 0 ) { + vel = VectorLength(pm->ps->velocity); + // slide along the ground plane + PM_ClipVelocity (pm->ps->velocity, pml.groundTrace.plane.normal, + pm->ps->velocity, OVERCLIP ); + + VectorNormalize(pm->ps->velocity); + VectorScale(pm->ps->velocity, vel, pm->ps->velocity); + } + + PM_SlideMove( qfalse ); +} + +#ifdef MISSIONPACK +/* +=================== +PM_InvulnerabilityMove + +Only with the invulnerability powerup +=================== +*/ +static void PM_InvulnerabilityMove( void ) { + pm->cmd.forwardmove = 0; + pm->cmd.rightmove = 0; + pm->cmd.upmove = 0; + VectorClear(pm->ps->velocity); +} +#endif + +/* +=================== +PM_FlyMove + +Only with the flight powerup +=================== +*/ +static void PM_FlyMove( void ) { + int i; + vec3_t wishvel; + float wishspeed; + vec3_t wishdir; + float scale; + + // normal slowdown + PM_Friction (); + + scale = PM_CmdScale( &pm->cmd ); + // + // user intentions + // + if ( !scale ) { + wishvel[0] = 0; + wishvel[1] = 0; + wishvel[2] = 0; + } else { + for (i=0 ; i<3 ; i++) { + wishvel[i] = scale * pml.forward[i]*pm->cmd.forwardmove + scale * pml.right[i]*pm->cmd.rightmove; + } + + wishvel[2] += scale * pm->cmd.upmove; + } + + VectorCopy (wishvel, wishdir); + wishspeed = VectorNormalize(wishdir); + + PM_Accelerate (wishdir, wishspeed, pm_flyaccelerate); + + PM_StepSlideMove( qfalse ); +} + + +/* +=================== +PM_AirMove + +=================== +*/ +static void PM_AirMove( void ) { + int i; + vec3_t wishvel; + float fmove, smove; + vec3_t wishdir; + float wishspeed; + float scale; + usercmd_t cmd; + + PM_Friction(); + + fmove = pm->cmd.forwardmove; + smove = pm->cmd.rightmove; + + cmd = pm->cmd; + scale = PM_CmdScale( &cmd ); + + // set the movementDir so clients can rotate the legs for strafing + PM_SetMovementDir(); + + // project moves down to flat plane + pml.forward[2] = 0; + pml.right[2] = 0; + VectorNormalize (pml.forward); + VectorNormalize (pml.right); + + for ( i = 0 ; i < 2 ; i++ ) { + wishvel[i] = pml.forward[i]*fmove + pml.right[i]*smove; + } + wishvel[2] = 0; + + VectorCopy (wishvel, wishdir); + wishspeed = VectorNormalize(wishdir); + wishspeed *= scale; + + // not on ground, so little effect on velocity + PM_Accelerate (wishdir, wishspeed, pm_airaccelerate); + + // we may have a ground plane that is very steep, even + // though we don't have a groundentity + // slide along the steep plane + if ( pml.groundPlane ) { + PM_ClipVelocity (pm->ps->velocity, pml.groundTrace.plane.normal, + pm->ps->velocity, OVERCLIP ); + } + +#if 0 + //ZOID: If we are on the grapple, try stair-stepping + //this allows a player to use the grapple to pull himself + //over a ledge + if (pm->ps->pm_flags & PMF_GRAPPLE_PULL) + PM_StepSlideMove ( qtrue ); + else + PM_SlideMove ( qtrue ); +#endif + + PM_StepSlideMove ( qtrue ); +} + +/* +=================== +PM_GrappleMove + +=================== +*/ +static void PM_GrappleMove( void ) { + vec3_t vel, v; + float vlen; + + VectorScale(pml.forward, -16, v); + VectorAdd(pm->ps->grapplePoint, v, v); + VectorSubtract(v, pm->ps->origin, vel); + vlen = VectorLength(vel); + VectorNormalize( vel ); + + if (vlen <= 100) + VectorScale(vel, 10 * vlen, vel); + else + VectorScale(vel, 800, vel); + + VectorCopy(vel, pm->ps->velocity); + + pml.groundPlane = qfalse; +} + +/* +=================== +PM_WalkMove + +=================== +*/ +static void PM_WalkMove( void ) { + int i; + vec3_t wishvel; + float fmove, smove; + vec3_t wishdir; + float wishspeed; + float scale; + usercmd_t cmd; + float accelerate; + float vel; + + if ( pm->waterlevel > 2 && DotProduct( pml.forward, pml.groundTrace.plane.normal ) > 0 ) { + // begin swimming + PM_WaterMove(); + return; + } + + + if ( PM_CheckJump () ) { + // jumped away + if ( pm->waterlevel > 1 ) { + PM_WaterMove(); + } else { + PM_AirMove(); + } + return; + } + + PM_Friction (); + + fmove = pm->cmd.forwardmove; + smove = pm->cmd.rightmove; + + cmd = pm->cmd; + scale = PM_CmdScale( &cmd ); + + // set the movementDir so clients can rotate the legs for strafing + PM_SetMovementDir(); + + // project moves down to flat plane + pml.forward[2] = 0; + pml.right[2] = 0; + + // project the forward and right directions onto the ground plane + PM_ClipVelocity (pml.forward, pml.groundTrace.plane.normal, pml.forward, OVERCLIP ); + PM_ClipVelocity (pml.right, pml.groundTrace.plane.normal, pml.right, OVERCLIP ); + // + VectorNormalize (pml.forward); + VectorNormalize (pml.right); + + for ( i = 0 ; i < 3 ; i++ ) { + wishvel[i] = pml.forward[i]*fmove + pml.right[i]*smove; + } + // when going up or down slopes the wish velocity should Not be zero +// wishvel[2] = 0; + + VectorCopy (wishvel, wishdir); + wishspeed = VectorNormalize(wishdir); + wishspeed *= scale; + + // clamp the speed lower if ducking + if ( pm->ps->pm_flags & PMF_DUCKED ) { + if ( wishspeed > pm->ps->speed * pm_duckScale ) { + wishspeed = pm->ps->speed * pm_duckScale; + } + } + + // clamp the speed lower if wading or walking on the bottom + if ( pm->waterlevel ) { + float waterScale; + + waterScale = pm->waterlevel / 3.0; + waterScale = 1.0 - ( 1.0 - pm_swimScale ) * waterScale; + if ( wishspeed > pm->ps->speed * waterScale ) { + wishspeed = pm->ps->speed * waterScale; + } + } + + // when a player gets hit, they temporarily lose + // full control, which allows them to be moved a bit + if ( ( pml.groundTrace.surfaceFlags & SURF_SLICK ) || pm->ps->pm_flags & PMF_TIME_KNOCKBACK ) { + accelerate = pm_airaccelerate; + } else { + accelerate = pm_accelerate; + } + + PM_Accelerate (wishdir, wishspeed, accelerate); + + //Com_Printf("velocity = %1.1f %1.1f %1.1f\n", pm->ps->velocity[0], pm->ps->velocity[1], pm->ps->velocity[2]); + //Com_Printf("velocity1 = %1.1f\n", VectorLength(pm->ps->velocity)); + + if ( ( pml.groundTrace.surfaceFlags & SURF_SLICK ) || pm->ps->pm_flags & PMF_TIME_KNOCKBACK ) { + pm->ps->velocity[2] -= pm->ps->gravity * pml.frametime; + } else { + // don't reset the z velocity for slopes +// pm->ps->velocity[2] = 0; + } + + vel = VectorLength(pm->ps->velocity); + + // slide along the ground plane + PM_ClipVelocity (pm->ps->velocity, pml.groundTrace.plane.normal, + pm->ps->velocity, OVERCLIP ); + + // don't decrease velocity when going up or down a slope + VectorNormalize(pm->ps->velocity); + VectorScale(pm->ps->velocity, vel, pm->ps->velocity); + + // don't do anything if standing still + if (!pm->ps->velocity[0] && !pm->ps->velocity[1]) { + return; + } + + PM_StepSlideMove( qfalse ); + + //Com_Printf("velocity2 = %1.1f\n", VectorLength(pm->ps->velocity)); + +} + + +/* +============== +PM_DeadMove +============== +*/ +static void PM_DeadMove( void ) { + float forward; + + if ( !pml.walking ) { + return; + } + + // extra friction + + forward = VectorLength (pm->ps->velocity); + forward -= 20; + if ( forward <= 0 ) { + VectorClear (pm->ps->velocity); + } else { + VectorNormalize (pm->ps->velocity); + VectorScale (pm->ps->velocity, forward, pm->ps->velocity); + } +} + + +/* +=============== +PM_NoclipMove +=============== +*/ +static void PM_NoclipMove( void ) { + float speed, drop, friction, control, newspeed; + int i; + vec3_t wishvel; + float fmove, smove; + vec3_t wishdir; + float wishspeed; + float scale; + + pm->ps->viewheight = DEFAULT_VIEWHEIGHT; + + // friction + + speed = VectorLength (pm->ps->velocity); + if (speed < 1) + { + VectorCopy (vec3_origin, pm->ps->velocity); + } + else + { + drop = 0; + + friction = pm_friction*1.5; // extra friction + control = speed < pm_stopspeed ? pm_stopspeed : speed; + drop += control*friction*pml.frametime; + + // scale the velocity + newspeed = speed - drop; + if (newspeed < 0) + newspeed = 0; + newspeed /= speed; + + VectorScale (pm->ps->velocity, newspeed, pm->ps->velocity); + } + + // accelerate + scale = PM_CmdScale( &pm->cmd ); + + fmove = pm->cmd.forwardmove; + smove = pm->cmd.rightmove; + + for (i=0 ; i<3 ; i++) + wishvel[i] = pml.forward[i]*fmove + pml.right[i]*smove; + wishvel[2] += pm->cmd.upmove; + + VectorCopy (wishvel, wishdir); + wishspeed = VectorNormalize(wishdir); + wishspeed *= scale; + + PM_Accelerate( wishdir, wishspeed, pm_accelerate ); + + // move + VectorMA (pm->ps->origin, pml.frametime, pm->ps->velocity, pm->ps->origin); +} + +//============================================================================ + +/* +================ +PM_FootstepForSurface + +Returns an event number apropriate for the groundsurface +================ +*/ +static int PM_FootstepForSurface( void ) { + if ( pml.groundTrace.surfaceFlags & SURF_NOSTEPS ) { + return 0; + } + if ( pml.groundTrace.surfaceFlags & SURF_METALSTEPS ) { + return EV_FOOTSTEP_METAL; + } + return EV_FOOTSTEP; +} + + +/* +================= +PM_CrashLand + +Check for hard landings that generate sound events +================= +*/ +static void PM_CrashLand( void ) { + float delta; + float dist; + float vel, acc; + float t; + float a, b, c, den; + + // decide which landing animation to use + if ( pm->ps->pm_flags & PMF_BACKWARDS_JUMP ) { + PM_ForceLegsAnim( LEGS_LANDB ); + } else { + PM_ForceLegsAnim( LEGS_LAND ); + } + + pm->ps->legsTimer = TIMER_LAND; + + // calculate the exact velocity on landing + dist = pm->ps->origin[2] - pml.previous_origin[2]; + vel = pml.previous_velocity[2]; + acc = -pm->ps->gravity; + + a = acc / 2; + b = vel; + c = -dist; + + den = b * b - 4 * a * c; + if ( den < 0 ) { + return; + } + t = (-b - sqrt( den ) ) / ( 2 * a ); + + delta = vel + t * acc; + delta = delta*delta * 0.0001; + + // ducking while falling doubles damage + if ( pm->ps->pm_flags & PMF_DUCKED ) { + delta *= 2; + } + + // never take falling damage if completely underwater + if ( pm->waterlevel == 3 ) { + return; + } + + // reduce falling damage if there is standing water + if ( pm->waterlevel == 2 ) { + delta *= 0.25; + } + if ( pm->waterlevel == 1 ) { + delta *= 0.5; + } + + if ( delta < 1 ) { + return; + } + + // create a local entity event to play the sound + + // SURF_NODAMAGE is used for bounce pads where you don't ever + // want to take damage or play a crunch sound + if ( !(pml.groundTrace.surfaceFlags & SURF_NODAMAGE) ) { + if ( delta > 60 ) { + PM_AddEvent( EV_FALL_FAR ); + } else if ( delta > 40 ) { + // this is a pain grunt, so don't play it if dead + if ( pm->ps->stats[STAT_HEALTH] > 0 ) { + PM_AddEvent( EV_FALL_MEDIUM ); + } + } else if ( delta > 7 ) { + PM_AddEvent( EV_FALL_SHORT ); + } else { + PM_AddEvent( PM_FootstepForSurface() ); + } + } + + // start footstep cycle over + pm->ps->bobCycle = 0; +} + +/* +============= +PM_CheckStuck +============= +*/ +/* +void PM_CheckStuck(void) { + trace_t trace; + + pm->trace (&trace, pm->ps->origin, pm->mins, pm->maxs, pm->ps->origin, pm->ps->clientNum, pm->tracemask); + if (trace.allsolid) { + //int shit = qtrue; + } +} +*/ + +/* +============= +PM_CorrectAllSolid +============= +*/ +static int PM_CorrectAllSolid( trace_t *trace ) { + int i, j, k; + vec3_t point; + + if ( pm->debugLevel ) { + Com_Printf("%i:allsolid\n", c_pmove); + } + + // jitter around + for (i = -1; i <= 1; i++) { + for (j = -1; j <= 1; j++) { + for (k = -1; k <= 1; k++) { + VectorCopy(pm->ps->origin, point); + point[0] += (float) i; + point[1] += (float) j; + point[2] += (float) k; + pm->trace (trace, point, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask); + if ( !trace->allsolid ) { + point[0] = pm->ps->origin[0]; + point[1] = pm->ps->origin[1]; + point[2] = pm->ps->origin[2] - 0.25; + + pm->trace (trace, pm->ps->origin, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask); + pml.groundTrace = *trace; + return qtrue; + } + } + } + } + + pm->ps->groundEntityNum = ENTITYNUM_NONE; + pml.groundPlane = qfalse; + pml.walking = qfalse; + + return qfalse; +} + + +/* +============= +PM_GroundTraceMissed + +The ground trace didn't hit a surface, so we are in freefall +============= +*/ +static void PM_GroundTraceMissed( void ) { + trace_t trace; + vec3_t point; + + if ( pm->ps->groundEntityNum != ENTITYNUM_NONE ) { + // we just transitioned into freefall + if ( pm->debugLevel ) { + Com_Printf("%i:lift\n", c_pmove); + } + + // if they aren't in a jumping animation and the ground is a ways away, force into it + // if we didn't do the trace, the player would be backflipping down staircases + VectorCopy( pm->ps->origin, point ); + point[2] -= 64; + + pm->trace (&trace, pm->ps->origin, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask); + if ( trace.fraction == 1.0 ) { + if ( pm->cmd.forwardmove >= 0 ) { + PM_ForceLegsAnim( LEGS_JUMP ); + pm->ps->pm_flags &= ~PMF_BACKWARDS_JUMP; + } else { + PM_ForceLegsAnim( LEGS_JUMPB ); + pm->ps->pm_flags |= PMF_BACKWARDS_JUMP; + } + } + } + + pm->ps->groundEntityNum = ENTITYNUM_NONE; + pml.groundPlane = qfalse; + pml.walking = qfalse; +} + + +/* +============= +PM_GroundTrace +============= +*/ +static void PM_GroundTrace( void ) { + vec3_t point; + trace_t trace; + + point[0] = pm->ps->origin[0]; + point[1] = pm->ps->origin[1]; + point[2] = pm->ps->origin[2] - 0.25; + + pm->trace (&trace, pm->ps->origin, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask); + pml.groundTrace = trace; + + // do something corrective if the trace starts in a solid... + if ( trace.allsolid ) { + if ( !PM_CorrectAllSolid(&trace) ) + return; + } + + // if the trace didn't hit anything, we are in free fall + if ( trace.fraction == 1.0 ) { + PM_GroundTraceMissed(); + pml.groundPlane = qfalse; + pml.walking = qfalse; + return; + } + + // check if getting thrown off the ground + if ( pm->ps->velocity[2] > 0 && DotProduct( pm->ps->velocity, trace.plane.normal ) > 10 ) { + if ( pm->debugLevel ) { + Com_Printf("%i:kickoff\n", c_pmove); + } + // go into jump animation + if ( pm->cmd.forwardmove >= 0 ) { + PM_ForceLegsAnim( LEGS_JUMP ); + pm->ps->pm_flags &= ~PMF_BACKWARDS_JUMP; + } else { + PM_ForceLegsAnim( LEGS_JUMPB ); + pm->ps->pm_flags |= PMF_BACKWARDS_JUMP; + } + + pm->ps->groundEntityNum = ENTITYNUM_NONE; + pml.groundPlane = qfalse; + pml.walking = qfalse; + return; + } + + // slopes that are too steep will not be considered onground + if ( trace.plane.normal[2] < MIN_WALK_NORMAL ) { + if ( pm->debugLevel ) { + Com_Printf("%i:steep\n", c_pmove); + } + // FIXME: if they can't slide down the slope, let them + // walk (sharp crevices) + pm->ps->groundEntityNum = ENTITYNUM_NONE; + pml.groundPlane = qtrue; + pml.walking = qfalse; + return; + } + + pml.groundPlane = qtrue; + pml.walking = qtrue; + + // hitting solid ground will end a waterjump + if (pm->ps->pm_flags & PMF_TIME_WATERJUMP) + { + pm->ps->pm_flags &= ~(PMF_TIME_WATERJUMP | PMF_TIME_LAND); + pm->ps->pm_time = 0; + } + + if ( pm->ps->groundEntityNum == ENTITYNUM_NONE ) { + // just hit the ground + if ( pm->debugLevel ) { + Com_Printf("%i:Land\n", c_pmove); + } + + PM_CrashLand(); + + // don't do landing time if we were just going down a slope + if ( pml.previous_velocity[2] < -200 ) { + // don't allow another jump for a little while + pm->ps->pm_flags |= PMF_TIME_LAND; + pm->ps->pm_time = 250; + } + } + + pm->ps->groundEntityNum = trace.entityNum; + + // don't reset the z velocity for slopes +// pm->ps->velocity[2] = 0; + + PM_AddTouchEnt( trace.entityNum ); +} + + +/* +============= +PM_SetWaterLevel FIXME: avoid this twice? certainly if not moving +============= +*/ +static void PM_SetWaterLevel( void ) { + vec3_t point; + int cont; + int sample1; + int sample2; + + // + // get waterlevel, accounting for ducking + // + pm->waterlevel = 0; + pm->watertype = 0; + + point[0] = pm->ps->origin[0]; + point[1] = pm->ps->origin[1]; + point[2] = pm->ps->origin[2] + MINS_Z + 1; + cont = pm->pointcontents( point, pm->ps->clientNum ); + + if ( cont & MASK_WATER ) { + sample2 = pm->ps->viewheight - MINS_Z; + sample1 = sample2 / 2; + + pm->watertype = cont; + pm->waterlevel = 1; + point[2] = pm->ps->origin[2] + MINS_Z + sample1; + cont = pm->pointcontents (point, pm->ps->clientNum ); + if ( cont & MASK_WATER ) { + pm->waterlevel = 2; + point[2] = pm->ps->origin[2] + MINS_Z + sample2; + cont = pm->pointcontents (point, pm->ps->clientNum ); + if ( cont & MASK_WATER ){ + pm->waterlevel = 3; + } + } + } + +} + +/* +============== +PM_CheckDuck + +Sets mins, maxs, and pm->ps->viewheight +============== +*/ +static void PM_CheckDuck (void) +{ + trace_t trace; + + if ( pm->ps->powerups[PW_INVULNERABILITY] ) { + if ( pm->ps->pm_flags & PMF_INVULEXPAND ) { + // invulnerability sphere has a 42 units radius + VectorSet( pm->mins, -42, -42, -42 ); + VectorSet( pm->maxs, 42, 42, 42 ); + } + else { + VectorSet( pm->mins, -15, -15, MINS_Z ); + VectorSet( pm->maxs, 15, 15, 16 ); + } + pm->ps->pm_flags |= PMF_DUCKED; + pm->ps->viewheight = CROUCH_VIEWHEIGHT; + return; + } + pm->ps->pm_flags &= ~PMF_INVULEXPAND; + + pm->mins[0] = -15; + pm->mins[1] = -15; + + pm->maxs[0] = 15; + pm->maxs[1] = 15; + + pm->mins[2] = MINS_Z; + + if (pm->ps->pm_type == PM_DEAD) + { + pm->maxs[2] = -8; + pm->ps->viewheight = DEAD_VIEWHEIGHT; + return; + } + + if (pm->cmd.upmove < 0) + { // duck + pm->ps->pm_flags |= PMF_DUCKED; + } + else + { // stand up if possible + if (pm->ps->pm_flags & PMF_DUCKED) + { + // try to stand up + pm->maxs[2] = 32; + pm->trace (&trace, pm->ps->origin, pm->mins, pm->maxs, pm->ps->origin, pm->ps->clientNum, pm->tracemask ); + if (!trace.allsolid) + pm->ps->pm_flags &= ~PMF_DUCKED; + } + } + + if (pm->ps->pm_flags & PMF_DUCKED) + { + pm->maxs[2] = 16; + pm->ps->viewheight = CROUCH_VIEWHEIGHT; + } + else + { + pm->maxs[2] = 32; + pm->ps->viewheight = DEFAULT_VIEWHEIGHT; + } +} + + + +//=================================================================== + + +/* +=============== +PM_Footsteps +=============== +*/ +static void PM_Footsteps( void ) { + float bobmove; + int old; + qboolean footstep; + + // + // calculate speed and cycle to be used for + // all cyclic walking effects + // + pm->xyspeed = sqrt( pm->ps->velocity[0] * pm->ps->velocity[0] + + pm->ps->velocity[1] * pm->ps->velocity[1] ); + + if ( pm->ps->groundEntityNum == ENTITYNUM_NONE ) { + + if ( pm->ps->powerups[PW_INVULNERABILITY] ) { + PM_ContinueLegsAnim( LEGS_IDLECR ); + } + // airborne leaves position in cycle intact, but doesn't advance + if ( pm->waterlevel > 1 ) { + PM_ContinueLegsAnim( LEGS_SWIM ); + } + return; + } + + // if not trying to move + if ( !pm->cmd.forwardmove && !pm->cmd.rightmove ) { + if ( pm->xyspeed < 5 ) { + pm->ps->bobCycle = 0; // start at beginning of cycle again + if ( pm->ps->pm_flags & PMF_DUCKED ) { + PM_ContinueLegsAnim( LEGS_IDLECR ); + } else { + PM_ContinueLegsAnim( LEGS_IDLE ); + } + } + return; + } + + + footstep = qfalse; + + if ( pm->ps->pm_flags & PMF_DUCKED ) { + bobmove = 0.5; // ducked characters bob much faster + if ( pm->ps->pm_flags & PMF_BACKWARDS_RUN ) { + PM_ContinueLegsAnim( LEGS_BACKCR ); + } + else { + PM_ContinueLegsAnim( LEGS_WALKCR ); + } + // ducked characters never play footsteps + /* + } else if ( pm->ps->pm_flags & PMF_BACKWARDS_RUN ) { + if ( !( pm->cmd.buttons & BUTTON_WALKING ) ) { + bobmove = 0.4; // faster speeds bob faster + footstep = qtrue; + } else { + bobmove = 0.3; + } + PM_ContinueLegsAnim( LEGS_BACK ); + */ + } else { + if ( !( pm->cmd.buttons & BUTTON_WALKING ) ) { + bobmove = 0.4f; // faster speeds bob faster + if ( pm->ps->pm_flags & PMF_BACKWARDS_RUN ) { + PM_ContinueLegsAnim( LEGS_BACK ); + } + else { + PM_ContinueLegsAnim( LEGS_RUN ); + } + footstep = qtrue; + } else { + bobmove = 0.3f; // walking bobs slow + if ( pm->ps->pm_flags & PMF_BACKWARDS_RUN ) { + PM_ContinueLegsAnim( LEGS_BACKWALK ); + } + else { + PM_ContinueLegsAnim( LEGS_WALK ); + } + } + } + + // check for footstep / splash sounds + old = pm->ps->bobCycle; + pm->ps->bobCycle = (int)( old + bobmove * pml.msec ) & 255; + + // if we just crossed a cycle boundary, play an apropriate footstep event + if ( ( ( old + 64 ) ^ ( pm->ps->bobCycle + 64 ) ) & 128 ) { + if ( pm->waterlevel == 0 ) { + // on ground will only play sounds if running + if ( footstep && !pm->noFootsteps ) { + PM_AddEvent( PM_FootstepForSurface() ); + } + } else if ( pm->waterlevel == 1 ) { + // splashing + PM_AddEvent( EV_FOOTSPLASH ); + } else if ( pm->waterlevel == 2 ) { + // wading / swimming at surface + PM_AddEvent( EV_SWIM ); + } else if ( pm->waterlevel == 3 ) { + // no sound when completely underwater + + } + } +} + +/* +============== +PM_WaterEvents + +Generate sound events for entering and leaving water +============== +*/ +static void PM_WaterEvents( void ) { // FIXME? + // + // if just entered a water volume, play a sound + // + if (!pml.previous_waterlevel && pm->waterlevel) { + PM_AddEvent( EV_WATER_TOUCH ); + } + + // + // if just completely exited a water volume, play a sound + // + if (pml.previous_waterlevel && !pm->waterlevel) { + PM_AddEvent( EV_WATER_LEAVE ); + } + + // + // check for head just going under water + // + if (pml.previous_waterlevel != 3 && pm->waterlevel == 3) { + PM_AddEvent( EV_WATER_UNDER ); + } + + // + // check for head just coming out of water + // + if (pml.previous_waterlevel == 3 && pm->waterlevel != 3) { + PM_AddEvent( EV_WATER_CLEAR ); + } +} + + +/* +=============== +PM_BeginWeaponChange +=============== +*/ +static void PM_BeginWeaponChange( int weapon ) { + if ( weapon <= WP_NONE || weapon >= WP_NUM_WEAPONS ) { + return; + } + + if ( !( pm->ps->stats[STAT_WEAPONS] & ( 1 << weapon ) ) ) { + return; + } + + if ( pm->ps->weaponstate == WEAPON_DROPPING ) { + return; + } + + PM_AddEvent( EV_CHANGE_WEAPON ); + pm->ps->weaponstate = WEAPON_DROPPING; + pm->ps->weaponTime += 200; + PM_StartTorsoAnim( TORSO_DROP ); +} + + +/* +=============== +PM_FinishWeaponChange +=============== +*/ +static void PM_FinishWeaponChange( void ) { + int weapon; + + weapon = pm->cmd.weapon; + if ( weapon < WP_NONE || weapon >= WP_NUM_WEAPONS ) { + weapon = WP_NONE; + } + + if ( !( pm->ps->stats[STAT_WEAPONS] & ( 1 << weapon ) ) ) { + weapon = WP_NONE; + } + + pm->ps->weapon = weapon; + pm->ps->weaponstate = WEAPON_RAISING; + pm->ps->weaponTime += 250; + PM_StartTorsoAnim( TORSO_RAISE ); +} + + +/* +============== +PM_TorsoAnimation + +============== +*/ +static void PM_TorsoAnimation( void ) { + if ( pm->ps->weaponstate == WEAPON_READY ) { + if ( pm->ps->weapon == WP_GAUNTLET ) { + PM_ContinueTorsoAnim( TORSO_STAND2 ); + } else { + PM_ContinueTorsoAnim( TORSO_STAND ); + } + return; + } +} + + +/* +============== +PM_Weapon + +Generates weapon events and modifes the weapon counter +============== +*/ +static void PM_Weapon( void ) { + int addTime; + + // don't allow attack until all buttons are up + if ( pm->ps->pm_flags & PMF_RESPAWNED ) { + return; + } + + // ignore if spectator + if ( pm->ps->persistant[PERS_TEAM] == TEAM_SPECTATOR ) { + return; + } + + // check for dead player + if ( pm->ps->stats[STAT_HEALTH] <= 0 ) { + pm->ps->weapon = WP_NONE; + return; + } + + // check for item using + if ( pm->cmd.buttons & BUTTON_USE_HOLDABLE ) { + if ( ! ( pm->ps->pm_flags & PMF_USE_ITEM_HELD ) ) { + if ( bg_itemlist[pm->ps->stats[STAT_HOLDABLE_ITEM]].giTag == HI_MEDKIT + && pm->ps->stats[STAT_HEALTH] >= (pm->ps->stats[STAT_MAX_HEALTH] + 25) ) { + // don't use medkit if at max health + } else { + pm->ps->pm_flags |= PMF_USE_ITEM_HELD; + PM_AddEvent( EV_USE_ITEM0 + bg_itemlist[pm->ps->stats[STAT_HOLDABLE_ITEM]].giTag ); + pm->ps->stats[STAT_HOLDABLE_ITEM] = 0; + } + return; + } + } else { + pm->ps->pm_flags &= ~PMF_USE_ITEM_HELD; + } + + + // make weapon function + if ( pm->ps->weaponTime > 0 ) { + pm->ps->weaponTime -= pml.msec; + } + + // check for weapon change + // can't change if weapon is firing, but can change + // again if lowering or raising + if ( pm->ps->weaponTime <= 0 || pm->ps->weaponstate != WEAPON_FIRING ) { + if ( pm->ps->weapon != pm->cmd.weapon ) { + PM_BeginWeaponChange( pm->cmd.weapon ); + } + } + + if ( pm->ps->weaponTime > 0 ) { + return; + } + + // change weapon if time + if ( pm->ps->weaponstate == WEAPON_DROPPING ) { + PM_FinishWeaponChange(); + return; + } + + if ( pm->ps->weaponstate == WEAPON_RAISING ) { + pm->ps->weaponstate = WEAPON_READY; + if ( pm->ps->weapon == WP_GAUNTLET ) { + PM_StartTorsoAnim( TORSO_STAND2 ); + } else { + PM_StartTorsoAnim( TORSO_STAND ); + } + return; + } + + // check for fire + if ( ! (pm->cmd.buttons & BUTTON_ATTACK) ) { + pm->ps->weaponTime = 0; + pm->ps->weaponstate = WEAPON_READY; + return; + } + + // start the animation even if out of ammo + if ( pm->ps->weapon == WP_GAUNTLET ) { + // the guantlet only "fires" when it actually hits something + if ( !pm->gauntletHit ) { + pm->ps->weaponTime = 0; + pm->ps->weaponstate = WEAPON_READY; + return; + } + PM_StartTorsoAnim( TORSO_ATTACK2 ); + } else { + PM_StartTorsoAnim( TORSO_ATTACK ); + } + + pm->ps->weaponstate = WEAPON_FIRING; + + // check for out of ammo + if ( ! pm->ps->ammo[ pm->ps->weapon ] ) { + PM_AddEvent( EV_NOAMMO ); + pm->ps->weaponTime += 500; + return; + } + + // take an ammo away if not infinite + if ( pm->ps->ammo[ pm->ps->weapon ] != -1 ) { + pm->ps->ammo[ pm->ps->weapon ]--; + } + + // fire weapon + PM_AddEvent( EV_FIRE_WEAPON ); + + switch( pm->ps->weapon ) { + default: + case WP_GAUNTLET: + addTime = 400; + break; + case WP_LIGHTNING: + addTime = 50; + break; + case WP_SHOTGUN: + addTime = 1000; + break; + case WP_MACHINEGUN: + addTime = 100; + break; + case WP_GRENADE_LAUNCHER: + addTime = 800; + break; + case WP_ROCKET_LAUNCHER: + addTime = 800; + break; + case WP_PLASMAGUN: + addTime = 100; + break; + case WP_RAILGUN: + addTime = 1500; + break; + case WP_BFG: + addTime = 200; + break; + case WP_GRAPPLING_HOOK: + addTime = 400; + break; +#ifdef MISSIONPACK + case WP_NAILGUN: + addTime = 1000; + break; + case WP_PROX_LAUNCHER: + addTime = 800; + break; + case WP_CHAINGUN: + addTime = 30; + break; +#endif + } + +#ifdef MISSIONPACK + if( bg_itemlist[pm->ps->stats[STAT_PERSISTANT_POWERUP]].giTag == PW_SCOUT ) { + addTime /= 1.5; + } + else + if( bg_itemlist[pm->ps->stats[STAT_PERSISTANT_POWERUP]].giTag == PW_AMMOREGEN ) { + addTime /= 1.3; + } + else +#endif + if ( pm->ps->powerups[PW_HASTE] ) { + addTime /= 1.3; + } + + pm->ps->weaponTime += addTime; +} + +/* +================ +PM_Animate +================ +*/ + +static void PM_Animate( void ) { + if ( pm->cmd.buttons & BUTTON_GESTURE ) { + if ( pm->ps->torsoTimer == 0 ) { + PM_StartTorsoAnim( TORSO_GESTURE ); + pm->ps->torsoTimer = TIMER_GESTURE; + PM_AddEvent( EV_TAUNT ); + } +#ifdef MISSIONPACK + } else if ( pm->cmd.buttons & BUTTON_GETFLAG ) { + if ( pm->ps->torsoTimer == 0 ) { + PM_StartTorsoAnim( TORSO_GETFLAG ); + pm->ps->torsoTimer = 600; //TIMER_GESTURE; + } + } else if ( pm->cmd.buttons & BUTTON_GUARDBASE ) { + if ( pm->ps->torsoTimer == 0 ) { + PM_StartTorsoAnim( TORSO_GUARDBASE ); + pm->ps->torsoTimer = 600; //TIMER_GESTURE; + } + } else if ( pm->cmd.buttons & BUTTON_PATROL ) { + if ( pm->ps->torsoTimer == 0 ) { + PM_StartTorsoAnim( TORSO_PATROL ); + pm->ps->torsoTimer = 600; //TIMER_GESTURE; + } + } else if ( pm->cmd.buttons & BUTTON_FOLLOWME ) { + if ( pm->ps->torsoTimer == 0 ) { + PM_StartTorsoAnim( TORSO_FOLLOWME ); + pm->ps->torsoTimer = 600; //TIMER_GESTURE; + } + } else if ( pm->cmd.buttons & BUTTON_AFFIRMATIVE ) { + if ( pm->ps->torsoTimer == 0 ) { + PM_StartTorsoAnim( TORSO_AFFIRMATIVE); + pm->ps->torsoTimer = 600; //TIMER_GESTURE; + } + } else if ( pm->cmd.buttons & BUTTON_NEGATIVE ) { + if ( pm->ps->torsoTimer == 0 ) { + PM_StartTorsoAnim( TORSO_NEGATIVE ); + pm->ps->torsoTimer = 600; //TIMER_GESTURE; + } +#endif + } +} + + +/* +================ +PM_DropTimers +================ +*/ +static void PM_DropTimers( void ) { + // drop misc timing counter + if ( pm->ps->pm_time ) { + if ( pml.msec >= pm->ps->pm_time ) { + pm->ps->pm_flags &= ~PMF_ALL_TIMES; + pm->ps->pm_time = 0; + } else { + pm->ps->pm_time -= pml.msec; + } + } + + // drop animation counter + if ( pm->ps->legsTimer > 0 ) { + pm->ps->legsTimer -= pml.msec; + if ( pm->ps->legsTimer < 0 ) { + pm->ps->legsTimer = 0; + } + } + + if ( pm->ps->torsoTimer > 0 ) { + pm->ps->torsoTimer -= pml.msec; + if ( pm->ps->torsoTimer < 0 ) { + pm->ps->torsoTimer = 0; + } + } +} + +/* +================ +PM_UpdateViewAngles + +This can be used as another entry point when only the viewangles +are being updated isntead of a full move +================ +*/ +void PM_UpdateViewAngles( playerState_t *ps, const usercmd_t *cmd ) { + short temp; + int i; + + if ( ps->pm_type == PM_INTERMISSION || ps->pm_type == PM_SPINTERMISSION) { + return; // no view changes at all + } + + if ( ps->pm_type != PM_SPECTATOR && ps->stats[STAT_HEALTH] <= 0 ) { + return; // no view changes at all + } + + // circularly clamp the angles with deltas + for (i=0 ; i<3 ; i++) { + temp = cmd->angles[i] + ps->delta_angles[i]; + if ( i == PITCH ) { + // don't let the player look up or down more than 90 degrees + if ( temp > 16000 ) { + ps->delta_angles[i] = 16000 - cmd->angles[i]; + temp = 16000; + } else if ( temp < -16000 ) { + ps->delta_angles[i] = -16000 - cmd->angles[i]; + temp = -16000; + } + } + ps->viewangles[i] = SHORT2ANGLE(temp); + } + +} + + +/* +================ +PmoveSingle + +================ +*/ +void trap_SnapVector( float *v ); + +void PmoveSingle (pmove_t *pmove) { + pm = pmove; + + // this counter lets us debug movement problems with a journal + // by setting a conditional breakpoint fot the previous frame + c_pmove++; + + // clear results + pm->numtouch = 0; + pm->watertype = 0; + pm->waterlevel = 0; + + if ( pm->ps->stats[STAT_HEALTH] <= 0 ) { + pm->tracemask &= ~CONTENTS_BODY; // corpses can fly through bodies + } + + // make sure walking button is clear if they are running, to avoid + // proxy no-footsteps cheats + if ( abs( pm->cmd.forwardmove ) > 64 || abs( pm->cmd.rightmove ) > 64 ) { + pm->cmd.buttons &= ~BUTTON_WALKING; + } + + // set the talk balloon flag + if ( pm->cmd.buttons & BUTTON_TALK ) { + pm->ps->eFlags |= EF_TALK; + } else { + pm->ps->eFlags &= ~EF_TALK; + } + + // set the firing flag for continuous beam weapons + if ( !(pm->ps->pm_flags & PMF_RESPAWNED) && pm->ps->pm_type != PM_INTERMISSION + && ( pm->cmd.buttons & BUTTON_ATTACK ) && pm->ps->ammo[ pm->ps->weapon ] ) { + pm->ps->eFlags |= EF_FIRING; + } else { + pm->ps->eFlags &= ~EF_FIRING; + } + + // clear the respawned flag if attack and use are cleared + if ( pm->ps->stats[STAT_HEALTH] > 0 && + !( pm->cmd.buttons & (BUTTON_ATTACK | BUTTON_USE_HOLDABLE) ) ) { + pm->ps->pm_flags &= ~PMF_RESPAWNED; + } + + // if talk button is down, dissallow all other input + // this is to prevent any possible intercept proxy from + // adding fake talk balloons + if ( pmove->cmd.buttons & BUTTON_TALK ) { + // keep the talk button set tho for when the cmd.serverTime > 66 msec + // and the same cmd is used multiple times in Pmove + pmove->cmd.buttons = BUTTON_TALK; + pmove->cmd.forwardmove = 0; + pmove->cmd.rightmove = 0; + pmove->cmd.upmove = 0; + } + + // clear all pmove local vars + memset (&pml, 0, sizeof(pml)); + + // determine the time + pml.msec = pmove->cmd.serverTime - pm->ps->commandTime; + if ( pml.msec < 1 ) { + pml.msec = 1; + } else if ( pml.msec > 200 ) { + pml.msec = 200; + } + pm->ps->commandTime = pmove->cmd.serverTime; + + // save old org in case we get stuck + VectorCopy (pm->ps->origin, pml.previous_origin); + + // save old velocity for crashlanding + VectorCopy (pm->ps->velocity, pml.previous_velocity); + + pml.frametime = pml.msec * 0.001; + + // update the viewangles + PM_UpdateViewAngles( pm->ps, &pm->cmd ); + + AngleVectors (pm->ps->viewangles, pml.forward, pml.right, pml.up); + + if ( pm->cmd.upmove < 10 ) { + // not holding jump + pm->ps->pm_flags &= ~PMF_JUMP_HELD; + } + + // decide if backpedaling animations should be used + if ( pm->cmd.forwardmove < 0 ) { + pm->ps->pm_flags |= PMF_BACKWARDS_RUN; + } else if ( pm->cmd.forwardmove > 0 || ( pm->cmd.forwardmove == 0 && pm->cmd.rightmove ) ) { + pm->ps->pm_flags &= ~PMF_BACKWARDS_RUN; + } + + if ( pm->ps->pm_type >= PM_DEAD ) { + pm->cmd.forwardmove = 0; + pm->cmd.rightmove = 0; + pm->cmd.upmove = 0; + } + + if ( pm->ps->pm_type == PM_SPECTATOR ) { + PM_CheckDuck (); + PM_FlyMove (); + PM_DropTimers (); + return; + } + + if ( pm->ps->pm_type == PM_NOCLIP ) { + PM_NoclipMove (); + PM_DropTimers (); + return; + } + + if (pm->ps->pm_type == PM_FREEZE) { + return; // no movement at all + } + + if ( pm->ps->pm_type == PM_INTERMISSION || pm->ps->pm_type == PM_SPINTERMISSION) { + return; // no movement at all + } + + // set watertype, and waterlevel + PM_SetWaterLevel(); + pml.previous_waterlevel = pmove->waterlevel; + + // set mins, maxs, and viewheight + PM_CheckDuck (); + + // set groundentity + PM_GroundTrace(); + + if ( pm->ps->pm_type == PM_DEAD ) { + PM_DeadMove (); + } + + PM_DropTimers(); + +#ifdef MISSIONPACK + if ( pm->ps->powerups[PW_INVULNERABILITY] ) { + PM_InvulnerabilityMove(); + } else +#endif + if ( pm->ps->powerups[PW_FLIGHT] ) { + // flight powerup doesn't allow jump and has different friction + PM_FlyMove(); + } else if (pm->ps->pm_flags & PMF_GRAPPLE_PULL) { + PM_GrappleMove(); + // We can wiggle a bit + PM_AirMove(); + } else if (pm->ps->pm_flags & PMF_TIME_WATERJUMP) { + PM_WaterJumpMove(); + } else if ( pm->waterlevel > 1 ) { + // swimming + PM_WaterMove(); + } else if ( pml.walking ) { + // walking on ground + PM_WalkMove(); + } else { + // airborne + PM_AirMove(); + } + + PM_Animate(); + + // set groundentity, watertype, and waterlevel + PM_GroundTrace(); + PM_SetWaterLevel(); + + // weapons + PM_Weapon(); + + // torso animation + PM_TorsoAnimation(); + + // footstep events / legs animations + PM_Footsteps(); + + // entering / leaving water splashes + PM_WaterEvents(); + + // snap some parts of playerstate to save network bandwidth + trap_SnapVector( pm->ps->velocity ); +} + + +/* +================ +Pmove + +Can be called by either the server or the client +================ +*/ +void Pmove (pmove_t *pmove) { + int finalTime; + + finalTime = pmove->cmd.serverTime; + + if ( finalTime < pmove->ps->commandTime ) { + return; // should not happen + } + + if ( finalTime > pmove->ps->commandTime + 1000 ) { + pmove->ps->commandTime = finalTime - 1000; + } + + pmove->ps->pmove_framecount = (pmove->ps->pmove_framecount+1) & ((1<ps->commandTime != finalTime ) { + int msec; + + msec = finalTime - pmove->ps->commandTime; + + if ( pmove->pmove_fixed ) { + if ( msec > pmove->pmove_msec ) { + msec = pmove->pmove_msec; + } + } + else { + if ( msec > 66 ) { + msec = 66; + } + } + pmove->cmd.serverTime = pmove->ps->commandTime + msec; + PmoveSingle( pmove ); + + if ( pmove->ps->pm_flags & PMF_JUMP_HELD ) { + pmove->cmd.upmove = 20; + } + } + + //PM_CheckStuck(); + +} + diff --git a/code/game/bg_public.h b/code/game/bg_public.h index 0dd59db..e6c24b5 100755 --- a/code/game/bg_public.h +++ b/code/game/bg_public.h @@ -1,738 +1,738 @@ -/* -=========================================================================== -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 Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// -// bg_public.h -- definitions shared by both the server game and client game modules - -// because games can change separately from the main system version, we need a -// second version that must match between game and cgame - -#define GAME_VERSION "baseq3-1" - -#define DEFAULT_GRAVITY 800 -#define GIB_HEALTH -40 -#define ARMOR_PROTECTION 0.66 - -#define MAX_ITEMS 256 - -#define RANK_TIED_FLAG 0x4000 - -#define DEFAULT_SHOTGUN_SPREAD 700 -#define DEFAULT_SHOTGUN_COUNT 11 - -#define ITEM_RADIUS 15 // item sizes are needed for client side pickup detection - -#define LIGHTNING_RANGE 768 - -#define SCORE_NOT_PRESENT -9999 // for the CS_SCORES[12] when only one player is present - -#define VOTE_TIME 30000 // 30 seconds before vote times out - -#define MINS_Z -24 -#define DEFAULT_VIEWHEIGHT 26 -#define CROUCH_VIEWHEIGHT 12 -#define DEAD_VIEWHEIGHT -16 - -// -// config strings are a general means of communicating variable length strings -// from the server to all connected clients. -// - -// CS_SERVERINFO and CS_SYSTEMINFO are defined in q_shared.h -#define CS_MUSIC 2 -#define CS_MESSAGE 3 // from the map worldspawn's message field -#define CS_MOTD 4 // g_motd string for server message of the day -#define CS_WARMUP 5 // server time when the match will be restarted -#define CS_SCORES1 6 -#define CS_SCORES2 7 -#define CS_VOTE_TIME 8 -#define CS_VOTE_STRING 9 -#define CS_VOTE_YES 10 -#define CS_VOTE_NO 11 - -#define CS_TEAMVOTE_TIME 12 -#define CS_TEAMVOTE_STRING 14 -#define CS_TEAMVOTE_YES 16 -#define CS_TEAMVOTE_NO 18 - -#define CS_GAME_VERSION 20 -#define CS_LEVEL_START_TIME 21 // so the timer only shows the current level -#define CS_INTERMISSION 22 // when 1, fraglimit/timelimit has been hit and intermission will start in a second or two -#define CS_FLAGSTATUS 23 // string indicating flag status in CTF -#define CS_SHADERSTATE 24 -#define CS_BOTINFO 25 - -#define CS_ITEMS 27 // string of 0's and 1's that tell which items are present - -#define CS_MODELS 32 -#define CS_SOUNDS (CS_MODELS+MAX_MODELS) -#define CS_PLAYERS (CS_SOUNDS+MAX_SOUNDS) -#define CS_LOCATIONS (CS_PLAYERS+MAX_CLIENTS) -#define CS_PARTICLES (CS_LOCATIONS+MAX_LOCATIONS) - -#define CS_MAX (CS_PARTICLES+MAX_LOCATIONS) - -#if (CS_MAX) > MAX_CONFIGSTRINGS -#error overflow: (CS_MAX) > MAX_CONFIGSTRINGS -#endif - -typedef enum { - GT_FFA, // free for all - GT_TOURNAMENT, // one on one tournament - GT_SINGLE_PLAYER, // single player ffa - - //-- team games go after this -- - - GT_TEAM, // team deathmatch - GT_CTF, // capture the flag - GT_1FCTF, - GT_OBELISK, - GT_HARVESTER, - GT_MAX_GAME_TYPE -} gametype_t; - -typedef enum { GENDER_MALE, GENDER_FEMALE, GENDER_NEUTER } gender_t; - -/* -=================================================================================== - -PMOVE MODULE - -The pmove code takes a player_state_t and a usercmd_t and generates a new player_state_t -and some other output data. Used for local prediction on the client game and true -movement on the server game. -=================================================================================== -*/ - -typedef enum { - PM_NORMAL, // can accelerate and turn - PM_NOCLIP, // noclip movement - PM_SPECTATOR, // still run into walls - PM_DEAD, // no acceleration or turning, but free falling - PM_FREEZE, // stuck in place with no control - PM_INTERMISSION, // no movement or status bar - PM_SPINTERMISSION // no movement or status bar -} pmtype_t; - -typedef enum { - WEAPON_READY, - WEAPON_RAISING, - WEAPON_DROPPING, - WEAPON_FIRING -} weaponstate_t; - -// pmove->pm_flags -#define PMF_DUCKED 1 -#define PMF_JUMP_HELD 2 -#define PMF_BACKWARDS_JUMP 8 // go into backwards land -#define PMF_BACKWARDS_RUN 16 // coast down to backwards run -#define PMF_TIME_LAND 32 // pm_time is time before rejump -#define PMF_TIME_KNOCKBACK 64 // pm_time is an air-accelerate only time -#define PMF_TIME_WATERJUMP 256 // pm_time is waterjump -#define PMF_RESPAWNED 512 // clear after attack and jump buttons come up -#define PMF_USE_ITEM_HELD 1024 -#define PMF_GRAPPLE_PULL 2048 // pull towards grapple location -#define PMF_FOLLOW 4096 // spectate following another player -#define PMF_SCOREBOARD 8192 // spectate as a scoreboard -#define PMF_INVULEXPAND 16384 // invulnerability sphere set to full size - -#define PMF_ALL_TIMES (PMF_TIME_WATERJUMP|PMF_TIME_LAND|PMF_TIME_KNOCKBACK) - -#define MAXTOUCH 32 -typedef struct { - // state (in / out) - playerState_t *ps; - - // command (in) - usercmd_t cmd; - int tracemask; // collide against these types of surfaces - int debugLevel; // if set, diagnostic output will be printed - qboolean noFootsteps; // if the game is setup for no footsteps by the server - qboolean gauntletHit; // true if a gauntlet attack would actually hit something - - int framecount; - - // results (out) - int numtouch; - int touchents[MAXTOUCH]; - - vec3_t mins, maxs; // bounding box size - - int watertype; - int waterlevel; - - float xyspeed; - - // for fixed msec Pmove - int pmove_fixed; - int pmove_msec; - - // callbacks to test the world - // these will be different functions during game and cgame - void (*trace)( trace_t *results, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int passEntityNum, int contentMask ); - int (*pointcontents)( const vec3_t point, int passEntityNum ); -} pmove_t; - -// if a full pmove isn't done on the client, you can just update the angles -void PM_UpdateViewAngles( playerState_t *ps, const usercmd_t *cmd ); -void Pmove (pmove_t *pmove); - -//=================================================================================== - - -// player_state->stats[] indexes -// NOTE: may not have more than 16 -typedef enum { - STAT_HEALTH, - STAT_HOLDABLE_ITEM, -#ifdef MISSIONPACK - STAT_PERSISTANT_POWERUP, -#endif - STAT_WEAPONS, // 16 bit fields - STAT_ARMOR, - STAT_DEAD_YAW, // look this direction when dead (FIXME: get rid of?) - STAT_CLIENTS_READY, // bit mask of clients wishing to exit the intermission (FIXME: configstring?) - STAT_MAX_HEALTH // health / armor limit, changable by handicap -} statIndex_t; - - -// player_state->persistant[] indexes -// these fields are the only part of player_state that isn't -// cleared on respawn -// NOTE: may not have more than 16 -typedef enum { - PERS_SCORE, // !!! MUST NOT CHANGE, SERVER AND GAME BOTH REFERENCE !!! - PERS_HITS, // total points damage inflicted so damage beeps can sound on change - PERS_RANK, // player rank or team rank - PERS_TEAM, // player team - PERS_SPAWN_COUNT, // incremented every respawn - PERS_PLAYEREVENTS, // 16 bits that can be flipped for events - PERS_ATTACKER, // clientnum of last damage inflicter - PERS_ATTACKEE_ARMOR, // health/armor of last person we attacked - PERS_KILLED, // count of the number of times you died - // player awards tracking - PERS_IMPRESSIVE_COUNT, // two railgun hits in a row - PERS_EXCELLENT_COUNT, // two successive kills in a short amount of time - PERS_DEFEND_COUNT, // defend awards - PERS_ASSIST_COUNT, // assist awards - PERS_GAUNTLET_FRAG_COUNT, // kills with the guantlet - PERS_CAPTURES // captures -} persEnum_t; - - -// entityState_t->eFlags -#define EF_DEAD 0x00000001 // don't draw a foe marker over players with EF_DEAD -#ifdef MISSIONPACK -#define EF_TICKING 0x00000002 // used to make players play the prox mine ticking sound -#endif -#define EF_TELEPORT_BIT 0x00000004 // toggled every time the origin abruptly changes -#define EF_AWARD_EXCELLENT 0x00000008 // draw an excellent sprite -#define EF_PLAYER_EVENT 0x00000010 -#define EF_BOUNCE 0x00000010 // for missiles -#define EF_BOUNCE_HALF 0x00000020 // for missiles -#define EF_AWARD_GAUNTLET 0x00000040 // draw a gauntlet sprite -#define EF_NODRAW 0x00000080 // may have an event, but no model (unspawned items) -#define EF_FIRING 0x00000100 // for lightning gun -#define EF_KAMIKAZE 0x00000200 -#define EF_MOVER_STOP 0x00000400 // will push otherwise -#define EF_AWARD_CAP 0x00000800 // draw the capture sprite -#define EF_TALK 0x00001000 // draw a talk balloon -#define EF_CONNECTION 0x00002000 // draw a connection trouble sprite -#define EF_VOTED 0x00004000 // already cast a vote -#define EF_AWARD_IMPRESSIVE 0x00008000 // draw an impressive sprite -#define EF_AWARD_DEFEND 0x00010000 // draw a defend sprite -#define EF_AWARD_ASSIST 0x00020000 // draw a assist sprite -#define EF_AWARD_DENIED 0x00040000 // denied -#define EF_TEAMVOTED 0x00080000 // already cast a team vote - -// NOTE: may not have more than 16 -typedef enum { - PW_NONE, - - PW_QUAD, - PW_BATTLESUIT, - PW_HASTE, - PW_INVIS, - PW_REGEN, - PW_FLIGHT, - - PW_REDFLAG, - PW_BLUEFLAG, - PW_NEUTRALFLAG, - - PW_SCOUT, - PW_GUARD, - PW_DOUBLER, - PW_AMMOREGEN, - PW_INVULNERABILITY, - - PW_NUM_POWERUPS - -} powerup_t; - -typedef enum { - HI_NONE, - - HI_TELEPORTER, - HI_MEDKIT, - HI_KAMIKAZE, - HI_PORTAL, - HI_INVULNERABILITY, - - HI_NUM_HOLDABLE -} holdable_t; - - -typedef enum { - WP_NONE, - - WP_GAUNTLET, - WP_MACHINEGUN, - WP_SHOTGUN, - WP_GRENADE_LAUNCHER, - WP_ROCKET_LAUNCHER, - WP_LIGHTNING, - WP_RAILGUN, - WP_PLASMAGUN, - WP_BFG, - WP_GRAPPLING_HOOK, -#ifdef MISSIONPACK - WP_NAILGUN, - WP_PROX_LAUNCHER, - WP_CHAINGUN, -#endif - - WP_NUM_WEAPONS -} weapon_t; - - -// reward sounds (stored in ps->persistant[PERS_PLAYEREVENTS]) -#define PLAYEREVENT_DENIEDREWARD 0x0001 -#define PLAYEREVENT_GAUNTLETREWARD 0x0002 -#define PLAYEREVENT_HOLYSHIT 0x0004 - -// entityState_t->event values -// entity events are for effects that take place reletive -// to an existing entities origin. Very network efficient. - -// two bits at the top of the entityState->event field -// will be incremented with each change in the event so -// that an identical event started twice in a row can -// be distinguished. And off the value with ~EV_EVENT_BITS -// to retrieve the actual event number -#define EV_EVENT_BIT1 0x00000100 -#define EV_EVENT_BIT2 0x00000200 -#define EV_EVENT_BITS (EV_EVENT_BIT1|EV_EVENT_BIT2) - -#define EVENT_VALID_MSEC 300 - -typedef enum { - EV_NONE, - - EV_FOOTSTEP, - EV_FOOTSTEP_METAL, - EV_FOOTSPLASH, - EV_FOOTWADE, - EV_SWIM, - - EV_STEP_4, - EV_STEP_8, - EV_STEP_12, - EV_STEP_16, - - EV_FALL_SHORT, - EV_FALL_MEDIUM, - EV_FALL_FAR, - - EV_JUMP_PAD, // boing sound at origin, jump sound on player - - EV_JUMP, - EV_WATER_TOUCH, // foot touches - EV_WATER_LEAVE, // foot leaves - EV_WATER_UNDER, // head touches - EV_WATER_CLEAR, // head leaves - - EV_ITEM_PICKUP, // normal item pickups are predictable - EV_GLOBAL_ITEM_PICKUP, // powerup / team sounds are broadcast to everyone - - EV_NOAMMO, - EV_CHANGE_WEAPON, - EV_FIRE_WEAPON, - - EV_USE_ITEM0, - EV_USE_ITEM1, - EV_USE_ITEM2, - EV_USE_ITEM3, - EV_USE_ITEM4, - EV_USE_ITEM5, - EV_USE_ITEM6, - EV_USE_ITEM7, - EV_USE_ITEM8, - EV_USE_ITEM9, - EV_USE_ITEM10, - EV_USE_ITEM11, - EV_USE_ITEM12, - EV_USE_ITEM13, - EV_USE_ITEM14, - EV_USE_ITEM15, - - EV_ITEM_RESPAWN, - EV_ITEM_POP, - EV_PLAYER_TELEPORT_IN, - EV_PLAYER_TELEPORT_OUT, - - EV_GRENADE_BOUNCE, // eventParm will be the soundindex - - EV_GENERAL_SOUND, - EV_GLOBAL_SOUND, // no attenuation - EV_GLOBAL_TEAM_SOUND, - - EV_BULLET_HIT_FLESH, - EV_BULLET_HIT_WALL, - - EV_MISSILE_HIT, - EV_MISSILE_MISS, - EV_MISSILE_MISS_METAL, - EV_RAILTRAIL, - EV_SHOTGUN, - EV_BULLET, // otherEntity is the shooter - - EV_PAIN, - EV_DEATH1, - EV_DEATH2, - EV_DEATH3, - EV_OBITUARY, - - EV_POWERUP_QUAD, - EV_POWERUP_BATTLESUIT, - EV_POWERUP_REGEN, - - EV_GIB_PLAYER, // gib a previously living player - EV_SCOREPLUM, // score plum - -//#ifdef MISSIONPACK - EV_PROXIMITY_MINE_STICK, - EV_PROXIMITY_MINE_TRIGGER, - EV_KAMIKAZE, // kamikaze explodes - EV_OBELISKEXPLODE, // obelisk explodes - EV_OBELISKPAIN, // obelisk is in pain - EV_INVUL_IMPACT, // invulnerability sphere impact - EV_JUICED, // invulnerability juiced effect - EV_LIGHTNINGBOLT, // lightning bolt bounced of invulnerability sphere -//#endif - - EV_DEBUG_LINE, - EV_STOPLOOPINGSOUND, - EV_TAUNT, - EV_TAUNT_YES, - EV_TAUNT_NO, - EV_TAUNT_FOLLOWME, - EV_TAUNT_GETFLAG, - EV_TAUNT_GUARDBASE, - EV_TAUNT_PATROL - -} entity_event_t; - - -typedef enum { - GTS_RED_CAPTURE, - GTS_BLUE_CAPTURE, - GTS_RED_RETURN, - GTS_BLUE_RETURN, - GTS_RED_TAKEN, - GTS_BLUE_TAKEN, - GTS_REDOBELISK_ATTACKED, - GTS_BLUEOBELISK_ATTACKED, - GTS_REDTEAM_SCORED, - GTS_BLUETEAM_SCORED, - GTS_REDTEAM_TOOK_LEAD, - GTS_BLUETEAM_TOOK_LEAD, - GTS_TEAMS_ARE_TIED, - GTS_KAMIKAZE -} global_team_sound_t; - -// animations -typedef enum { - BOTH_DEATH1, - BOTH_DEAD1, - BOTH_DEATH2, - BOTH_DEAD2, - BOTH_DEATH3, - BOTH_DEAD3, - - TORSO_GESTURE, - - TORSO_ATTACK, - TORSO_ATTACK2, - - TORSO_DROP, - TORSO_RAISE, - - TORSO_STAND, - TORSO_STAND2, - - LEGS_WALKCR, - LEGS_WALK, - LEGS_RUN, - LEGS_BACK, - LEGS_SWIM, - - LEGS_JUMP, - LEGS_LAND, - - LEGS_JUMPB, - LEGS_LANDB, - - LEGS_IDLE, - LEGS_IDLECR, - - LEGS_TURN, - - TORSO_GETFLAG, - TORSO_GUARDBASE, - TORSO_PATROL, - TORSO_FOLLOWME, - TORSO_AFFIRMATIVE, - TORSO_NEGATIVE, - - MAX_ANIMATIONS, - - LEGS_BACKCR, - LEGS_BACKWALK, - FLAG_RUN, - FLAG_STAND, - FLAG_STAND2RUN, - - MAX_TOTALANIMATIONS -} animNumber_t; - - -typedef struct animation_s { - int firstFrame; - int numFrames; - int loopFrames; // 0 to numFrames - int frameLerp; // msec between frames - int initialLerp; // msec to get to first frame - int reversed; // true if animation is reversed - int flipflop; // true if animation should flipflop back to base -} animation_t; - - -// flip the togglebit every time an animation -// changes so a restart of the same anim can be detected -#define ANIM_TOGGLEBIT 128 - - -typedef enum { - TEAM_FREE, - TEAM_RED, - TEAM_BLUE, - TEAM_SPECTATOR, - - TEAM_NUM_TEAMS -} team_t; - -// Time between location updates -#define TEAM_LOCATION_UPDATE_TIME 1000 - -// How many players on the overlay -#define TEAM_MAXOVERLAY 32 - -//team task -typedef enum { - TEAMTASK_NONE, - TEAMTASK_OFFENSE, - TEAMTASK_DEFENSE, - TEAMTASK_PATROL, - TEAMTASK_FOLLOW, - TEAMTASK_RETRIEVE, - TEAMTASK_ESCORT, - TEAMTASK_CAMP -} teamtask_t; - -// means of death -typedef enum { - MOD_UNKNOWN, - MOD_SHOTGUN, - MOD_GAUNTLET, - MOD_MACHINEGUN, - MOD_GRENADE, - MOD_GRENADE_SPLASH, - MOD_ROCKET, - MOD_ROCKET_SPLASH, - MOD_PLASMA, - MOD_PLASMA_SPLASH, - MOD_RAILGUN, - MOD_LIGHTNING, - MOD_BFG, - MOD_BFG_SPLASH, - MOD_WATER, - MOD_SLIME, - MOD_LAVA, - MOD_CRUSH, - MOD_TELEFRAG, - MOD_FALLING, - MOD_SUICIDE, - MOD_TARGET_LASER, - MOD_TRIGGER_HURT, -#ifdef MISSIONPACK - MOD_NAIL, - MOD_CHAINGUN, - MOD_PROXIMITY_MINE, - MOD_KAMIKAZE, - MOD_JUICED, -#endif - MOD_GRAPPLE -} meansOfDeath_t; - - -//--------------------------------------------------------- - -// gitem_t->type -typedef enum { - IT_BAD, - IT_WEAPON, // EFX: rotate + upscale + minlight - IT_AMMO, // EFX: rotate - IT_ARMOR, // EFX: rotate + minlight - IT_HEALTH, // EFX: static external sphere + rotating internal - IT_POWERUP, // instant on, timer based - // EFX: rotate + external ring that rotates - IT_HOLDABLE, // single use, holdable item - // EFX: rotate + bob - IT_PERSISTANT_POWERUP, - IT_TEAM -} itemType_t; - -#define MAX_ITEM_MODELS 4 - -typedef struct gitem_s { - char *classname; // spawning name - char *pickup_sound; - char *world_model[MAX_ITEM_MODELS]; - - char *icon; - char *pickup_name; // for printing on pickup - - int quantity; // for ammo how much, or duration of powerup - itemType_t giType; // IT_* flags - - int giTag; - - char *precaches; // string of all models and images this item will use - char *sounds; // string of all sounds this item will use -} gitem_t; - -// included in both the game dll and the client -extern gitem_t bg_itemlist[]; -extern int bg_numItems; - -gitem_t *BG_FindItem( const char *pickupName ); -gitem_t *BG_FindItemForWeapon( weapon_t weapon ); -gitem_t *BG_FindItemForPowerup( powerup_t pw ); -gitem_t *BG_FindItemForHoldable( holdable_t pw ); -#define ITEM_INDEX(x) ((x)-bg_itemlist) - -qboolean BG_CanItemBeGrabbed( int gametype, const entityState_t *ent, const playerState_t *ps ); - - -// g_dmflags->integer flags -#define DF_NO_FALLING 8 -#define DF_FIXED_FOV 16 -#define DF_NO_FOOTSTEPS 32 - -// content masks -#define MASK_ALL (-1) -#define MASK_SOLID (CONTENTS_SOLID) -#define MASK_PLAYERSOLID (CONTENTS_SOLID|CONTENTS_PLAYERCLIP|CONTENTS_BODY) -#define MASK_DEADSOLID (CONTENTS_SOLID|CONTENTS_PLAYERCLIP) -#define MASK_WATER (CONTENTS_WATER|CONTENTS_LAVA|CONTENTS_SLIME) -#define MASK_OPAQUE (CONTENTS_SOLID|CONTENTS_SLIME|CONTENTS_LAVA) -#define MASK_SHOT (CONTENTS_SOLID|CONTENTS_BODY|CONTENTS_CORPSE) - - -// -// entityState_t->eType -// -typedef enum { - ET_GENERAL, - ET_PLAYER, - ET_ITEM, - ET_MISSILE, - ET_MOVER, - ET_BEAM, - ET_PORTAL, - ET_SPEAKER, - ET_PUSH_TRIGGER, - ET_TELEPORT_TRIGGER, - ET_INVISIBLE, - ET_GRAPPLE, // grapple hooked on wall - ET_TEAM, - - ET_EVENTS // any of the EV_* events can be added freestanding - // by setting eType to ET_EVENTS + eventNum - // this avoids having to set eFlags and eventNum -} entityType_t; - - - -void BG_EvaluateTrajectory( const trajectory_t *tr, int atTime, vec3_t result ); -void BG_EvaluateTrajectoryDelta( const trajectory_t *tr, int atTime, vec3_t result ); - -void BG_AddPredictableEventToPlayerstate( int newEvent, int eventParm, playerState_t *ps ); - -void BG_TouchJumpPad( playerState_t *ps, entityState_t *jumppad ); - -void BG_PlayerStateToEntityState( playerState_t *ps, entityState_t *s, qboolean snap ); -void BG_PlayerStateToEntityStateExtraPolate( playerState_t *ps, entityState_t *s, int time, qboolean snap ); - -qboolean BG_PlayerTouchesItem( playerState_t *ps, entityState_t *item, int atTime ); - - -#define ARENAS_PER_TIER 4 -#define MAX_ARENAS 1024 -#define MAX_ARENAS_TEXT 8192 - -#define MAX_BOTS 1024 -#define MAX_BOTS_TEXT 8192 - - -// Kamikaze - -// 1st shockwave times -#define KAMI_SHOCKWAVE_STARTTIME 0 -#define KAMI_SHOCKWAVEFADE_STARTTIME 1500 -#define KAMI_SHOCKWAVE_ENDTIME 2000 -// explosion/implosion times -#define KAMI_EXPLODE_STARTTIME 250 -#define KAMI_IMPLODE_STARTTIME 2000 -#define KAMI_IMPLODE_ENDTIME 2250 -// 2nd shockwave times -#define KAMI_SHOCKWAVE2_STARTTIME 2000 -#define KAMI_SHOCKWAVE2FADE_STARTTIME 2500 -#define KAMI_SHOCKWAVE2_ENDTIME 3000 -// radius of the models without scaling -#define KAMI_SHOCKWAVEMODEL_RADIUS 88 -#define KAMI_BOOMSPHEREMODEL_RADIUS 72 -// maximum radius of the models during the effect -#define KAMI_SHOCKWAVE_MAXRADIUS 1320 -#define KAMI_BOOMSPHERE_MAXRADIUS 720 -#define KAMI_SHOCKWAVE2_MAXRADIUS 704 - +/* +=========================================================================== +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 Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// +// bg_public.h -- definitions shared by both the server game and client game modules + +// because games can change separately from the main system version, we need a +// second version that must match between game and cgame + +#define GAME_VERSION "baseq3-1" + +#define DEFAULT_GRAVITY 800 +#define GIB_HEALTH -40 +#define ARMOR_PROTECTION 0.66 + +#define MAX_ITEMS 256 + +#define RANK_TIED_FLAG 0x4000 + +#define DEFAULT_SHOTGUN_SPREAD 700 +#define DEFAULT_SHOTGUN_COUNT 11 + +#define ITEM_RADIUS 15 // item sizes are needed for client side pickup detection + +#define LIGHTNING_RANGE 768 + +#define SCORE_NOT_PRESENT -9999 // for the CS_SCORES[12] when only one player is present + +#define VOTE_TIME 30000 // 30 seconds before vote times out + +#define MINS_Z -24 +#define DEFAULT_VIEWHEIGHT 26 +#define CROUCH_VIEWHEIGHT 12 +#define DEAD_VIEWHEIGHT -16 + +// +// config strings are a general means of communicating variable length strings +// from the server to all connected clients. +// + +// CS_SERVERINFO and CS_SYSTEMINFO are defined in q_shared.h +#define CS_MUSIC 2 +#define CS_MESSAGE 3 // from the map worldspawn's message field +#define CS_MOTD 4 // g_motd string for server message of the day +#define CS_WARMUP 5 // server time when the match will be restarted +#define CS_SCORES1 6 +#define CS_SCORES2 7 +#define CS_VOTE_TIME 8 +#define CS_VOTE_STRING 9 +#define CS_VOTE_YES 10 +#define CS_VOTE_NO 11 + +#define CS_TEAMVOTE_TIME 12 +#define CS_TEAMVOTE_STRING 14 +#define CS_TEAMVOTE_YES 16 +#define CS_TEAMVOTE_NO 18 + +#define CS_GAME_VERSION 20 +#define CS_LEVEL_START_TIME 21 // so the timer only shows the current level +#define CS_INTERMISSION 22 // when 1, fraglimit/timelimit has been hit and intermission will start in a second or two +#define CS_FLAGSTATUS 23 // string indicating flag status in CTF +#define CS_SHADERSTATE 24 +#define CS_BOTINFO 25 + +#define CS_ITEMS 27 // string of 0's and 1's that tell which items are present + +#define CS_MODELS 32 +#define CS_SOUNDS (CS_MODELS+MAX_MODELS) +#define CS_PLAYERS (CS_SOUNDS+MAX_SOUNDS) +#define CS_LOCATIONS (CS_PLAYERS+MAX_CLIENTS) +#define CS_PARTICLES (CS_LOCATIONS+MAX_LOCATIONS) + +#define CS_MAX (CS_PARTICLES+MAX_LOCATIONS) + +#if (CS_MAX) > MAX_CONFIGSTRINGS +#error overflow: (CS_MAX) > MAX_CONFIGSTRINGS +#endif + +typedef enum { + GT_FFA, // free for all + GT_TOURNAMENT, // one on one tournament + GT_SINGLE_PLAYER, // single player ffa + + //-- team games go after this -- + + GT_TEAM, // team deathmatch + GT_CTF, // capture the flag + GT_1FCTF, + GT_OBELISK, + GT_HARVESTER, + GT_MAX_GAME_TYPE +} gametype_t; + +typedef enum { GENDER_MALE, GENDER_FEMALE, GENDER_NEUTER } gender_t; + +/* +=================================================================================== + +PMOVE MODULE + +The pmove code takes a player_state_t and a usercmd_t and generates a new player_state_t +and some other output data. Used for local prediction on the client game and true +movement on the server game. +=================================================================================== +*/ + +typedef enum { + PM_NORMAL, // can accelerate and turn + PM_NOCLIP, // noclip movement + PM_SPECTATOR, // still run into walls + PM_DEAD, // no acceleration or turning, but free falling + PM_FREEZE, // stuck in place with no control + PM_INTERMISSION, // no movement or status bar + PM_SPINTERMISSION // no movement or status bar +} pmtype_t; + +typedef enum { + WEAPON_READY, + WEAPON_RAISING, + WEAPON_DROPPING, + WEAPON_FIRING +} weaponstate_t; + +// pmove->pm_flags +#define PMF_DUCKED 1 +#define PMF_JUMP_HELD 2 +#define PMF_BACKWARDS_JUMP 8 // go into backwards land +#define PMF_BACKWARDS_RUN 16 // coast down to backwards run +#define PMF_TIME_LAND 32 // pm_time is time before rejump +#define PMF_TIME_KNOCKBACK 64 // pm_time is an air-accelerate only time +#define PMF_TIME_WATERJUMP 256 // pm_time is waterjump +#define PMF_RESPAWNED 512 // clear after attack and jump buttons come up +#define PMF_USE_ITEM_HELD 1024 +#define PMF_GRAPPLE_PULL 2048 // pull towards grapple location +#define PMF_FOLLOW 4096 // spectate following another player +#define PMF_SCOREBOARD 8192 // spectate as a scoreboard +#define PMF_INVULEXPAND 16384 // invulnerability sphere set to full size + +#define PMF_ALL_TIMES (PMF_TIME_WATERJUMP|PMF_TIME_LAND|PMF_TIME_KNOCKBACK) + +#define MAXTOUCH 32 +typedef struct { + // state (in / out) + playerState_t *ps; + + // command (in) + usercmd_t cmd; + int tracemask; // collide against these types of surfaces + int debugLevel; // if set, diagnostic output will be printed + qboolean noFootsteps; // if the game is setup for no footsteps by the server + qboolean gauntletHit; // true if a gauntlet attack would actually hit something + + int framecount; + + // results (out) + int numtouch; + int touchents[MAXTOUCH]; + + vec3_t mins, maxs; // bounding box size + + int watertype; + int waterlevel; + + float xyspeed; + + // for fixed msec Pmove + int pmove_fixed; + int pmove_msec; + + // callbacks to test the world + // these will be different functions during game and cgame + void (*trace)( trace_t *results, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int passEntityNum, int contentMask ); + int (*pointcontents)( const vec3_t point, int passEntityNum ); +} pmove_t; + +// if a full pmove isn't done on the client, you can just update the angles +void PM_UpdateViewAngles( playerState_t *ps, const usercmd_t *cmd ); +void Pmove (pmove_t *pmove); + +//=================================================================================== + + +// player_state->stats[] indexes +// NOTE: may not have more than 16 +typedef enum { + STAT_HEALTH, + STAT_HOLDABLE_ITEM, +#ifdef MISSIONPACK + STAT_PERSISTANT_POWERUP, +#endif + STAT_WEAPONS, // 16 bit fields + STAT_ARMOR, + STAT_DEAD_YAW, // look this direction when dead (FIXME: get rid of?) + STAT_CLIENTS_READY, // bit mask of clients wishing to exit the intermission (FIXME: configstring?) + STAT_MAX_HEALTH // health / armor limit, changable by handicap +} statIndex_t; + + +// player_state->persistant[] indexes +// these fields are the only part of player_state that isn't +// cleared on respawn +// NOTE: may not have more than 16 +typedef enum { + PERS_SCORE, // !!! MUST NOT CHANGE, SERVER AND GAME BOTH REFERENCE !!! + PERS_HITS, // total points damage inflicted so damage beeps can sound on change + PERS_RANK, // player rank or team rank + PERS_TEAM, // player team + PERS_SPAWN_COUNT, // incremented every respawn + PERS_PLAYEREVENTS, // 16 bits that can be flipped for events + PERS_ATTACKER, // clientnum of last damage inflicter + PERS_ATTACKEE_ARMOR, // health/armor of last person we attacked + PERS_KILLED, // count of the number of times you died + // player awards tracking + PERS_IMPRESSIVE_COUNT, // two railgun hits in a row + PERS_EXCELLENT_COUNT, // two successive kills in a short amount of time + PERS_DEFEND_COUNT, // defend awards + PERS_ASSIST_COUNT, // assist awards + PERS_GAUNTLET_FRAG_COUNT, // kills with the guantlet + PERS_CAPTURES // captures +} persEnum_t; + + +// entityState_t->eFlags +#define EF_DEAD 0x00000001 // don't draw a foe marker over players with EF_DEAD +#ifdef MISSIONPACK +#define EF_TICKING 0x00000002 // used to make players play the prox mine ticking sound +#endif +#define EF_TELEPORT_BIT 0x00000004 // toggled every time the origin abruptly changes +#define EF_AWARD_EXCELLENT 0x00000008 // draw an excellent sprite +#define EF_PLAYER_EVENT 0x00000010 +#define EF_BOUNCE 0x00000010 // for missiles +#define EF_BOUNCE_HALF 0x00000020 // for missiles +#define EF_AWARD_GAUNTLET 0x00000040 // draw a gauntlet sprite +#define EF_NODRAW 0x00000080 // may have an event, but no model (unspawned items) +#define EF_FIRING 0x00000100 // for lightning gun +#define EF_KAMIKAZE 0x00000200 +#define EF_MOVER_STOP 0x00000400 // will push otherwise +#define EF_AWARD_CAP 0x00000800 // draw the capture sprite +#define EF_TALK 0x00001000 // draw a talk balloon +#define EF_CONNECTION 0x00002000 // draw a connection trouble sprite +#define EF_VOTED 0x00004000 // already cast a vote +#define EF_AWARD_IMPRESSIVE 0x00008000 // draw an impressive sprite +#define EF_AWARD_DEFEND 0x00010000 // draw a defend sprite +#define EF_AWARD_ASSIST 0x00020000 // draw a assist sprite +#define EF_AWARD_DENIED 0x00040000 // denied +#define EF_TEAMVOTED 0x00080000 // already cast a team vote + +// NOTE: may not have more than 16 +typedef enum { + PW_NONE, + + PW_QUAD, + PW_BATTLESUIT, + PW_HASTE, + PW_INVIS, + PW_REGEN, + PW_FLIGHT, + + PW_REDFLAG, + PW_BLUEFLAG, + PW_NEUTRALFLAG, + + PW_SCOUT, + PW_GUARD, + PW_DOUBLER, + PW_AMMOREGEN, + PW_INVULNERABILITY, + + PW_NUM_POWERUPS + +} powerup_t; + +typedef enum { + HI_NONE, + + HI_TELEPORTER, + HI_MEDKIT, + HI_KAMIKAZE, + HI_PORTAL, + HI_INVULNERABILITY, + + HI_NUM_HOLDABLE +} holdable_t; + + +typedef enum { + WP_NONE, + + WP_GAUNTLET, + WP_MACHINEGUN, + WP_SHOTGUN, + WP_GRENADE_LAUNCHER, + WP_ROCKET_LAUNCHER, + WP_LIGHTNING, + WP_RAILGUN, + WP_PLASMAGUN, + WP_BFG, + WP_GRAPPLING_HOOK, +#ifdef MISSIONPACK + WP_NAILGUN, + WP_PROX_LAUNCHER, + WP_CHAINGUN, +#endif + + WP_NUM_WEAPONS +} weapon_t; + + +// reward sounds (stored in ps->persistant[PERS_PLAYEREVENTS]) +#define PLAYEREVENT_DENIEDREWARD 0x0001 +#define PLAYEREVENT_GAUNTLETREWARD 0x0002 +#define PLAYEREVENT_HOLYSHIT 0x0004 + +// entityState_t->event values +// entity events are for effects that take place reletive +// to an existing entities origin. Very network efficient. + +// two bits at the top of the entityState->event field +// will be incremented with each change in the event so +// that an identical event started twice in a row can +// be distinguished. And off the value with ~EV_EVENT_BITS +// to retrieve the actual event number +#define EV_EVENT_BIT1 0x00000100 +#define EV_EVENT_BIT2 0x00000200 +#define EV_EVENT_BITS (EV_EVENT_BIT1|EV_EVENT_BIT2) + +#define EVENT_VALID_MSEC 300 + +typedef enum { + EV_NONE, + + EV_FOOTSTEP, + EV_FOOTSTEP_METAL, + EV_FOOTSPLASH, + EV_FOOTWADE, + EV_SWIM, + + EV_STEP_4, + EV_STEP_8, + EV_STEP_12, + EV_STEP_16, + + EV_FALL_SHORT, + EV_FALL_MEDIUM, + EV_FALL_FAR, + + EV_JUMP_PAD, // boing sound at origin, jump sound on player + + EV_JUMP, + EV_WATER_TOUCH, // foot touches + EV_WATER_LEAVE, // foot leaves + EV_WATER_UNDER, // head touches + EV_WATER_CLEAR, // head leaves + + EV_ITEM_PICKUP, // normal item pickups are predictable + EV_GLOBAL_ITEM_PICKUP, // powerup / team sounds are broadcast to everyone + + EV_NOAMMO, + EV_CHANGE_WEAPON, + EV_FIRE_WEAPON, + + EV_USE_ITEM0, + EV_USE_ITEM1, + EV_USE_ITEM2, + EV_USE_ITEM3, + EV_USE_ITEM4, + EV_USE_ITEM5, + EV_USE_ITEM6, + EV_USE_ITEM7, + EV_USE_ITEM8, + EV_USE_ITEM9, + EV_USE_ITEM10, + EV_USE_ITEM11, + EV_USE_ITEM12, + EV_USE_ITEM13, + EV_USE_ITEM14, + EV_USE_ITEM15, + + EV_ITEM_RESPAWN, + EV_ITEM_POP, + EV_PLAYER_TELEPORT_IN, + EV_PLAYER_TELEPORT_OUT, + + EV_GRENADE_BOUNCE, // eventParm will be the soundindex + + EV_GENERAL_SOUND, + EV_GLOBAL_SOUND, // no attenuation + EV_GLOBAL_TEAM_SOUND, + + EV_BULLET_HIT_FLESH, + EV_BULLET_HIT_WALL, + + EV_MISSILE_HIT, + EV_MISSILE_MISS, + EV_MISSILE_MISS_METAL, + EV_RAILTRAIL, + EV_SHOTGUN, + EV_BULLET, // otherEntity is the shooter + + EV_PAIN, + EV_DEATH1, + EV_DEATH2, + EV_DEATH3, + EV_OBITUARY, + + EV_POWERUP_QUAD, + EV_POWERUP_BATTLESUIT, + EV_POWERUP_REGEN, + + EV_GIB_PLAYER, // gib a previously living player + EV_SCOREPLUM, // score plum + +//#ifdef MISSIONPACK + EV_PROXIMITY_MINE_STICK, + EV_PROXIMITY_MINE_TRIGGER, + EV_KAMIKAZE, // kamikaze explodes + EV_OBELISKEXPLODE, // obelisk explodes + EV_OBELISKPAIN, // obelisk is in pain + EV_INVUL_IMPACT, // invulnerability sphere impact + EV_JUICED, // invulnerability juiced effect + EV_LIGHTNINGBOLT, // lightning bolt bounced of invulnerability sphere +//#endif + + EV_DEBUG_LINE, + EV_STOPLOOPINGSOUND, + EV_TAUNT, + EV_TAUNT_YES, + EV_TAUNT_NO, + EV_TAUNT_FOLLOWME, + EV_TAUNT_GETFLAG, + EV_TAUNT_GUARDBASE, + EV_TAUNT_PATROL + +} entity_event_t; + + +typedef enum { + GTS_RED_CAPTURE, + GTS_BLUE_CAPTURE, + GTS_RED_RETURN, + GTS_BLUE_RETURN, + GTS_RED_TAKEN, + GTS_BLUE_TAKEN, + GTS_REDOBELISK_ATTACKED, + GTS_BLUEOBELISK_ATTACKED, + GTS_REDTEAM_SCORED, + GTS_BLUETEAM_SCORED, + GTS_REDTEAM_TOOK_LEAD, + GTS_BLUETEAM_TOOK_LEAD, + GTS_TEAMS_ARE_TIED, + GTS_KAMIKAZE +} global_team_sound_t; + +// animations +typedef enum { + BOTH_DEATH1, + BOTH_DEAD1, + BOTH_DEATH2, + BOTH_DEAD2, + BOTH_DEATH3, + BOTH_DEAD3, + + TORSO_GESTURE, + + TORSO_ATTACK, + TORSO_ATTACK2, + + TORSO_DROP, + TORSO_RAISE, + + TORSO_STAND, + TORSO_STAND2, + + LEGS_WALKCR, + LEGS_WALK, + LEGS_RUN, + LEGS_BACK, + LEGS_SWIM, + + LEGS_JUMP, + LEGS_LAND, + + LEGS_JUMPB, + LEGS_LANDB, + + LEGS_IDLE, + LEGS_IDLECR, + + LEGS_TURN, + + TORSO_GETFLAG, + TORSO_GUARDBASE, + TORSO_PATROL, + TORSO_FOLLOWME, + TORSO_AFFIRMATIVE, + TORSO_NEGATIVE, + + MAX_ANIMATIONS, + + LEGS_BACKCR, + LEGS_BACKWALK, + FLAG_RUN, + FLAG_STAND, + FLAG_STAND2RUN, + + MAX_TOTALANIMATIONS +} animNumber_t; + + +typedef struct animation_s { + int firstFrame; + int numFrames; + int loopFrames; // 0 to numFrames + int frameLerp; // msec between frames + int initialLerp; // msec to get to first frame + int reversed; // true if animation is reversed + int flipflop; // true if animation should flipflop back to base +} animation_t; + + +// flip the togglebit every time an animation +// changes so a restart of the same anim can be detected +#define ANIM_TOGGLEBIT 128 + + +typedef enum { + TEAM_FREE, + TEAM_RED, + TEAM_BLUE, + TEAM_SPECTATOR, + + TEAM_NUM_TEAMS +} team_t; + +// Time between location updates +#define TEAM_LOCATION_UPDATE_TIME 1000 + +// How many players on the overlay +#define TEAM_MAXOVERLAY 32 + +//team task +typedef enum { + TEAMTASK_NONE, + TEAMTASK_OFFENSE, + TEAMTASK_DEFENSE, + TEAMTASK_PATROL, + TEAMTASK_FOLLOW, + TEAMTASK_RETRIEVE, + TEAMTASK_ESCORT, + TEAMTASK_CAMP +} teamtask_t; + +// means of death +typedef enum { + MOD_UNKNOWN, + MOD_SHOTGUN, + MOD_GAUNTLET, + MOD_MACHINEGUN, + MOD_GRENADE, + MOD_GRENADE_SPLASH, + MOD_ROCKET, + MOD_ROCKET_SPLASH, + MOD_PLASMA, + MOD_PLASMA_SPLASH, + MOD_RAILGUN, + MOD_LIGHTNING, + MOD_BFG, + MOD_BFG_SPLASH, + MOD_WATER, + MOD_SLIME, + MOD_LAVA, + MOD_CRUSH, + MOD_TELEFRAG, + MOD_FALLING, + MOD_SUICIDE, + MOD_TARGET_LASER, + MOD_TRIGGER_HURT, +#ifdef MISSIONPACK + MOD_NAIL, + MOD_CHAINGUN, + MOD_PROXIMITY_MINE, + MOD_KAMIKAZE, + MOD_JUICED, +#endif + MOD_GRAPPLE +} meansOfDeath_t; + + +//--------------------------------------------------------- + +// gitem_t->type +typedef enum { + IT_BAD, + IT_WEAPON, // EFX: rotate + upscale + minlight + IT_AMMO, // EFX: rotate + IT_ARMOR, // EFX: rotate + minlight + IT_HEALTH, // EFX: static external sphere + rotating internal + IT_POWERUP, // instant on, timer based + // EFX: rotate + external ring that rotates + IT_HOLDABLE, // single use, holdable item + // EFX: rotate + bob + IT_PERSISTANT_POWERUP, + IT_TEAM +} itemType_t; + +#define MAX_ITEM_MODELS 4 + +typedef struct gitem_s { + char *classname; // spawning name + char *pickup_sound; + char *world_model[MAX_ITEM_MODELS]; + + char *icon; + char *pickup_name; // for printing on pickup + + int quantity; // for ammo how much, or duration of powerup + itemType_t giType; // IT_* flags + + int giTag; + + char *precaches; // string of all models and images this item will use + char *sounds; // string of all sounds this item will use +} gitem_t; + +// included in both the game dll and the client +extern gitem_t bg_itemlist[]; +extern int bg_numItems; + +gitem_t *BG_FindItem( const char *pickupName ); +gitem_t *BG_FindItemForWeapon( weapon_t weapon ); +gitem_t *BG_FindItemForPowerup( powerup_t pw ); +gitem_t *BG_FindItemForHoldable( holdable_t pw ); +#define ITEM_INDEX(x) ((x)-bg_itemlist) + +qboolean BG_CanItemBeGrabbed( int gametype, const entityState_t *ent, const playerState_t *ps ); + + +// g_dmflags->integer flags +#define DF_NO_FALLING 8 +#define DF_FIXED_FOV 16 +#define DF_NO_FOOTSTEPS 32 + +// content masks +#define MASK_ALL (-1) +#define MASK_SOLID (CONTENTS_SOLID) +#define MASK_PLAYERSOLID (CONTENTS_SOLID|CONTENTS_PLAYERCLIP|CONTENTS_BODY) +#define MASK_DEADSOLID (CONTENTS_SOLID|CONTENTS_PLAYERCLIP) +#define MASK_WATER (CONTENTS_WATER|CONTENTS_LAVA|CONTENTS_SLIME) +#define MASK_OPAQUE (CONTENTS_SOLID|CONTENTS_SLIME|CONTENTS_LAVA) +#define MASK_SHOT (CONTENTS_SOLID|CONTENTS_BODY|CONTENTS_CORPSE) + + +// +// entityState_t->eType +// +typedef enum { + ET_GENERAL, + ET_PLAYER, + ET_ITEM, + ET_MISSILE, + ET_MOVER, + ET_BEAM, + ET_PORTAL, + ET_SPEAKER, + ET_PUSH_TRIGGER, + ET_TELEPORT_TRIGGER, + ET_INVISIBLE, + ET_GRAPPLE, // grapple hooked on wall + ET_TEAM, + + ET_EVENTS // any of the EV_* events can be added freestanding + // by setting eType to ET_EVENTS + eventNum + // this avoids having to set eFlags and eventNum +} entityType_t; + + + +void BG_EvaluateTrajectory( const trajectory_t *tr, int atTime, vec3_t result ); +void BG_EvaluateTrajectoryDelta( const trajectory_t *tr, int atTime, vec3_t result ); + +void BG_AddPredictableEventToPlayerstate( int newEvent, int eventParm, playerState_t *ps ); + +void BG_TouchJumpPad( playerState_t *ps, entityState_t *jumppad ); + +void BG_PlayerStateToEntityState( playerState_t *ps, entityState_t *s, qboolean snap ); +void BG_PlayerStateToEntityStateExtraPolate( playerState_t *ps, entityState_t *s, int time, qboolean snap ); + +qboolean BG_PlayerTouchesItem( playerState_t *ps, entityState_t *item, int atTime ); + + +#define ARENAS_PER_TIER 4 +#define MAX_ARENAS 1024 +#define MAX_ARENAS_TEXT 8192 + +#define MAX_BOTS 1024 +#define MAX_BOTS_TEXT 8192 + + +// Kamikaze + +// 1st shockwave times +#define KAMI_SHOCKWAVE_STARTTIME 0 +#define KAMI_SHOCKWAVEFADE_STARTTIME 1500 +#define KAMI_SHOCKWAVE_ENDTIME 2000 +// explosion/implosion times +#define KAMI_EXPLODE_STARTTIME 250 +#define KAMI_IMPLODE_STARTTIME 2000 +#define KAMI_IMPLODE_ENDTIME 2250 +// 2nd shockwave times +#define KAMI_SHOCKWAVE2_STARTTIME 2000 +#define KAMI_SHOCKWAVE2FADE_STARTTIME 2500 +#define KAMI_SHOCKWAVE2_ENDTIME 3000 +// radius of the models without scaling +#define KAMI_SHOCKWAVEMODEL_RADIUS 88 +#define KAMI_BOOMSPHEREMODEL_RADIUS 72 +// maximum radius of the models during the effect +#define KAMI_SHOCKWAVE_MAXRADIUS 1320 +#define KAMI_BOOMSPHERE_MAXRADIUS 720 +#define KAMI_SHOCKWAVE2_MAXRADIUS 704 + diff --git a/code/game/bg_slidemove.c b/code/game/bg_slidemove.c index ed1349f..6c957a5 100755 --- a/code/game/bg_slidemove.c +++ b/code/game/bg_slidemove.c @@ -1,325 +1,325 @@ -/* -=========================================================================== -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 Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// -// bg_slidemove.c -- part of bg_pmove functionality - -#include "q_shared.h" -#include "bg_public.h" -#include "bg_local.h" - -/* - -input: origin, velocity, bounds, groundPlane, trace function - -output: origin, velocity, impacts, stairup boolean - -*/ - -/* -================== -PM_SlideMove - -Returns qtrue if the velocity was clipped in some way -================== -*/ -#define MAX_CLIP_PLANES 5 -qboolean PM_SlideMove( qboolean gravity ) { - int bumpcount, numbumps; - vec3_t dir; - float d; - int numplanes; - vec3_t planes[MAX_CLIP_PLANES]; - vec3_t primal_velocity; - vec3_t clipVelocity; - int i, j, k; - trace_t trace; - vec3_t end; - float time_left; - float into; - vec3_t endVelocity; - vec3_t endClipVelocity; - - numbumps = 4; - - VectorCopy (pm->ps->velocity, primal_velocity); - - if ( gravity ) { - VectorCopy( pm->ps->velocity, endVelocity ); - endVelocity[2] -= pm->ps->gravity * pml.frametime; - pm->ps->velocity[2] = ( pm->ps->velocity[2] + endVelocity[2] ) * 0.5; - primal_velocity[2] = endVelocity[2]; - if ( pml.groundPlane ) { - // slide along the ground plane - PM_ClipVelocity (pm->ps->velocity, pml.groundTrace.plane.normal, - pm->ps->velocity, OVERCLIP ); - } - } - - time_left = pml.frametime; - - // never turn against the ground plane - if ( pml.groundPlane ) { - numplanes = 1; - VectorCopy( pml.groundTrace.plane.normal, planes[0] ); - } else { - numplanes = 0; - } - - // never turn against original velocity - VectorNormalize2( pm->ps->velocity, planes[numplanes] ); - numplanes++; - - for ( bumpcount=0 ; bumpcount < numbumps ; bumpcount++ ) { - - // calculate position we are trying to move to - VectorMA( pm->ps->origin, time_left, pm->ps->velocity, end ); - - // see if we can make it there - pm->trace ( &trace, pm->ps->origin, pm->mins, pm->maxs, end, pm->ps->clientNum, pm->tracemask); - - if (trace.allsolid) { - // entity is completely trapped in another solid - pm->ps->velocity[2] = 0; // don't build up falling damage, but allow sideways acceleration - return qtrue; - } - - if (trace.fraction > 0) { - // actually covered some distance - VectorCopy (trace.endpos, pm->ps->origin); - } - - if (trace.fraction == 1) { - break; // moved the entire distance - } - - // save entity for contact - PM_AddTouchEnt( trace.entityNum ); - - time_left -= time_left * trace.fraction; - - if (numplanes >= MAX_CLIP_PLANES) { - // this shouldn't really happen - VectorClear( pm->ps->velocity ); - return qtrue; - } - - // - // if this is the same plane we hit before, nudge velocity - // out along it, which fixes some epsilon issues with - // non-axial planes - // - for ( i = 0 ; i < numplanes ; i++ ) { - if ( DotProduct( trace.plane.normal, planes[i] ) > 0.99 ) { - VectorAdd( trace.plane.normal, pm->ps->velocity, pm->ps->velocity ); - break; - } - } - if ( i < numplanes ) { - continue; - } - VectorCopy (trace.plane.normal, planes[numplanes]); - numplanes++; - - // - // modify velocity so it parallels all of the clip planes - // - - // find a plane that it enters - for ( i = 0 ; i < numplanes ; i++ ) { - into = DotProduct( pm->ps->velocity, planes[i] ); - if ( into >= 0.1 ) { - continue; // move doesn't interact with the plane - } - - // see how hard we are hitting things - if ( -into > pml.impactSpeed ) { - pml.impactSpeed = -into; - } - - // slide along the plane - PM_ClipVelocity (pm->ps->velocity, planes[i], clipVelocity, OVERCLIP ); - - // slide along the plane - PM_ClipVelocity (endVelocity, planes[i], endClipVelocity, OVERCLIP ); - - // see if there is a second plane that the new move enters - for ( j = 0 ; j < numplanes ; j++ ) { - if ( j == i ) { - continue; - } - if ( DotProduct( clipVelocity, planes[j] ) >= 0.1 ) { - continue; // move doesn't interact with the plane - } - - // try clipping the move to the plane - PM_ClipVelocity( clipVelocity, planes[j], clipVelocity, OVERCLIP ); - PM_ClipVelocity( endClipVelocity, planes[j], endClipVelocity, OVERCLIP ); - - // see if it goes back into the first clip plane - if ( DotProduct( clipVelocity, planes[i] ) >= 0 ) { - continue; - } - - // slide the original velocity along the crease - CrossProduct (planes[i], planes[j], dir); - VectorNormalize( dir ); - d = DotProduct( dir, pm->ps->velocity ); - VectorScale( dir, d, clipVelocity ); - - CrossProduct (planes[i], planes[j], dir); - VectorNormalize( dir ); - d = DotProduct( dir, endVelocity ); - VectorScale( dir, d, endClipVelocity ); - - // see if there is a third plane the the new move enters - for ( k = 0 ; k < numplanes ; k++ ) { - if ( k == i || k == j ) { - continue; - } - if ( DotProduct( clipVelocity, planes[k] ) >= 0.1 ) { - continue; // move doesn't interact with the plane - } - - // stop dead at a tripple plane interaction - VectorClear( pm->ps->velocity ); - return qtrue; - } - } - - // if we have fixed all interactions, try another move - VectorCopy( clipVelocity, pm->ps->velocity ); - VectorCopy( endClipVelocity, endVelocity ); - break; - } - } - - if ( gravity ) { - VectorCopy( endVelocity, pm->ps->velocity ); - } - - // don't change velocity if in a timer (FIXME: is this correct?) - if ( pm->ps->pm_time ) { - VectorCopy( primal_velocity, pm->ps->velocity ); - } - - return ( bumpcount != 0 ); -} - -/* -================== -PM_StepSlideMove - -================== -*/ -void PM_StepSlideMove( qboolean gravity ) { - vec3_t start_o, start_v; - vec3_t down_o, down_v; - trace_t trace; -// float down_dist, up_dist; -// vec3_t delta, delta2; - vec3_t up, down; - float stepSize; - - VectorCopy (pm->ps->origin, start_o); - VectorCopy (pm->ps->velocity, start_v); - - if ( PM_SlideMove( gravity ) == 0 ) { - return; // we got exactly where we wanted to go first try - } - - VectorCopy(start_o, down); - down[2] -= STEPSIZE; - pm->trace (&trace, start_o, pm->mins, pm->maxs, down, pm->ps->clientNum, pm->tracemask); - VectorSet(up, 0, 0, 1); - // never step up when you still have up velocity - if ( pm->ps->velocity[2] > 0 && (trace.fraction == 1.0 || - DotProduct(trace.plane.normal, up) < 0.7)) { - return; - } - - VectorCopy (pm->ps->origin, down_o); - VectorCopy (pm->ps->velocity, down_v); - - VectorCopy (start_o, up); - up[2] += STEPSIZE; - - // test the player position if they were a stepheight higher - pm->trace (&trace, start_o, pm->mins, pm->maxs, up, pm->ps->clientNum, pm->tracemask); - if ( trace.allsolid ) { - if ( pm->debugLevel ) { - Com_Printf("%i:bend can't step\n", c_pmove); - } - return; // can't step up - } - - stepSize = trace.endpos[2] - start_o[2]; - // try slidemove from this position - VectorCopy (trace.endpos, pm->ps->origin); - VectorCopy (start_v, pm->ps->velocity); - - PM_SlideMove( gravity ); - - // push down the final amount - VectorCopy (pm->ps->origin, down); - down[2] -= stepSize; - pm->trace (&trace, pm->ps->origin, pm->mins, pm->maxs, down, pm->ps->clientNum, pm->tracemask); - if ( !trace.allsolid ) { - VectorCopy (trace.endpos, pm->ps->origin); - } - if ( trace.fraction < 1.0 ) { - PM_ClipVelocity( pm->ps->velocity, trace.plane.normal, pm->ps->velocity, OVERCLIP ); - } - -#if 0 - // if the down trace can trace back to the original position directly, don't step - pm->trace( &trace, pm->ps->origin, pm->mins, pm->maxs, start_o, pm->ps->clientNum, pm->tracemask); - if ( trace.fraction == 1.0 ) { - // use the original move - VectorCopy (down_o, pm->ps->origin); - VectorCopy (down_v, pm->ps->velocity); - if ( pm->debugLevel ) { - Com_Printf("%i:bend\n", c_pmove); - } - } else -#endif - { - // use the step move - float delta; - - delta = pm->ps->origin[2] - start_o[2]; - if ( delta > 2 ) { - if ( delta < 7 ) { - PM_AddEvent( EV_STEP_4 ); - } else if ( delta < 11 ) { - PM_AddEvent( EV_STEP_8 ); - } else if ( delta < 15 ) { - PM_AddEvent( EV_STEP_12 ); - } else { - PM_AddEvent( EV_STEP_16 ); - } - } - if ( pm->debugLevel ) { - Com_Printf("%i:stepped\n", c_pmove); - } - } -} - +/* +=========================================================================== +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 Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// +// bg_slidemove.c -- part of bg_pmove functionality + +#include "q_shared.h" +#include "bg_public.h" +#include "bg_local.h" + +/* + +input: origin, velocity, bounds, groundPlane, trace function + +output: origin, velocity, impacts, stairup boolean + +*/ + +/* +================== +PM_SlideMove + +Returns qtrue if the velocity was clipped in some way +================== +*/ +#define MAX_CLIP_PLANES 5 +qboolean PM_SlideMove( qboolean gravity ) { + int bumpcount, numbumps; + vec3_t dir; + float d; + int numplanes; + vec3_t planes[MAX_CLIP_PLANES]; + vec3_t primal_velocity; + vec3_t clipVelocity; + int i, j, k; + trace_t trace; + vec3_t end; + float time_left; + float into; + vec3_t endVelocity; + vec3_t endClipVelocity; + + numbumps = 4; + + VectorCopy (pm->ps->velocity, primal_velocity); + + if ( gravity ) { + VectorCopy( pm->ps->velocity, endVelocity ); + endVelocity[2] -= pm->ps->gravity * pml.frametime; + pm->ps->velocity[2] = ( pm->ps->velocity[2] + endVelocity[2] ) * 0.5; + primal_velocity[2] = endVelocity[2]; + if ( pml.groundPlane ) { + // slide along the ground plane + PM_ClipVelocity (pm->ps->velocity, pml.groundTrace.plane.normal, + pm->ps->velocity, OVERCLIP ); + } + } + + time_left = pml.frametime; + + // never turn against the ground plane + if ( pml.groundPlane ) { + numplanes = 1; + VectorCopy( pml.groundTrace.plane.normal, planes[0] ); + } else { + numplanes = 0; + } + + // never turn against original velocity + VectorNormalize2( pm->ps->velocity, planes[numplanes] ); + numplanes++; + + for ( bumpcount=0 ; bumpcount < numbumps ; bumpcount++ ) { + + // calculate position we are trying to move to + VectorMA( pm->ps->origin, time_left, pm->ps->velocity, end ); + + // see if we can make it there + pm->trace ( &trace, pm->ps->origin, pm->mins, pm->maxs, end, pm->ps->clientNum, pm->tracemask); + + if (trace.allsolid) { + // entity is completely trapped in another solid + pm->ps->velocity[2] = 0; // don't build up falling damage, but allow sideways acceleration + return qtrue; + } + + if (trace.fraction > 0) { + // actually covered some distance + VectorCopy (trace.endpos, pm->ps->origin); + } + + if (trace.fraction == 1) { + break; // moved the entire distance + } + + // save entity for contact + PM_AddTouchEnt( trace.entityNum ); + + time_left -= time_left * trace.fraction; + + if (numplanes >= MAX_CLIP_PLANES) { + // this shouldn't really happen + VectorClear( pm->ps->velocity ); + return qtrue; + } + + // + // if this is the same plane we hit before, nudge velocity + // out along it, which fixes some epsilon issues with + // non-axial planes + // + for ( i = 0 ; i < numplanes ; i++ ) { + if ( DotProduct( trace.plane.normal, planes[i] ) > 0.99 ) { + VectorAdd( trace.plane.normal, pm->ps->velocity, pm->ps->velocity ); + break; + } + } + if ( i < numplanes ) { + continue; + } + VectorCopy (trace.plane.normal, planes[numplanes]); + numplanes++; + + // + // modify velocity so it parallels all of the clip planes + // + + // find a plane that it enters + for ( i = 0 ; i < numplanes ; i++ ) { + into = DotProduct( pm->ps->velocity, planes[i] ); + if ( into >= 0.1 ) { + continue; // move doesn't interact with the plane + } + + // see how hard we are hitting things + if ( -into > pml.impactSpeed ) { + pml.impactSpeed = -into; + } + + // slide along the plane + PM_ClipVelocity (pm->ps->velocity, planes[i], clipVelocity, OVERCLIP ); + + // slide along the plane + PM_ClipVelocity (endVelocity, planes[i], endClipVelocity, OVERCLIP ); + + // see if there is a second plane that the new move enters + for ( j = 0 ; j < numplanes ; j++ ) { + if ( j == i ) { + continue; + } + if ( DotProduct( clipVelocity, planes[j] ) >= 0.1 ) { + continue; // move doesn't interact with the plane + } + + // try clipping the move to the plane + PM_ClipVelocity( clipVelocity, planes[j], clipVelocity, OVERCLIP ); + PM_ClipVelocity( endClipVelocity, planes[j], endClipVelocity, OVERCLIP ); + + // see if it goes back into the first clip plane + if ( DotProduct( clipVelocity, planes[i] ) >= 0 ) { + continue; + } + + // slide the original velocity along the crease + CrossProduct (planes[i], planes[j], dir); + VectorNormalize( dir ); + d = DotProduct( dir, pm->ps->velocity ); + VectorScale( dir, d, clipVelocity ); + + CrossProduct (planes[i], planes[j], dir); + VectorNormalize( dir ); + d = DotProduct( dir, endVelocity ); + VectorScale( dir, d, endClipVelocity ); + + // see if there is a third plane the the new move enters + for ( k = 0 ; k < numplanes ; k++ ) { + if ( k == i || k == j ) { + continue; + } + if ( DotProduct( clipVelocity, planes[k] ) >= 0.1 ) { + continue; // move doesn't interact with the plane + } + + // stop dead at a tripple plane interaction + VectorClear( pm->ps->velocity ); + return qtrue; + } + } + + // if we have fixed all interactions, try another move + VectorCopy( clipVelocity, pm->ps->velocity ); + VectorCopy( endClipVelocity, endVelocity ); + break; + } + } + + if ( gravity ) { + VectorCopy( endVelocity, pm->ps->velocity ); + } + + // don't change velocity if in a timer (FIXME: is this correct?) + if ( pm->ps->pm_time ) { + VectorCopy( primal_velocity, pm->ps->velocity ); + } + + return ( bumpcount != 0 ); +} + +/* +================== +PM_StepSlideMove + +================== +*/ +void PM_StepSlideMove( qboolean gravity ) { + vec3_t start_o, start_v; + vec3_t down_o, down_v; + trace_t trace; +// float down_dist, up_dist; +// vec3_t delta, delta2; + vec3_t up, down; + float stepSize; + + VectorCopy (pm->ps->origin, start_o); + VectorCopy (pm->ps->velocity, start_v); + + if ( PM_SlideMove( gravity ) == 0 ) { + return; // we got exactly where we wanted to go first try + } + + VectorCopy(start_o, down); + down[2] -= STEPSIZE; + pm->trace (&trace, start_o, pm->mins, pm->maxs, down, pm->ps->clientNum, pm->tracemask); + VectorSet(up, 0, 0, 1); + // never step up when you still have up velocity + if ( pm->ps->velocity[2] > 0 && (trace.fraction == 1.0 || + DotProduct(trace.plane.normal, up) < 0.7)) { + return; + } + + VectorCopy (pm->ps->origin, down_o); + VectorCopy (pm->ps->velocity, down_v); + + VectorCopy (start_o, up); + up[2] += STEPSIZE; + + // test the player position if they were a stepheight higher + pm->trace (&trace, start_o, pm->mins, pm->maxs, up, pm->ps->clientNum, pm->tracemask); + if ( trace.allsolid ) { + if ( pm->debugLevel ) { + Com_Printf("%i:bend can't step\n", c_pmove); + } + return; // can't step up + } + + stepSize = trace.endpos[2] - start_o[2]; + // try slidemove from this position + VectorCopy (trace.endpos, pm->ps->origin); + VectorCopy (start_v, pm->ps->velocity); + + PM_SlideMove( gravity ); + + // push down the final amount + VectorCopy (pm->ps->origin, down); + down[2] -= stepSize; + pm->trace (&trace, pm->ps->origin, pm->mins, pm->maxs, down, pm->ps->clientNum, pm->tracemask); + if ( !trace.allsolid ) { + VectorCopy (trace.endpos, pm->ps->origin); + } + if ( trace.fraction < 1.0 ) { + PM_ClipVelocity( pm->ps->velocity, trace.plane.normal, pm->ps->velocity, OVERCLIP ); + } + +#if 0 + // if the down trace can trace back to the original position directly, don't step + pm->trace( &trace, pm->ps->origin, pm->mins, pm->maxs, start_o, pm->ps->clientNum, pm->tracemask); + if ( trace.fraction == 1.0 ) { + // use the original move + VectorCopy (down_o, pm->ps->origin); + VectorCopy (down_v, pm->ps->velocity); + if ( pm->debugLevel ) { + Com_Printf("%i:bend\n", c_pmove); + } + } else +#endif + { + // use the step move + float delta; + + delta = pm->ps->origin[2] - start_o[2]; + if ( delta > 2 ) { + if ( delta < 7 ) { + PM_AddEvent( EV_STEP_4 ); + } else if ( delta < 11 ) { + PM_AddEvent( EV_STEP_8 ); + } else if ( delta < 15 ) { + PM_AddEvent( EV_STEP_12 ); + } else { + PM_AddEvent( EV_STEP_16 ); + } + } + if ( pm->debugLevel ) { + Com_Printf("%i:stepped\n", c_pmove); + } + } +} + diff --git a/code/game/botlib.h b/code/game/botlib.h index a9b45db..687affe 100755 --- a/code/game/botlib.h +++ b/code/game/botlib.h @@ -1,516 +1,516 @@ -/* -=========================================================================== -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 Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// -/***************************************************************************** - * name: botlib.h - * - * desc: bot AI library - * - * $Archive: /source/code/game/botai.h $ - * - *****************************************************************************/ - -#define BOTLIB_API_VERSION 2 - -struct aas_clientmove_s; -struct aas_entityinfo_s; -struct aas_areainfo_s; -struct aas_altroutegoal_s; -struct aas_predictroute_s; -struct bot_consolemessage_s; -struct bot_match_s; -struct bot_goal_s; -struct bot_moveresult_s; -struct bot_initmove_s; -struct weaponinfo_s; - -#define BOTFILESBASEFOLDER "botfiles" -//debug line colors -#define LINECOLOR_NONE -1 -#define LINECOLOR_RED 1//0xf2f2f0f0L -#define LINECOLOR_GREEN 2//0xd0d1d2d3L -#define LINECOLOR_BLUE 3//0xf3f3f1f1L -#define LINECOLOR_YELLOW 4//0xdcdddedfL -#define LINECOLOR_ORANGE 5//0xe0e1e2e3L - -//Print types -#define PRT_MESSAGE 1 -#define PRT_WARNING 2 -#define PRT_ERROR 3 -#define PRT_FATAL 4 -#define PRT_EXIT 5 - -//console message types -#define CMS_NORMAL 0 -#define CMS_CHAT 1 - -//botlib error codes -#define BLERR_NOERROR 0 //no error -#define BLERR_LIBRARYNOTSETUP 1 //library not setup -#define BLERR_INVALIDENTITYNUMBER 2 //invalid entity number -#define BLERR_NOAASFILE 3 //no AAS file available -#define BLERR_CANNOTOPENAASFILE 4 //cannot open AAS file -#define BLERR_WRONGAASFILEID 5 //incorrect AAS file id -#define BLERR_WRONGAASFILEVERSION 6 //incorrect AAS file version -#define BLERR_CANNOTREADAASLUMP 7 //cannot read AAS file lump -#define BLERR_CANNOTLOADICHAT 8 //cannot load initial chats -#define BLERR_CANNOTLOADITEMWEIGHTS 9 //cannot load item weights -#define BLERR_CANNOTLOADITEMCONFIG 10 //cannot load item config -#define BLERR_CANNOTLOADWEAPONWEIGHTS 11 //cannot load weapon weights -#define BLERR_CANNOTLOADWEAPONCONFIG 12 //cannot load weapon config - -//action flags -#define ACTION_ATTACK 0x0000001 -#define ACTION_USE 0x0000002 -#define ACTION_RESPAWN 0x0000008 -#define ACTION_JUMP 0x0000010 -#define ACTION_MOVEUP 0x0000020 -#define ACTION_CROUCH 0x0000080 -#define ACTION_MOVEDOWN 0x0000100 -#define ACTION_MOVEFORWARD 0x0000200 -#define ACTION_MOVEBACK 0x0000800 -#define ACTION_MOVELEFT 0x0001000 -#define ACTION_MOVERIGHT 0x0002000 -#define ACTION_DELAYEDJUMP 0x0008000 -#define ACTION_TALK 0x0010000 -#define ACTION_GESTURE 0x0020000 -#define ACTION_WALK 0x0080000 -#define ACTION_AFFIRMATIVE 0x0100000 -#define ACTION_NEGATIVE 0x0200000 -#define ACTION_GETFLAG 0x0800000 -#define ACTION_GUARDBASE 0x1000000 -#define ACTION_PATROL 0x2000000 -#define ACTION_FOLLOWME 0x8000000 - -//the bot input, will be converted to an usercmd_t -typedef struct bot_input_s -{ - float thinktime; //time since last output (in seconds) - vec3_t dir; //movement direction - float speed; //speed in the range [0, 400] - vec3_t viewangles; //the view angles - int actionflags; //one of the ACTION_? flags - int weapon; //weapon to use -} bot_input_t; - -#ifndef BSPTRACE - -#define BSPTRACE - -//bsp_trace_t hit surface -typedef struct bsp_surface_s -{ - char name[16]; - int flags; - int value; -} bsp_surface_t; - -//remove the bsp_trace_s structure definition l8r on -//a trace is returned when a box is swept through the world -typedef struct bsp_trace_s -{ - qboolean allsolid; // if true, plane is not valid - qboolean startsolid; // if true, the initial point was in a solid area - float fraction; // time completed, 1.0 = didn't hit anything - vec3_t endpos; // final position - cplane_t plane; // surface normal at impact - float exp_dist; // expanded plane distance - int sidenum; // number of the brush side hit - bsp_surface_t surface; // the hit point surface - int contents; // contents on other side of surface hit - int ent; // number of entity hit -} bsp_trace_t; - -#endif // BSPTRACE - -//entity state -typedef struct bot_entitystate_s -{ - int type; // entity type - int flags; // entity flags - vec3_t origin; // origin of the entity - vec3_t angles; // angles of the model - vec3_t old_origin; // for lerping - vec3_t mins; // bounding box minimums - vec3_t maxs; // bounding box maximums - int groundent; // ground entity - int solid; // solid type - int modelindex; // model used - int modelindex2; // weapons, CTF flags, etc - int frame; // model frame number - int event; // impulse events -- muzzle flashes, footsteps, etc - int eventParm; // even parameter - int powerups; // bit flags - int weapon; // determines weapon and flash model, etc - int legsAnim; // mask off ANIM_TOGGLEBIT - int torsoAnim; // mask off ANIM_TOGGLEBIT -} bot_entitystate_t; - -//bot AI library exported functions -typedef struct botlib_import_s -{ - //print messages from the bot library - void (QDECL *Print)(int type, char *fmt, ...); - //trace a bbox through the world - void (*Trace)(bsp_trace_t *trace, vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, int passent, int contentmask); - //trace a bbox against a specific entity - void (*EntityTrace)(bsp_trace_t *trace, vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, int entnum, int contentmask); - //retrieve the contents at the given point - int (*PointContents)(vec3_t point); - //check if the point is in potential visible sight - int (*inPVS)(vec3_t p1, vec3_t p2); - //retrieve the BSP entity data lump - char *(*BSPEntityData)(void); - // - void (*BSPModelMinsMaxsOrigin)(int modelnum, vec3_t angles, vec3_t mins, vec3_t maxs, vec3_t origin); - //send a bot client command - void (*BotClientCommand)(int client, char *command); - //memory allocation - void *(*GetMemory)(int size); // allocate from Zone - void (*FreeMemory)(void *ptr); // free memory from Zone - int (*AvailableMemory)(void); // available Zone memory - void *(*HunkAlloc)(int size); // allocate from hunk - //file system access - int (*FS_FOpenFile)( const char *qpath, fileHandle_t *file, fsMode_t mode ); - int (*FS_Read)( void *buffer, int len, fileHandle_t f ); - int (*FS_Write)( const void *buffer, int len, fileHandle_t f ); - void (*FS_FCloseFile)( fileHandle_t f ); - int (*FS_Seek)( fileHandle_t f, long offset, int origin ); - //debug visualisation stuff - int (*DebugLineCreate)(void); - void (*DebugLineDelete)(int line); - void (*DebugLineShow)(int line, vec3_t start, vec3_t end, int color); - // - int (*DebugPolygonCreate)(int color, int numPoints, vec3_t *points); - void (*DebugPolygonDelete)(int id); -} botlib_import_t; - -typedef struct aas_export_s -{ - //----------------------------------- - // be_aas_entity.h - //----------------------------------- - void (*AAS_EntityInfo)(int entnum, struct aas_entityinfo_s *info); - //----------------------------------- - // be_aas_main.h - //----------------------------------- - int (*AAS_Initialized)(void); - void (*AAS_PresenceTypeBoundingBox)(int presencetype, vec3_t mins, vec3_t maxs); - float (*AAS_Time)(void); - //-------------------------------------------- - // be_aas_sample.c - //-------------------------------------------- - int (*AAS_PointAreaNum)(vec3_t point); - int (*AAS_PointReachabilityAreaIndex)( vec3_t point ); - int (*AAS_TraceAreas)(vec3_t start, vec3_t end, int *areas, vec3_t *points, int maxareas); - int (*AAS_BBoxAreas)(vec3_t absmins, vec3_t absmaxs, int *areas, int maxareas); - int (*AAS_AreaInfo)( int areanum, struct aas_areainfo_s *info ); - //-------------------------------------------- - // be_aas_bspq3.c - //-------------------------------------------- - int (*AAS_PointContents)(vec3_t point); - int (*AAS_NextBSPEntity)(int ent); - int (*AAS_ValueForBSPEpairKey)(int ent, char *key, char *value, int size); - int (*AAS_VectorForBSPEpairKey)(int ent, char *key, vec3_t v); - int (*AAS_FloatForBSPEpairKey)(int ent, char *key, float *value); - int (*AAS_IntForBSPEpairKey)(int ent, char *key, int *value); - //-------------------------------------------- - // be_aas_reach.c - //-------------------------------------------- - int (*AAS_AreaReachability)(int areanum); - //-------------------------------------------- - // be_aas_route.c - //-------------------------------------------- - int (*AAS_AreaTravelTimeToGoalArea)(int areanum, vec3_t origin, int goalareanum, int travelflags); - int (*AAS_EnableRoutingArea)(int areanum, int enable); - int (*AAS_PredictRoute)(struct aas_predictroute_s *route, int areanum, vec3_t origin, - int goalareanum, int travelflags, int maxareas, int maxtime, - int stopevent, int stopcontents, int stoptfl, int stopareanum); - //-------------------------------------------- - // be_aas_altroute.c - //-------------------------------------------- - int (*AAS_AlternativeRouteGoals)(vec3_t start, int startareanum, vec3_t goal, int goalareanum, int travelflags, - struct aas_altroutegoal_s *altroutegoals, int maxaltroutegoals, - int type); - //-------------------------------------------- - // be_aas_move.c - //-------------------------------------------- - int (*AAS_Swimming)(vec3_t origin); - int (*AAS_PredictClientMovement)(struct aas_clientmove_s *move, - int entnum, vec3_t origin, - int presencetype, int onground, - vec3_t velocity, vec3_t cmdmove, - int cmdframes, - int maxframes, float frametime, - int stopevent, int stopareanum, int visualize); -} aas_export_t; - -typedef struct ea_export_s -{ - //ClientCommand elementary actions - void (*EA_Command)(int client, char *command ); - void (*EA_Say)(int client, char *str); - void (*EA_SayTeam)(int client, char *str); - // - void (*EA_Action)(int client, int action); - void (*EA_Gesture)(int client); - void (*EA_Talk)(int client); - void (*EA_Attack)(int client); - void (*EA_Use)(int client); - void (*EA_Respawn)(int client); - void (*EA_MoveUp)(int client); - void (*EA_MoveDown)(int client); - void (*EA_MoveForward)(int client); - void (*EA_MoveBack)(int client); - void (*EA_MoveLeft)(int client); - void (*EA_MoveRight)(int client); - void (*EA_Crouch)(int client); - - void (*EA_SelectWeapon)(int client, int weapon); - void (*EA_Jump)(int client); - void (*EA_DelayedJump)(int client); - void (*EA_Move)(int client, vec3_t dir, float speed); - void (*EA_View)(int client, vec3_t viewangles); - //send regular input to the server - void (*EA_EndRegular)(int client, float thinktime); - void (*EA_GetInput)(int client, float thinktime, bot_input_t *input); - void (*EA_ResetInput)(int client); -} ea_export_t; - -typedef struct ai_export_s -{ - //----------------------------------- - // be_ai_char.h - //----------------------------------- - int (*BotLoadCharacter)(char *charfile, float skill); - void (*BotFreeCharacter)(int character); - float (*Characteristic_Float)(int character, int index); - float (*Characteristic_BFloat)(int character, int index, float min, float max); - int (*Characteristic_Integer)(int character, int index); - int (*Characteristic_BInteger)(int character, int index, int min, int max); - void (*Characteristic_String)(int character, int index, char *buf, int size); - //----------------------------------- - // be_ai_chat.h - //----------------------------------- - int (*BotAllocChatState)(void); - void (*BotFreeChatState)(int handle); - void (*BotQueueConsoleMessage)(int chatstate, int type, char *message); - void (*BotRemoveConsoleMessage)(int chatstate, int handle); - int (*BotNextConsoleMessage)(int chatstate, struct bot_consolemessage_s *cm); - int (*BotNumConsoleMessages)(int chatstate); - void (*BotInitialChat)(int chatstate, char *type, int mcontext, char *var0, char *var1, char *var2, char *var3, char *var4, char *var5, char *var6, char *var7); - int (*BotNumInitialChats)(int chatstate, char *type); - int (*BotReplyChat)(int chatstate, char *message, int mcontext, int vcontext, char *var0, char *var1, char *var2, char *var3, char *var4, char *var5, char *var6, char *var7); - int (*BotChatLength)(int chatstate); - void (*BotEnterChat)(int chatstate, int client, int sendto); - void (*BotGetChatMessage)(int chatstate, char *buf, int size); - int (*StringContains)(char *str1, char *str2, int casesensitive); - int (*BotFindMatch)(char *str, struct bot_match_s *match, unsigned long int context); - void (*BotMatchVariable)(struct bot_match_s *match, int variable, char *buf, int size); - void (*UnifyWhiteSpaces)(char *string); - void (*BotReplaceSynonyms)(char *string, unsigned long int context); - int (*BotLoadChatFile)(int chatstate, char *chatfile, char *chatname); - void (*BotSetChatGender)(int chatstate, int gender); - void (*BotSetChatName)(int chatstate, char *name, int client); - //----------------------------------- - // be_ai_goal.h - //----------------------------------- - void (*BotResetGoalState)(int goalstate); - void (*BotResetAvoidGoals)(int goalstate); - void (*BotRemoveFromAvoidGoals)(int goalstate, int number); - void (*BotPushGoal)(int goalstate, struct bot_goal_s *goal); - void (*BotPopGoal)(int goalstate); - void (*BotEmptyGoalStack)(int goalstate); - void (*BotDumpAvoidGoals)(int goalstate); - void (*BotDumpGoalStack)(int goalstate); - void (*BotGoalName)(int number, char *name, int size); - int (*BotGetTopGoal)(int goalstate, struct bot_goal_s *goal); - int (*BotGetSecondGoal)(int goalstate, struct bot_goal_s *goal); - int (*BotChooseLTGItem)(int goalstate, vec3_t origin, int *inventory, int travelflags); - int (*BotChooseNBGItem)(int goalstate, vec3_t origin, int *inventory, int travelflags, - struct bot_goal_s *ltg, float maxtime); - int (*BotTouchingGoal)(vec3_t origin, struct bot_goal_s *goal); - int (*BotItemGoalInVisButNotVisible)(int viewer, vec3_t eye, vec3_t viewangles, struct bot_goal_s *goal); - int (*BotGetLevelItemGoal)(int index, char *classname, struct bot_goal_s *goal); - int (*BotGetNextCampSpotGoal)(int num, struct bot_goal_s *goal); - int (*BotGetMapLocationGoal)(char *name, struct bot_goal_s *goal); - float (*BotAvoidGoalTime)(int goalstate, int number); - void (*BotSetAvoidGoalTime)(int goalstate, int number, float avoidtime); - void (*BotInitLevelItems)(void); - void (*BotUpdateEntityItems)(void); - int (*BotLoadItemWeights)(int goalstate, char *filename); - void (*BotFreeItemWeights)(int goalstate); - void (*BotInterbreedGoalFuzzyLogic)(int parent1, int parent2, int child); - void (*BotSaveGoalFuzzyLogic)(int goalstate, char *filename); - void (*BotMutateGoalFuzzyLogic)(int goalstate, float range); - int (*BotAllocGoalState)(int client); - void (*BotFreeGoalState)(int handle); - //----------------------------------- - // be_ai_move.h - //----------------------------------- - void (*BotResetMoveState)(int movestate); - void (*BotMoveToGoal)(struct bot_moveresult_s *result, int movestate, struct bot_goal_s *goal, int travelflags); - int (*BotMoveInDirection)(int movestate, vec3_t dir, float speed, int type); - void (*BotResetAvoidReach)(int movestate); - void (*BotResetLastAvoidReach)(int movestate); - int (*BotReachabilityArea)(vec3_t origin, int testground); - int (*BotMovementViewTarget)(int movestate, struct bot_goal_s *goal, int travelflags, float lookahead, vec3_t target); - int (*BotPredictVisiblePosition)(vec3_t origin, int areanum, struct bot_goal_s *goal, int travelflags, vec3_t target); - int (*BotAllocMoveState)(void); - void (*BotFreeMoveState)(int handle); - void (*BotInitMoveState)(int handle, struct bot_initmove_s *initmove); - void (*BotAddAvoidSpot)(int movestate, vec3_t origin, float radius, int type); - //----------------------------------- - // be_ai_weap.h - //----------------------------------- - int (*BotChooseBestFightWeapon)(int weaponstate, int *inventory); - void (*BotGetWeaponInfo)(int weaponstate, int weapon, struct weaponinfo_s *weaponinfo); - int (*BotLoadWeaponWeights)(int weaponstate, char *filename); - int (*BotAllocWeaponState)(void); - void (*BotFreeWeaponState)(int weaponstate); - void (*BotResetWeaponState)(int weaponstate); - //----------------------------------- - // be_ai_gen.h - //----------------------------------- - int (*GeneticParentsAndChildSelection)(int numranks, float *ranks, int *parent1, int *parent2, int *child); -} ai_export_t; - -//bot AI library imported functions -typedef struct botlib_export_s -{ - //Area Awareness System functions - aas_export_t aas; - //Elementary Action functions - ea_export_t ea; - //AI functions - ai_export_t ai; - //setup the bot library, returns BLERR_ - int (*BotLibSetup)(void); - //shutdown the bot library, returns BLERR_ - int (*BotLibShutdown)(void); - //sets a library variable returns BLERR_ - int (*BotLibVarSet)(char *var_name, char *value); - //gets a library variable returns BLERR_ - int (*BotLibVarGet)(char *var_name, char *value, int size); - - //sets a C-like define returns BLERR_ - int (*PC_AddGlobalDefine)(char *string); - int (*PC_LoadSourceHandle)(const char *filename); - int (*PC_FreeSourceHandle)(int handle); - int (*PC_ReadTokenHandle)(int handle, pc_token_t *pc_token); - int (*PC_SourceFileAndLine)(int handle, char *filename, int *line); - - //start a frame in the bot library - int (*BotLibStartFrame)(float time); - //load a new map in the bot library - int (*BotLibLoadMap)(const char *mapname); - //entity updates - int (*BotLibUpdateEntity)(int ent, bot_entitystate_t *state); - //just for testing - int (*Test)(int parm0, char *parm1, vec3_t parm2, vec3_t parm3); -} botlib_export_t; - -//linking of bot library -botlib_export_t *GetBotLibAPI( int apiVersion, botlib_import_t *import ); - -/* Library variables: - -name: default: module(s): description: - -"basedir" "" l_utils.c base directory -"gamedir" "" l_utils.c game directory -"cddir" "" l_utils.c CD directory - -"log" "0" l_log.c enable/disable creating a log file -"maxclients" "4" be_interface.c maximum number of clients -"maxentities" "1024" be_interface.c maximum number of entities -"bot_developer" "0" be_interface.c bot developer mode - -"phys_friction" "6" be_aas_move.c ground friction -"phys_stopspeed" "100" be_aas_move.c stop speed -"phys_gravity" "800" be_aas_move.c gravity value -"phys_waterfriction" "1" be_aas_move.c water friction -"phys_watergravity" "400" be_aas_move.c gravity in water -"phys_maxvelocity" "320" be_aas_move.c maximum velocity -"phys_maxwalkvelocity" "320" be_aas_move.c maximum walk velocity -"phys_maxcrouchvelocity" "100" be_aas_move.c maximum crouch velocity -"phys_maxswimvelocity" "150" be_aas_move.c maximum swim velocity -"phys_walkaccelerate" "10" be_aas_move.c walk acceleration -"phys_airaccelerate" "1" be_aas_move.c air acceleration -"phys_swimaccelerate" "4" be_aas_move.c swim acceleration -"phys_maxstep" "18" be_aas_move.c maximum step height -"phys_maxsteepness" "0.7" be_aas_move.c maximum floor steepness -"phys_maxbarrier" "32" be_aas_move.c maximum barrier height -"phys_maxwaterjump" "19" be_aas_move.c maximum waterjump height -"phys_jumpvel" "270" be_aas_move.c jump z velocity -"phys_falldelta5" "40" be_aas_move.c -"phys_falldelta10" "60" be_aas_move.c -"rs_waterjump" "400" be_aas_move.c -"rs_teleport" "50" be_aas_move.c -"rs_barrierjump" "100" be_aas_move.c -"rs_startcrouch" "300" be_aas_move.c -"rs_startgrapple" "500" be_aas_move.c -"rs_startwalkoffledge" "70" be_aas_move.c -"rs_startjump" "300" be_aas_move.c -"rs_rocketjump" "500" be_aas_move.c -"rs_bfgjump" "500" be_aas_move.c -"rs_jumppad" "250" be_aas_move.c -"rs_aircontrolledjumppad" "300" be_aas_move.c -"rs_funcbob" "300" be_aas_move.c -"rs_startelevator" "50" be_aas_move.c -"rs_falldamage5" "300" be_aas_move.c -"rs_falldamage10" "500" be_aas_move.c -"rs_maxjumpfallheight" "450" be_aas_move.c - -"max_aaslinks" "4096" be_aas_sample.c maximum links in the AAS -"max_routingcache" "4096" be_aas_route.c maximum routing cache size in KB -"forceclustering" "0" be_aas_main.c force recalculation of clusters -"forcereachability" "0" be_aas_main.c force recalculation of reachabilities -"forcewrite" "0" be_aas_main.c force writing of aas file -"aasoptimize" "0" be_aas_main.c enable aas optimization -"sv_mapChecksum" "0" be_aas_main.c BSP file checksum -"bot_visualizejumppads" "0" be_aas_reach.c visualize jump pads - -"bot_reloadcharacters" "0" - reload bot character files -"ai_gametype" "0" be_ai_goal.c game type -"droppedweight" "1000" be_ai_goal.c additional dropped item weight -"weapindex_rocketlauncher" "5" be_ai_move.c rl weapon index for rocket jumping -"weapindex_bfg10k" "9" be_ai_move.c bfg weapon index for bfg jumping -"weapindex_grapple" "10" be_ai_move.c grapple weapon index for grappling -"entitytypemissile" "3" be_ai_move.c ET_MISSILE -"offhandgrapple" "0" be_ai_move.c enable off hand grapple hook -"cmd_grappleon" "grappleon" be_ai_move.c command to activate off hand grapple -"cmd_grappleoff" "grappleoff" be_ai_move.c command to deactivate off hand grapple -"itemconfig" "items.c" be_ai_goal.c item configuration file -"weaponconfig" "weapons.c" be_ai_weap.c weapon configuration file -"synfile" "syn.c" be_ai_chat.c file with synonyms -"rndfile" "rnd.c" be_ai_chat.c file with random strings -"matchfile" "match.c" be_ai_chat.c file with match strings -"nochat" "0" be_ai_chat.c disable chats -"max_messages" "1024" be_ai_chat.c console message heap size -"max_weaponinfo" "32" be_ai_weap.c maximum number of weapon info -"max_projectileinfo" "32" be_ai_weap.c maximum number of projectile info -"max_iteminfo" "256" be_ai_goal.c maximum number of item info -"max_levelitems" "256" be_ai_goal.c maximum number of level items - -*/ - +/* +=========================================================================== +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 Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// +/***************************************************************************** + * name: botlib.h + * + * desc: bot AI library + * + * $Archive: /source/code/game/botai.h $ + * + *****************************************************************************/ + +#define BOTLIB_API_VERSION 2 + +struct aas_clientmove_s; +struct aas_entityinfo_s; +struct aas_areainfo_s; +struct aas_altroutegoal_s; +struct aas_predictroute_s; +struct bot_consolemessage_s; +struct bot_match_s; +struct bot_goal_s; +struct bot_moveresult_s; +struct bot_initmove_s; +struct weaponinfo_s; + +#define BOTFILESBASEFOLDER "botfiles" +//debug line colors +#define LINECOLOR_NONE -1 +#define LINECOLOR_RED 1//0xf2f2f0f0L +#define LINECOLOR_GREEN 2//0xd0d1d2d3L +#define LINECOLOR_BLUE 3//0xf3f3f1f1L +#define LINECOLOR_YELLOW 4//0xdcdddedfL +#define LINECOLOR_ORANGE 5//0xe0e1e2e3L + +//Print types +#define PRT_MESSAGE 1 +#define PRT_WARNING 2 +#define PRT_ERROR 3 +#define PRT_FATAL 4 +#define PRT_EXIT 5 + +//console message types +#define CMS_NORMAL 0 +#define CMS_CHAT 1 + +//botlib error codes +#define BLERR_NOERROR 0 //no error +#define BLERR_LIBRARYNOTSETUP 1 //library not setup +#define BLERR_INVALIDENTITYNUMBER 2 //invalid entity number +#define BLERR_NOAASFILE 3 //no AAS file available +#define BLERR_CANNOTOPENAASFILE 4 //cannot open AAS file +#define BLERR_WRONGAASFILEID 5 //incorrect AAS file id +#define BLERR_WRONGAASFILEVERSION 6 //incorrect AAS file version +#define BLERR_CANNOTREADAASLUMP 7 //cannot read AAS file lump +#define BLERR_CANNOTLOADICHAT 8 //cannot load initial chats +#define BLERR_CANNOTLOADITEMWEIGHTS 9 //cannot load item weights +#define BLERR_CANNOTLOADITEMCONFIG 10 //cannot load item config +#define BLERR_CANNOTLOADWEAPONWEIGHTS 11 //cannot load weapon weights +#define BLERR_CANNOTLOADWEAPONCONFIG 12 //cannot load weapon config + +//action flags +#define ACTION_ATTACK 0x0000001 +#define ACTION_USE 0x0000002 +#define ACTION_RESPAWN 0x0000008 +#define ACTION_JUMP 0x0000010 +#define ACTION_MOVEUP 0x0000020 +#define ACTION_CROUCH 0x0000080 +#define ACTION_MOVEDOWN 0x0000100 +#define ACTION_MOVEFORWARD 0x0000200 +#define ACTION_MOVEBACK 0x0000800 +#define ACTION_MOVELEFT 0x0001000 +#define ACTION_MOVERIGHT 0x0002000 +#define ACTION_DELAYEDJUMP 0x0008000 +#define ACTION_TALK 0x0010000 +#define ACTION_GESTURE 0x0020000 +#define ACTION_WALK 0x0080000 +#define ACTION_AFFIRMATIVE 0x0100000 +#define ACTION_NEGATIVE 0x0200000 +#define ACTION_GETFLAG 0x0800000 +#define ACTION_GUARDBASE 0x1000000 +#define ACTION_PATROL 0x2000000 +#define ACTION_FOLLOWME 0x8000000 + +//the bot input, will be converted to an usercmd_t +typedef struct bot_input_s +{ + float thinktime; //time since last output (in seconds) + vec3_t dir; //movement direction + float speed; //speed in the range [0, 400] + vec3_t viewangles; //the view angles + int actionflags; //one of the ACTION_? flags + int weapon; //weapon to use +} bot_input_t; + +#ifndef BSPTRACE + +#define BSPTRACE + +//bsp_trace_t hit surface +typedef struct bsp_surface_s +{ + char name[16]; + int flags; + int value; +} bsp_surface_t; + +//remove the bsp_trace_s structure definition l8r on +//a trace is returned when a box is swept through the world +typedef struct bsp_trace_s +{ + qboolean allsolid; // if true, plane is not valid + qboolean startsolid; // if true, the initial point was in a solid area + float fraction; // time completed, 1.0 = didn't hit anything + vec3_t endpos; // final position + cplane_t plane; // surface normal at impact + float exp_dist; // expanded plane distance + int sidenum; // number of the brush side hit + bsp_surface_t surface; // the hit point surface + int contents; // contents on other side of surface hit + int ent; // number of entity hit +} bsp_trace_t; + +#endif // BSPTRACE + +//entity state +typedef struct bot_entitystate_s +{ + int type; // entity type + int flags; // entity flags + vec3_t origin; // origin of the entity + vec3_t angles; // angles of the model + vec3_t old_origin; // for lerping + vec3_t mins; // bounding box minimums + vec3_t maxs; // bounding box maximums + int groundent; // ground entity + int solid; // solid type + int modelindex; // model used + int modelindex2; // weapons, CTF flags, etc + int frame; // model frame number + int event; // impulse events -- muzzle flashes, footsteps, etc + int eventParm; // even parameter + int powerups; // bit flags + int weapon; // determines weapon and flash model, etc + int legsAnim; // mask off ANIM_TOGGLEBIT + int torsoAnim; // mask off ANIM_TOGGLEBIT +} bot_entitystate_t; + +//bot AI library exported functions +typedef struct botlib_import_s +{ + //print messages from the bot library + void (QDECL *Print)(int type, char *fmt, ...); + //trace a bbox through the world + void (*Trace)(bsp_trace_t *trace, vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, int passent, int contentmask); + //trace a bbox against a specific entity + void (*EntityTrace)(bsp_trace_t *trace, vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, int entnum, int contentmask); + //retrieve the contents at the given point + int (*PointContents)(vec3_t point); + //check if the point is in potential visible sight + int (*inPVS)(vec3_t p1, vec3_t p2); + //retrieve the BSP entity data lump + char *(*BSPEntityData)(void); + // + void (*BSPModelMinsMaxsOrigin)(int modelnum, vec3_t angles, vec3_t mins, vec3_t maxs, vec3_t origin); + //send a bot client command + void (*BotClientCommand)(int client, char *command); + //memory allocation + void *(*GetMemory)(int size); // allocate from Zone + void (*FreeMemory)(void *ptr); // free memory from Zone + int (*AvailableMemory)(void); // available Zone memory + void *(*HunkAlloc)(int size); // allocate from hunk + //file system access + int (*FS_FOpenFile)( const char *qpath, fileHandle_t *file, fsMode_t mode ); + int (*FS_Read)( void *buffer, int len, fileHandle_t f ); + int (*FS_Write)( const void *buffer, int len, fileHandle_t f ); + void (*FS_FCloseFile)( fileHandle_t f ); + int (*FS_Seek)( fileHandle_t f, long offset, int origin ); + //debug visualisation stuff + int (*DebugLineCreate)(void); + void (*DebugLineDelete)(int line); + void (*DebugLineShow)(int line, vec3_t start, vec3_t end, int color); + // + int (*DebugPolygonCreate)(int color, int numPoints, vec3_t *points); + void (*DebugPolygonDelete)(int id); +} botlib_import_t; + +typedef struct aas_export_s +{ + //----------------------------------- + // be_aas_entity.h + //----------------------------------- + void (*AAS_EntityInfo)(int entnum, struct aas_entityinfo_s *info); + //----------------------------------- + // be_aas_main.h + //----------------------------------- + int (*AAS_Initialized)(void); + void (*AAS_PresenceTypeBoundingBox)(int presencetype, vec3_t mins, vec3_t maxs); + float (*AAS_Time)(void); + //-------------------------------------------- + // be_aas_sample.c + //-------------------------------------------- + int (*AAS_PointAreaNum)(vec3_t point); + int (*AAS_PointReachabilityAreaIndex)( vec3_t point ); + int (*AAS_TraceAreas)(vec3_t start, vec3_t end, int *areas, vec3_t *points, int maxareas); + int (*AAS_BBoxAreas)(vec3_t absmins, vec3_t absmaxs, int *areas, int maxareas); + int (*AAS_AreaInfo)( int areanum, struct aas_areainfo_s *info ); + //-------------------------------------------- + // be_aas_bspq3.c + //-------------------------------------------- + int (*AAS_PointContents)(vec3_t point); + int (*AAS_NextBSPEntity)(int ent); + int (*AAS_ValueForBSPEpairKey)(int ent, char *key, char *value, int size); + int (*AAS_VectorForBSPEpairKey)(int ent, char *key, vec3_t v); + int (*AAS_FloatForBSPEpairKey)(int ent, char *key, float *value); + int (*AAS_IntForBSPEpairKey)(int ent, char *key, int *value); + //-------------------------------------------- + // be_aas_reach.c + //-------------------------------------------- + int (*AAS_AreaReachability)(int areanum); + //-------------------------------------------- + // be_aas_route.c + //-------------------------------------------- + int (*AAS_AreaTravelTimeToGoalArea)(int areanum, vec3_t origin, int goalareanum, int travelflags); + int (*AAS_EnableRoutingArea)(int areanum, int enable); + int (*AAS_PredictRoute)(struct aas_predictroute_s *route, int areanum, vec3_t origin, + int goalareanum, int travelflags, int maxareas, int maxtime, + int stopevent, int stopcontents, int stoptfl, int stopareanum); + //-------------------------------------------- + // be_aas_altroute.c + //-------------------------------------------- + int (*AAS_AlternativeRouteGoals)(vec3_t start, int startareanum, vec3_t goal, int goalareanum, int travelflags, + struct aas_altroutegoal_s *altroutegoals, int maxaltroutegoals, + int type); + //-------------------------------------------- + // be_aas_move.c + //-------------------------------------------- + int (*AAS_Swimming)(vec3_t origin); + int (*AAS_PredictClientMovement)(struct aas_clientmove_s *move, + int entnum, vec3_t origin, + int presencetype, int onground, + vec3_t velocity, vec3_t cmdmove, + int cmdframes, + int maxframes, float frametime, + int stopevent, int stopareanum, int visualize); +} aas_export_t; + +typedef struct ea_export_s +{ + //ClientCommand elementary actions + void (*EA_Command)(int client, char *command ); + void (*EA_Say)(int client, char *str); + void (*EA_SayTeam)(int client, char *str); + // + void (*EA_Action)(int client, int action); + void (*EA_Gesture)(int client); + void (*EA_Talk)(int client); + void (*EA_Attack)(int client); + void (*EA_Use)(int client); + void (*EA_Respawn)(int client); + void (*EA_MoveUp)(int client); + void (*EA_MoveDown)(int client); + void (*EA_MoveForward)(int client); + void (*EA_MoveBack)(int client); + void (*EA_MoveLeft)(int client); + void (*EA_MoveRight)(int client); + void (*EA_Crouch)(int client); + + void (*EA_SelectWeapon)(int client, int weapon); + void (*EA_Jump)(int client); + void (*EA_DelayedJump)(int client); + void (*EA_Move)(int client, vec3_t dir, float speed); + void (*EA_View)(int client, vec3_t viewangles); + //send regular input to the server + void (*EA_EndRegular)(int client, float thinktime); + void (*EA_GetInput)(int client, float thinktime, bot_input_t *input); + void (*EA_ResetInput)(int client); +} ea_export_t; + +typedef struct ai_export_s +{ + //----------------------------------- + // be_ai_char.h + //----------------------------------- + int (*BotLoadCharacter)(char *charfile, float skill); + void (*BotFreeCharacter)(int character); + float (*Characteristic_Float)(int character, int index); + float (*Characteristic_BFloat)(int character, int index, float min, float max); + int (*Characteristic_Integer)(int character, int index); + int (*Characteristic_BInteger)(int character, int index, int min, int max); + void (*Characteristic_String)(int character, int index, char *buf, int size); + //----------------------------------- + // be_ai_chat.h + //----------------------------------- + int (*BotAllocChatState)(void); + void (*BotFreeChatState)(int handle); + void (*BotQueueConsoleMessage)(int chatstate, int type, char *message); + void (*BotRemoveConsoleMessage)(int chatstate, int handle); + int (*BotNextConsoleMessage)(int chatstate, struct bot_consolemessage_s *cm); + int (*BotNumConsoleMessages)(int chatstate); + void (*BotInitialChat)(int chatstate, char *type, int mcontext, char *var0, char *var1, char *var2, char *var3, char *var4, char *var5, char *var6, char *var7); + int (*BotNumInitialChats)(int chatstate, char *type); + int (*BotReplyChat)(int chatstate, char *message, int mcontext, int vcontext, char *var0, char *var1, char *var2, char *var3, char *var4, char *var5, char *var6, char *var7); + int (*BotChatLength)(int chatstate); + void (*BotEnterChat)(int chatstate, int client, int sendto); + void (*BotGetChatMessage)(int chatstate, char *buf, int size); + int (*StringContains)(char *str1, char *str2, int casesensitive); + int (*BotFindMatch)(char *str, struct bot_match_s *match, unsigned long int context); + void (*BotMatchVariable)(struct bot_match_s *match, int variable, char *buf, int size); + void (*UnifyWhiteSpaces)(char *string); + void (*BotReplaceSynonyms)(char *string, unsigned long int context); + int (*BotLoadChatFile)(int chatstate, char *chatfile, char *chatname); + void (*BotSetChatGender)(int chatstate, int gender); + void (*BotSetChatName)(int chatstate, char *name, int client); + //----------------------------------- + // be_ai_goal.h + //----------------------------------- + void (*BotResetGoalState)(int goalstate); + void (*BotResetAvoidGoals)(int goalstate); + void (*BotRemoveFromAvoidGoals)(int goalstate, int number); + void (*BotPushGoal)(int goalstate, struct bot_goal_s *goal); + void (*BotPopGoal)(int goalstate); + void (*BotEmptyGoalStack)(int goalstate); + void (*BotDumpAvoidGoals)(int goalstate); + void (*BotDumpGoalStack)(int goalstate); + void (*BotGoalName)(int number, char *name, int size); + int (*BotGetTopGoal)(int goalstate, struct bot_goal_s *goal); + int (*BotGetSecondGoal)(int goalstate, struct bot_goal_s *goal); + int (*BotChooseLTGItem)(int goalstate, vec3_t origin, int *inventory, int travelflags); + int (*BotChooseNBGItem)(int goalstate, vec3_t origin, int *inventory, int travelflags, + struct bot_goal_s *ltg, float maxtime); + int (*BotTouchingGoal)(vec3_t origin, struct bot_goal_s *goal); + int (*BotItemGoalInVisButNotVisible)(int viewer, vec3_t eye, vec3_t viewangles, struct bot_goal_s *goal); + int (*BotGetLevelItemGoal)(int index, char *classname, struct bot_goal_s *goal); + int (*BotGetNextCampSpotGoal)(int num, struct bot_goal_s *goal); + int (*BotGetMapLocationGoal)(char *name, struct bot_goal_s *goal); + float (*BotAvoidGoalTime)(int goalstate, int number); + void (*BotSetAvoidGoalTime)(int goalstate, int number, float avoidtime); + void (*BotInitLevelItems)(void); + void (*BotUpdateEntityItems)(void); + int (*BotLoadItemWeights)(int goalstate, char *filename); + void (*BotFreeItemWeights)(int goalstate); + void (*BotInterbreedGoalFuzzyLogic)(int parent1, int parent2, int child); + void (*BotSaveGoalFuzzyLogic)(int goalstate, char *filename); + void (*BotMutateGoalFuzzyLogic)(int goalstate, float range); + int (*BotAllocGoalState)(int client); + void (*BotFreeGoalState)(int handle); + //----------------------------------- + // be_ai_move.h + //----------------------------------- + void (*BotResetMoveState)(int movestate); + void (*BotMoveToGoal)(struct bot_moveresult_s *result, int movestate, struct bot_goal_s *goal, int travelflags); + int (*BotMoveInDirection)(int movestate, vec3_t dir, float speed, int type); + void (*BotResetAvoidReach)(int movestate); + void (*BotResetLastAvoidReach)(int movestate); + int (*BotReachabilityArea)(vec3_t origin, int testground); + int (*BotMovementViewTarget)(int movestate, struct bot_goal_s *goal, int travelflags, float lookahead, vec3_t target); + int (*BotPredictVisiblePosition)(vec3_t origin, int areanum, struct bot_goal_s *goal, int travelflags, vec3_t target); + int (*BotAllocMoveState)(void); + void (*BotFreeMoveState)(int handle); + void (*BotInitMoveState)(int handle, struct bot_initmove_s *initmove); + void (*BotAddAvoidSpot)(int movestate, vec3_t origin, float radius, int type); + //----------------------------------- + // be_ai_weap.h + //----------------------------------- + int (*BotChooseBestFightWeapon)(int weaponstate, int *inventory); + void (*BotGetWeaponInfo)(int weaponstate, int weapon, struct weaponinfo_s *weaponinfo); + int (*BotLoadWeaponWeights)(int weaponstate, char *filename); + int (*BotAllocWeaponState)(void); + void (*BotFreeWeaponState)(int weaponstate); + void (*BotResetWeaponState)(int weaponstate); + //----------------------------------- + // be_ai_gen.h + //----------------------------------- + int (*GeneticParentsAndChildSelection)(int numranks, float *ranks, int *parent1, int *parent2, int *child); +} ai_export_t; + +//bot AI library imported functions +typedef struct botlib_export_s +{ + //Area Awareness System functions + aas_export_t aas; + //Elementary Action functions + ea_export_t ea; + //AI functions + ai_export_t ai; + //setup the bot library, returns BLERR_ + int (*BotLibSetup)(void); + //shutdown the bot library, returns BLERR_ + int (*BotLibShutdown)(void); + //sets a library variable returns BLERR_ + int (*BotLibVarSet)(char *var_name, char *value); + //gets a library variable returns BLERR_ + int (*BotLibVarGet)(char *var_name, char *value, int size); + + //sets a C-like define returns BLERR_ + int (*PC_AddGlobalDefine)(char *string); + int (*PC_LoadSourceHandle)(const char *filename); + int (*PC_FreeSourceHandle)(int handle); + int (*PC_ReadTokenHandle)(int handle, pc_token_t *pc_token); + int (*PC_SourceFileAndLine)(int handle, char *filename, int *line); + + //start a frame in the bot library + int (*BotLibStartFrame)(float time); + //load a new map in the bot library + int (*BotLibLoadMap)(const char *mapname); + //entity updates + int (*BotLibUpdateEntity)(int ent, bot_entitystate_t *state); + //just for testing + int (*Test)(int parm0, char *parm1, vec3_t parm2, vec3_t parm3); +} botlib_export_t; + +//linking of bot library +botlib_export_t *GetBotLibAPI( int apiVersion, botlib_import_t *import ); + +/* Library variables: + +name: default: module(s): description: + +"basedir" "" l_utils.c base directory +"gamedir" "" l_utils.c game directory +"cddir" "" l_utils.c CD directory + +"log" "0" l_log.c enable/disable creating a log file +"maxclients" "4" be_interface.c maximum number of clients +"maxentities" "1024" be_interface.c maximum number of entities +"bot_developer" "0" be_interface.c bot developer mode + +"phys_friction" "6" be_aas_move.c ground friction +"phys_stopspeed" "100" be_aas_move.c stop speed +"phys_gravity" "800" be_aas_move.c gravity value +"phys_waterfriction" "1" be_aas_move.c water friction +"phys_watergravity" "400" be_aas_move.c gravity in water +"phys_maxvelocity" "320" be_aas_move.c maximum velocity +"phys_maxwalkvelocity" "320" be_aas_move.c maximum walk velocity +"phys_maxcrouchvelocity" "100" be_aas_move.c maximum crouch velocity +"phys_maxswimvelocity" "150" be_aas_move.c maximum swim velocity +"phys_walkaccelerate" "10" be_aas_move.c walk acceleration +"phys_airaccelerate" "1" be_aas_move.c air acceleration +"phys_swimaccelerate" "4" be_aas_move.c swim acceleration +"phys_maxstep" "18" be_aas_move.c maximum step height +"phys_maxsteepness" "0.7" be_aas_move.c maximum floor steepness +"phys_maxbarrier" "32" be_aas_move.c maximum barrier height +"phys_maxwaterjump" "19" be_aas_move.c maximum waterjump height +"phys_jumpvel" "270" be_aas_move.c jump z velocity +"phys_falldelta5" "40" be_aas_move.c +"phys_falldelta10" "60" be_aas_move.c +"rs_waterjump" "400" be_aas_move.c +"rs_teleport" "50" be_aas_move.c +"rs_barrierjump" "100" be_aas_move.c +"rs_startcrouch" "300" be_aas_move.c +"rs_startgrapple" "500" be_aas_move.c +"rs_startwalkoffledge" "70" be_aas_move.c +"rs_startjump" "300" be_aas_move.c +"rs_rocketjump" "500" be_aas_move.c +"rs_bfgjump" "500" be_aas_move.c +"rs_jumppad" "250" be_aas_move.c +"rs_aircontrolledjumppad" "300" be_aas_move.c +"rs_funcbob" "300" be_aas_move.c +"rs_startelevator" "50" be_aas_move.c +"rs_falldamage5" "300" be_aas_move.c +"rs_falldamage10" "500" be_aas_move.c +"rs_maxjumpfallheight" "450" be_aas_move.c + +"max_aaslinks" "4096" be_aas_sample.c maximum links in the AAS +"max_routingcache" "4096" be_aas_route.c maximum routing cache size in KB +"forceclustering" "0" be_aas_main.c force recalculation of clusters +"forcereachability" "0" be_aas_main.c force recalculation of reachabilities +"forcewrite" "0" be_aas_main.c force writing of aas file +"aasoptimize" "0" be_aas_main.c enable aas optimization +"sv_mapChecksum" "0" be_aas_main.c BSP file checksum +"bot_visualizejumppads" "0" be_aas_reach.c visualize jump pads + +"bot_reloadcharacters" "0" - reload bot character files +"ai_gametype" "0" be_ai_goal.c game type +"droppedweight" "1000" be_ai_goal.c additional dropped item weight +"weapindex_rocketlauncher" "5" be_ai_move.c rl weapon index for rocket jumping +"weapindex_bfg10k" "9" be_ai_move.c bfg weapon index for bfg jumping +"weapindex_grapple" "10" be_ai_move.c grapple weapon index for grappling +"entitytypemissile" "3" be_ai_move.c ET_MISSILE +"offhandgrapple" "0" be_ai_move.c enable off hand grapple hook +"cmd_grappleon" "grappleon" be_ai_move.c command to activate off hand grapple +"cmd_grappleoff" "grappleoff" be_ai_move.c command to deactivate off hand grapple +"itemconfig" "items.c" be_ai_goal.c item configuration file +"weaponconfig" "weapons.c" be_ai_weap.c weapon configuration file +"synfile" "syn.c" be_ai_chat.c file with synonyms +"rndfile" "rnd.c" be_ai_chat.c file with random strings +"matchfile" "match.c" be_ai_chat.c file with match strings +"nochat" "0" be_ai_chat.c disable chats +"max_messages" "1024" be_ai_chat.c console message heap size +"max_weaponinfo" "32" be_ai_weap.c maximum number of weapon info +"max_projectileinfo" "32" be_ai_weap.c maximum number of projectile info +"max_iteminfo" "256" be_ai_goal.c maximum number of item info +"max_levelitems" "256" be_ai_goal.c maximum number of level items + +*/ + diff --git a/code/game/chars.h b/code/game/chars.h index c5b1861..ae96f56 100755 --- a/code/game/chars.h +++ b/code/game/chars.h @@ -1,134 +1,134 @@ -/* -=========================================================================== -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 Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -//======================================================== -//======================================================== -//name -#define CHARACTERISTIC_NAME 0 //string -//gender of the bot -#define CHARACTERISTIC_GENDER 1 //string ("male", "female", "it") -//attack skill -// > 0.0 && < 0.2 = don't move -// > 0.3 && < 1.0 = aim at enemy during retreat -// > 0.0 && < 0.4 = only move forward/backward -// >= 0.4 && < 1.0 = circle strafing -// > 0.7 && < 1.0 = random strafe direction change -#define CHARACTERISTIC_ATTACK_SKILL 2 //float [0, 1] -//weapon weight file -#define CHARACTERISTIC_WEAPONWEIGHTS 3 //string -//view angle difference to angle change factor -#define CHARACTERISTIC_VIEW_FACTOR 4 //float <0, 1] -//maximum view angle change -#define CHARACTERISTIC_VIEW_MAXCHANGE 5 //float [1, 360] -//reaction time in seconds -#define CHARACTERISTIC_REACTIONTIME 6 //float [0, 5] -//accuracy when aiming -#define CHARACTERISTIC_AIM_ACCURACY 7 //float [0, 1] -//weapon specific aim accuracy -#define CHARACTERISTIC_AIM_ACCURACY_MACHINEGUN 8 //float [0, 1] -#define CHARACTERISTIC_AIM_ACCURACY_SHOTGUN 9 //float [0, 1] -#define CHARACTERISTIC_AIM_ACCURACY_ROCKETLAUNCHER 10 //float [0, 1] -#define CHARACTERISTIC_AIM_ACCURACY_GRENADELAUNCHER 11 //float [0, 1] -#define CHARACTERISTIC_AIM_ACCURACY_LIGHTNING 12 -#define CHARACTERISTIC_AIM_ACCURACY_PLASMAGUN 13 //float [0, 1] -#define CHARACTERISTIC_AIM_ACCURACY_RAILGUN 14 -#define CHARACTERISTIC_AIM_ACCURACY_BFG10K 15 //float [0, 1] -//skill when aiming -// > 0.0 && < 0.9 = aim is affected by enemy movement -// > 0.4 && <= 0.8 = enemy linear leading -// > 0.8 && <= 1.0 = enemy exact movement leading -// > 0.5 && <= 1.0 = prediction shots when enemy is not visible -// > 0.6 && <= 1.0 = splash damage by shooting nearby geometry -#define CHARACTERISTIC_AIM_SKILL 16 //float [0, 1] -//weapon specific aim skill -#define CHARACTERISTIC_AIM_SKILL_ROCKETLAUNCHER 17 //float [0, 1] -#define CHARACTERISTIC_AIM_SKILL_GRENADELAUNCHER 18 //float [0, 1] -#define CHARACTERISTIC_AIM_SKILL_PLASMAGUN 19 //float [0, 1] -#define CHARACTERISTIC_AIM_SKILL_BFG10K 20 //float [0, 1] -//======================================================== -//chat -//======================================================== -//file with chats -#define CHARACTERISTIC_CHAT_FILE 21 //string -//name of the chat character -#define CHARACTERISTIC_CHAT_NAME 22 //string -//characters per minute type speed -#define CHARACTERISTIC_CHAT_CPM 23 //integer [1, 4000] -//tendency to insult/praise -#define CHARACTERISTIC_CHAT_INSULT 24 //float [0, 1] -//tendency to chat misc -#define CHARACTERISTIC_CHAT_MISC 25 //float [0, 1] -//tendency to chat at start or end of level -#define CHARACTERISTIC_CHAT_STARTENDLEVEL 26 //float [0, 1] -//tendency to chat entering or exiting the game -#define CHARACTERISTIC_CHAT_ENTEREXITGAME 27 //float [0, 1] -//tendency to chat when killed someone -#define CHARACTERISTIC_CHAT_KILL 28 //float [0, 1] -//tendency to chat when died -#define CHARACTERISTIC_CHAT_DEATH 29 //float [0, 1] -//tendency to chat when enemy suicides -#define CHARACTERISTIC_CHAT_ENEMYSUICIDE 30 //float [0, 1] -//tendency to chat when hit while talking -#define CHARACTERISTIC_CHAT_HITTALKING 31 //float [0, 1] -//tendency to chat when bot was hit but didn't dye -#define CHARACTERISTIC_CHAT_HITNODEATH 32 //float [0, 1] -//tendency to chat when bot hit the enemy but enemy didn't dye -#define CHARACTERISTIC_CHAT_HITNOKILL 33 //float [0, 1] -//tendency to randomly chat -#define CHARACTERISTIC_CHAT_RANDOM 34 //float [0, 1] -//tendency to reply -#define CHARACTERISTIC_CHAT_REPLY 35 //float [0, 1] -//======================================================== -//movement -//======================================================== -//tendency to crouch -#define CHARACTERISTIC_CROUCHER 36 //float [0, 1] -//tendency to jump -#define CHARACTERISTIC_JUMPER 37 //float [0, 1] -//tendency to walk -#define CHARACTERISTIC_WALKER 48 //float [0, 1] -//tendency to jump using a weapon -#define CHARACTERISTIC_WEAPONJUMPING 38 //float [0, 1] -//tendency to use the grapple hook when available -#define CHARACTERISTIC_GRAPPLE_USER 39 //float [0, 1] //use this!! -//======================================================== -//goal -//======================================================== -//item weight file -#define CHARACTERISTIC_ITEMWEIGHTS 40 //string -//the aggression of the bot -#define CHARACTERISTIC_AGGRESSION 41 //float [0, 1] -//the self preservation of the bot (rockets near walls etc.) -#define CHARACTERISTIC_SELFPRESERVATION 42 //float [0, 1] -//how likely the bot is to take revenge -#define CHARACTERISTIC_VENGEFULNESS 43 //float [0, 1] //use this!! -//tendency to camp -#define CHARACTERISTIC_CAMPER 44 //float [0, 1] -//======================================================== -//======================================================== -//tendency to get easy frags -#define CHARACTERISTIC_EASY_FRAGGER 45 //float [0, 1] -//how alert the bot is (view distance) -#define CHARACTERISTIC_ALERTNESS 46 //float [0, 1] -//how much the bot fires it's weapon -#define CHARACTERISTIC_FIRETHROTTLE 47 //float [0, 1] - +/* +=========================================================================== +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 Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +//======================================================== +//======================================================== +//name +#define CHARACTERISTIC_NAME 0 //string +//gender of the bot +#define CHARACTERISTIC_GENDER 1 //string ("male", "female", "it") +//attack skill +// > 0.0 && < 0.2 = don't move +// > 0.3 && < 1.0 = aim at enemy during retreat +// > 0.0 && < 0.4 = only move forward/backward +// >= 0.4 && < 1.0 = circle strafing +// > 0.7 && < 1.0 = random strafe direction change +#define CHARACTERISTIC_ATTACK_SKILL 2 //float [0, 1] +//weapon weight file +#define CHARACTERISTIC_WEAPONWEIGHTS 3 //string +//view angle difference to angle change factor +#define CHARACTERISTIC_VIEW_FACTOR 4 //float <0, 1] +//maximum view angle change +#define CHARACTERISTIC_VIEW_MAXCHANGE 5 //float [1, 360] +//reaction time in seconds +#define CHARACTERISTIC_REACTIONTIME 6 //float [0, 5] +//accuracy when aiming +#define CHARACTERISTIC_AIM_ACCURACY 7 //float [0, 1] +//weapon specific aim accuracy +#define CHARACTERISTIC_AIM_ACCURACY_MACHINEGUN 8 //float [0, 1] +#define CHARACTERISTIC_AIM_ACCURACY_SHOTGUN 9 //float [0, 1] +#define CHARACTERISTIC_AIM_ACCURACY_ROCKETLAUNCHER 10 //float [0, 1] +#define CHARACTERISTIC_AIM_ACCURACY_GRENADELAUNCHER 11 //float [0, 1] +#define CHARACTERISTIC_AIM_ACCURACY_LIGHTNING 12 +#define CHARACTERISTIC_AIM_ACCURACY_PLASMAGUN 13 //float [0, 1] +#define CHARACTERISTIC_AIM_ACCURACY_RAILGUN 14 +#define CHARACTERISTIC_AIM_ACCURACY_BFG10K 15 //float [0, 1] +//skill when aiming +// > 0.0 && < 0.9 = aim is affected by enemy movement +// > 0.4 && <= 0.8 = enemy linear leading +// > 0.8 && <= 1.0 = enemy exact movement leading +// > 0.5 && <= 1.0 = prediction shots when enemy is not visible +// > 0.6 && <= 1.0 = splash damage by shooting nearby geometry +#define CHARACTERISTIC_AIM_SKILL 16 //float [0, 1] +//weapon specific aim skill +#define CHARACTERISTIC_AIM_SKILL_ROCKETLAUNCHER 17 //float [0, 1] +#define CHARACTERISTIC_AIM_SKILL_GRENADELAUNCHER 18 //float [0, 1] +#define CHARACTERISTIC_AIM_SKILL_PLASMAGUN 19 //float [0, 1] +#define CHARACTERISTIC_AIM_SKILL_BFG10K 20 //float [0, 1] +//======================================================== +//chat +//======================================================== +//file with chats +#define CHARACTERISTIC_CHAT_FILE 21 //string +//name of the chat character +#define CHARACTERISTIC_CHAT_NAME 22 //string +//characters per minute type speed +#define CHARACTERISTIC_CHAT_CPM 23 //integer [1, 4000] +//tendency to insult/praise +#define CHARACTERISTIC_CHAT_INSULT 24 //float [0, 1] +//tendency to chat misc +#define CHARACTERISTIC_CHAT_MISC 25 //float [0, 1] +//tendency to chat at start or end of level +#define CHARACTERISTIC_CHAT_STARTENDLEVEL 26 //float [0, 1] +//tendency to chat entering or exiting the game +#define CHARACTERISTIC_CHAT_ENTEREXITGAME 27 //float [0, 1] +//tendency to chat when killed someone +#define CHARACTERISTIC_CHAT_KILL 28 //float [0, 1] +//tendency to chat when died +#define CHARACTERISTIC_CHAT_DEATH 29 //float [0, 1] +//tendency to chat when enemy suicides +#define CHARACTERISTIC_CHAT_ENEMYSUICIDE 30 //float [0, 1] +//tendency to chat when hit while talking +#define CHARACTERISTIC_CHAT_HITTALKING 31 //float [0, 1] +//tendency to chat when bot was hit but didn't dye +#define CHARACTERISTIC_CHAT_HITNODEATH 32 //float [0, 1] +//tendency to chat when bot hit the enemy but enemy didn't dye +#define CHARACTERISTIC_CHAT_HITNOKILL 33 //float [0, 1] +//tendency to randomly chat +#define CHARACTERISTIC_CHAT_RANDOM 34 //float [0, 1] +//tendency to reply +#define CHARACTERISTIC_CHAT_REPLY 35 //float [0, 1] +//======================================================== +//movement +//======================================================== +//tendency to crouch +#define CHARACTERISTIC_CROUCHER 36 //float [0, 1] +//tendency to jump +#define CHARACTERISTIC_JUMPER 37 //float [0, 1] +//tendency to walk +#define CHARACTERISTIC_WALKER 48 //float [0, 1] +//tendency to jump using a weapon +#define CHARACTERISTIC_WEAPONJUMPING 38 //float [0, 1] +//tendency to use the grapple hook when available +#define CHARACTERISTIC_GRAPPLE_USER 39 //float [0, 1] //use this!! +//======================================================== +//goal +//======================================================== +//item weight file +#define CHARACTERISTIC_ITEMWEIGHTS 40 //string +//the aggression of the bot +#define CHARACTERISTIC_AGGRESSION 41 //float [0, 1] +//the self preservation of the bot (rockets near walls etc.) +#define CHARACTERISTIC_SELFPRESERVATION 42 //float [0, 1] +//how likely the bot is to take revenge +#define CHARACTERISTIC_VENGEFULNESS 43 //float [0, 1] //use this!! +//tendency to camp +#define CHARACTERISTIC_CAMPER 44 //float [0, 1] +//======================================================== +//======================================================== +//tendency to get easy frags +#define CHARACTERISTIC_EASY_FRAGGER 45 //float [0, 1] +//how alert the bot is (view distance) +#define CHARACTERISTIC_ALERTNESS 46 //float [0, 1] +//how much the bot fires it's weapon +#define CHARACTERISTIC_FIRETHROTTLE 47 //float [0, 1] + diff --git a/code/game/g_active.c b/code/game/g_active.c index 853f5fb..923a5e1 100755 --- a/code/game/g_active.c +++ b/code/game/g_active.c @@ -1,1191 +1,1191 @@ -/* -=========================================================================== -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 Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// - -#include "g_local.h" - - -/* -=============== -G_DamageFeedback - -Called just before a snapshot is sent to the given player. -Totals up all damage and generates both the player_state_t -damage values to that client for pain blends and kicks, and -global pain sound events for all clients. -=============== -*/ -void P_DamageFeedback( gentity_t *player ) { - gclient_t *client; - float count; - vec3_t angles; - - client = player->client; - if ( client->ps.pm_type == PM_DEAD ) { - return; - } - - // total points of damage shot at the player this frame - count = client->damage_blood + client->damage_armor; - if ( count == 0 ) { - return; // didn't take any damage - } - - if ( count > 255 ) { - count = 255; - } - - // send the information to the client - - // world damage (falling, slime, etc) uses a special code - // to make the blend blob centered instead of positional - if ( client->damage_fromWorld ) { - client->ps.damagePitch = 255; - client->ps.damageYaw = 255; - - client->damage_fromWorld = qfalse; - } else { - vectoangles( client->damage_from, angles ); - client->ps.damagePitch = angles[PITCH]/360.0 * 256; - client->ps.damageYaw = angles[YAW]/360.0 * 256; - } - - // play an apropriate pain sound - if ( (level.time > player->pain_debounce_time) && !(player->flags & FL_GODMODE) ) { - player->pain_debounce_time = level.time + 700; - G_AddEvent( player, EV_PAIN, player->health ); - client->ps.damageEvent++; - } - - - client->ps.damageCount = count; - - // - // clear totals - // - client->damage_blood = 0; - client->damage_armor = 0; - client->damage_knockback = 0; -} - - - -/* -============= -P_WorldEffects - -Check for lava / slime contents and drowning -============= -*/ -void P_WorldEffects( gentity_t *ent ) { - qboolean envirosuit; - int waterlevel; - - if ( ent->client->noclip ) { - ent->client->airOutTime = level.time + 12000; // don't need air - return; - } - - waterlevel = ent->waterlevel; - - envirosuit = ent->client->ps.powerups[PW_BATTLESUIT] > level.time; - - // - // check for drowning - // - if ( waterlevel == 3 ) { - // envirosuit give air - if ( envirosuit ) { - ent->client->airOutTime = level.time + 10000; - } - - // if out of air, start drowning - if ( ent->client->airOutTime < level.time) { - // drown! - ent->client->airOutTime += 1000; - if ( ent->health > 0 ) { - // take more damage the longer underwater - ent->damage += 2; - if (ent->damage > 15) - ent->damage = 15; - - // play a gurp sound instead of a normal pain sound - if (ent->health <= ent->damage) { - G_Sound(ent, CHAN_VOICE, G_SoundIndex("*drown.wav")); - } else if (rand()&1) { - G_Sound(ent, CHAN_VOICE, G_SoundIndex("sound/player/gurp1.wav")); - } else { - G_Sound(ent, CHAN_VOICE, G_SoundIndex("sound/player/gurp2.wav")); - } - - // don't play a normal pain sound - ent->pain_debounce_time = level.time + 200; - - G_Damage (ent, NULL, NULL, NULL, NULL, - ent->damage, DAMAGE_NO_ARMOR, MOD_WATER); - } - } - } else { - ent->client->airOutTime = level.time + 12000; - ent->damage = 2; - } - - // - // check for sizzle damage (move to pmove?) - // - if (waterlevel && - (ent->watertype&(CONTENTS_LAVA|CONTENTS_SLIME)) ) { - if (ent->health > 0 - && ent->pain_debounce_time <= level.time ) { - - if ( envirosuit ) { - G_AddEvent( ent, EV_POWERUP_BATTLESUIT, 0 ); - } else { - if (ent->watertype & CONTENTS_LAVA) { - G_Damage (ent, NULL, NULL, NULL, NULL, - 30*waterlevel, 0, MOD_LAVA); - } - - if (ent->watertype & CONTENTS_SLIME) { - G_Damage (ent, NULL, NULL, NULL, NULL, - 10*waterlevel, 0, MOD_SLIME); - } - } - } - } -} - - - -/* -=============== -G_SetClientSound -=============== -*/ -void G_SetClientSound( gentity_t *ent ) { -#ifdef MISSIONPACK - if( ent->s.eFlags & EF_TICKING ) { - ent->client->ps.loopSound = G_SoundIndex( "sound/weapons/proxmine/wstbtick.wav"); - } - else -#endif - if (ent->waterlevel && (ent->watertype&(CONTENTS_LAVA|CONTENTS_SLIME)) ) { - ent->client->ps.loopSound = level.snd_fry; - } else { - ent->client->ps.loopSound = 0; - } -} - - - -//============================================================== - -/* -============== -ClientImpacts -============== -*/ -void ClientImpacts( gentity_t *ent, pmove_t *pm ) { - int i, j; - trace_t trace; - gentity_t *other; - - memset( &trace, 0, sizeof( trace ) ); - for (i=0 ; inumtouch ; i++) { - for (j=0 ; jtouchents[j] == pm->touchents[i] ) { - break; - } - } - if (j != i) { - continue; // duplicated - } - other = &g_entities[ pm->touchents[i] ]; - - if ( ( ent->r.svFlags & SVF_BOT ) && ( ent->touch ) ) { - ent->touch( ent, other, &trace ); - } - - if ( !other->touch ) { - continue; - } - - other->touch( other, ent, &trace ); - } - -} - -/* -============ -G_TouchTriggers - -Find all trigger entities that ent's current position touches. -Spectators will only interact with teleporters. -============ -*/ -void G_TouchTriggers( gentity_t *ent ) { - int i, num; - int touch[MAX_GENTITIES]; - gentity_t *hit; - trace_t trace; - vec3_t mins, maxs; - static vec3_t range = { 40, 40, 52 }; - - if ( !ent->client ) { - return; - } - - // dead clients don't activate triggers! - if ( ent->client->ps.stats[STAT_HEALTH] <= 0 ) { - return; - } - - VectorSubtract( ent->client->ps.origin, range, mins ); - VectorAdd( ent->client->ps.origin, range, maxs ); - - num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES ); - - // can't use ent->absmin, because that has a one unit pad - VectorAdd( ent->client->ps.origin, ent->r.mins, mins ); - VectorAdd( ent->client->ps.origin, ent->r.maxs, maxs ); - - for ( i=0 ; itouch && !ent->touch ) { - continue; - } - if ( !( hit->r.contents & CONTENTS_TRIGGER ) ) { - continue; - } - - // ignore most entities if a spectator - if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) { - if ( hit->s.eType != ET_TELEPORT_TRIGGER && - // this is ugly but adding a new ET_? type will - // most likely cause network incompatibilities - hit->touch != Touch_DoorTrigger) { - continue; - } - } - - // use seperate code for determining if an item is picked up - // so you don't have to actually contact its bounding box - if ( hit->s.eType == ET_ITEM ) { - if ( !BG_PlayerTouchesItem( &ent->client->ps, &hit->s, level.time ) ) { - continue; - } - } else { - if ( !trap_EntityContact( mins, maxs, hit ) ) { - continue; - } - } - - memset( &trace, 0, sizeof(trace) ); - - if ( hit->touch ) { - hit->touch (hit, ent, &trace); - } - - if ( ( ent->r.svFlags & SVF_BOT ) && ( ent->touch ) ) { - ent->touch( ent, hit, &trace ); - } - } - - // if we didn't touch a jump pad this pmove frame - if ( ent->client->ps.jumppad_frame != ent->client->ps.pmove_framecount ) { - ent->client->ps.jumppad_frame = 0; - ent->client->ps.jumppad_ent = 0; - } -} - -/* -================= -SpectatorThink -================= -*/ -void SpectatorThink( gentity_t *ent, usercmd_t *ucmd ) { - pmove_t pm; - gclient_t *client; - - client = ent->client; - - if ( client->sess.spectatorState != SPECTATOR_FOLLOW ) { - client->ps.pm_type = PM_SPECTATOR; - client->ps.speed = 400; // faster than normal - - // set up for pmove - memset (&pm, 0, sizeof(pm)); - pm.ps = &client->ps; - pm.cmd = *ucmd; - pm.tracemask = MASK_PLAYERSOLID & ~CONTENTS_BODY; // spectators can fly through bodies - pm.trace = trap_Trace; - pm.pointcontents = trap_PointContents; - - // perform a pmove - Pmove (&pm); - // save results of pmove - VectorCopy( client->ps.origin, ent->s.origin ); - - G_TouchTriggers( ent ); - trap_UnlinkEntity( ent ); - } - - client->oldbuttons = client->buttons; - client->buttons = ucmd->buttons; - - // attack button cycles through spectators - if ( ( client->buttons & BUTTON_ATTACK ) && ! ( client->oldbuttons & BUTTON_ATTACK ) ) { - Cmd_FollowCycle_f( ent, 1 ); - } -} - - - -/* -================= -ClientInactivityTimer - -Returns qfalse if the client is dropped -================= -*/ -qboolean ClientInactivityTimer( gclient_t *client ) { - if ( ! g_inactivity.integer ) { - // give everyone some time, so if the operator sets g_inactivity during - // gameplay, everyone isn't kicked - client->inactivityTime = level.time + 60 * 1000; - client->inactivityWarning = qfalse; - } else if ( client->pers.cmd.forwardmove || - client->pers.cmd.rightmove || - client->pers.cmd.upmove || - (client->pers.cmd.buttons & BUTTON_ATTACK) ) { - client->inactivityTime = level.time + g_inactivity.integer * 1000; - client->inactivityWarning = qfalse; - } else if ( !client->pers.localClient ) { - if ( level.time > client->inactivityTime ) { - trap_DropClient( client - level.clients, "Dropped due to inactivity" ); - return qfalse; - } - if ( level.time > client->inactivityTime - 10000 && !client->inactivityWarning ) { - client->inactivityWarning = qtrue; - trap_SendServerCommand( client - level.clients, "cp \"Ten seconds until inactivity drop!\n\"" ); - } - } - return qtrue; -} - -/* -================== -ClientTimerActions - -Actions that happen once a second -================== -*/ -void ClientTimerActions( gentity_t *ent, int msec ) { - gclient_t *client; -#ifdef MISSIONPACK - int maxHealth; -#endif - - client = ent->client; - client->timeResidual += msec; - - while ( client->timeResidual >= 1000 ) { - client->timeResidual -= 1000; - - // regenerate -#ifdef MISSIONPACK - if( bg_itemlist[client->ps.stats[STAT_PERSISTANT_POWERUP]].giTag == PW_GUARD ) { - maxHealth = client->ps.stats[STAT_MAX_HEALTH] / 2; - } - else if ( client->ps.powerups[PW_REGEN] ) { - maxHealth = client->ps.stats[STAT_MAX_HEALTH]; - } - else { - maxHealth = 0; - } - if( maxHealth ) { - if ( ent->health < maxHealth ) { - ent->health += 15; - if ( ent->health > maxHealth * 1.1 ) { - ent->health = maxHealth * 1.1; - } - G_AddEvent( ent, EV_POWERUP_REGEN, 0 ); - } else if ( ent->health < maxHealth * 2) { - ent->health += 5; - if ( ent->health > maxHealth * 2 ) { - ent->health = maxHealth * 2; - } - G_AddEvent( ent, EV_POWERUP_REGEN, 0 ); - } -#else - if ( client->ps.powerups[PW_REGEN] ) { - if ( ent->health < client->ps.stats[STAT_MAX_HEALTH]) { - ent->health += 15; - if ( ent->health > client->ps.stats[STAT_MAX_HEALTH] * 1.1 ) { - ent->health = client->ps.stats[STAT_MAX_HEALTH] * 1.1; - } - G_AddEvent( ent, EV_POWERUP_REGEN, 0 ); - } else if ( ent->health < client->ps.stats[STAT_MAX_HEALTH] * 2) { - ent->health += 5; - if ( ent->health > client->ps.stats[STAT_MAX_HEALTH] * 2 ) { - ent->health = client->ps.stats[STAT_MAX_HEALTH] * 2; - } - G_AddEvent( ent, EV_POWERUP_REGEN, 0 ); - } -#endif - } else { - // count down health when over max - if ( ent->health > client->ps.stats[STAT_MAX_HEALTH] ) { - ent->health--; - } - } - - // count down armor when over max - if ( client->ps.stats[STAT_ARMOR] > client->ps.stats[STAT_MAX_HEALTH] ) { - client->ps.stats[STAT_ARMOR]--; - } - } -#ifdef MISSIONPACK - if( bg_itemlist[client->ps.stats[STAT_PERSISTANT_POWERUP]].giTag == PW_AMMOREGEN ) { - int w, max, inc, t, i; - int weapList[]={WP_MACHINEGUN,WP_SHOTGUN,WP_GRENADE_LAUNCHER,WP_ROCKET_LAUNCHER,WP_LIGHTNING,WP_RAILGUN,WP_PLASMAGUN,WP_BFG,WP_NAILGUN,WP_PROX_LAUNCHER,WP_CHAINGUN}; - int weapCount = sizeof(weapList) / sizeof(int); - // - for (i = 0; i < weapCount; i++) { - w = weapList[i]; - - switch(w) { - case WP_MACHINEGUN: max = 50; inc = 4; t = 1000; break; - case WP_SHOTGUN: max = 10; inc = 1; t = 1500; break; - case WP_GRENADE_LAUNCHER: max = 10; inc = 1; t = 2000; break; - case WP_ROCKET_LAUNCHER: max = 10; inc = 1; t = 1750; break; - case WP_LIGHTNING: max = 50; inc = 5; t = 1500; break; - case WP_RAILGUN: max = 10; inc = 1; t = 1750; break; - case WP_PLASMAGUN: max = 50; inc = 5; t = 1500; break; - case WP_BFG: max = 10; inc = 1; t = 4000; break; - case WP_NAILGUN: max = 10; inc = 1; t = 1250; break; - case WP_PROX_LAUNCHER: max = 5; inc = 1; t = 2000; break; - case WP_CHAINGUN: max = 100; inc = 5; t = 1000; break; - default: max = 0; inc = 0; t = 1000; break; - } - client->ammoTimes[w] += msec; - if ( client->ps.ammo[w] >= max ) { - client->ammoTimes[w] = 0; - } - if ( client->ammoTimes[w] >= t ) { - while ( client->ammoTimes[w] >= t ) - client->ammoTimes[w] -= t; - client->ps.ammo[w] += inc; - if ( client->ps.ammo[w] > max ) { - client->ps.ammo[w] = max; - } - } - } - } -#endif -} - -/* -==================== -ClientIntermissionThink -==================== -*/ -void ClientIntermissionThink( gclient_t *client ) { - client->ps.eFlags &= ~EF_TALK; - client->ps.eFlags &= ~EF_FIRING; - - // the level will exit when everyone wants to or after timeouts - - // swap and latch button actions - client->oldbuttons = client->buttons; - client->buttons = client->pers.cmd.buttons; - if ( client->buttons & ( BUTTON_ATTACK | BUTTON_USE_HOLDABLE ) & ( client->oldbuttons ^ client->buttons ) ) { - // this used to be an ^1 but once a player says ready, it should stick - client->readyToExit = 1; - } -} - - -/* -================ -ClientEvents - -Events will be passed on to the clients for presentation, -but any server game effects are handled here -================ -*/ -void ClientEvents( gentity_t *ent, int oldEventSequence ) { - int i, j; - int event; - gclient_t *client; - int damage; - vec3_t dir; - vec3_t origin, angles; -// qboolean fired; - gitem_t *item; - gentity_t *drop; - - client = ent->client; - - if ( oldEventSequence < client->ps.eventSequence - MAX_PS_EVENTS ) { - oldEventSequence = client->ps.eventSequence - MAX_PS_EVENTS; - } - for ( i = oldEventSequence ; i < client->ps.eventSequence ; i++ ) { - event = client->ps.events[ i & (MAX_PS_EVENTS-1) ]; - - switch ( event ) { - case EV_FALL_MEDIUM: - case EV_FALL_FAR: - if ( ent->s.eType != ET_PLAYER ) { - break; // not in the player model - } - if ( g_dmflags.integer & DF_NO_FALLING ) { - break; - } - if ( event == EV_FALL_FAR ) { - damage = 10; - } else { - damage = 5; - } - VectorSet (dir, 0, 0, 1); - ent->pain_debounce_time = level.time + 200; // no normal pain sound - G_Damage (ent, NULL, NULL, NULL, NULL, damage, 0, MOD_FALLING); - break; - - case EV_FIRE_WEAPON: - FireWeapon( ent ); - break; - - case EV_USE_ITEM1: // teleporter - // drop flags in CTF - item = NULL; - j = 0; - - if ( ent->client->ps.powerups[ PW_REDFLAG ] ) { - item = BG_FindItemForPowerup( PW_REDFLAG ); - j = PW_REDFLAG; - } else if ( ent->client->ps.powerups[ PW_BLUEFLAG ] ) { - item = BG_FindItemForPowerup( PW_BLUEFLAG ); - j = PW_BLUEFLAG; - } else if ( ent->client->ps.powerups[ PW_NEUTRALFLAG ] ) { - item = BG_FindItemForPowerup( PW_NEUTRALFLAG ); - j = PW_NEUTRALFLAG; - } - - if ( item ) { - drop = Drop_Item( ent, item, 0 ); - // decide how many seconds it has left - drop->count = ( ent->client->ps.powerups[ j ] - level.time ) / 1000; - if ( drop->count < 1 ) { - drop->count = 1; - } - - ent->client->ps.powerups[ j ] = 0; - } - -#ifdef MISSIONPACK - if ( g_gametype.integer == GT_HARVESTER ) { - if ( ent->client->ps.generic1 > 0 ) { - if ( ent->client->sess.sessionTeam == TEAM_RED ) { - item = BG_FindItem( "Blue Cube" ); - } else { - item = BG_FindItem( "Red Cube" ); - } - if ( item ) { - for ( j = 0; j < ent->client->ps.generic1; j++ ) { - drop = Drop_Item( ent, item, 0 ); - if ( ent->client->sess.sessionTeam == TEAM_RED ) { - drop->spawnflags = TEAM_BLUE; - } else { - drop->spawnflags = TEAM_RED; - } - } - } - ent->client->ps.generic1 = 0; - } - } -#endif - SelectSpawnPoint( ent->client->ps.origin, origin, angles ); - TeleportPlayer( ent, origin, angles ); - break; - - case EV_USE_ITEM2: // medkit - ent->health = ent->client->ps.stats[STAT_MAX_HEALTH] + 25; - - break; - -#ifdef MISSIONPACK - case EV_USE_ITEM3: // kamikaze - // make sure the invulnerability is off - ent->client->invulnerabilityTime = 0; - // start the kamikze - G_StartKamikaze( ent ); - break; - - case EV_USE_ITEM4: // portal - if( ent->client->portalID ) { - DropPortalSource( ent ); - } - else { - DropPortalDestination( ent ); - } - break; - case EV_USE_ITEM5: // invulnerability - ent->client->invulnerabilityTime = level.time + 10000; - break; -#endif - - default: - break; - } - } - -} - -#ifdef MISSIONPACK -/* -============== -StuckInOtherClient -============== -*/ -static int StuckInOtherClient(gentity_t *ent) { - int i; - gentity_t *ent2; - - ent2 = &g_entities[0]; - for ( i = 0; i < MAX_CLIENTS; i++, ent2++ ) { - if ( ent2 == ent ) { - continue; - } - if ( !ent2->inuse ) { - continue; - } - if ( !ent2->client ) { - continue; - } - if ( ent2->health <= 0 ) { - continue; - } - // - if (ent2->r.absmin[0] > ent->r.absmax[0]) - continue; - if (ent2->r.absmin[1] > ent->r.absmax[1]) - continue; - if (ent2->r.absmin[2] > ent->r.absmax[2]) - continue; - if (ent2->r.absmax[0] < ent->r.absmin[0]) - continue; - if (ent2->r.absmax[1] < ent->r.absmin[1]) - continue; - if (ent2->r.absmax[2] < ent->r.absmin[2]) - continue; - return qtrue; - } - return qfalse; -} -#endif - -void BotTestSolid(vec3_t origin); - -/* -============== -SendPendingPredictableEvents -============== -*/ -void SendPendingPredictableEvents( playerState_t *ps ) { - gentity_t *t; - int event, seq; - int extEvent, number; - - // if there are still events pending - if ( ps->entityEventSequence < ps->eventSequence ) { - // create a temporary entity for this event which is sent to everyone - // except the client who generated the event - seq = ps->entityEventSequence & (MAX_PS_EVENTS-1); - event = ps->events[ seq ] | ( ( ps->entityEventSequence & 3 ) << 8 ); - // set external event to zero before calling BG_PlayerStateToEntityState - extEvent = ps->externalEvent; - ps->externalEvent = 0; - // create temporary entity for event - t = G_TempEntity( ps->origin, event ); - number = t->s.number; - BG_PlayerStateToEntityState( ps, &t->s, qtrue ); - t->s.number = number; - t->s.eType = ET_EVENTS + event; - t->s.eFlags |= EF_PLAYER_EVENT; - t->s.otherEntityNum = ps->clientNum; - // send to everyone except the client who generated the event - t->r.svFlags |= SVF_NOTSINGLECLIENT; - t->r.singleClient = ps->clientNum; - // set back external event - ps->externalEvent = extEvent; - } -} - -/* -============== -ClientThink - -This will be called once for each client frame, which will -usually be a couple times for each server frame on fast clients. - -If "g_synchronousClients 1" is set, this will be called exactly -once for each server frame, which makes for smooth demo recording. -============== -*/ -void ClientThink_real( gentity_t *ent ) { - gclient_t *client; - pmove_t pm; - int oldEventSequence; - int msec; - usercmd_t *ucmd; - - client = ent->client; - - // don't think if the client is not yet connected (and thus not yet spawned in) - if (client->pers.connected != CON_CONNECTED) { - return; - } - // mark the time, so the connection sprite can be removed - ucmd = &ent->client->pers.cmd; - - // sanity check the command time to prevent speedup cheating - if ( ucmd->serverTime > level.time + 200 ) { - ucmd->serverTime = level.time + 200; -// G_Printf("serverTime <<<<<\n" ); - } - if ( ucmd->serverTime < level.time - 1000 ) { - ucmd->serverTime = level.time - 1000; -// G_Printf("serverTime >>>>>\n" ); - } - - msec = ucmd->serverTime - client->ps.commandTime; - // following others may result in bad times, but we still want - // to check for follow toggles - if ( msec < 1 && client->sess.spectatorState != SPECTATOR_FOLLOW ) { - return; - } - if ( msec > 200 ) { - msec = 200; - } - - if ( pmove_msec.integer < 8 ) { - trap_Cvar_Set("pmove_msec", "8"); - } - else if (pmove_msec.integer > 33) { - trap_Cvar_Set("pmove_msec", "33"); - } - - if ( pmove_fixed.integer || client->pers.pmoveFixed ) { - ucmd->serverTime = ((ucmd->serverTime + pmove_msec.integer-1) / pmove_msec.integer) * pmove_msec.integer; - //if (ucmd->serverTime - client->ps.commandTime <= 0) - // return; - } - - // - // check for exiting intermission - // - if ( level.intermissiontime ) { - ClientIntermissionThink( client ); - return; - } - - // spectators don't do much - if ( client->sess.sessionTeam == TEAM_SPECTATOR ) { - if ( client->sess.spectatorState == SPECTATOR_SCOREBOARD ) { - return; - } - SpectatorThink( ent, ucmd ); - return; - } - - // check for inactivity timer, but never drop the local client of a non-dedicated server - if ( !ClientInactivityTimer( client ) ) { - return; - } - - // clear the rewards if time - if ( level.time > client->rewardTime ) { - client->ps.eFlags &= ~(EF_AWARD_IMPRESSIVE | EF_AWARD_EXCELLENT | EF_AWARD_GAUNTLET | EF_AWARD_ASSIST | EF_AWARD_DEFEND | EF_AWARD_CAP ); - } - - if ( client->noclip ) { - client->ps.pm_type = PM_NOCLIP; - } else if ( client->ps.stats[STAT_HEALTH] <= 0 ) { - client->ps.pm_type = PM_DEAD; - } else { - client->ps.pm_type = PM_NORMAL; - } - - client->ps.gravity = g_gravity.value; - - // set speed - client->ps.speed = g_speed.value; - -#ifdef MISSIONPACK - if( bg_itemlist[client->ps.stats[STAT_PERSISTANT_POWERUP]].giTag == PW_SCOUT ) { - client->ps.speed *= 1.5; - } - else -#endif - if ( client->ps.powerups[PW_HASTE] ) { - client->ps.speed *= 1.3; - } - - // Let go of the hook if we aren't firing - if ( client->ps.weapon == WP_GRAPPLING_HOOK && - client->hook && !( ucmd->buttons & BUTTON_ATTACK ) ) { - Weapon_HookFree(client->hook); - } - - // set up for pmove - oldEventSequence = client->ps.eventSequence; - - memset (&pm, 0, sizeof(pm)); - - // check for the hit-scan gauntlet, don't let the action - // go through as an attack unless it actually hits something - if ( client->ps.weapon == WP_GAUNTLET && !( ucmd->buttons & BUTTON_TALK ) && - ( ucmd->buttons & BUTTON_ATTACK ) && client->ps.weaponTime <= 0 ) { - pm.gauntletHit = CheckGauntletAttack( ent ); - } - - if ( ent->flags & FL_FORCE_GESTURE ) { - ent->flags &= ~FL_FORCE_GESTURE; - ent->client->pers.cmd.buttons |= BUTTON_GESTURE; - } - -#ifdef MISSIONPACK - // check for invulnerability expansion before doing the Pmove - if (client->ps.powerups[PW_INVULNERABILITY] ) { - if ( !(client->ps.pm_flags & PMF_INVULEXPAND) ) { - vec3_t mins = { -42, -42, -42 }; - vec3_t maxs = { 42, 42, 42 }; - vec3_t oldmins, oldmaxs; - - VectorCopy (ent->r.mins, oldmins); - VectorCopy (ent->r.maxs, oldmaxs); - // expand - VectorCopy (mins, ent->r.mins); - VectorCopy (maxs, ent->r.maxs); - trap_LinkEntity(ent); - // check if this would get anyone stuck in this player - if ( !StuckInOtherClient(ent) ) { - // set flag so the expanded size will be set in PM_CheckDuck - client->ps.pm_flags |= PMF_INVULEXPAND; - } - // set back - VectorCopy (oldmins, ent->r.mins); - VectorCopy (oldmaxs, ent->r.maxs); - trap_LinkEntity(ent); - } - } -#endif - - pm.ps = &client->ps; - pm.cmd = *ucmd; - if ( pm.ps->pm_type == PM_DEAD ) { - pm.tracemask = MASK_PLAYERSOLID & ~CONTENTS_BODY; - } - else if ( ent->r.svFlags & SVF_BOT ) { - pm.tracemask = MASK_PLAYERSOLID | CONTENTS_BOTCLIP; - } - else { - pm.tracemask = MASK_PLAYERSOLID; - } - pm.trace = trap_Trace; - pm.pointcontents = trap_PointContents; - pm.debugLevel = g_debugMove.integer; - pm.noFootsteps = ( g_dmflags.integer & DF_NO_FOOTSTEPS ) > 0; - - pm.pmove_fixed = pmove_fixed.integer | client->pers.pmoveFixed; - pm.pmove_msec = pmove_msec.integer; - - VectorCopy( client->ps.origin, client->oldOrigin ); - -#ifdef MISSIONPACK - if (level.intermissionQueued != 0 && g_singlePlayer.integer) { - if ( level.time - level.intermissionQueued >= 1000 ) { - pm.cmd.buttons = 0; - pm.cmd.forwardmove = 0; - pm.cmd.rightmove = 0; - pm.cmd.upmove = 0; - if ( level.time - level.intermissionQueued >= 2000 && level.time - level.intermissionQueued <= 2500 ) { - trap_SendConsoleCommand( EXEC_APPEND, "centerview\n"); - } - ent->client->ps.pm_type = PM_SPINTERMISSION; - } - } - Pmove (&pm); -#else - Pmove (&pm); -#endif - - // save results of pmove - if ( ent->client->ps.eventSequence != oldEventSequence ) { - ent->eventTime = level.time; - } - if (g_smoothClients.integer) { - BG_PlayerStateToEntityStateExtraPolate( &ent->client->ps, &ent->s, ent->client->ps.commandTime, qtrue ); - } - else { - BG_PlayerStateToEntityState( &ent->client->ps, &ent->s, qtrue ); - } - SendPendingPredictableEvents( &ent->client->ps ); - - if ( !( ent->client->ps.eFlags & EF_FIRING ) ) { - client->fireHeld = qfalse; // for grapple - } - - // use the snapped origin for linking so it matches client predicted versions - VectorCopy( ent->s.pos.trBase, ent->r.currentOrigin ); - - VectorCopy (pm.mins, ent->r.mins); - VectorCopy (pm.maxs, ent->r.maxs); - - ent->waterlevel = pm.waterlevel; - ent->watertype = pm.watertype; - - // execute client events - ClientEvents( ent, oldEventSequence ); - - // link entity now, after any personal teleporters have been used - trap_LinkEntity (ent); - if ( !ent->client->noclip ) { - G_TouchTriggers( ent ); - } - - // NOTE: now copy the exact origin over otherwise clients can be snapped into solid - VectorCopy( ent->client->ps.origin, ent->r.currentOrigin ); - - //test for solid areas in the AAS file - BotTestAAS(ent->r.currentOrigin); - - // touch other objects - ClientImpacts( ent, &pm ); - - // save results of triggers and client events - if (ent->client->ps.eventSequence != oldEventSequence) { - ent->eventTime = level.time; - } - - // swap and latch button actions - client->oldbuttons = client->buttons; - client->buttons = ucmd->buttons; - client->latched_buttons |= client->buttons & ~client->oldbuttons; - - // check for respawning - if ( client->ps.stats[STAT_HEALTH] <= 0 ) { - // wait for the attack button to be pressed - if ( level.time > client->respawnTime ) { - // forcerespawn is to prevent users from waiting out powerups - if ( g_forcerespawn.integer > 0 && - ( level.time - client->respawnTime ) > g_forcerespawn.integer * 1000 ) { - respawn( ent ); - return; - } - - // pressing attack or use is the normal respawn method - if ( ucmd->buttons & ( BUTTON_ATTACK | BUTTON_USE_HOLDABLE ) ) { - respawn( ent ); - } - } - return; - } - - // perform once-a-second actions - ClientTimerActions( ent, msec ); -} - -/* -================== -ClientThink - -A new command has arrived from the client -================== -*/ -void ClientThink( int clientNum ) { - gentity_t *ent; - - ent = g_entities + clientNum; - trap_GetUsercmd( clientNum, &ent->client->pers.cmd ); - - // mark the time we got info, so we can display the - // phone jack if they don't get any for a while - ent->client->lastCmdTime = level.time; - - if ( !(ent->r.svFlags & SVF_BOT) && !g_synchronousClients.integer ) { - ClientThink_real( ent ); - } -} - - -void G_RunClient( gentity_t *ent ) { - if ( !(ent->r.svFlags & SVF_BOT) && !g_synchronousClients.integer ) { - return; - } - ent->client->pers.cmd.serverTime = level.time; - ClientThink_real( ent ); -} - - -/* -================== -SpectatorClientEndFrame - -================== -*/ -void SpectatorClientEndFrame( gentity_t *ent ) { - gclient_t *cl; - - // if we are doing a chase cam or a remote view, grab the latest info - if ( ent->client->sess.spectatorState == SPECTATOR_FOLLOW ) { - int clientNum, flags; - - clientNum = ent->client->sess.spectatorClient; - - // team follow1 and team follow2 go to whatever clients are playing - if ( clientNum == -1 ) { - clientNum = level.follow1; - } else if ( clientNum == -2 ) { - clientNum = level.follow2; - } - if ( clientNum >= 0 ) { - cl = &level.clients[ clientNum ]; - if ( cl->pers.connected == CON_CONNECTED && cl->sess.sessionTeam != TEAM_SPECTATOR ) { - flags = (cl->ps.eFlags & ~(EF_VOTED | EF_TEAMVOTED)) | (ent->client->ps.eFlags & (EF_VOTED | EF_TEAMVOTED)); - ent->client->ps = cl->ps; - ent->client->ps.pm_flags |= PMF_FOLLOW; - ent->client->ps.eFlags = flags; - return; - } else { - // drop them to free spectators unless they are dedicated camera followers - if ( ent->client->sess.spectatorClient >= 0 ) { - ent->client->sess.spectatorState = SPECTATOR_FREE; - ClientBegin( ent->client - level.clients ); - } - } - } - } - - if ( ent->client->sess.spectatorState == SPECTATOR_SCOREBOARD ) { - ent->client->ps.pm_flags |= PMF_SCOREBOARD; - } else { - ent->client->ps.pm_flags &= ~PMF_SCOREBOARD; - } -} - -/* -============== -ClientEndFrame - -Called at the end of each server frame for each connected client -A fast client will have multiple ClientThink for each ClientEdFrame, -while a slow client may have multiple ClientEndFrame between ClientThink. -============== -*/ -void ClientEndFrame( gentity_t *ent ) { - int i; - clientPersistant_t *pers; - - if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) { - SpectatorClientEndFrame( ent ); - return; - } - - pers = &ent->client->pers; - - // turn off any expired powerups - for ( i = 0 ; i < MAX_POWERUPS ; i++ ) { - if ( ent->client->ps.powerups[ i ] < level.time ) { - ent->client->ps.powerups[ i ] = 0; - } - } - -#ifdef MISSIONPACK - // set powerup for player animation - if( bg_itemlist[ent->client->ps.stats[STAT_PERSISTANT_POWERUP]].giTag == PW_GUARD ) { - ent->client->ps.powerups[PW_GUARD] = level.time; - } - if( bg_itemlist[ent->client->ps.stats[STAT_PERSISTANT_POWERUP]].giTag == PW_SCOUT ) { - ent->client->ps.powerups[PW_SCOUT] = level.time; - } - if( bg_itemlist[ent->client->ps.stats[STAT_PERSISTANT_POWERUP]].giTag == PW_DOUBLER ) { - ent->client->ps.powerups[PW_DOUBLER] = level.time; - } - if( bg_itemlist[ent->client->ps.stats[STAT_PERSISTANT_POWERUP]].giTag == PW_AMMOREGEN ) { - ent->client->ps.powerups[PW_AMMOREGEN] = level.time; - } - if ( ent->client->invulnerabilityTime > level.time ) { - ent->client->ps.powerups[PW_INVULNERABILITY] = level.time; - } -#endif - - // save network bandwidth -#if 0 - if ( !g_synchronousClients->integer && ent->client->ps.pm_type == PM_NORMAL ) { - // FIXME: this must change eventually for non-sync demo recording - VectorClear( ent->client->ps.viewangles ); - } -#endif - - // - // If the end of unit layout is displayed, don't give - // the player any normal movement attributes - // - if ( level.intermissiontime ) { - return; - } - - // burn from lava, etc - P_WorldEffects (ent); - - // apply all the damage taken this frame - P_DamageFeedback (ent); - - // add the EF_CONNECTION flag if we haven't gotten commands recently - if ( level.time - ent->client->lastCmdTime > 1000 ) { - ent->s.eFlags |= EF_CONNECTION; - } else { - ent->s.eFlags &= ~EF_CONNECTION; - } - - ent->client->ps.stats[STAT_HEALTH] = ent->health; // FIXME: get rid of ent->health... - - G_SetClientSound (ent); - - // set the latest infor - if (g_smoothClients.integer) { - BG_PlayerStateToEntityStateExtraPolate( &ent->client->ps, &ent->s, ent->client->ps.commandTime, qtrue ); - } - else { - BG_PlayerStateToEntityState( &ent->client->ps, &ent->s, qtrue ); - } - SendPendingPredictableEvents( &ent->client->ps ); - - // set the bit for the reachability area the client is currently in -// i = trap_AAS_PointReachabilityAreaIndex( ent->client->ps.origin ); -// ent->client->areabits[i >> 3] |= 1 << (i & 7); -} - - +/* +=========================================================================== +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 Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// + +#include "g_local.h" + + +/* +=============== +G_DamageFeedback + +Called just before a snapshot is sent to the given player. +Totals up all damage and generates both the player_state_t +damage values to that client for pain blends and kicks, and +global pain sound events for all clients. +=============== +*/ +void P_DamageFeedback( gentity_t *player ) { + gclient_t *client; + float count; + vec3_t angles; + + client = player->client; + if ( client->ps.pm_type == PM_DEAD ) { + return; + } + + // total points of damage shot at the player this frame + count = client->damage_blood + client->damage_armor; + if ( count == 0 ) { + return; // didn't take any damage + } + + if ( count > 255 ) { + count = 255; + } + + // send the information to the client + + // world damage (falling, slime, etc) uses a special code + // to make the blend blob centered instead of positional + if ( client->damage_fromWorld ) { + client->ps.damagePitch = 255; + client->ps.damageYaw = 255; + + client->damage_fromWorld = qfalse; + } else { + vectoangles( client->damage_from, angles ); + client->ps.damagePitch = angles[PITCH]/360.0 * 256; + client->ps.damageYaw = angles[YAW]/360.0 * 256; + } + + // play an apropriate pain sound + if ( (level.time > player->pain_debounce_time) && !(player->flags & FL_GODMODE) ) { + player->pain_debounce_time = level.time + 700; + G_AddEvent( player, EV_PAIN, player->health ); + client->ps.damageEvent++; + } + + + client->ps.damageCount = count; + + // + // clear totals + // + client->damage_blood = 0; + client->damage_armor = 0; + client->damage_knockback = 0; +} + + + +/* +============= +P_WorldEffects + +Check for lava / slime contents and drowning +============= +*/ +void P_WorldEffects( gentity_t *ent ) { + qboolean envirosuit; + int waterlevel; + + if ( ent->client->noclip ) { + ent->client->airOutTime = level.time + 12000; // don't need air + return; + } + + waterlevel = ent->waterlevel; + + envirosuit = ent->client->ps.powerups[PW_BATTLESUIT] > level.time; + + // + // check for drowning + // + if ( waterlevel == 3 ) { + // envirosuit give air + if ( envirosuit ) { + ent->client->airOutTime = level.time + 10000; + } + + // if out of air, start drowning + if ( ent->client->airOutTime < level.time) { + // drown! + ent->client->airOutTime += 1000; + if ( ent->health > 0 ) { + // take more damage the longer underwater + ent->damage += 2; + if (ent->damage > 15) + ent->damage = 15; + + // play a gurp sound instead of a normal pain sound + if (ent->health <= ent->damage) { + G_Sound(ent, CHAN_VOICE, G_SoundIndex("*drown.wav")); + } else if (rand()&1) { + G_Sound(ent, CHAN_VOICE, G_SoundIndex("sound/player/gurp1.wav")); + } else { + G_Sound(ent, CHAN_VOICE, G_SoundIndex("sound/player/gurp2.wav")); + } + + // don't play a normal pain sound + ent->pain_debounce_time = level.time + 200; + + G_Damage (ent, NULL, NULL, NULL, NULL, + ent->damage, DAMAGE_NO_ARMOR, MOD_WATER); + } + } + } else { + ent->client->airOutTime = level.time + 12000; + ent->damage = 2; + } + + // + // check for sizzle damage (move to pmove?) + // + if (waterlevel && + (ent->watertype&(CONTENTS_LAVA|CONTENTS_SLIME)) ) { + if (ent->health > 0 + && ent->pain_debounce_time <= level.time ) { + + if ( envirosuit ) { + G_AddEvent( ent, EV_POWERUP_BATTLESUIT, 0 ); + } else { + if (ent->watertype & CONTENTS_LAVA) { + G_Damage (ent, NULL, NULL, NULL, NULL, + 30*waterlevel, 0, MOD_LAVA); + } + + if (ent->watertype & CONTENTS_SLIME) { + G_Damage (ent, NULL, NULL, NULL, NULL, + 10*waterlevel, 0, MOD_SLIME); + } + } + } + } +} + + + +/* +=============== +G_SetClientSound +=============== +*/ +void G_SetClientSound( gentity_t *ent ) { +#ifdef MISSIONPACK + if( ent->s.eFlags & EF_TICKING ) { + ent->client->ps.loopSound = G_SoundIndex( "sound/weapons/proxmine/wstbtick.wav"); + } + else +#endif + if (ent->waterlevel && (ent->watertype&(CONTENTS_LAVA|CONTENTS_SLIME)) ) { + ent->client->ps.loopSound = level.snd_fry; + } else { + ent->client->ps.loopSound = 0; + } +} + + + +//============================================================== + +/* +============== +ClientImpacts +============== +*/ +void ClientImpacts( gentity_t *ent, pmove_t *pm ) { + int i, j; + trace_t trace; + gentity_t *other; + + memset( &trace, 0, sizeof( trace ) ); + for (i=0 ; inumtouch ; i++) { + for (j=0 ; jtouchents[j] == pm->touchents[i] ) { + break; + } + } + if (j != i) { + continue; // duplicated + } + other = &g_entities[ pm->touchents[i] ]; + + if ( ( ent->r.svFlags & SVF_BOT ) && ( ent->touch ) ) { + ent->touch( ent, other, &trace ); + } + + if ( !other->touch ) { + continue; + } + + other->touch( other, ent, &trace ); + } + +} + +/* +============ +G_TouchTriggers + +Find all trigger entities that ent's current position touches. +Spectators will only interact with teleporters. +============ +*/ +void G_TouchTriggers( gentity_t *ent ) { + int i, num; + int touch[MAX_GENTITIES]; + gentity_t *hit; + trace_t trace; + vec3_t mins, maxs; + static vec3_t range = { 40, 40, 52 }; + + if ( !ent->client ) { + return; + } + + // dead clients don't activate triggers! + if ( ent->client->ps.stats[STAT_HEALTH] <= 0 ) { + return; + } + + VectorSubtract( ent->client->ps.origin, range, mins ); + VectorAdd( ent->client->ps.origin, range, maxs ); + + num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES ); + + // can't use ent->absmin, because that has a one unit pad + VectorAdd( ent->client->ps.origin, ent->r.mins, mins ); + VectorAdd( ent->client->ps.origin, ent->r.maxs, maxs ); + + for ( i=0 ; itouch && !ent->touch ) { + continue; + } + if ( !( hit->r.contents & CONTENTS_TRIGGER ) ) { + continue; + } + + // ignore most entities if a spectator + if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) { + if ( hit->s.eType != ET_TELEPORT_TRIGGER && + // this is ugly but adding a new ET_? type will + // most likely cause network incompatibilities + hit->touch != Touch_DoorTrigger) { + continue; + } + } + + // use seperate code for determining if an item is picked up + // so you don't have to actually contact its bounding box + if ( hit->s.eType == ET_ITEM ) { + if ( !BG_PlayerTouchesItem( &ent->client->ps, &hit->s, level.time ) ) { + continue; + } + } else { + if ( !trap_EntityContact( mins, maxs, hit ) ) { + continue; + } + } + + memset( &trace, 0, sizeof(trace) ); + + if ( hit->touch ) { + hit->touch (hit, ent, &trace); + } + + if ( ( ent->r.svFlags & SVF_BOT ) && ( ent->touch ) ) { + ent->touch( ent, hit, &trace ); + } + } + + // if we didn't touch a jump pad this pmove frame + if ( ent->client->ps.jumppad_frame != ent->client->ps.pmove_framecount ) { + ent->client->ps.jumppad_frame = 0; + ent->client->ps.jumppad_ent = 0; + } +} + +/* +================= +SpectatorThink +================= +*/ +void SpectatorThink( gentity_t *ent, usercmd_t *ucmd ) { + pmove_t pm; + gclient_t *client; + + client = ent->client; + + if ( client->sess.spectatorState != SPECTATOR_FOLLOW ) { + client->ps.pm_type = PM_SPECTATOR; + client->ps.speed = 400; // faster than normal + + // set up for pmove + memset (&pm, 0, sizeof(pm)); + pm.ps = &client->ps; + pm.cmd = *ucmd; + pm.tracemask = MASK_PLAYERSOLID & ~CONTENTS_BODY; // spectators can fly through bodies + pm.trace = trap_Trace; + pm.pointcontents = trap_PointContents; + + // perform a pmove + Pmove (&pm); + // save results of pmove + VectorCopy( client->ps.origin, ent->s.origin ); + + G_TouchTriggers( ent ); + trap_UnlinkEntity( ent ); + } + + client->oldbuttons = client->buttons; + client->buttons = ucmd->buttons; + + // attack button cycles through spectators + if ( ( client->buttons & BUTTON_ATTACK ) && ! ( client->oldbuttons & BUTTON_ATTACK ) ) { + Cmd_FollowCycle_f( ent, 1 ); + } +} + + + +/* +================= +ClientInactivityTimer + +Returns qfalse if the client is dropped +================= +*/ +qboolean ClientInactivityTimer( gclient_t *client ) { + if ( ! g_inactivity.integer ) { + // give everyone some time, so if the operator sets g_inactivity during + // gameplay, everyone isn't kicked + client->inactivityTime = level.time + 60 * 1000; + client->inactivityWarning = qfalse; + } else if ( client->pers.cmd.forwardmove || + client->pers.cmd.rightmove || + client->pers.cmd.upmove || + (client->pers.cmd.buttons & BUTTON_ATTACK) ) { + client->inactivityTime = level.time + g_inactivity.integer * 1000; + client->inactivityWarning = qfalse; + } else if ( !client->pers.localClient ) { + if ( level.time > client->inactivityTime ) { + trap_DropClient( client - level.clients, "Dropped due to inactivity" ); + return qfalse; + } + if ( level.time > client->inactivityTime - 10000 && !client->inactivityWarning ) { + client->inactivityWarning = qtrue; + trap_SendServerCommand( client - level.clients, "cp \"Ten seconds until inactivity drop!\n\"" ); + } + } + return qtrue; +} + +/* +================== +ClientTimerActions + +Actions that happen once a second +================== +*/ +void ClientTimerActions( gentity_t *ent, int msec ) { + gclient_t *client; +#ifdef MISSIONPACK + int maxHealth; +#endif + + client = ent->client; + client->timeResidual += msec; + + while ( client->timeResidual >= 1000 ) { + client->timeResidual -= 1000; + + // regenerate +#ifdef MISSIONPACK + if( bg_itemlist[client->ps.stats[STAT_PERSISTANT_POWERUP]].giTag == PW_GUARD ) { + maxHealth = client->ps.stats[STAT_MAX_HEALTH] / 2; + } + else if ( client->ps.powerups[PW_REGEN] ) { + maxHealth = client->ps.stats[STAT_MAX_HEALTH]; + } + else { + maxHealth = 0; + } + if( maxHealth ) { + if ( ent->health < maxHealth ) { + ent->health += 15; + if ( ent->health > maxHealth * 1.1 ) { + ent->health = maxHealth * 1.1; + } + G_AddEvent( ent, EV_POWERUP_REGEN, 0 ); + } else if ( ent->health < maxHealth * 2) { + ent->health += 5; + if ( ent->health > maxHealth * 2 ) { + ent->health = maxHealth * 2; + } + G_AddEvent( ent, EV_POWERUP_REGEN, 0 ); + } +#else + if ( client->ps.powerups[PW_REGEN] ) { + if ( ent->health < client->ps.stats[STAT_MAX_HEALTH]) { + ent->health += 15; + if ( ent->health > client->ps.stats[STAT_MAX_HEALTH] * 1.1 ) { + ent->health = client->ps.stats[STAT_MAX_HEALTH] * 1.1; + } + G_AddEvent( ent, EV_POWERUP_REGEN, 0 ); + } else if ( ent->health < client->ps.stats[STAT_MAX_HEALTH] * 2) { + ent->health += 5; + if ( ent->health > client->ps.stats[STAT_MAX_HEALTH] * 2 ) { + ent->health = client->ps.stats[STAT_MAX_HEALTH] * 2; + } + G_AddEvent( ent, EV_POWERUP_REGEN, 0 ); + } +#endif + } else { + // count down health when over max + if ( ent->health > client->ps.stats[STAT_MAX_HEALTH] ) { + ent->health--; + } + } + + // count down armor when over max + if ( client->ps.stats[STAT_ARMOR] > client->ps.stats[STAT_MAX_HEALTH] ) { + client->ps.stats[STAT_ARMOR]--; + } + } +#ifdef MISSIONPACK + if( bg_itemlist[client->ps.stats[STAT_PERSISTANT_POWERUP]].giTag == PW_AMMOREGEN ) { + int w, max, inc, t, i; + int weapList[]={WP_MACHINEGUN,WP_SHOTGUN,WP_GRENADE_LAUNCHER,WP_ROCKET_LAUNCHER,WP_LIGHTNING,WP_RAILGUN,WP_PLASMAGUN,WP_BFG,WP_NAILGUN,WP_PROX_LAUNCHER,WP_CHAINGUN}; + int weapCount = sizeof(weapList) / sizeof(int); + // + for (i = 0; i < weapCount; i++) { + w = weapList[i]; + + switch(w) { + case WP_MACHINEGUN: max = 50; inc = 4; t = 1000; break; + case WP_SHOTGUN: max = 10; inc = 1; t = 1500; break; + case WP_GRENADE_LAUNCHER: max = 10; inc = 1; t = 2000; break; + case WP_ROCKET_LAUNCHER: max = 10; inc = 1; t = 1750; break; + case WP_LIGHTNING: max = 50; inc = 5; t = 1500; break; + case WP_RAILGUN: max = 10; inc = 1; t = 1750; break; + case WP_PLASMAGUN: max = 50; inc = 5; t = 1500; break; + case WP_BFG: max = 10; inc = 1; t = 4000; break; + case WP_NAILGUN: max = 10; inc = 1; t = 1250; break; + case WP_PROX_LAUNCHER: max = 5; inc = 1; t = 2000; break; + case WP_CHAINGUN: max = 100; inc = 5; t = 1000; break; + default: max = 0; inc = 0; t = 1000; break; + } + client->ammoTimes[w] += msec; + if ( client->ps.ammo[w] >= max ) { + client->ammoTimes[w] = 0; + } + if ( client->ammoTimes[w] >= t ) { + while ( client->ammoTimes[w] >= t ) + client->ammoTimes[w] -= t; + client->ps.ammo[w] += inc; + if ( client->ps.ammo[w] > max ) { + client->ps.ammo[w] = max; + } + } + } + } +#endif +} + +/* +==================== +ClientIntermissionThink +==================== +*/ +void ClientIntermissionThink( gclient_t *client ) { + client->ps.eFlags &= ~EF_TALK; + client->ps.eFlags &= ~EF_FIRING; + + // the level will exit when everyone wants to or after timeouts + + // swap and latch button actions + client->oldbuttons = client->buttons; + client->buttons = client->pers.cmd.buttons; + if ( client->buttons & ( BUTTON_ATTACK | BUTTON_USE_HOLDABLE ) & ( client->oldbuttons ^ client->buttons ) ) { + // this used to be an ^1 but once a player says ready, it should stick + client->readyToExit = 1; + } +} + + +/* +================ +ClientEvents + +Events will be passed on to the clients for presentation, +but any server game effects are handled here +================ +*/ +void ClientEvents( gentity_t *ent, int oldEventSequence ) { + int i, j; + int event; + gclient_t *client; + int damage; + vec3_t dir; + vec3_t origin, angles; +// qboolean fired; + gitem_t *item; + gentity_t *drop; + + client = ent->client; + + if ( oldEventSequence < client->ps.eventSequence - MAX_PS_EVENTS ) { + oldEventSequence = client->ps.eventSequence - MAX_PS_EVENTS; + } + for ( i = oldEventSequence ; i < client->ps.eventSequence ; i++ ) { + event = client->ps.events[ i & (MAX_PS_EVENTS-1) ]; + + switch ( event ) { + case EV_FALL_MEDIUM: + case EV_FALL_FAR: + if ( ent->s.eType != ET_PLAYER ) { + break; // not in the player model + } + if ( g_dmflags.integer & DF_NO_FALLING ) { + break; + } + if ( event == EV_FALL_FAR ) { + damage = 10; + } else { + damage = 5; + } + VectorSet (dir, 0, 0, 1); + ent->pain_debounce_time = level.time + 200; // no normal pain sound + G_Damage (ent, NULL, NULL, NULL, NULL, damage, 0, MOD_FALLING); + break; + + case EV_FIRE_WEAPON: + FireWeapon( ent ); + break; + + case EV_USE_ITEM1: // teleporter + // drop flags in CTF + item = NULL; + j = 0; + + if ( ent->client->ps.powerups[ PW_REDFLAG ] ) { + item = BG_FindItemForPowerup( PW_REDFLAG ); + j = PW_REDFLAG; + } else if ( ent->client->ps.powerups[ PW_BLUEFLAG ] ) { + item = BG_FindItemForPowerup( PW_BLUEFLAG ); + j = PW_BLUEFLAG; + } else if ( ent->client->ps.powerups[ PW_NEUTRALFLAG ] ) { + item = BG_FindItemForPowerup( PW_NEUTRALFLAG ); + j = PW_NEUTRALFLAG; + } + + if ( item ) { + drop = Drop_Item( ent, item, 0 ); + // decide how many seconds it has left + drop->count = ( ent->client->ps.powerups[ j ] - level.time ) / 1000; + if ( drop->count < 1 ) { + drop->count = 1; + } + + ent->client->ps.powerups[ j ] = 0; + } + +#ifdef MISSIONPACK + if ( g_gametype.integer == GT_HARVESTER ) { + if ( ent->client->ps.generic1 > 0 ) { + if ( ent->client->sess.sessionTeam == TEAM_RED ) { + item = BG_FindItem( "Blue Cube" ); + } else { + item = BG_FindItem( "Red Cube" ); + } + if ( item ) { + for ( j = 0; j < ent->client->ps.generic1; j++ ) { + drop = Drop_Item( ent, item, 0 ); + if ( ent->client->sess.sessionTeam == TEAM_RED ) { + drop->spawnflags = TEAM_BLUE; + } else { + drop->spawnflags = TEAM_RED; + } + } + } + ent->client->ps.generic1 = 0; + } + } +#endif + SelectSpawnPoint( ent->client->ps.origin, origin, angles ); + TeleportPlayer( ent, origin, angles ); + break; + + case EV_USE_ITEM2: // medkit + ent->health = ent->client->ps.stats[STAT_MAX_HEALTH] + 25; + + break; + +#ifdef MISSIONPACK + case EV_USE_ITEM3: // kamikaze + // make sure the invulnerability is off + ent->client->invulnerabilityTime = 0; + // start the kamikze + G_StartKamikaze( ent ); + break; + + case EV_USE_ITEM4: // portal + if( ent->client->portalID ) { + DropPortalSource( ent ); + } + else { + DropPortalDestination( ent ); + } + break; + case EV_USE_ITEM5: // invulnerability + ent->client->invulnerabilityTime = level.time + 10000; + break; +#endif + + default: + break; + } + } + +} + +#ifdef MISSIONPACK +/* +============== +StuckInOtherClient +============== +*/ +static int StuckInOtherClient(gentity_t *ent) { + int i; + gentity_t *ent2; + + ent2 = &g_entities[0]; + for ( i = 0; i < MAX_CLIENTS; i++, ent2++ ) { + if ( ent2 == ent ) { + continue; + } + if ( !ent2->inuse ) { + continue; + } + if ( !ent2->client ) { + continue; + } + if ( ent2->health <= 0 ) { + continue; + } + // + if (ent2->r.absmin[0] > ent->r.absmax[0]) + continue; + if (ent2->r.absmin[1] > ent->r.absmax[1]) + continue; + if (ent2->r.absmin[2] > ent->r.absmax[2]) + continue; + if (ent2->r.absmax[0] < ent->r.absmin[0]) + continue; + if (ent2->r.absmax[1] < ent->r.absmin[1]) + continue; + if (ent2->r.absmax[2] < ent->r.absmin[2]) + continue; + return qtrue; + } + return qfalse; +} +#endif + +void BotTestSolid(vec3_t origin); + +/* +============== +SendPendingPredictableEvents +============== +*/ +void SendPendingPredictableEvents( playerState_t *ps ) { + gentity_t *t; + int event, seq; + int extEvent, number; + + // if there are still events pending + if ( ps->entityEventSequence < ps->eventSequence ) { + // create a temporary entity for this event which is sent to everyone + // except the client who generated the event + seq = ps->entityEventSequence & (MAX_PS_EVENTS-1); + event = ps->events[ seq ] | ( ( ps->entityEventSequence & 3 ) << 8 ); + // set external event to zero before calling BG_PlayerStateToEntityState + extEvent = ps->externalEvent; + ps->externalEvent = 0; + // create temporary entity for event + t = G_TempEntity( ps->origin, event ); + number = t->s.number; + BG_PlayerStateToEntityState( ps, &t->s, qtrue ); + t->s.number = number; + t->s.eType = ET_EVENTS + event; + t->s.eFlags |= EF_PLAYER_EVENT; + t->s.otherEntityNum = ps->clientNum; + // send to everyone except the client who generated the event + t->r.svFlags |= SVF_NOTSINGLECLIENT; + t->r.singleClient = ps->clientNum; + // set back external event + ps->externalEvent = extEvent; + } +} + +/* +============== +ClientThink + +This will be called once for each client frame, which will +usually be a couple times for each server frame on fast clients. + +If "g_synchronousClients 1" is set, this will be called exactly +once for each server frame, which makes for smooth demo recording. +============== +*/ +void ClientThink_real( gentity_t *ent ) { + gclient_t *client; + pmove_t pm; + int oldEventSequence; + int msec; + usercmd_t *ucmd; + + client = ent->client; + + // don't think if the client is not yet connected (and thus not yet spawned in) + if (client->pers.connected != CON_CONNECTED) { + return; + } + // mark the time, so the connection sprite can be removed + ucmd = &ent->client->pers.cmd; + + // sanity check the command time to prevent speedup cheating + if ( ucmd->serverTime > level.time + 200 ) { + ucmd->serverTime = level.time + 200; +// G_Printf("serverTime <<<<<\n" ); + } + if ( ucmd->serverTime < level.time - 1000 ) { + ucmd->serverTime = level.time - 1000; +// G_Printf("serverTime >>>>>\n" ); + } + + msec = ucmd->serverTime - client->ps.commandTime; + // following others may result in bad times, but we still want + // to check for follow toggles + if ( msec < 1 && client->sess.spectatorState != SPECTATOR_FOLLOW ) { + return; + } + if ( msec > 200 ) { + msec = 200; + } + + if ( pmove_msec.integer < 8 ) { + trap_Cvar_Set("pmove_msec", "8"); + } + else if (pmove_msec.integer > 33) { + trap_Cvar_Set("pmove_msec", "33"); + } + + if ( pmove_fixed.integer || client->pers.pmoveFixed ) { + ucmd->serverTime = ((ucmd->serverTime + pmove_msec.integer-1) / pmove_msec.integer) * pmove_msec.integer; + //if (ucmd->serverTime - client->ps.commandTime <= 0) + // return; + } + + // + // check for exiting intermission + // + if ( level.intermissiontime ) { + ClientIntermissionThink( client ); + return; + } + + // spectators don't do much + if ( client->sess.sessionTeam == TEAM_SPECTATOR ) { + if ( client->sess.spectatorState == SPECTATOR_SCOREBOARD ) { + return; + } + SpectatorThink( ent, ucmd ); + return; + } + + // check for inactivity timer, but never drop the local client of a non-dedicated server + if ( !ClientInactivityTimer( client ) ) { + return; + } + + // clear the rewards if time + if ( level.time > client->rewardTime ) { + client->ps.eFlags &= ~(EF_AWARD_IMPRESSIVE | EF_AWARD_EXCELLENT | EF_AWARD_GAUNTLET | EF_AWARD_ASSIST | EF_AWARD_DEFEND | EF_AWARD_CAP ); + } + + if ( client->noclip ) { + client->ps.pm_type = PM_NOCLIP; + } else if ( client->ps.stats[STAT_HEALTH] <= 0 ) { + client->ps.pm_type = PM_DEAD; + } else { + client->ps.pm_type = PM_NORMAL; + } + + client->ps.gravity = g_gravity.value; + + // set speed + client->ps.speed = g_speed.value; + +#ifdef MISSIONPACK + if( bg_itemlist[client->ps.stats[STAT_PERSISTANT_POWERUP]].giTag == PW_SCOUT ) { + client->ps.speed *= 1.5; + } + else +#endif + if ( client->ps.powerups[PW_HASTE] ) { + client->ps.speed *= 1.3; + } + + // Let go of the hook if we aren't firing + if ( client->ps.weapon == WP_GRAPPLING_HOOK && + client->hook && !( ucmd->buttons & BUTTON_ATTACK ) ) { + Weapon_HookFree(client->hook); + } + + // set up for pmove + oldEventSequence = client->ps.eventSequence; + + memset (&pm, 0, sizeof(pm)); + + // check for the hit-scan gauntlet, don't let the action + // go through as an attack unless it actually hits something + if ( client->ps.weapon == WP_GAUNTLET && !( ucmd->buttons & BUTTON_TALK ) && + ( ucmd->buttons & BUTTON_ATTACK ) && client->ps.weaponTime <= 0 ) { + pm.gauntletHit = CheckGauntletAttack( ent ); + } + + if ( ent->flags & FL_FORCE_GESTURE ) { + ent->flags &= ~FL_FORCE_GESTURE; + ent->client->pers.cmd.buttons |= BUTTON_GESTURE; + } + +#ifdef MISSIONPACK + // check for invulnerability expansion before doing the Pmove + if (client->ps.powerups[PW_INVULNERABILITY] ) { + if ( !(client->ps.pm_flags & PMF_INVULEXPAND) ) { + vec3_t mins = { -42, -42, -42 }; + vec3_t maxs = { 42, 42, 42 }; + vec3_t oldmins, oldmaxs; + + VectorCopy (ent->r.mins, oldmins); + VectorCopy (ent->r.maxs, oldmaxs); + // expand + VectorCopy (mins, ent->r.mins); + VectorCopy (maxs, ent->r.maxs); + trap_LinkEntity(ent); + // check if this would get anyone stuck in this player + if ( !StuckInOtherClient(ent) ) { + // set flag so the expanded size will be set in PM_CheckDuck + client->ps.pm_flags |= PMF_INVULEXPAND; + } + // set back + VectorCopy (oldmins, ent->r.mins); + VectorCopy (oldmaxs, ent->r.maxs); + trap_LinkEntity(ent); + } + } +#endif + + pm.ps = &client->ps; + pm.cmd = *ucmd; + if ( pm.ps->pm_type == PM_DEAD ) { + pm.tracemask = MASK_PLAYERSOLID & ~CONTENTS_BODY; + } + else if ( ent->r.svFlags & SVF_BOT ) { + pm.tracemask = MASK_PLAYERSOLID | CONTENTS_BOTCLIP; + } + else { + pm.tracemask = MASK_PLAYERSOLID; + } + pm.trace = trap_Trace; + pm.pointcontents = trap_PointContents; + pm.debugLevel = g_debugMove.integer; + pm.noFootsteps = ( g_dmflags.integer & DF_NO_FOOTSTEPS ) > 0; + + pm.pmove_fixed = pmove_fixed.integer | client->pers.pmoveFixed; + pm.pmove_msec = pmove_msec.integer; + + VectorCopy( client->ps.origin, client->oldOrigin ); + +#ifdef MISSIONPACK + if (level.intermissionQueued != 0 && g_singlePlayer.integer) { + if ( level.time - level.intermissionQueued >= 1000 ) { + pm.cmd.buttons = 0; + pm.cmd.forwardmove = 0; + pm.cmd.rightmove = 0; + pm.cmd.upmove = 0; + if ( level.time - level.intermissionQueued >= 2000 && level.time - level.intermissionQueued <= 2500 ) { + trap_SendConsoleCommand( EXEC_APPEND, "centerview\n"); + } + ent->client->ps.pm_type = PM_SPINTERMISSION; + } + } + Pmove (&pm); +#else + Pmove (&pm); +#endif + + // save results of pmove + if ( ent->client->ps.eventSequence != oldEventSequence ) { + ent->eventTime = level.time; + } + if (g_smoothClients.integer) { + BG_PlayerStateToEntityStateExtraPolate( &ent->client->ps, &ent->s, ent->client->ps.commandTime, qtrue ); + } + else { + BG_PlayerStateToEntityState( &ent->client->ps, &ent->s, qtrue ); + } + SendPendingPredictableEvents( &ent->client->ps ); + + if ( !( ent->client->ps.eFlags & EF_FIRING ) ) { + client->fireHeld = qfalse; // for grapple + } + + // use the snapped origin for linking so it matches client predicted versions + VectorCopy( ent->s.pos.trBase, ent->r.currentOrigin ); + + VectorCopy (pm.mins, ent->r.mins); + VectorCopy (pm.maxs, ent->r.maxs); + + ent->waterlevel = pm.waterlevel; + ent->watertype = pm.watertype; + + // execute client events + ClientEvents( ent, oldEventSequence ); + + // link entity now, after any personal teleporters have been used + trap_LinkEntity (ent); + if ( !ent->client->noclip ) { + G_TouchTriggers( ent ); + } + + // NOTE: now copy the exact origin over otherwise clients can be snapped into solid + VectorCopy( ent->client->ps.origin, ent->r.currentOrigin ); + + //test for solid areas in the AAS file + BotTestAAS(ent->r.currentOrigin); + + // touch other objects + ClientImpacts( ent, &pm ); + + // save results of triggers and client events + if (ent->client->ps.eventSequence != oldEventSequence) { + ent->eventTime = level.time; + } + + // swap and latch button actions + client->oldbuttons = client->buttons; + client->buttons = ucmd->buttons; + client->latched_buttons |= client->buttons & ~client->oldbuttons; + + // check for respawning + if ( client->ps.stats[STAT_HEALTH] <= 0 ) { + // wait for the attack button to be pressed + if ( level.time > client->respawnTime ) { + // forcerespawn is to prevent users from waiting out powerups + if ( g_forcerespawn.integer > 0 && + ( level.time - client->respawnTime ) > g_forcerespawn.integer * 1000 ) { + respawn( ent ); + return; + } + + // pressing attack or use is the normal respawn method + if ( ucmd->buttons & ( BUTTON_ATTACK | BUTTON_USE_HOLDABLE ) ) { + respawn( ent ); + } + } + return; + } + + // perform once-a-second actions + ClientTimerActions( ent, msec ); +} + +/* +================== +ClientThink + +A new command has arrived from the client +================== +*/ +void ClientThink( int clientNum ) { + gentity_t *ent; + + ent = g_entities + clientNum; + trap_GetUsercmd( clientNum, &ent->client->pers.cmd ); + + // mark the time we got info, so we can display the + // phone jack if they don't get any for a while + ent->client->lastCmdTime = level.time; + + if ( !(ent->r.svFlags & SVF_BOT) && !g_synchronousClients.integer ) { + ClientThink_real( ent ); + } +} + + +void G_RunClient( gentity_t *ent ) { + if ( !(ent->r.svFlags & SVF_BOT) && !g_synchronousClients.integer ) { + return; + } + ent->client->pers.cmd.serverTime = level.time; + ClientThink_real( ent ); +} + + +/* +================== +SpectatorClientEndFrame + +================== +*/ +void SpectatorClientEndFrame( gentity_t *ent ) { + gclient_t *cl; + + // if we are doing a chase cam or a remote view, grab the latest info + if ( ent->client->sess.spectatorState == SPECTATOR_FOLLOW ) { + int clientNum, flags; + + clientNum = ent->client->sess.spectatorClient; + + // team follow1 and team follow2 go to whatever clients are playing + if ( clientNum == -1 ) { + clientNum = level.follow1; + } else if ( clientNum == -2 ) { + clientNum = level.follow2; + } + if ( clientNum >= 0 ) { + cl = &level.clients[ clientNum ]; + if ( cl->pers.connected == CON_CONNECTED && cl->sess.sessionTeam != TEAM_SPECTATOR ) { + flags = (cl->ps.eFlags & ~(EF_VOTED | EF_TEAMVOTED)) | (ent->client->ps.eFlags & (EF_VOTED | EF_TEAMVOTED)); + ent->client->ps = cl->ps; + ent->client->ps.pm_flags |= PMF_FOLLOW; + ent->client->ps.eFlags = flags; + return; + } else { + // drop them to free spectators unless they are dedicated camera followers + if ( ent->client->sess.spectatorClient >= 0 ) { + ent->client->sess.spectatorState = SPECTATOR_FREE; + ClientBegin( ent->client - level.clients ); + } + } + } + } + + if ( ent->client->sess.spectatorState == SPECTATOR_SCOREBOARD ) { + ent->client->ps.pm_flags |= PMF_SCOREBOARD; + } else { + ent->client->ps.pm_flags &= ~PMF_SCOREBOARD; + } +} + +/* +============== +ClientEndFrame + +Called at the end of each server frame for each connected client +A fast client will have multiple ClientThink for each ClientEdFrame, +while a slow client may have multiple ClientEndFrame between ClientThink. +============== +*/ +void ClientEndFrame( gentity_t *ent ) { + int i; + clientPersistant_t *pers; + + if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) { + SpectatorClientEndFrame( ent ); + return; + } + + pers = &ent->client->pers; + + // turn off any expired powerups + for ( i = 0 ; i < MAX_POWERUPS ; i++ ) { + if ( ent->client->ps.powerups[ i ] < level.time ) { + ent->client->ps.powerups[ i ] = 0; + } + } + +#ifdef MISSIONPACK + // set powerup for player animation + if( bg_itemlist[ent->client->ps.stats[STAT_PERSISTANT_POWERUP]].giTag == PW_GUARD ) { + ent->client->ps.powerups[PW_GUARD] = level.time; + } + if( bg_itemlist[ent->client->ps.stats[STAT_PERSISTANT_POWERUP]].giTag == PW_SCOUT ) { + ent->client->ps.powerups[PW_SCOUT] = level.time; + } + if( bg_itemlist[ent->client->ps.stats[STAT_PERSISTANT_POWERUP]].giTag == PW_DOUBLER ) { + ent->client->ps.powerups[PW_DOUBLER] = level.time; + } + if( bg_itemlist[ent->client->ps.stats[STAT_PERSISTANT_POWERUP]].giTag == PW_AMMOREGEN ) { + ent->client->ps.powerups[PW_AMMOREGEN] = level.time; + } + if ( ent->client->invulnerabilityTime > level.time ) { + ent->client->ps.powerups[PW_INVULNERABILITY] = level.time; + } +#endif + + // save network bandwidth +#if 0 + if ( !g_synchronousClients->integer && ent->client->ps.pm_type == PM_NORMAL ) { + // FIXME: this must change eventually for non-sync demo recording + VectorClear( ent->client->ps.viewangles ); + } +#endif + + // + // If the end of unit layout is displayed, don't give + // the player any normal movement attributes + // + if ( level.intermissiontime ) { + return; + } + + // burn from lava, etc + P_WorldEffects (ent); + + // apply all the damage taken this frame + P_DamageFeedback (ent); + + // add the EF_CONNECTION flag if we haven't gotten commands recently + if ( level.time - ent->client->lastCmdTime > 1000 ) { + ent->s.eFlags |= EF_CONNECTION; + } else { + ent->s.eFlags &= ~EF_CONNECTION; + } + + ent->client->ps.stats[STAT_HEALTH] = ent->health; // FIXME: get rid of ent->health... + + G_SetClientSound (ent); + + // set the latest infor + if (g_smoothClients.integer) { + BG_PlayerStateToEntityStateExtraPolate( &ent->client->ps, &ent->s, ent->client->ps.commandTime, qtrue ); + } + else { + BG_PlayerStateToEntityState( &ent->client->ps, &ent->s, qtrue ); + } + SendPendingPredictableEvents( &ent->client->ps ); + + // set the bit for the reachability area the client is currently in +// i = trap_AAS_PointReachabilityAreaIndex( ent->client->ps.origin ); +// ent->client->areabits[i >> 3] |= 1 << (i & 7); +} + + diff --git a/code/game/g_arenas.c b/code/game/g_arenas.c index 1e6476b..d9cf4d5 100755 --- a/code/game/g_arenas.c +++ b/code/game/g_arenas.c @@ -1,376 +1,376 @@ -/* -=========================================================================== -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 Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// -// -// g_arenas.c -// - -#include "g_local.h" - - -gentity_t *podium1; -gentity_t *podium2; -gentity_t *podium3; - - -/* -================== -UpdateTournamentInfo -================== -*/ -void UpdateTournamentInfo( void ) { - int i; - gentity_t *player; - int playerClientNum; - int n, accuracy, perfect, msglen; - int buflen; -#ifdef MISSIONPACK // bk001205 - int score1, score2; - qboolean won; -#endif - char buf[32]; - char msg[MAX_STRING_CHARS]; - - // find the real player - player = NULL; - for (i = 0; i < level.maxclients; i++ ) { - player = &g_entities[i]; - if ( !player->inuse ) { - continue; - } - if ( !( player->r.svFlags & SVF_BOT ) ) { - break; - } - } - // this should never happen! - if ( !player || i == level.maxclients ) { - return; - } - playerClientNum = i; - - CalculateRanks(); - - if ( level.clients[playerClientNum].sess.sessionTeam == TEAM_SPECTATOR ) { -#ifdef MISSIONPACK - Com_sprintf( msg, sizeof(msg), "postgame %i %i 0 0 0 0 0 0 0 0 0 0 0", level.numNonSpectatorClients, playerClientNum ); -#else - Com_sprintf( msg, sizeof(msg), "postgame %i %i 0 0 0 0 0 0", level.numNonSpectatorClients, playerClientNum ); -#endif - } - else { - if( player->client->accuracy_shots ) { - accuracy = player->client->accuracy_hits * 100 / player->client->accuracy_shots; - } - else { - accuracy = 0; - } -#ifdef MISSIONPACK - won = qfalse; - if (g_gametype.integer >= GT_CTF) { - score1 = level.teamScores[TEAM_RED]; - score2 = level.teamScores[TEAM_BLUE]; - if (level.clients[playerClientNum].sess.sessionTeam == TEAM_RED) { - won = (level.teamScores[TEAM_RED] > level.teamScores[TEAM_BLUE]); - } else { - won = (level.teamScores[TEAM_BLUE] > level.teamScores[TEAM_RED]); - } - } else { - if (&level.clients[playerClientNum] == &level.clients[ level.sortedClients[0] ]) { - won = qtrue; - score1 = level.clients[ level.sortedClients[0] ].ps.persistant[PERS_SCORE]; - score2 = level.clients[ level.sortedClients[1] ].ps.persistant[PERS_SCORE]; - } else { - score2 = level.clients[ level.sortedClients[0] ].ps.persistant[PERS_SCORE]; - score1 = level.clients[ level.sortedClients[1] ].ps.persistant[PERS_SCORE]; - } - } - if (won && player->client->ps.persistant[PERS_KILLED] == 0) { - perfect = 1; - } else { - perfect = 0; - } - Com_sprintf( msg, sizeof(msg), "postgame %i %i %i %i %i %i %i %i %i %i %i %i %i %i", level.numNonSpectatorClients, playerClientNum, accuracy, - player->client->ps.persistant[PERS_IMPRESSIVE_COUNT], player->client->ps.persistant[PERS_EXCELLENT_COUNT],player->client->ps.persistant[PERS_DEFEND_COUNT], - player->client->ps.persistant[PERS_ASSIST_COUNT], player->client->ps.persistant[PERS_GAUNTLET_FRAG_COUNT], player->client->ps.persistant[PERS_SCORE], - perfect, score1, score2, level.time, player->client->ps.persistant[PERS_CAPTURES] ); - -#else - perfect = ( level.clients[playerClientNum].ps.persistant[PERS_RANK] == 0 && player->client->ps.persistant[PERS_KILLED] == 0 ) ? 1 : 0; - Com_sprintf( msg, sizeof(msg), "postgame %i %i %i %i %i %i %i %i", level.numNonSpectatorClients, playerClientNum, accuracy, - player->client->ps.persistant[PERS_IMPRESSIVE_COUNT], player->client->ps.persistant[PERS_EXCELLENT_COUNT], - player->client->ps.persistant[PERS_GAUNTLET_FRAG_COUNT], player->client->ps.persistant[PERS_SCORE], - perfect ); -#endif - } - - msglen = strlen( msg ); - for( i = 0; i < level.numNonSpectatorClients; i++ ) { - n = level.sortedClients[i]; - Com_sprintf( buf, sizeof(buf), " %i %i %i", n, level.clients[n].ps.persistant[PERS_RANK], level.clients[n].ps.persistant[PERS_SCORE] ); - buflen = strlen( buf ); - if( msglen + buflen + 1 >= sizeof(msg) ) { - break; - } - strcat( msg, buf ); - } - trap_SendConsoleCommand( EXEC_APPEND, msg ); -} - - -static gentity_t *SpawnModelOnVictoryPad( gentity_t *pad, vec3_t offset, gentity_t *ent, int place ) { - gentity_t *body; - vec3_t vec; - vec3_t f, r, u; - - body = G_Spawn(); - if ( !body ) { - G_Printf( S_COLOR_RED "ERROR: out of gentities\n" ); - return NULL; - } - - body->classname = ent->client->pers.netname; - body->client = ent->client; - body->s = ent->s; - body->s.eType = ET_PLAYER; // could be ET_INVISIBLE - body->s.eFlags = 0; // clear EF_TALK, etc - body->s.powerups = 0; // clear powerups - body->s.loopSound = 0; // clear lava burning - body->s.number = body - g_entities; - body->timestamp = level.time; - body->physicsObject = qtrue; - body->physicsBounce = 0; // don't bounce - body->s.event = 0; - body->s.pos.trType = TR_STATIONARY; - body->s.groundEntityNum = ENTITYNUM_WORLD; - body->s.legsAnim = LEGS_IDLE; - body->s.torsoAnim = TORSO_STAND; - if( body->s.weapon == WP_NONE ) { - body->s.weapon = WP_MACHINEGUN; - } - if( body->s.weapon == WP_GAUNTLET) { - body->s.torsoAnim = TORSO_STAND2; - } - body->s.event = 0; - body->r.svFlags = ent->r.svFlags; - VectorCopy (ent->r.mins, body->r.mins); - VectorCopy (ent->r.maxs, body->r.maxs); - VectorCopy (ent->r.absmin, body->r.absmin); - VectorCopy (ent->r.absmax, body->r.absmax); - body->clipmask = CONTENTS_SOLID | CONTENTS_PLAYERCLIP; - body->r.contents = CONTENTS_BODY; - body->r.ownerNum = ent->r.ownerNum; - body->takedamage = qfalse; - - VectorSubtract( level.intermission_origin, pad->r.currentOrigin, vec ); - vectoangles( vec, body->s.apos.trBase ); - body->s.apos.trBase[PITCH] = 0; - body->s.apos.trBase[ROLL] = 0; - - AngleVectors( body->s.apos.trBase, f, r, u ); - VectorMA( pad->r.currentOrigin, offset[0], f, vec ); - VectorMA( vec, offset[1], r, vec ); - VectorMA( vec, offset[2], u, vec ); - - G_SetOrigin( body, vec ); - - trap_LinkEntity (body); - - body->count = place; - - return body; -} - - -static void CelebrateStop( gentity_t *player ) { - int anim; - - if( player->s.weapon == WP_GAUNTLET) { - anim = TORSO_STAND2; - } - else { - anim = TORSO_STAND; - } - player->s.torsoAnim = ( ( player->s.torsoAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | anim; -} - - -#define TIMER_GESTURE (34*66+50) -static void CelebrateStart( gentity_t *player ) { - player->s.torsoAnim = ( ( player->s.torsoAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | TORSO_GESTURE; - player->nextthink = level.time + TIMER_GESTURE; - player->think = CelebrateStop; - - /* - player->client->ps.events[player->client->ps.eventSequence & (MAX_PS_EVENTS-1)] = EV_TAUNT; - player->client->ps.eventParms[player->client->ps.eventSequence & (MAX_PS_EVENTS-1)] = 0; - player->client->ps.eventSequence++; - */ - G_AddEvent(player, EV_TAUNT, 0); -} - - -static vec3_t offsetFirst = {0, 0, 74}; -static vec3_t offsetSecond = {-10, 60, 54}; -static vec3_t offsetThird = {-19, -60, 45}; - -static void PodiumPlacementThink( gentity_t *podium ) { - vec3_t vec; - vec3_t origin; - vec3_t f, r, u; - - podium->nextthink = level.time + 100; - - AngleVectors( level.intermission_angle, vec, NULL, NULL ); - VectorMA( level.intermission_origin, trap_Cvar_VariableIntegerValue( "g_podiumDist" ), vec, origin ); - origin[2] -= trap_Cvar_VariableIntegerValue( "g_podiumDrop" ); - G_SetOrigin( podium, origin ); - - if( podium1 ) { - VectorSubtract( level.intermission_origin, podium->r.currentOrigin, vec ); - vectoangles( vec, podium1->s.apos.trBase ); - podium1->s.apos.trBase[PITCH] = 0; - podium1->s.apos.trBase[ROLL] = 0; - - AngleVectors( podium1->s.apos.trBase, f, r, u ); - VectorMA( podium->r.currentOrigin, offsetFirst[0], f, vec ); - VectorMA( vec, offsetFirst[1], r, vec ); - VectorMA( vec, offsetFirst[2], u, vec ); - - G_SetOrigin( podium1, vec ); - } - - if( podium2 ) { - VectorSubtract( level.intermission_origin, podium->r.currentOrigin, vec ); - vectoangles( vec, podium2->s.apos.trBase ); - podium2->s.apos.trBase[PITCH] = 0; - podium2->s.apos.trBase[ROLL] = 0; - - AngleVectors( podium2->s.apos.trBase, f, r, u ); - VectorMA( podium->r.currentOrigin, offsetSecond[0], f, vec ); - VectorMA( vec, offsetSecond[1], r, vec ); - VectorMA( vec, offsetSecond[2], u, vec ); - - G_SetOrigin( podium2, vec ); - } - - if( podium3 ) { - VectorSubtract( level.intermission_origin, podium->r.currentOrigin, vec ); - vectoangles( vec, podium3->s.apos.trBase ); - podium3->s.apos.trBase[PITCH] = 0; - podium3->s.apos.trBase[ROLL] = 0; - - AngleVectors( podium3->s.apos.trBase, f, r, u ); - VectorMA( podium->r.currentOrigin, offsetThird[0], f, vec ); - VectorMA( vec, offsetThird[1], r, vec ); - VectorMA( vec, offsetThird[2], u, vec ); - - G_SetOrigin( podium3, vec ); - } -} - - -static gentity_t *SpawnPodium( void ) { - gentity_t *podium; - vec3_t vec; - vec3_t origin; - - podium = G_Spawn(); - if ( !podium ) { - return NULL; - } - - podium->classname = "podium"; - podium->s.eType = ET_GENERAL; - podium->s.number = podium - g_entities; - podium->clipmask = CONTENTS_SOLID; - podium->r.contents = CONTENTS_SOLID; - podium->s.modelindex = G_ModelIndex( SP_PODIUM_MODEL ); - - AngleVectors( level.intermission_angle, vec, NULL, NULL ); - VectorMA( level.intermission_origin, trap_Cvar_VariableIntegerValue( "g_podiumDist" ), vec, origin ); - origin[2] -= trap_Cvar_VariableIntegerValue( "g_podiumDrop" ); - G_SetOrigin( podium, origin ); - - VectorSubtract( level.intermission_origin, podium->r.currentOrigin, vec ); - podium->s.apos.trBase[YAW] = vectoyaw( vec ); - trap_LinkEntity (podium); - - podium->think = PodiumPlacementThink; - podium->nextthink = level.time + 100; - return podium; -} - - -/* -================== -SpawnModelsOnVictoryPads -================== -*/ -void SpawnModelsOnVictoryPads( void ) { - gentity_t *player; - gentity_t *podium; - - podium1 = NULL; - podium2 = NULL; - podium3 = NULL; - - podium = SpawnPodium(); - - player = SpawnModelOnVictoryPad( podium, offsetFirst, &g_entities[level.sortedClients[0]], - level.clients[ level.sortedClients[0] ].ps.persistant[PERS_RANK] &~ RANK_TIED_FLAG ); - if ( player ) { - player->nextthink = level.time + 2000; - player->think = CelebrateStart; - podium1 = player; - } - - player = SpawnModelOnVictoryPad( podium, offsetSecond, &g_entities[level.sortedClients[1]], - level.clients[ level.sortedClients[1] ].ps.persistant[PERS_RANK] &~ RANK_TIED_FLAG ); - if ( player ) { - podium2 = player; - } - - if ( level.numNonSpectatorClients > 2 ) { - player = SpawnModelOnVictoryPad( podium, offsetThird, &g_entities[level.sortedClients[2]], - level.clients[ level.sortedClients[2] ].ps.persistant[PERS_RANK] &~ RANK_TIED_FLAG ); - if ( player ) { - podium3 = player; - } - } -} - - -/* -=============== -Svcmd_AbortPodium_f -=============== -*/ -void Svcmd_AbortPodium_f( void ) { - if( g_gametype.integer != GT_SINGLE_PLAYER ) { - return; - } - - if( podium1 ) { - podium1->nextthink = level.time; - podium1->think = CelebrateStop; - } -} +/* +=========================================================================== +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 Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// +// +// g_arenas.c +// + +#include "g_local.h" + + +gentity_t *podium1; +gentity_t *podium2; +gentity_t *podium3; + + +/* +================== +UpdateTournamentInfo +================== +*/ +void UpdateTournamentInfo( void ) { + int i; + gentity_t *player; + int playerClientNum; + int n, accuracy, perfect, msglen; + int buflen; +#ifdef MISSIONPACK // bk001205 + int score1, score2; + qboolean won; +#endif + char buf[32]; + char msg[MAX_STRING_CHARS]; + + // find the real player + player = NULL; + for (i = 0; i < level.maxclients; i++ ) { + player = &g_entities[i]; + if ( !player->inuse ) { + continue; + } + if ( !( player->r.svFlags & SVF_BOT ) ) { + break; + } + } + // this should never happen! + if ( !player || i == level.maxclients ) { + return; + } + playerClientNum = i; + + CalculateRanks(); + + if ( level.clients[playerClientNum].sess.sessionTeam == TEAM_SPECTATOR ) { +#ifdef MISSIONPACK + Com_sprintf( msg, sizeof(msg), "postgame %i %i 0 0 0 0 0 0 0 0 0 0 0", level.numNonSpectatorClients, playerClientNum ); +#else + Com_sprintf( msg, sizeof(msg), "postgame %i %i 0 0 0 0 0 0", level.numNonSpectatorClients, playerClientNum ); +#endif + } + else { + if( player->client->accuracy_shots ) { + accuracy = player->client->accuracy_hits * 100 / player->client->accuracy_shots; + } + else { + accuracy = 0; + } +#ifdef MISSIONPACK + won = qfalse; + if (g_gametype.integer >= GT_CTF) { + score1 = level.teamScores[TEAM_RED]; + score2 = level.teamScores[TEAM_BLUE]; + if (level.clients[playerClientNum].sess.sessionTeam == TEAM_RED) { + won = (level.teamScores[TEAM_RED] > level.teamScores[TEAM_BLUE]); + } else { + won = (level.teamScores[TEAM_BLUE] > level.teamScores[TEAM_RED]); + } + } else { + if (&level.clients[playerClientNum] == &level.clients[ level.sortedClients[0] ]) { + won = qtrue; + score1 = level.clients[ level.sortedClients[0] ].ps.persistant[PERS_SCORE]; + score2 = level.clients[ level.sortedClients[1] ].ps.persistant[PERS_SCORE]; + } else { + score2 = level.clients[ level.sortedClients[0] ].ps.persistant[PERS_SCORE]; + score1 = level.clients[ level.sortedClients[1] ].ps.persistant[PERS_SCORE]; + } + } + if (won && player->client->ps.persistant[PERS_KILLED] == 0) { + perfect = 1; + } else { + perfect = 0; + } + Com_sprintf( msg, sizeof(msg), "postgame %i %i %i %i %i %i %i %i %i %i %i %i %i %i", level.numNonSpectatorClients, playerClientNum, accuracy, + player->client->ps.persistant[PERS_IMPRESSIVE_COUNT], player->client->ps.persistant[PERS_EXCELLENT_COUNT],player->client->ps.persistant[PERS_DEFEND_COUNT], + player->client->ps.persistant[PERS_ASSIST_COUNT], player->client->ps.persistant[PERS_GAUNTLET_FRAG_COUNT], player->client->ps.persistant[PERS_SCORE], + perfect, score1, score2, level.time, player->client->ps.persistant[PERS_CAPTURES] ); + +#else + perfect = ( level.clients[playerClientNum].ps.persistant[PERS_RANK] == 0 && player->client->ps.persistant[PERS_KILLED] == 0 ) ? 1 : 0; + Com_sprintf( msg, sizeof(msg), "postgame %i %i %i %i %i %i %i %i", level.numNonSpectatorClients, playerClientNum, accuracy, + player->client->ps.persistant[PERS_IMPRESSIVE_COUNT], player->client->ps.persistant[PERS_EXCELLENT_COUNT], + player->client->ps.persistant[PERS_GAUNTLET_FRAG_COUNT], player->client->ps.persistant[PERS_SCORE], + perfect ); +#endif + } + + msglen = strlen( msg ); + for( i = 0; i < level.numNonSpectatorClients; i++ ) { + n = level.sortedClients[i]; + Com_sprintf( buf, sizeof(buf), " %i %i %i", n, level.clients[n].ps.persistant[PERS_RANK], level.clients[n].ps.persistant[PERS_SCORE] ); + buflen = strlen( buf ); + if( msglen + buflen + 1 >= sizeof(msg) ) { + break; + } + strcat( msg, buf ); + } + trap_SendConsoleCommand( EXEC_APPEND, msg ); +} + + +static gentity_t *SpawnModelOnVictoryPad( gentity_t *pad, vec3_t offset, gentity_t *ent, int place ) { + gentity_t *body; + vec3_t vec; + vec3_t f, r, u; + + body = G_Spawn(); + if ( !body ) { + G_Printf( S_COLOR_RED "ERROR: out of gentities\n" ); + return NULL; + } + + body->classname = ent->client->pers.netname; + body->client = ent->client; + body->s = ent->s; + body->s.eType = ET_PLAYER; // could be ET_INVISIBLE + body->s.eFlags = 0; // clear EF_TALK, etc + body->s.powerups = 0; // clear powerups + body->s.loopSound = 0; // clear lava burning + body->s.number = body - g_entities; + body->timestamp = level.time; + body->physicsObject = qtrue; + body->physicsBounce = 0; // don't bounce + body->s.event = 0; + body->s.pos.trType = TR_STATIONARY; + body->s.groundEntityNum = ENTITYNUM_WORLD; + body->s.legsAnim = LEGS_IDLE; + body->s.torsoAnim = TORSO_STAND; + if( body->s.weapon == WP_NONE ) { + body->s.weapon = WP_MACHINEGUN; + } + if( body->s.weapon == WP_GAUNTLET) { + body->s.torsoAnim = TORSO_STAND2; + } + body->s.event = 0; + body->r.svFlags = ent->r.svFlags; + VectorCopy (ent->r.mins, body->r.mins); + VectorCopy (ent->r.maxs, body->r.maxs); + VectorCopy (ent->r.absmin, body->r.absmin); + VectorCopy (ent->r.absmax, body->r.absmax); + body->clipmask = CONTENTS_SOLID | CONTENTS_PLAYERCLIP; + body->r.contents = CONTENTS_BODY; + body->r.ownerNum = ent->r.ownerNum; + body->takedamage = qfalse; + + VectorSubtract( level.intermission_origin, pad->r.currentOrigin, vec ); + vectoangles( vec, body->s.apos.trBase ); + body->s.apos.trBase[PITCH] = 0; + body->s.apos.trBase[ROLL] = 0; + + AngleVectors( body->s.apos.trBase, f, r, u ); + VectorMA( pad->r.currentOrigin, offset[0], f, vec ); + VectorMA( vec, offset[1], r, vec ); + VectorMA( vec, offset[2], u, vec ); + + G_SetOrigin( body, vec ); + + trap_LinkEntity (body); + + body->count = place; + + return body; +} + + +static void CelebrateStop( gentity_t *player ) { + int anim; + + if( player->s.weapon == WP_GAUNTLET) { + anim = TORSO_STAND2; + } + else { + anim = TORSO_STAND; + } + player->s.torsoAnim = ( ( player->s.torsoAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | anim; +} + + +#define TIMER_GESTURE (34*66+50) +static void CelebrateStart( gentity_t *player ) { + player->s.torsoAnim = ( ( player->s.torsoAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | TORSO_GESTURE; + player->nextthink = level.time + TIMER_GESTURE; + player->think = CelebrateStop; + + /* + player->client->ps.events[player->client->ps.eventSequence & (MAX_PS_EVENTS-1)] = EV_TAUNT; + player->client->ps.eventParms[player->client->ps.eventSequence & (MAX_PS_EVENTS-1)] = 0; + player->client->ps.eventSequence++; + */ + G_AddEvent(player, EV_TAUNT, 0); +} + + +static vec3_t offsetFirst = {0, 0, 74}; +static vec3_t offsetSecond = {-10, 60, 54}; +static vec3_t offsetThird = {-19, -60, 45}; + +static void PodiumPlacementThink( gentity_t *podium ) { + vec3_t vec; + vec3_t origin; + vec3_t f, r, u; + + podium->nextthink = level.time + 100; + + AngleVectors( level.intermission_angle, vec, NULL, NULL ); + VectorMA( level.intermission_origin, trap_Cvar_VariableIntegerValue( "g_podiumDist" ), vec, origin ); + origin[2] -= trap_Cvar_VariableIntegerValue( "g_podiumDrop" ); + G_SetOrigin( podium, origin ); + + if( podium1 ) { + VectorSubtract( level.intermission_origin, podium->r.currentOrigin, vec ); + vectoangles( vec, podium1->s.apos.trBase ); + podium1->s.apos.trBase[PITCH] = 0; + podium1->s.apos.trBase[ROLL] = 0; + + AngleVectors( podium1->s.apos.trBase, f, r, u ); + VectorMA( podium->r.currentOrigin, offsetFirst[0], f, vec ); + VectorMA( vec, offsetFirst[1], r, vec ); + VectorMA( vec, offsetFirst[2], u, vec ); + + G_SetOrigin( podium1, vec ); + } + + if( podium2 ) { + VectorSubtract( level.intermission_origin, podium->r.currentOrigin, vec ); + vectoangles( vec, podium2->s.apos.trBase ); + podium2->s.apos.trBase[PITCH] = 0; + podium2->s.apos.trBase[ROLL] = 0; + + AngleVectors( podium2->s.apos.trBase, f, r, u ); + VectorMA( podium->r.currentOrigin, offsetSecond[0], f, vec ); + VectorMA( vec, offsetSecond[1], r, vec ); + VectorMA( vec, offsetSecond[2], u, vec ); + + G_SetOrigin( podium2, vec ); + } + + if( podium3 ) { + VectorSubtract( level.intermission_origin, podium->r.currentOrigin, vec ); + vectoangles( vec, podium3->s.apos.trBase ); + podium3->s.apos.trBase[PITCH] = 0; + podium3->s.apos.trBase[ROLL] = 0; + + AngleVectors( podium3->s.apos.trBase, f, r, u ); + VectorMA( podium->r.currentOrigin, offsetThird[0], f, vec ); + VectorMA( vec, offsetThird[1], r, vec ); + VectorMA( vec, offsetThird[2], u, vec ); + + G_SetOrigin( podium3, vec ); + } +} + + +static gentity_t *SpawnPodium( void ) { + gentity_t *podium; + vec3_t vec; + vec3_t origin; + + podium = G_Spawn(); + if ( !podium ) { + return NULL; + } + + podium->classname = "podium"; + podium->s.eType = ET_GENERAL; + podium->s.number = podium - g_entities; + podium->clipmask = CONTENTS_SOLID; + podium->r.contents = CONTENTS_SOLID; + podium->s.modelindex = G_ModelIndex( SP_PODIUM_MODEL ); + + AngleVectors( level.intermission_angle, vec, NULL, NULL ); + VectorMA( level.intermission_origin, trap_Cvar_VariableIntegerValue( "g_podiumDist" ), vec, origin ); + origin[2] -= trap_Cvar_VariableIntegerValue( "g_podiumDrop" ); + G_SetOrigin( podium, origin ); + + VectorSubtract( level.intermission_origin, podium->r.currentOrigin, vec ); + podium->s.apos.trBase[YAW] = vectoyaw( vec ); + trap_LinkEntity (podium); + + podium->think = PodiumPlacementThink; + podium->nextthink = level.time + 100; + return podium; +} + + +/* +================== +SpawnModelsOnVictoryPads +================== +*/ +void SpawnModelsOnVictoryPads( void ) { + gentity_t *player; + gentity_t *podium; + + podium1 = NULL; + podium2 = NULL; + podium3 = NULL; + + podium = SpawnPodium(); + + player = SpawnModelOnVictoryPad( podium, offsetFirst, &g_entities[level.sortedClients[0]], + level.clients[ level.sortedClients[0] ].ps.persistant[PERS_RANK] &~ RANK_TIED_FLAG ); + if ( player ) { + player->nextthink = level.time + 2000; + player->think = CelebrateStart; + podium1 = player; + } + + player = SpawnModelOnVictoryPad( podium, offsetSecond, &g_entities[level.sortedClients[1]], + level.clients[ level.sortedClients[1] ].ps.persistant[PERS_RANK] &~ RANK_TIED_FLAG ); + if ( player ) { + podium2 = player; + } + + if ( level.numNonSpectatorClients > 2 ) { + player = SpawnModelOnVictoryPad( podium, offsetThird, &g_entities[level.sortedClients[2]], + level.clients[ level.sortedClients[2] ].ps.persistant[PERS_RANK] &~ RANK_TIED_FLAG ); + if ( player ) { + podium3 = player; + } + } +} + + +/* +=============== +Svcmd_AbortPodium_f +=============== +*/ +void Svcmd_AbortPodium_f( void ) { + if( g_gametype.integer != GT_SINGLE_PLAYER ) { + return; + } + + if( podium1 ) { + podium1->nextthink = level.time; + podium1->think = CelebrateStop; + } +} diff --git a/code/game/g_bot.c b/code/game/g_bot.c index e42013d..1987cfa 100755 --- a/code/game/g_bot.c +++ b/code/game/g_bot.c @@ -1,1017 +1,1017 @@ -/* -=========================================================================== -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 Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// -// g_bot.c - -#include "g_local.h" - - -static int g_numBots; -static char *g_botInfos[MAX_BOTS]; - - -int g_numArenas; -static char *g_arenaInfos[MAX_ARENAS]; - - -#define BOT_BEGIN_DELAY_BASE 2000 -#define BOT_BEGIN_DELAY_INCREMENT 1500 - -#define BOT_SPAWN_QUEUE_DEPTH 16 - -typedef struct { - int clientNum; - int spawnTime; -} botSpawnQueue_t; - -//static int botBeginDelay = 0; // bk001206 - unused, init -static botSpawnQueue_t botSpawnQueue[BOT_SPAWN_QUEUE_DEPTH]; - -vmCvar_t bot_minplayers; - -extern gentity_t *podium1; -extern gentity_t *podium2; -extern gentity_t *podium3; - -float trap_Cvar_VariableValue( const char *var_name ) { - char buf[128]; - - trap_Cvar_VariableStringBuffer(var_name, buf, sizeof(buf)); - return atof(buf); -} - - - -/* -=============== -G_ParseInfos -=============== -*/ -int G_ParseInfos( char *buf, int max, char *infos[] ) { - char *token; - int count; - char key[MAX_TOKEN_CHARS]; - char info[MAX_INFO_STRING]; - - count = 0; - - while ( 1 ) { - token = COM_Parse( &buf ); - if ( !token[0] ) { - break; - } - if ( strcmp( token, "{" ) ) { - Com_Printf( "Missing { in info file\n" ); - break; - } - - if ( count == max ) { - Com_Printf( "Max infos exceeded\n" ); - break; - } - - info[0] = '\0'; - while ( 1 ) { - token = COM_ParseExt( &buf, qtrue ); - if ( !token[0] ) { - Com_Printf( "Unexpected end of info file\n" ); - break; - } - if ( !strcmp( token, "}" ) ) { - break; - } - Q_strncpyz( key, token, sizeof( key ) ); - - token = COM_ParseExt( &buf, qfalse ); - if ( !token[0] ) { - strcpy( token, "" ); - } - Info_SetValueForKey( info, key, token ); - } - //NOTE: extra space for arena number - infos[count] = G_Alloc(strlen(info) + strlen("\\num\\") + strlen(va("%d", MAX_ARENAS)) + 1); - if (infos[count]) { - strcpy(infos[count], info); - count++; - } - } - return count; -} - -/* -=============== -G_LoadArenasFromFile -=============== -*/ -static void G_LoadArenasFromFile( char *filename ) { - int len; - fileHandle_t f; - char buf[MAX_ARENAS_TEXT]; - - len = trap_FS_FOpenFile( filename, &f, FS_READ ); - if ( !f ) { - trap_Printf( va( S_COLOR_RED "file not found: %s\n", filename ) ); - return; - } - if ( len >= MAX_ARENAS_TEXT ) { - trap_Printf( va( S_COLOR_RED "file too large: %s is %i, max allowed is %i", filename, len, MAX_ARENAS_TEXT ) ); - trap_FS_FCloseFile( f ); - return; - } - - trap_FS_Read( buf, len, f ); - buf[len] = 0; - trap_FS_FCloseFile( f ); - - g_numArenas += G_ParseInfos( buf, MAX_ARENAS - g_numArenas, &g_arenaInfos[g_numArenas] ); -} - -/* -=============== -G_LoadArenas -=============== -*/ -static void G_LoadArenas( void ) { - int numdirs; - vmCvar_t arenasFile; - char filename[128]; - char dirlist[1024]; - char* dirptr; - int i, n; - int dirlen; - - g_numArenas = 0; - - trap_Cvar_Register( &arenasFile, "g_arenasFile", "", CVAR_INIT|CVAR_ROM ); - if( *arenasFile.string ) { - G_LoadArenasFromFile(arenasFile.string); - } - else { - G_LoadArenasFromFile("scripts/arenas.txt"); - } - - // get all arenas from .arena files - numdirs = trap_FS_GetFileList("scripts", ".arena", dirlist, 1024 ); - dirptr = dirlist; - for (i = 0; i < numdirs; i++, dirptr += dirlen+1) { - dirlen = strlen(dirptr); - strcpy(filename, "scripts/"); - strcat(filename, dirptr); - G_LoadArenasFromFile(filename); - } - trap_Printf( va( "%i arenas parsed\n", g_numArenas ) ); - - for( n = 0; n < g_numArenas; n++ ) { - Info_SetValueForKey( g_arenaInfos[n], "num", va( "%i", n ) ); - } -} - - -/* -=============== -G_GetArenaInfoByNumber -=============== -*/ -const char *G_GetArenaInfoByMap( const char *map ) { - int n; - - for( n = 0; n < g_numArenas; n++ ) { - if( Q_stricmp( Info_ValueForKey( g_arenaInfos[n], "map" ), map ) == 0 ) { - return g_arenaInfos[n]; - } - } - - return NULL; -} - - -/* -================= -PlayerIntroSound -================= -*/ -static void PlayerIntroSound( const char *modelAndSkin ) { - char model[MAX_QPATH]; - char *skin; - - Q_strncpyz( model, modelAndSkin, sizeof(model) ); - skin = Q_strrchr( model, '/' ); - if ( skin ) { - *skin++ = '\0'; - } - else { - skin = model; - } - - if( Q_stricmp( skin, "default" ) == 0 ) { - skin = model; - } - - trap_SendConsoleCommand( EXEC_APPEND, va( "play sound/player/announce/%s.wav\n", skin ) ); -} - -/* -=============== -G_AddRandomBot -=============== -*/ -void G_AddRandomBot( int team ) { - int i, n, num; - float skill; - char *value, netname[36], *teamstr; - gclient_t *cl; - - num = 0; - for ( n = 0; n < g_numBots ; n++ ) { - value = Info_ValueForKey( g_botInfos[n], "name" ); - // - for ( i=0 ; i< g_maxclients.integer ; i++ ) { - cl = level.clients + i; - if ( cl->pers.connected != CON_CONNECTED ) { - continue; - } - if ( !(g_entities[cl->ps.clientNum].r.svFlags & SVF_BOT) ) { - continue; - } - if ( team >= 0 && cl->sess.sessionTeam != team ) { - continue; - } - if ( !Q_stricmp( value, cl->pers.netname ) ) { - break; - } - } - if (i >= g_maxclients.integer) { - num++; - } - } - num = random() * num; - for ( n = 0; n < g_numBots ; n++ ) { - value = Info_ValueForKey( g_botInfos[n], "name" ); - // - for ( i=0 ; i< g_maxclients.integer ; i++ ) { - cl = level.clients + i; - if ( cl->pers.connected != CON_CONNECTED ) { - continue; - } - if ( !(g_entities[cl->ps.clientNum].r.svFlags & SVF_BOT) ) { - continue; - } - if ( team >= 0 && cl->sess.sessionTeam != team ) { - continue; - } - if ( !Q_stricmp( value, cl->pers.netname ) ) { - break; - } - } - if (i >= g_maxclients.integer) { - num--; - if (num <= 0) { - skill = trap_Cvar_VariableValue( "g_spSkill" ); - if (team == TEAM_RED) teamstr = "red"; - else if (team == TEAM_BLUE) teamstr = "blue"; - else teamstr = ""; - strncpy(netname, value, sizeof(netname)-1); - netname[sizeof(netname)-1] = '\0'; - Q_CleanStr(netname); - trap_SendConsoleCommand( EXEC_INSERT, va("addbot %s %f %s %i\n", netname, skill, teamstr, 0) ); - return; - } - } - } -} - -/* -=============== -G_RemoveRandomBot -=============== -*/ -int G_RemoveRandomBot( int team ) { - int i; - char netname[36]; - gclient_t *cl; - - for ( i=0 ; i< g_maxclients.integer ; i++ ) { - cl = level.clients + i; - if ( cl->pers.connected != CON_CONNECTED ) { - continue; - } - if ( !(g_entities[cl->ps.clientNum].r.svFlags & SVF_BOT) ) { - continue; - } - if ( team >= 0 && cl->sess.sessionTeam != team ) { - continue; - } - strcpy(netname, cl->pers.netname); - Q_CleanStr(netname); - trap_SendConsoleCommand( EXEC_INSERT, va("kick %s\n", netname) ); - return qtrue; - } - return qfalse; -} - -/* -=============== -G_CountHumanPlayers -=============== -*/ -int G_CountHumanPlayers( int team ) { - int i, num; - gclient_t *cl; - - num = 0; - for ( i=0 ; i< g_maxclients.integer ; i++ ) { - cl = level.clients + i; - if ( cl->pers.connected != CON_CONNECTED ) { - continue; - } - if ( g_entities[cl->ps.clientNum].r.svFlags & SVF_BOT ) { - continue; - } - if ( team >= 0 && cl->sess.sessionTeam != team ) { - continue; - } - num++; - } - return num; -} - -/* -=============== -G_CountBotPlayers -=============== -*/ -int G_CountBotPlayers( int team ) { - int i, n, num; - gclient_t *cl; - - num = 0; - for ( i=0 ; i< g_maxclients.integer ; i++ ) { - cl = level.clients + i; - if ( cl->pers.connected != CON_CONNECTED ) { - continue; - } - if ( !(g_entities[cl->ps.clientNum].r.svFlags & SVF_BOT) ) { - continue; - } - if ( team >= 0 && cl->sess.sessionTeam != team ) { - continue; - } - num++; - } - for( n = 0; n < BOT_SPAWN_QUEUE_DEPTH; n++ ) { - if( !botSpawnQueue[n].spawnTime ) { - continue; - } - if ( botSpawnQueue[n].spawnTime > level.time ) { - continue; - } - num++; - } - return num; -} - -/* -=============== -G_CheckMinimumPlayers -=============== -*/ -void G_CheckMinimumPlayers( void ) { - int minplayers; - int humanplayers, botplayers; - static int checkminimumplayers_time; - - if (level.intermissiontime) return; - //only check once each 10 seconds - if (checkminimumplayers_time > level.time - 10000) { - return; - } - checkminimumplayers_time = level.time; - trap_Cvar_Update(&bot_minplayers); - minplayers = bot_minplayers.integer; - if (minplayers <= 0) return; - - if (g_gametype.integer >= GT_TEAM) { - if (minplayers >= g_maxclients.integer / 2) { - minplayers = (g_maxclients.integer / 2) -1; - } - - humanplayers = G_CountHumanPlayers( TEAM_RED ); - botplayers = G_CountBotPlayers( TEAM_RED ); - // - if (humanplayers + botplayers < minplayers) { - G_AddRandomBot( TEAM_RED ); - } else if (humanplayers + botplayers > minplayers && botplayers) { - G_RemoveRandomBot( TEAM_RED ); - } - // - humanplayers = G_CountHumanPlayers( TEAM_BLUE ); - botplayers = G_CountBotPlayers( TEAM_BLUE ); - // - if (humanplayers + botplayers < minplayers) { - G_AddRandomBot( TEAM_BLUE ); - } else if (humanplayers + botplayers > minplayers && botplayers) { - G_RemoveRandomBot( TEAM_BLUE ); - } - } - else if (g_gametype.integer == GT_TOURNAMENT ) { - if (minplayers >= g_maxclients.integer) { - minplayers = g_maxclients.integer-1; - } - humanplayers = G_CountHumanPlayers( -1 ); - botplayers = G_CountBotPlayers( -1 ); - // - if (humanplayers + botplayers < minplayers) { - G_AddRandomBot( TEAM_FREE ); - } else if (humanplayers + botplayers > minplayers && botplayers) { - // try to remove spectators first - if (!G_RemoveRandomBot( TEAM_SPECTATOR )) { - // just remove the bot that is playing - G_RemoveRandomBot( -1 ); - } - } - } - else if (g_gametype.integer == GT_FFA) { - if (minplayers >= g_maxclients.integer) { - minplayers = g_maxclients.integer-1; - } - humanplayers = G_CountHumanPlayers( TEAM_FREE ); - botplayers = G_CountBotPlayers( TEAM_FREE ); - // - if (humanplayers + botplayers < minplayers) { - G_AddRandomBot( TEAM_FREE ); - } else if (humanplayers + botplayers > minplayers && botplayers) { - G_RemoveRandomBot( TEAM_FREE ); - } - } -} - -/* -=============== -G_CheckBotSpawn -=============== -*/ -void G_CheckBotSpawn( void ) { - int n; - char userinfo[MAX_INFO_VALUE]; - - G_CheckMinimumPlayers(); - - for( n = 0; n < BOT_SPAWN_QUEUE_DEPTH; n++ ) { - if( !botSpawnQueue[n].spawnTime ) { - continue; - } - if ( botSpawnQueue[n].spawnTime > level.time ) { - continue; - } - ClientBegin( botSpawnQueue[n].clientNum ); - botSpawnQueue[n].spawnTime = 0; - - if( g_gametype.integer == GT_SINGLE_PLAYER ) { - trap_GetUserinfo( botSpawnQueue[n].clientNum, userinfo, sizeof(userinfo) ); - PlayerIntroSound( Info_ValueForKey (userinfo, "model") ); - } - } -} - - -/* -=============== -AddBotToSpawnQueue -=============== -*/ -static void AddBotToSpawnQueue( int clientNum, int delay ) { - int n; - - for( n = 0; n < BOT_SPAWN_QUEUE_DEPTH; n++ ) { - if( !botSpawnQueue[n].spawnTime ) { - botSpawnQueue[n].spawnTime = level.time + delay; - botSpawnQueue[n].clientNum = clientNum; - return; - } - } - - G_Printf( S_COLOR_YELLOW "Unable to delay spawn\n" ); - ClientBegin( clientNum ); -} - - -/* -=============== -G_RemoveQueuedBotBegin - -Called on client disconnect to make sure the delayed spawn -doesn't happen on a freed index -=============== -*/ -void G_RemoveQueuedBotBegin( int clientNum ) { - int n; - - for( n = 0; n < BOT_SPAWN_QUEUE_DEPTH; n++ ) { - if( botSpawnQueue[n].clientNum == clientNum ) { - botSpawnQueue[n].spawnTime = 0; - return; - } - } -} - - -/* -=============== -G_BotConnect -=============== -*/ -qboolean G_BotConnect( int clientNum, qboolean restart ) { - bot_settings_t settings; - char userinfo[MAX_INFO_STRING]; - - trap_GetUserinfo( clientNum, userinfo, sizeof(userinfo) ); - - Q_strncpyz( settings.characterfile, Info_ValueForKey( userinfo, "characterfile" ), sizeof(settings.characterfile) ); - settings.skill = atof( Info_ValueForKey( userinfo, "skill" ) ); - Q_strncpyz( settings.team, Info_ValueForKey( userinfo, "team" ), sizeof(settings.team) ); - - if (!BotAISetupClient( clientNum, &settings, restart )) { - trap_DropClient( clientNum, "BotAISetupClient failed" ); - return qfalse; - } - - return qtrue; -} - - -/* -=============== -G_AddBot -=============== -*/ -static void G_AddBot( const char *name, float skill, const char *team, int delay, char *altname) { - int clientNum; - char *botinfo; - gentity_t *bot; - char *key; - char *s; - char *botname; - char *model; - char *headmodel; - char userinfo[MAX_INFO_STRING]; - - // get the botinfo from bots.txt - botinfo = G_GetBotInfoByName( name ); - if ( !botinfo ) { - G_Printf( S_COLOR_RED "Error: Bot '%s' not defined\n", name ); - return; - } - - // create the bot's userinfo - userinfo[0] = '\0'; - - botname = Info_ValueForKey( botinfo, "funname" ); - if( !botname[0] ) { - botname = Info_ValueForKey( botinfo, "name" ); - } - // check for an alternative name - if (altname && altname[0]) { - botname = altname; - } - Info_SetValueForKey( userinfo, "name", botname ); - Info_SetValueForKey( userinfo, "rate", "25000" ); - Info_SetValueForKey( userinfo, "snaps", "20" ); - Info_SetValueForKey( userinfo, "skill", va("%1.2f", skill) ); - - if ( skill >= 1 && skill < 2 ) { - Info_SetValueForKey( userinfo, "handicap", "50" ); - } - else if ( skill >= 2 && skill < 3 ) { - Info_SetValueForKey( userinfo, "handicap", "70" ); - } - else if ( skill >= 3 && skill < 4 ) { - Info_SetValueForKey( userinfo, "handicap", "90" ); - } - - key = "model"; - model = Info_ValueForKey( botinfo, key ); - if ( !*model ) { - model = "visor/default"; - } - Info_SetValueForKey( userinfo, key, model ); - key = "team_model"; - Info_SetValueForKey( userinfo, key, model ); - - key = "headmodel"; - headmodel = Info_ValueForKey( botinfo, key ); - if ( !*headmodel ) { - headmodel = model; - } - Info_SetValueForKey( userinfo, key, headmodel ); - key = "team_headmodel"; - Info_SetValueForKey( userinfo, key, headmodel ); - - key = "gender"; - s = Info_ValueForKey( botinfo, key ); - if ( !*s ) { - s = "male"; - } - Info_SetValueForKey( userinfo, "sex", s ); - - key = "color1"; - s = Info_ValueForKey( botinfo, key ); - if ( !*s ) { - s = "4"; - } - Info_SetValueForKey( userinfo, key, s ); - - key = "color2"; - s = Info_ValueForKey( botinfo, key ); - if ( !*s ) { - s = "5"; - } - Info_SetValueForKey( userinfo, key, s ); - - s = Info_ValueForKey(botinfo, "aifile"); - if (!*s ) { - trap_Printf( S_COLOR_RED "Error: bot has no aifile specified\n" ); - return; - } - - // have the server allocate a client slot - clientNum = trap_BotAllocateClient(); - if ( clientNum == -1 ) { - G_Printf( S_COLOR_RED "Unable to add bot. All player slots are in use.\n" ); - G_Printf( S_COLOR_RED "Start server with more 'open' slots (or check setting of sv_maxclients cvar).\n" ); - return; - } - - // initialize the bot settings - if( !team || !*team ) { - if( g_gametype.integer >= GT_TEAM ) { - if( PickTeam(clientNum) == TEAM_RED) { - team = "red"; - } - else { - team = "blue"; - } - } - else { - team = "red"; - } - } - Info_SetValueForKey( userinfo, "characterfile", Info_ValueForKey( botinfo, "aifile" ) ); - Info_SetValueForKey( userinfo, "skill", va( "%5.2f", skill ) ); - Info_SetValueForKey( userinfo, "team", team ); - - bot = &g_entities[ clientNum ]; - bot->r.svFlags |= SVF_BOT; - bot->inuse = qtrue; - - // register the userinfo - trap_SetUserinfo( clientNum, userinfo ); - - // have it connect to the game as a normal client - if ( ClientConnect( clientNum, qtrue, qtrue ) ) { - return; - } - - if( delay == 0 ) { - ClientBegin( clientNum ); - return; - } - - AddBotToSpawnQueue( clientNum, delay ); -} - - -/* -=============== -Svcmd_AddBot_f -=============== -*/ -void Svcmd_AddBot_f( void ) { - float skill; - int delay; - char name[MAX_TOKEN_CHARS]; - char altname[MAX_TOKEN_CHARS]; - char string[MAX_TOKEN_CHARS]; - char team[MAX_TOKEN_CHARS]; - - // are bots enabled? - if ( !trap_Cvar_VariableIntegerValue( "bot_enable" ) ) { - return; - } - - // name - trap_Argv( 1, name, sizeof( name ) ); - if ( !name[0] ) { - trap_Printf( "Usage: Addbot [skill 1-5] [team] [msec delay] [altname]\n" ); - return; - } - - // skill - trap_Argv( 2, string, sizeof( string ) ); - if ( !string[0] ) { - skill = 4; - } - else { - skill = atof( string ); - } - - // team - trap_Argv( 3, team, sizeof( team ) ); - - // delay - trap_Argv( 4, string, sizeof( string ) ); - if ( !string[0] ) { - delay = 0; - } - else { - delay = atoi( string ); - } - - // alternative name - trap_Argv( 5, altname, sizeof( altname ) ); - - G_AddBot( name, skill, team, delay, altname ); - - // if this was issued during gameplay and we are playing locally, - // go ahead and load the bot's media immediately - if ( level.time - level.startTime > 1000 && - trap_Cvar_VariableIntegerValue( "cl_running" ) ) { - trap_SendServerCommand( -1, "loaddefered\n" ); // FIXME: spelled wrong, but not changing for demo - } -} - -/* -=============== -Svcmd_BotList_f -=============== -*/ -void Svcmd_BotList_f( void ) { - int i; - char name[MAX_TOKEN_CHARS]; - char funname[MAX_TOKEN_CHARS]; - char model[MAX_TOKEN_CHARS]; - char aifile[MAX_TOKEN_CHARS]; - - trap_Printf("^1name model aifile funname\n"); - for (i = 0; i < g_numBots; i++) { - strcpy(name, Info_ValueForKey( g_botInfos[i], "name" )); - if ( !*name ) { - strcpy(name, "UnnamedPlayer"); - } - strcpy(funname, Info_ValueForKey( g_botInfos[i], "funname" )); - if ( !*funname ) { - strcpy(funname, ""); - } - strcpy(model, Info_ValueForKey( g_botInfos[i], "model" )); - if ( !*model ) { - strcpy(model, "visor/default"); - } - strcpy(aifile, Info_ValueForKey( g_botInfos[i], "aifile")); - if (!*aifile ) { - strcpy(aifile, "bots/default_c.c"); - } - trap_Printf(va("%-16s %-16s %-20s %-20s\n", name, model, aifile, funname)); - } -} - - -/* -=============== -G_SpawnBots -=============== -*/ -static void G_SpawnBots( char *botList, int baseDelay ) { - char *bot; - char *p; - float skill; - int delay; - char bots[MAX_INFO_VALUE]; - - podium1 = NULL; - podium2 = NULL; - podium3 = NULL; - - skill = trap_Cvar_VariableValue( "g_spSkill" ); - if( skill < 1 ) { - trap_Cvar_Set( "g_spSkill", "1" ); - skill = 1; - } - else if ( skill > 5 ) { - trap_Cvar_Set( "g_spSkill", "5" ); - skill = 5; - } - - Q_strncpyz( bots, botList, sizeof(bots) ); - p = &bots[0]; - delay = baseDelay; - while( *p ) { - //skip spaces - while( *p && *p == ' ' ) { - p++; - } - if( !p ) { - break; - } - - // mark start of bot name - bot = p; - - // skip until space of null - while( *p && *p != ' ' ) { - p++; - } - if( *p ) { - *p++ = 0; - } - - // we must add the bot this way, calling G_AddBot directly at this stage - // does "Bad Things" - trap_SendConsoleCommand( EXEC_INSERT, va("addbot %s %f free %i\n", bot, skill, delay) ); - - delay += BOT_BEGIN_DELAY_INCREMENT; - } -} - - -/* -=============== -G_LoadBotsFromFile -=============== -*/ -static void G_LoadBotsFromFile( char *filename ) { - int len; - fileHandle_t f; - char buf[MAX_BOTS_TEXT]; - - len = trap_FS_FOpenFile( filename, &f, FS_READ ); - if ( !f ) { - trap_Printf( va( S_COLOR_RED "file not found: %s\n", filename ) ); - return; - } - if ( len >= MAX_BOTS_TEXT ) { - trap_Printf( va( S_COLOR_RED "file too large: %s is %i, max allowed is %i", filename, len, MAX_BOTS_TEXT ) ); - trap_FS_FCloseFile( f ); - return; - } - - trap_FS_Read( buf, len, f ); - buf[len] = 0; - trap_FS_FCloseFile( f ); - - g_numBots += G_ParseInfos( buf, MAX_BOTS - g_numBots, &g_botInfos[g_numBots] ); -} - -/* -=============== -G_LoadBots -=============== -*/ -static void G_LoadBots( void ) { - vmCvar_t botsFile; - int numdirs; - char filename[128]; - char dirlist[1024]; - char* dirptr; - int i; - int dirlen; - - if ( !trap_Cvar_VariableIntegerValue( "bot_enable" ) ) { - return; - } - - g_numBots = 0; - - trap_Cvar_Register( &botsFile, "g_botsFile", "", CVAR_INIT|CVAR_ROM ); - if( *botsFile.string ) { - G_LoadBotsFromFile(botsFile.string); - } - else { - G_LoadBotsFromFile("scripts/bots.txt"); - } - - // get all bots from .bot files - numdirs = trap_FS_GetFileList("scripts", ".bot", dirlist, 1024 ); - dirptr = dirlist; - for (i = 0; i < numdirs; i++, dirptr += dirlen+1) { - dirlen = strlen(dirptr); - strcpy(filename, "scripts/"); - strcat(filename, dirptr); - G_LoadBotsFromFile(filename); - } - trap_Printf( va( "%i bots parsed\n", g_numBots ) ); -} - - - -/* -=============== -G_GetBotInfoByNumber -=============== -*/ -char *G_GetBotInfoByNumber( int num ) { - if( num < 0 || num >= g_numBots ) { - trap_Printf( va( S_COLOR_RED "Invalid bot number: %i\n", num ) ); - return NULL; - } - return g_botInfos[num]; -} - - -/* -=============== -G_GetBotInfoByName -=============== -*/ -char *G_GetBotInfoByName( const char *name ) { - int n; - char *value; - - for ( n = 0; n < g_numBots ; n++ ) { - value = Info_ValueForKey( g_botInfos[n], "name" ); - if ( !Q_stricmp( value, name ) ) { - return g_botInfos[n]; - } - } - - return NULL; -} - -/* -=============== -G_InitBots -=============== -*/ -void G_InitBots( qboolean restart ) { - int fragLimit; - int timeLimit; - const char *arenainfo; - char *strValue; - int basedelay; - char map[MAX_QPATH]; - char serverinfo[MAX_INFO_STRING]; - - G_LoadBots(); - G_LoadArenas(); - - trap_Cvar_Register( &bot_minplayers, "bot_minplayers", "0", CVAR_SERVERINFO ); - - if( g_gametype.integer == GT_SINGLE_PLAYER ) { - trap_GetServerinfo( serverinfo, sizeof(serverinfo) ); - Q_strncpyz( map, Info_ValueForKey( serverinfo, "mapname" ), sizeof(map) ); - arenainfo = G_GetArenaInfoByMap( map ); - if ( !arenainfo ) { - return; - } - - strValue = Info_ValueForKey( arenainfo, "fraglimit" ); - fragLimit = atoi( strValue ); - if ( fragLimit ) { - trap_Cvar_Set( "fraglimit", strValue ); - } - else { - trap_Cvar_Set( "fraglimit", "0" ); - } - - strValue = Info_ValueForKey( arenainfo, "timelimit" ); - timeLimit = atoi( strValue ); - if ( timeLimit ) { - trap_Cvar_Set( "timelimit", strValue ); - } - else { - trap_Cvar_Set( "timelimit", "0" ); - } - - if ( !fragLimit && !timeLimit ) { - trap_Cvar_Set( "fraglimit", "10" ); - trap_Cvar_Set( "timelimit", "0" ); - } - - basedelay = BOT_BEGIN_DELAY_BASE; - strValue = Info_ValueForKey( arenainfo, "special" ); - if( Q_stricmp( strValue, "training" ) == 0 ) { - basedelay += 10000; - } - - if( !restart ) { - G_SpawnBots( Info_ValueForKey( arenainfo, "bots" ), basedelay ); - } - } -} +/* +=========================================================================== +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 Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// +// g_bot.c + +#include "g_local.h" + + +static int g_numBots; +static char *g_botInfos[MAX_BOTS]; + + +int g_numArenas; +static char *g_arenaInfos[MAX_ARENAS]; + + +#define BOT_BEGIN_DELAY_BASE 2000 +#define BOT_BEGIN_DELAY_INCREMENT 1500 + +#define BOT_SPAWN_QUEUE_DEPTH 16 + +typedef struct { + int clientNum; + int spawnTime; +} botSpawnQueue_t; + +//static int botBeginDelay = 0; // bk001206 - unused, init +static botSpawnQueue_t botSpawnQueue[BOT_SPAWN_QUEUE_DEPTH]; + +vmCvar_t bot_minplayers; + +extern gentity_t *podium1; +extern gentity_t *podium2; +extern gentity_t *podium3; + +float trap_Cvar_VariableValue( const char *var_name ) { + char buf[128]; + + trap_Cvar_VariableStringBuffer(var_name, buf, sizeof(buf)); + return atof(buf); +} + + + +/* +=============== +G_ParseInfos +=============== +*/ +int G_ParseInfos( char *buf, int max, char *infos[] ) { + char *token; + int count; + char key[MAX_TOKEN_CHARS]; + char info[MAX_INFO_STRING]; + + count = 0; + + while ( 1 ) { + token = COM_Parse( &buf ); + if ( !token[0] ) { + break; + } + if ( strcmp( token, "{" ) ) { + Com_Printf( "Missing { in info file\n" ); + break; + } + + if ( count == max ) { + Com_Printf( "Max infos exceeded\n" ); + break; + } + + info[0] = '\0'; + while ( 1 ) { + token = COM_ParseExt( &buf, qtrue ); + if ( !token[0] ) { + Com_Printf( "Unexpected end of info file\n" ); + break; + } + if ( !strcmp( token, "}" ) ) { + break; + } + Q_strncpyz( key, token, sizeof( key ) ); + + token = COM_ParseExt( &buf, qfalse ); + if ( !token[0] ) { + strcpy( token, "" ); + } + Info_SetValueForKey( info, key, token ); + } + //NOTE: extra space for arena number + infos[count] = G_Alloc(strlen(info) + strlen("\\num\\") + strlen(va("%d", MAX_ARENAS)) + 1); + if (infos[count]) { + strcpy(infos[count], info); + count++; + } + } + return count; +} + +/* +=============== +G_LoadArenasFromFile +=============== +*/ +static void G_LoadArenasFromFile( char *filename ) { + int len; + fileHandle_t f; + char buf[MAX_ARENAS_TEXT]; + + len = trap_FS_FOpenFile( filename, &f, FS_READ ); + if ( !f ) { + trap_Printf( va( S_COLOR_RED "file not found: %s\n", filename ) ); + return; + } + if ( len >= MAX_ARENAS_TEXT ) { + trap_Printf( va( S_COLOR_RED "file too large: %s is %i, max allowed is %i", filename, len, MAX_ARENAS_TEXT ) ); + trap_FS_FCloseFile( f ); + return; + } + + trap_FS_Read( buf, len, f ); + buf[len] = 0; + trap_FS_FCloseFile( f ); + + g_numArenas += G_ParseInfos( buf, MAX_ARENAS - g_numArenas, &g_arenaInfos[g_numArenas] ); +} + +/* +=============== +G_LoadArenas +=============== +*/ +static void G_LoadArenas( void ) { + int numdirs; + vmCvar_t arenasFile; + char filename[128]; + char dirlist[1024]; + char* dirptr; + int i, n; + int dirlen; + + g_numArenas = 0; + + trap_Cvar_Register( &arenasFile, "g_arenasFile", "", CVAR_INIT|CVAR_ROM ); + if( *arenasFile.string ) { + G_LoadArenasFromFile(arenasFile.string); + } + else { + G_LoadArenasFromFile("scripts/arenas.txt"); + } + + // get all arenas from .arena files + numdirs = trap_FS_GetFileList("scripts", ".arena", dirlist, 1024 ); + dirptr = dirlist; + for (i = 0; i < numdirs; i++, dirptr += dirlen+1) { + dirlen = strlen(dirptr); + strcpy(filename, "scripts/"); + strcat(filename, dirptr); + G_LoadArenasFromFile(filename); + } + trap_Printf( va( "%i arenas parsed\n", g_numArenas ) ); + + for( n = 0; n < g_numArenas; n++ ) { + Info_SetValueForKey( g_arenaInfos[n], "num", va( "%i", n ) ); + } +} + + +/* +=============== +G_GetArenaInfoByNumber +=============== +*/ +const char *G_GetArenaInfoByMap( const char *map ) { + int n; + + for( n = 0; n < g_numArenas; n++ ) { + if( Q_stricmp( Info_ValueForKey( g_arenaInfos[n], "map" ), map ) == 0 ) { + return g_arenaInfos[n]; + } + } + + return NULL; +} + + +/* +================= +PlayerIntroSound +================= +*/ +static void PlayerIntroSound( const char *modelAndSkin ) { + char model[MAX_QPATH]; + char *skin; + + Q_strncpyz( model, modelAndSkin, sizeof(model) ); + skin = Q_strrchr( model, '/' ); + if ( skin ) { + *skin++ = '\0'; + } + else { + skin = model; + } + + if( Q_stricmp( skin, "default" ) == 0 ) { + skin = model; + } + + trap_SendConsoleCommand( EXEC_APPEND, va( "play sound/player/announce/%s.wav\n", skin ) ); +} + +/* +=============== +G_AddRandomBot +=============== +*/ +void G_AddRandomBot( int team ) { + int i, n, num; + float skill; + char *value, netname[36], *teamstr; + gclient_t *cl; + + num = 0; + for ( n = 0; n < g_numBots ; n++ ) { + value = Info_ValueForKey( g_botInfos[n], "name" ); + // + for ( i=0 ; i< g_maxclients.integer ; i++ ) { + cl = level.clients + i; + if ( cl->pers.connected != CON_CONNECTED ) { + continue; + } + if ( !(g_entities[cl->ps.clientNum].r.svFlags & SVF_BOT) ) { + continue; + } + if ( team >= 0 && cl->sess.sessionTeam != team ) { + continue; + } + if ( !Q_stricmp( value, cl->pers.netname ) ) { + break; + } + } + if (i >= g_maxclients.integer) { + num++; + } + } + num = random() * num; + for ( n = 0; n < g_numBots ; n++ ) { + value = Info_ValueForKey( g_botInfos[n], "name" ); + // + for ( i=0 ; i< g_maxclients.integer ; i++ ) { + cl = level.clients + i; + if ( cl->pers.connected != CON_CONNECTED ) { + continue; + } + if ( !(g_entities[cl->ps.clientNum].r.svFlags & SVF_BOT) ) { + continue; + } + if ( team >= 0 && cl->sess.sessionTeam != team ) { + continue; + } + if ( !Q_stricmp( value, cl->pers.netname ) ) { + break; + } + } + if (i >= g_maxclients.integer) { + num--; + if (num <= 0) { + skill = trap_Cvar_VariableValue( "g_spSkill" ); + if (team == TEAM_RED) teamstr = "red"; + else if (team == TEAM_BLUE) teamstr = "blue"; + else teamstr = ""; + strncpy(netname, value, sizeof(netname)-1); + netname[sizeof(netname)-1] = '\0'; + Q_CleanStr(netname); + trap_SendConsoleCommand( EXEC_INSERT, va("addbot %s %f %s %i\n", netname, skill, teamstr, 0) ); + return; + } + } + } +} + +/* +=============== +G_RemoveRandomBot +=============== +*/ +int G_RemoveRandomBot( int team ) { + int i; + char netname[36]; + gclient_t *cl; + + for ( i=0 ; i< g_maxclients.integer ; i++ ) { + cl = level.clients + i; + if ( cl->pers.connected != CON_CONNECTED ) { + continue; + } + if ( !(g_entities[cl->ps.clientNum].r.svFlags & SVF_BOT) ) { + continue; + } + if ( team >= 0 && cl->sess.sessionTeam != team ) { + continue; + } + strcpy(netname, cl->pers.netname); + Q_CleanStr(netname); + trap_SendConsoleCommand( EXEC_INSERT, va("kick %s\n", netname) ); + return qtrue; + } + return qfalse; +} + +/* +=============== +G_CountHumanPlayers +=============== +*/ +int G_CountHumanPlayers( int team ) { + int i, num; + gclient_t *cl; + + num = 0; + for ( i=0 ; i< g_maxclients.integer ; i++ ) { + cl = level.clients + i; + if ( cl->pers.connected != CON_CONNECTED ) { + continue; + } + if ( g_entities[cl->ps.clientNum].r.svFlags & SVF_BOT ) { + continue; + } + if ( team >= 0 && cl->sess.sessionTeam != team ) { + continue; + } + num++; + } + return num; +} + +/* +=============== +G_CountBotPlayers +=============== +*/ +int G_CountBotPlayers( int team ) { + int i, n, num; + gclient_t *cl; + + num = 0; + for ( i=0 ; i< g_maxclients.integer ; i++ ) { + cl = level.clients + i; + if ( cl->pers.connected != CON_CONNECTED ) { + continue; + } + if ( !(g_entities[cl->ps.clientNum].r.svFlags & SVF_BOT) ) { + continue; + } + if ( team >= 0 && cl->sess.sessionTeam != team ) { + continue; + } + num++; + } + for( n = 0; n < BOT_SPAWN_QUEUE_DEPTH; n++ ) { + if( !botSpawnQueue[n].spawnTime ) { + continue; + } + if ( botSpawnQueue[n].spawnTime > level.time ) { + continue; + } + num++; + } + return num; +} + +/* +=============== +G_CheckMinimumPlayers +=============== +*/ +void G_CheckMinimumPlayers( void ) { + int minplayers; + int humanplayers, botplayers; + static int checkminimumplayers_time; + + if (level.intermissiontime) return; + //only check once each 10 seconds + if (checkminimumplayers_time > level.time - 10000) { + return; + } + checkminimumplayers_time = level.time; + trap_Cvar_Update(&bot_minplayers); + minplayers = bot_minplayers.integer; + if (minplayers <= 0) return; + + if (g_gametype.integer >= GT_TEAM) { + if (minplayers >= g_maxclients.integer / 2) { + minplayers = (g_maxclients.integer / 2) -1; + } + + humanplayers = G_CountHumanPlayers( TEAM_RED ); + botplayers = G_CountBotPlayers( TEAM_RED ); + // + if (humanplayers + botplayers < minplayers) { + G_AddRandomBot( TEAM_RED ); + } else if (humanplayers + botplayers > minplayers && botplayers) { + G_RemoveRandomBot( TEAM_RED ); + } + // + humanplayers = G_CountHumanPlayers( TEAM_BLUE ); + botplayers = G_CountBotPlayers( TEAM_BLUE ); + // + if (humanplayers + botplayers < minplayers) { + G_AddRandomBot( TEAM_BLUE ); + } else if (humanplayers + botplayers > minplayers && botplayers) { + G_RemoveRandomBot( TEAM_BLUE ); + } + } + else if (g_gametype.integer == GT_TOURNAMENT ) { + if (minplayers >= g_maxclients.integer) { + minplayers = g_maxclients.integer-1; + } + humanplayers = G_CountHumanPlayers( -1 ); + botplayers = G_CountBotPlayers( -1 ); + // + if (humanplayers + botplayers < minplayers) { + G_AddRandomBot( TEAM_FREE ); + } else if (humanplayers + botplayers > minplayers && botplayers) { + // try to remove spectators first + if (!G_RemoveRandomBot( TEAM_SPECTATOR )) { + // just remove the bot that is playing + G_RemoveRandomBot( -1 ); + } + } + } + else if (g_gametype.integer == GT_FFA) { + if (minplayers >= g_maxclients.integer) { + minplayers = g_maxclients.integer-1; + } + humanplayers = G_CountHumanPlayers( TEAM_FREE ); + botplayers = G_CountBotPlayers( TEAM_FREE ); + // + if (humanplayers + botplayers < minplayers) { + G_AddRandomBot( TEAM_FREE ); + } else if (humanplayers + botplayers > minplayers && botplayers) { + G_RemoveRandomBot( TEAM_FREE ); + } + } +} + +/* +=============== +G_CheckBotSpawn +=============== +*/ +void G_CheckBotSpawn( void ) { + int n; + char userinfo[MAX_INFO_VALUE]; + + G_CheckMinimumPlayers(); + + for( n = 0; n < BOT_SPAWN_QUEUE_DEPTH; n++ ) { + if( !botSpawnQueue[n].spawnTime ) { + continue; + } + if ( botSpawnQueue[n].spawnTime > level.time ) { + continue; + } + ClientBegin( botSpawnQueue[n].clientNum ); + botSpawnQueue[n].spawnTime = 0; + + if( g_gametype.integer == GT_SINGLE_PLAYER ) { + trap_GetUserinfo( botSpawnQueue[n].clientNum, userinfo, sizeof(userinfo) ); + PlayerIntroSound( Info_ValueForKey (userinfo, "model") ); + } + } +} + + +/* +=============== +AddBotToSpawnQueue +=============== +*/ +static void AddBotToSpawnQueue( int clientNum, int delay ) { + int n; + + for( n = 0; n < BOT_SPAWN_QUEUE_DEPTH; n++ ) { + if( !botSpawnQueue[n].spawnTime ) { + botSpawnQueue[n].spawnTime = level.time + delay; + botSpawnQueue[n].clientNum = clientNum; + return; + } + } + + G_Printf( S_COLOR_YELLOW "Unable to delay spawn\n" ); + ClientBegin( clientNum ); +} + + +/* +=============== +G_RemoveQueuedBotBegin + +Called on client disconnect to make sure the delayed spawn +doesn't happen on a freed index +=============== +*/ +void G_RemoveQueuedBotBegin( int clientNum ) { + int n; + + for( n = 0; n < BOT_SPAWN_QUEUE_DEPTH; n++ ) { + if( botSpawnQueue[n].clientNum == clientNum ) { + botSpawnQueue[n].spawnTime = 0; + return; + } + } +} + + +/* +=============== +G_BotConnect +=============== +*/ +qboolean G_BotConnect( int clientNum, qboolean restart ) { + bot_settings_t settings; + char userinfo[MAX_INFO_STRING]; + + trap_GetUserinfo( clientNum, userinfo, sizeof(userinfo) ); + + Q_strncpyz( settings.characterfile, Info_ValueForKey( userinfo, "characterfile" ), sizeof(settings.characterfile) ); + settings.skill = atof( Info_ValueForKey( userinfo, "skill" ) ); + Q_strncpyz( settings.team, Info_ValueForKey( userinfo, "team" ), sizeof(settings.team) ); + + if (!BotAISetupClient( clientNum, &settings, restart )) { + trap_DropClient( clientNum, "BotAISetupClient failed" ); + return qfalse; + } + + return qtrue; +} + + +/* +=============== +G_AddBot +=============== +*/ +static void G_AddBot( const char *name, float skill, const char *team, int delay, char *altname) { + int clientNum; + char *botinfo; + gentity_t *bot; + char *key; + char *s; + char *botname; + char *model; + char *headmodel; + char userinfo[MAX_INFO_STRING]; + + // get the botinfo from bots.txt + botinfo = G_GetBotInfoByName( name ); + if ( !botinfo ) { + G_Printf( S_COLOR_RED "Error: Bot '%s' not defined\n", name ); + return; + } + + // create the bot's userinfo + userinfo[0] = '\0'; + + botname = Info_ValueForKey( botinfo, "funname" ); + if( !botname[0] ) { + botname = Info_ValueForKey( botinfo, "name" ); + } + // check for an alternative name + if (altname && altname[0]) { + botname = altname; + } + Info_SetValueForKey( userinfo, "name", botname ); + Info_SetValueForKey( userinfo, "rate", "25000" ); + Info_SetValueForKey( userinfo, "snaps", "20" ); + Info_SetValueForKey( userinfo, "skill", va("%1.2f", skill) ); + + if ( skill >= 1 && skill < 2 ) { + Info_SetValueForKey( userinfo, "handicap", "50" ); + } + else if ( skill >= 2 && skill < 3 ) { + Info_SetValueForKey( userinfo, "handicap", "70" ); + } + else if ( skill >= 3 && skill < 4 ) { + Info_SetValueForKey( userinfo, "handicap", "90" ); + } + + key = "model"; + model = Info_ValueForKey( botinfo, key ); + if ( !*model ) { + model = "visor/default"; + } + Info_SetValueForKey( userinfo, key, model ); + key = "team_model"; + Info_SetValueForKey( userinfo, key, model ); + + key = "headmodel"; + headmodel = Info_ValueForKey( botinfo, key ); + if ( !*headmodel ) { + headmodel = model; + } + Info_SetValueForKey( userinfo, key, headmodel ); + key = "team_headmodel"; + Info_SetValueForKey( userinfo, key, headmodel ); + + key = "gender"; + s = Info_ValueForKey( botinfo, key ); + if ( !*s ) { + s = "male"; + } + Info_SetValueForKey( userinfo, "sex", s ); + + key = "color1"; + s = Info_ValueForKey( botinfo, key ); + if ( !*s ) { + s = "4"; + } + Info_SetValueForKey( userinfo, key, s ); + + key = "color2"; + s = Info_ValueForKey( botinfo, key ); + if ( !*s ) { + s = "5"; + } + Info_SetValueForKey( userinfo, key, s ); + + s = Info_ValueForKey(botinfo, "aifile"); + if (!*s ) { + trap_Printf( S_COLOR_RED "Error: bot has no aifile specified\n" ); + return; + } + + // have the server allocate a client slot + clientNum = trap_BotAllocateClient(); + if ( clientNum == -1 ) { + G_Printf( S_COLOR_RED "Unable to add bot. All player slots are in use.\n" ); + G_Printf( S_COLOR_RED "Start server with more 'open' slots (or check setting of sv_maxclients cvar).\n" ); + return; + } + + // initialize the bot settings + if( !team || !*team ) { + if( g_gametype.integer >= GT_TEAM ) { + if( PickTeam(clientNum) == TEAM_RED) { + team = "red"; + } + else { + team = "blue"; + } + } + else { + team = "red"; + } + } + Info_SetValueForKey( userinfo, "characterfile", Info_ValueForKey( botinfo, "aifile" ) ); + Info_SetValueForKey( userinfo, "skill", va( "%5.2f", skill ) ); + Info_SetValueForKey( userinfo, "team", team ); + + bot = &g_entities[ clientNum ]; + bot->r.svFlags |= SVF_BOT; + bot->inuse = qtrue; + + // register the userinfo + trap_SetUserinfo( clientNum, userinfo ); + + // have it connect to the game as a normal client + if ( ClientConnect( clientNum, qtrue, qtrue ) ) { + return; + } + + if( delay == 0 ) { + ClientBegin( clientNum ); + return; + } + + AddBotToSpawnQueue( clientNum, delay ); +} + + +/* +=============== +Svcmd_AddBot_f +=============== +*/ +void Svcmd_AddBot_f( void ) { + float skill; + int delay; + char name[MAX_TOKEN_CHARS]; + char altname[MAX_TOKEN_CHARS]; + char string[MAX_TOKEN_CHARS]; + char team[MAX_TOKEN_CHARS]; + + // are bots enabled? + if ( !trap_Cvar_VariableIntegerValue( "bot_enable" ) ) { + return; + } + + // name + trap_Argv( 1, name, sizeof( name ) ); + if ( !name[0] ) { + trap_Printf( "Usage: Addbot [skill 1-5] [team] [msec delay] [altname]\n" ); + return; + } + + // skill + trap_Argv( 2, string, sizeof( string ) ); + if ( !string[0] ) { + skill = 4; + } + else { + skill = atof( string ); + } + + // team + trap_Argv( 3, team, sizeof( team ) ); + + // delay + trap_Argv( 4, string, sizeof( string ) ); + if ( !string[0] ) { + delay = 0; + } + else { + delay = atoi( string ); + } + + // alternative name + trap_Argv( 5, altname, sizeof( altname ) ); + + G_AddBot( name, skill, team, delay, altname ); + + // if this was issued during gameplay and we are playing locally, + // go ahead and load the bot's media immediately + if ( level.time - level.startTime > 1000 && + trap_Cvar_VariableIntegerValue( "cl_running" ) ) { + trap_SendServerCommand( -1, "loaddefered\n" ); // FIXME: spelled wrong, but not changing for demo + } +} + +/* +=============== +Svcmd_BotList_f +=============== +*/ +void Svcmd_BotList_f( void ) { + int i; + char name[MAX_TOKEN_CHARS]; + char funname[MAX_TOKEN_CHARS]; + char model[MAX_TOKEN_CHARS]; + char aifile[MAX_TOKEN_CHARS]; + + trap_Printf("^1name model aifile funname\n"); + for (i = 0; i < g_numBots; i++) { + strcpy(name, Info_ValueForKey( g_botInfos[i], "name" )); + if ( !*name ) { + strcpy(name, "UnnamedPlayer"); + } + strcpy(funname, Info_ValueForKey( g_botInfos[i], "funname" )); + if ( !*funname ) { + strcpy(funname, ""); + } + strcpy(model, Info_ValueForKey( g_botInfos[i], "model" )); + if ( !*model ) { + strcpy(model, "visor/default"); + } + strcpy(aifile, Info_ValueForKey( g_botInfos[i], "aifile")); + if (!*aifile ) { + strcpy(aifile, "bots/default_c.c"); + } + trap_Printf(va("%-16s %-16s %-20s %-20s\n", name, model, aifile, funname)); + } +} + + +/* +=============== +G_SpawnBots +=============== +*/ +static void G_SpawnBots( char *botList, int baseDelay ) { + char *bot; + char *p; + float skill; + int delay; + char bots[MAX_INFO_VALUE]; + + podium1 = NULL; + podium2 = NULL; + podium3 = NULL; + + skill = trap_Cvar_VariableValue( "g_spSkill" ); + if( skill < 1 ) { + trap_Cvar_Set( "g_spSkill", "1" ); + skill = 1; + } + else if ( skill > 5 ) { + trap_Cvar_Set( "g_spSkill", "5" ); + skill = 5; + } + + Q_strncpyz( bots, botList, sizeof(bots) ); + p = &bots[0]; + delay = baseDelay; + while( *p ) { + //skip spaces + while( *p && *p == ' ' ) { + p++; + } + if( !p ) { + break; + } + + // mark start of bot name + bot = p; + + // skip until space of null + while( *p && *p != ' ' ) { + p++; + } + if( *p ) { + *p++ = 0; + } + + // we must add the bot this way, calling G_AddBot directly at this stage + // does "Bad Things" + trap_SendConsoleCommand( EXEC_INSERT, va("addbot %s %f free %i\n", bot, skill, delay) ); + + delay += BOT_BEGIN_DELAY_INCREMENT; + } +} + + +/* +=============== +G_LoadBotsFromFile +=============== +*/ +static void G_LoadBotsFromFile( char *filename ) { + int len; + fileHandle_t f; + char buf[MAX_BOTS_TEXT]; + + len = trap_FS_FOpenFile( filename, &f, FS_READ ); + if ( !f ) { + trap_Printf( va( S_COLOR_RED "file not found: %s\n", filename ) ); + return; + } + if ( len >= MAX_BOTS_TEXT ) { + trap_Printf( va( S_COLOR_RED "file too large: %s is %i, max allowed is %i", filename, len, MAX_BOTS_TEXT ) ); + trap_FS_FCloseFile( f ); + return; + } + + trap_FS_Read( buf, len, f ); + buf[len] = 0; + trap_FS_FCloseFile( f ); + + g_numBots += G_ParseInfos( buf, MAX_BOTS - g_numBots, &g_botInfos[g_numBots] ); +} + +/* +=============== +G_LoadBots +=============== +*/ +static void G_LoadBots( void ) { + vmCvar_t botsFile; + int numdirs; + char filename[128]; + char dirlist[1024]; + char* dirptr; + int i; + int dirlen; + + if ( !trap_Cvar_VariableIntegerValue( "bot_enable" ) ) { + return; + } + + g_numBots = 0; + + trap_Cvar_Register( &botsFile, "g_botsFile", "", CVAR_INIT|CVAR_ROM ); + if( *botsFile.string ) { + G_LoadBotsFromFile(botsFile.string); + } + else { + G_LoadBotsFromFile("scripts/bots.txt"); + } + + // get all bots from .bot files + numdirs = trap_FS_GetFileList("scripts", ".bot", dirlist, 1024 ); + dirptr = dirlist; + for (i = 0; i < numdirs; i++, dirptr += dirlen+1) { + dirlen = strlen(dirptr); + strcpy(filename, "scripts/"); + strcat(filename, dirptr); + G_LoadBotsFromFile(filename); + } + trap_Printf( va( "%i bots parsed\n", g_numBots ) ); +} + + + +/* +=============== +G_GetBotInfoByNumber +=============== +*/ +char *G_GetBotInfoByNumber( int num ) { + if( num < 0 || num >= g_numBots ) { + trap_Printf( va( S_COLOR_RED "Invalid bot number: %i\n", num ) ); + return NULL; + } + return g_botInfos[num]; +} + + +/* +=============== +G_GetBotInfoByName +=============== +*/ +char *G_GetBotInfoByName( const char *name ) { + int n; + char *value; + + for ( n = 0; n < g_numBots ; n++ ) { + value = Info_ValueForKey( g_botInfos[n], "name" ); + if ( !Q_stricmp( value, name ) ) { + return g_botInfos[n]; + } + } + + return NULL; +} + +/* +=============== +G_InitBots +=============== +*/ +void G_InitBots( qboolean restart ) { + int fragLimit; + int timeLimit; + const char *arenainfo; + char *strValue; + int basedelay; + char map[MAX_QPATH]; + char serverinfo[MAX_INFO_STRING]; + + G_LoadBots(); + G_LoadArenas(); + + trap_Cvar_Register( &bot_minplayers, "bot_minplayers", "0", CVAR_SERVERINFO ); + + if( g_gametype.integer == GT_SINGLE_PLAYER ) { + trap_GetServerinfo( serverinfo, sizeof(serverinfo) ); + Q_strncpyz( map, Info_ValueForKey( serverinfo, "mapname" ), sizeof(map) ); + arenainfo = G_GetArenaInfoByMap( map ); + if ( !arenainfo ) { + return; + } + + strValue = Info_ValueForKey( arenainfo, "fraglimit" ); + fragLimit = atoi( strValue ); + if ( fragLimit ) { + trap_Cvar_Set( "fraglimit", strValue ); + } + else { + trap_Cvar_Set( "fraglimit", "0" ); + } + + strValue = Info_ValueForKey( arenainfo, "timelimit" ); + timeLimit = atoi( strValue ); + if ( timeLimit ) { + trap_Cvar_Set( "timelimit", strValue ); + } + else { + trap_Cvar_Set( "timelimit", "0" ); + } + + if ( !fragLimit && !timeLimit ) { + trap_Cvar_Set( "fraglimit", "10" ); + trap_Cvar_Set( "timelimit", "0" ); + } + + basedelay = BOT_BEGIN_DELAY_BASE; + strValue = Info_ValueForKey( arenainfo, "special" ); + if( Q_stricmp( strValue, "training" ) == 0 ) { + basedelay += 10000; + } + + if( !restart ) { + G_SpawnBots( Info_ValueForKey( arenainfo, "bots" ), basedelay ); + } + } +} diff --git a/code/game/g_client.c b/code/game/g_client.c index 5a584d4..355077b 100755 --- a/code/game/g_client.c +++ b/code/game/g_client.c @@ -1,1344 +1,1344 @@ -/* -=========================================================================== -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 Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// -#include "g_local.h" - -// g_client.c -- client functions that don't happen every frame - -static vec3_t playerMins = {-15, -15, -24}; -static vec3_t playerMaxs = {15, 15, 32}; - -/*QUAKED info_player_deathmatch (1 0 1) (-16 -16 -24) (16 16 32) initial -potential spawning position for deathmatch games. -The first time a player enters the game, they will be at an 'initial' spot. -Targets will be fired when someone spawns in on them. -"nobots" will prevent bots from using this spot. -"nohumans" will prevent non-bots from using this spot. -*/ -void SP_info_player_deathmatch( gentity_t *ent ) { - int i; - - G_SpawnInt( "nobots", "0", &i); - if ( i ) { - ent->flags |= FL_NO_BOTS; - } - G_SpawnInt( "nohumans", "0", &i ); - if ( i ) { - ent->flags |= FL_NO_HUMANS; - } -} - -/*QUAKED info_player_start (1 0 0) (-16 -16 -24) (16 16 32) -equivelant to info_player_deathmatch -*/ -void SP_info_player_start(gentity_t *ent) { - ent->classname = "info_player_deathmatch"; - SP_info_player_deathmatch( ent ); -} - -/*QUAKED info_player_intermission (1 0 1) (-16 -16 -24) (16 16 32) -The intermission will be viewed from this point. Target an info_notnull for the view direction. -*/ -void SP_info_player_intermission( gentity_t *ent ) { - -} - - - -/* -======================================================================= - - SelectSpawnPoint - -======================================================================= -*/ - -/* -================ -SpotWouldTelefrag - -================ -*/ -qboolean SpotWouldTelefrag( gentity_t *spot ) { - int i, num; - int touch[MAX_GENTITIES]; - gentity_t *hit; - vec3_t mins, maxs; - - VectorAdd( spot->s.origin, playerMins, mins ); - VectorAdd( spot->s.origin, playerMaxs, maxs ); - num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES ); - - for (i=0 ; iclient && hit->client->ps.stats[STAT_HEALTH] > 0 ) { - if ( hit->client) { - return qtrue; - } - - } - - return qfalse; -} - -/* -================ -SelectNearestDeathmatchSpawnPoint - -Find the spot that we DON'T want to use -================ -*/ -#define MAX_SPAWN_POINTS 128 -gentity_t *SelectNearestDeathmatchSpawnPoint( vec3_t from ) { - gentity_t *spot; - vec3_t delta; - float dist, nearestDist; - gentity_t *nearestSpot; - - nearestDist = 999999; - nearestSpot = NULL; - spot = NULL; - - while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL) { - - VectorSubtract( spot->s.origin, from, delta ); - dist = VectorLength( delta ); - if ( dist < nearestDist ) { - nearestDist = dist; - nearestSpot = spot; - } - } - - return nearestSpot; -} - - -/* -================ -SelectRandomDeathmatchSpawnPoint - -go to a random point that doesn't telefrag -================ -*/ -#define MAX_SPAWN_POINTS 128 -gentity_t *SelectRandomDeathmatchSpawnPoint( void ) { - gentity_t *spot; - int count; - int selection; - gentity_t *spots[MAX_SPAWN_POINTS]; - - count = 0; - spot = NULL; - - while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL) { - if ( SpotWouldTelefrag( spot ) ) { - continue; - } - spots[ count ] = spot; - count++; - } - - if ( !count ) { // no spots that won't telefrag - return G_Find( NULL, FOFS(classname), "info_player_deathmatch"); - } - - selection = rand() % count; - return spots[ selection ]; -} - -/* -=========== -SelectRandomFurthestSpawnPoint - -Chooses a player start, deathmatch start, etc -============ -*/ -gentity_t *SelectRandomFurthestSpawnPoint ( vec3_t avoidPoint, vec3_t origin, vec3_t angles ) { - gentity_t *spot; - vec3_t delta; - float dist; - float list_dist[64]; - gentity_t *list_spot[64]; - int numSpots, rnd, i, j; - - numSpots = 0; - spot = NULL; - - while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL) { - if ( SpotWouldTelefrag( spot ) ) { - continue; - } - VectorSubtract( spot->s.origin, avoidPoint, delta ); - dist = VectorLength( delta ); - for (i = 0; i < numSpots; i++) { - if ( dist > list_dist[i] ) { - if ( numSpots >= 64 ) - numSpots = 64-1; - for (j = numSpots; j > i; j--) { - list_dist[j] = list_dist[j-1]; - list_spot[j] = list_spot[j-1]; - } - list_dist[i] = dist; - list_spot[i] = spot; - numSpots++; - if (numSpots > 64) - numSpots = 64; - break; - } - } - if (i >= numSpots && numSpots < 64) { - list_dist[numSpots] = dist; - list_spot[numSpots] = spot; - numSpots++; - } - } - if (!numSpots) { - spot = G_Find( NULL, FOFS(classname), "info_player_deathmatch"); - if (!spot) - G_Error( "Couldn't find a spawn point" ); - VectorCopy (spot->s.origin, origin); - origin[2] += 9; - VectorCopy (spot->s.angles, angles); - return spot; - } - - // select a random spot from the spawn points furthest away - rnd = random() * (numSpots / 2); - - VectorCopy (list_spot[rnd]->s.origin, origin); - origin[2] += 9; - VectorCopy (list_spot[rnd]->s.angles, angles); - - return list_spot[rnd]; -} - -/* -=========== -SelectSpawnPoint - -Chooses a player start, deathmatch start, etc -============ -*/ -gentity_t *SelectSpawnPoint ( vec3_t avoidPoint, vec3_t origin, vec3_t angles ) { - return SelectRandomFurthestSpawnPoint( avoidPoint, origin, angles ); - - /* - gentity_t *spot; - gentity_t *nearestSpot; - - nearestSpot = SelectNearestDeathmatchSpawnPoint( avoidPoint ); - - spot = SelectRandomDeathmatchSpawnPoint ( ); - if ( spot == nearestSpot ) { - // roll again if it would be real close to point of death - spot = SelectRandomDeathmatchSpawnPoint ( ); - if ( spot == nearestSpot ) { - // last try - spot = SelectRandomDeathmatchSpawnPoint ( ); - } - } - - // find a single player start spot - if (!spot) { - G_Error( "Couldn't find a spawn point" ); - } - - VectorCopy (spot->s.origin, origin); - origin[2] += 9; - VectorCopy (spot->s.angles, angles); - - return spot; - */ -} - -/* -=========== -SelectInitialSpawnPoint - -Try to find a spawn point marked 'initial', otherwise -use normal spawn selection. -============ -*/ -gentity_t *SelectInitialSpawnPoint( vec3_t origin, vec3_t angles ) { - gentity_t *spot; - - spot = NULL; - while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL) { - if ( spot->spawnflags & 1 ) { - break; - } - } - - if ( !spot || SpotWouldTelefrag( spot ) ) { - return SelectSpawnPoint( vec3_origin, origin, angles ); - } - - VectorCopy (spot->s.origin, origin); - origin[2] += 9; - VectorCopy (spot->s.angles, angles); - - return spot; -} - -/* -=========== -SelectSpectatorSpawnPoint - -============ -*/ -gentity_t *SelectSpectatorSpawnPoint( vec3_t origin, vec3_t angles ) { - FindIntermissionPoint(); - - VectorCopy( level.intermission_origin, origin ); - VectorCopy( level.intermission_angle, angles ); - - return NULL; -} - -/* -======================================================================= - -BODYQUE - -======================================================================= -*/ - -/* -=============== -InitBodyQue -=============== -*/ -void InitBodyQue (void) { - int i; - gentity_t *ent; - - level.bodyQueIndex = 0; - for (i=0; iclassname = "bodyque"; - ent->neverFree = qtrue; - level.bodyQue[i] = ent; - } -} - -/* -============= -BodySink - -After sitting around for five seconds, fall into the ground and dissapear -============= -*/ -void BodySink( gentity_t *ent ) { - if ( level.time - ent->timestamp > 6500 ) { - // the body ques are never actually freed, they are just unlinked - trap_UnlinkEntity( ent ); - ent->physicsObject = qfalse; - return; - } - ent->nextthink = level.time + 100; - ent->s.pos.trBase[2] -= 1; -} - -/* -============= -CopyToBodyQue - -A player is respawning, so make an entity that looks -just like the existing corpse to leave behind. -============= -*/ -void CopyToBodyQue( gentity_t *ent ) { -#ifdef MISSIONPACK - gentity_t *e; - int i; -#endif - gentity_t *body; - int contents; - - trap_UnlinkEntity (ent); - - // if client is in a nodrop area, don't leave the body - contents = trap_PointContents( ent->s.origin, -1 ); - if ( contents & CONTENTS_NODROP ) { - return; - } - - // grab a body que and cycle to the next one - body = level.bodyQue[ level.bodyQueIndex ]; - level.bodyQueIndex = (level.bodyQueIndex + 1) % BODY_QUEUE_SIZE; - - trap_UnlinkEntity (body); - - body->s = ent->s; - body->s.eFlags = EF_DEAD; // clear EF_TALK, etc -#ifdef MISSIONPACK - if ( ent->s.eFlags & EF_KAMIKAZE ) { - body->s.eFlags |= EF_KAMIKAZE; - - // check if there is a kamikaze timer around for this owner - for (i = 0; i < MAX_GENTITIES; i++) { - e = &g_entities[i]; - if (!e->inuse) - continue; - if (e->activator != ent) - continue; - if (strcmp(e->classname, "kamikaze timer")) - continue; - e->activator = body; - break; - } - } -#endif - body->s.powerups = 0; // clear powerups - body->s.loopSound = 0; // clear lava burning - body->s.number = body - g_entities; - body->timestamp = level.time; - body->physicsObject = qtrue; - body->physicsBounce = 0; // don't bounce - if ( body->s.groundEntityNum == ENTITYNUM_NONE ) { - body->s.pos.trType = TR_GRAVITY; - body->s.pos.trTime = level.time; - VectorCopy( ent->client->ps.velocity, body->s.pos.trDelta ); - } else { - body->s.pos.trType = TR_STATIONARY; - } - body->s.event = 0; - - // change the animation to the last-frame only, so the sequence - // doesn't repeat anew for the body - switch ( body->s.legsAnim & ~ANIM_TOGGLEBIT ) { - case BOTH_DEATH1: - case BOTH_DEAD1: - body->s.torsoAnim = body->s.legsAnim = BOTH_DEAD1; - break; - case BOTH_DEATH2: - case BOTH_DEAD2: - body->s.torsoAnim = body->s.legsAnim = BOTH_DEAD2; - break; - case BOTH_DEATH3: - case BOTH_DEAD3: - default: - body->s.torsoAnim = body->s.legsAnim = BOTH_DEAD3; - break; - } - - body->r.svFlags = ent->r.svFlags; - VectorCopy (ent->r.mins, body->r.mins); - VectorCopy (ent->r.maxs, body->r.maxs); - VectorCopy (ent->r.absmin, body->r.absmin); - VectorCopy (ent->r.absmax, body->r.absmax); - - body->clipmask = CONTENTS_SOLID | CONTENTS_PLAYERCLIP; - body->r.contents = CONTENTS_CORPSE; - body->r.ownerNum = ent->s.number; - - body->nextthink = level.time + 5000; - body->think = BodySink; - - body->die = body_die; - - // don't take more damage if already gibbed - if ( ent->health <= GIB_HEALTH ) { - body->takedamage = qfalse; - } else { - body->takedamage = qtrue; - } - - - VectorCopy ( body->s.pos.trBase, body->r.currentOrigin ); - trap_LinkEntity (body); -} - -//====================================================================== - - -/* -================== -SetClientViewAngle - -================== -*/ -void SetClientViewAngle( gentity_t *ent, vec3_t angle ) { - int i; - - // set the delta angle - for (i=0 ; i<3 ; i++) { - int cmdAngle; - - cmdAngle = ANGLE2SHORT(angle[i]); - ent->client->ps.delta_angles[i] = cmdAngle - ent->client->pers.cmd.angles[i]; - } - VectorCopy( angle, ent->s.angles ); - VectorCopy (ent->s.angles, ent->client->ps.viewangles); -} - -/* -================ -respawn -================ -*/ -void respawn( gentity_t *ent ) { - gentity_t *tent; - - CopyToBodyQue (ent); - ClientSpawn(ent); - - // add a teleportation effect - tent = G_TempEntity( ent->client->ps.origin, EV_PLAYER_TELEPORT_IN ); - tent->s.clientNum = ent->s.clientNum; -} - -/* -================ -TeamCount - -Returns number of players on a team -================ -*/ -team_t TeamCount( int ignoreClientNum, int team ) { - int i; - int count = 0; - - for ( i = 0 ; i < level.maxclients ; i++ ) { - if ( i == ignoreClientNum ) { - continue; - } - if ( level.clients[i].pers.connected == CON_DISCONNECTED ) { - continue; - } - if ( level.clients[i].sess.sessionTeam == team ) { - count++; - } - } - - return count; -} - -/* -================ -TeamLeader - -Returns the client number of the team leader -================ -*/ -int TeamLeader( int team ) { - int i; - - for ( i = 0 ; i < level.maxclients ; i++ ) { - if ( level.clients[i].pers.connected == CON_DISCONNECTED ) { - continue; - } - if ( level.clients[i].sess.sessionTeam == team ) { - if ( level.clients[i].sess.teamLeader ) - return i; - } - } - - return -1; -} - - -/* -================ -PickTeam - -================ -*/ -team_t PickTeam( int ignoreClientNum ) { - int counts[TEAM_NUM_TEAMS]; - - counts[TEAM_BLUE] = TeamCount( ignoreClientNum, TEAM_BLUE ); - counts[TEAM_RED] = TeamCount( ignoreClientNum, TEAM_RED ); - - if ( counts[TEAM_BLUE] > counts[TEAM_RED] ) { - return TEAM_RED; - } - if ( counts[TEAM_RED] > counts[TEAM_BLUE] ) { - return TEAM_BLUE; - } - // equal team count, so join the team with the lowest score - if ( level.teamScores[TEAM_BLUE] > level.teamScores[TEAM_RED] ) { - return TEAM_RED; - } - return TEAM_BLUE; -} - -/* -=========== -ForceClientSkin - -Forces a client's skin (for teamplay) -=========== -*/ -/* -static void ForceClientSkin( gclient_t *client, char *model, const char *skin ) { - char *p; - - if ((p = Q_strrchr(model, '/')) != 0) { - *p = 0; - } - - Q_strcat(model, MAX_QPATH, "/"); - Q_strcat(model, MAX_QPATH, skin); -} -*/ - -/* -=========== -ClientCheckName -============ -*/ -static void ClientCleanName( const char *in, char *out, int outSize ) { - int len, colorlessLen; - char ch; - char *p; - int spaces; - - //save room for trailing null byte - outSize--; - - len = 0; - colorlessLen = 0; - p = out; - *p = 0; - spaces = 0; - - while( 1 ) { - ch = *in++; - if( !ch ) { - break; - } - - // don't allow leading spaces - if( !*p && ch == ' ' ) { - continue; - } - - // check colors - if( ch == Q_COLOR_ESCAPE ) { - // solo trailing carat is not a color prefix - if( !*in ) { - break; - } - - // don't allow black in a name, period - if( ColorIndex(*in) == 0 ) { - in++; - continue; - } - - // make sure room in dest for both chars - if( len > outSize - 2 ) { - break; - } - - *out++ = ch; - *out++ = *in++; - len += 2; - continue; - } - - // don't allow too many consecutive spaces - if( ch == ' ' ) { - spaces++; - if( spaces > 3 ) { - continue; - } - } - else { - spaces = 0; - } - - if( len > outSize - 1 ) { - break; - } - - *out++ = ch; - colorlessLen++; - len++; - } - *out = 0; - - // don't allow empty names - if( *p == 0 || colorlessLen == 0 ) { - Q_strncpyz( p, "UnnamedPlayer", outSize ); - } -} - - -/* -=========== -ClientUserInfoChanged - -Called from ClientConnect when the player first connects and -directly by the server system when the player updates a userinfo variable. - -The game can override any of the settings and call trap_SetUserinfo -if desired. -============ -*/ -void ClientUserinfoChanged( int clientNum ) { - gentity_t *ent; - int teamTask, teamLeader, team, health; - char *s; - char model[MAX_QPATH]; - char headModel[MAX_QPATH]; - char oldname[MAX_STRING_CHARS]; - gclient_t *client; - char c1[MAX_INFO_STRING]; - char c2[MAX_INFO_STRING]; - char redTeam[MAX_INFO_STRING]; - char blueTeam[MAX_INFO_STRING]; - char userinfo[MAX_INFO_STRING]; - - ent = g_entities + clientNum; - client = ent->client; - - trap_GetUserinfo( clientNum, userinfo, sizeof( userinfo ) ); - - // check for malformed or illegal info strings - if ( !Info_Validate(userinfo) ) { - strcpy (userinfo, "\\name\\badinfo"); - } - - // check for local client - s = Info_ValueForKey( userinfo, "ip" ); - if ( !strcmp( s, "localhost" ) ) { - client->pers.localClient = qtrue; - } - - // check the item prediction - s = Info_ValueForKey( userinfo, "cg_predictItems" ); - if ( !atoi( s ) ) { - client->pers.predictItemPickup = qfalse; - } else { - client->pers.predictItemPickup = qtrue; - } - - // set name - Q_strncpyz ( oldname, client->pers.netname, sizeof( oldname ) ); - s = Info_ValueForKey (userinfo, "name"); - ClientCleanName( s, client->pers.netname, sizeof(client->pers.netname) ); - - if ( client->sess.sessionTeam == TEAM_SPECTATOR ) { - if ( client->sess.spectatorState == SPECTATOR_SCOREBOARD ) { - Q_strncpyz( client->pers.netname, "scoreboard", sizeof(client->pers.netname) ); - } - } - - if ( client->pers.connected == CON_CONNECTED ) { - if ( strcmp( oldname, client->pers.netname ) ) { - trap_SendServerCommand( -1, va("print \"%s" S_COLOR_WHITE " renamed to %s\n\"", oldname, - client->pers.netname) ); - } - } - - // set max health -#ifdef MISSIONPACK - if (client->ps.powerups[PW_GUARD]) { - client->pers.maxHealth = 200; - } else { - health = atoi( Info_ValueForKey( userinfo, "handicap" ) ); - client->pers.maxHealth = health; - if ( client->pers.maxHealth < 1 || client->pers.maxHealth > 100 ) { - client->pers.maxHealth = 100; - } - } -#else - health = atoi( Info_ValueForKey( userinfo, "handicap" ) ); - client->pers.maxHealth = health; - if ( client->pers.maxHealth < 1 || client->pers.maxHealth > 100 ) { - client->pers.maxHealth = 100; - } -#endif - client->ps.stats[STAT_MAX_HEALTH] = client->pers.maxHealth; - - // set model - if( g_gametype.integer >= GT_TEAM ) { - Q_strncpyz( model, Info_ValueForKey (userinfo, "team_model"), sizeof( model ) ); - Q_strncpyz( headModel, Info_ValueForKey (userinfo, "team_headmodel"), sizeof( headModel ) ); - } else { - Q_strncpyz( model, Info_ValueForKey (userinfo, "model"), sizeof( model ) ); - Q_strncpyz( headModel, Info_ValueForKey (userinfo, "headmodel"), sizeof( headModel ) ); - } - - // bots set their team a few frames later - if (g_gametype.integer >= GT_TEAM && g_entities[clientNum].r.svFlags & SVF_BOT) { - s = Info_ValueForKey( userinfo, "team" ); - if ( !Q_stricmp( s, "red" ) || !Q_stricmp( s, "r" ) ) { - team = TEAM_RED; - } else if ( !Q_stricmp( s, "blue" ) || !Q_stricmp( s, "b" ) ) { - team = TEAM_BLUE; - } else { - // pick the team with the least number of players - team = PickTeam( clientNum ); - } - } - else { - team = client->sess.sessionTeam; - } - -/* NOTE: all client side now - - // team - switch( team ) { - case TEAM_RED: - ForceClientSkin(client, model, "red"); -// ForceClientSkin(client, headModel, "red"); - break; - case TEAM_BLUE: - ForceClientSkin(client, model, "blue"); -// ForceClientSkin(client, headModel, "blue"); - break; - } - // don't ever use a default skin in teamplay, it would just waste memory - // however bots will always join a team but they spawn in as spectator - if ( g_gametype.integer >= GT_TEAM && team == TEAM_SPECTATOR) { - ForceClientSkin(client, model, "red"); -// ForceClientSkin(client, headModel, "red"); - } -*/ - -#ifdef MISSIONPACK - if (g_gametype.integer >= GT_TEAM) { - client->pers.teamInfo = qtrue; - } else { - s = Info_ValueForKey( userinfo, "teamoverlay" ); - if ( ! *s || atoi( s ) != 0 ) { - client->pers.teamInfo = qtrue; - } else { - client->pers.teamInfo = qfalse; - } - } -#else - // teamInfo - s = Info_ValueForKey( userinfo, "teamoverlay" ); - if ( ! *s || atoi( s ) != 0 ) { - client->pers.teamInfo = qtrue; - } else { - client->pers.teamInfo = qfalse; - } -#endif - /* - s = Info_ValueForKey( userinfo, "cg_pmove_fixed" ); - if ( !*s || atoi( s ) == 0 ) { - client->pers.pmoveFixed = qfalse; - } - else { - client->pers.pmoveFixed = qtrue; - } - */ - - // team task (0 = none, 1 = offence, 2 = defence) - teamTask = atoi(Info_ValueForKey(userinfo, "teamtask")); - // team Leader (1 = leader, 0 is normal player) - teamLeader = client->sess.teamLeader; - - // colors - strcpy(c1, Info_ValueForKey( userinfo, "color1" )); - strcpy(c2, Info_ValueForKey( userinfo, "color2" )); - - strcpy(redTeam, Info_ValueForKey( userinfo, "g_redteam" )); - strcpy(blueTeam, Info_ValueForKey( userinfo, "g_blueteam" )); - - // send over a subset of the userinfo keys so other clients can - // print scoreboards, display models, and play custom sounds - if ( ent->r.svFlags & SVF_BOT ) { - s = va("n\\%s\\t\\%i\\model\\%s\\hmodel\\%s\\c1\\%s\\c2\\%s\\hc\\%i\\w\\%i\\l\\%i\\skill\\%s\\tt\\%d\\tl\\%d", - client->pers.netname, team, model, headModel, c1, c2, - client->pers.maxHealth, client->sess.wins, client->sess.losses, - Info_ValueForKey( userinfo, "skill" ), teamTask, teamLeader ); - } else { - s = va("n\\%s\\t\\%i\\model\\%s\\hmodel\\%s\\g_redteam\\%s\\g_blueteam\\%s\\c1\\%s\\c2\\%s\\hc\\%i\\w\\%i\\l\\%i\\tt\\%d\\tl\\%d", - client->pers.netname, client->sess.sessionTeam, model, headModel, redTeam, blueTeam, c1, c2, - client->pers.maxHealth, client->sess.wins, client->sess.losses, teamTask, teamLeader); - } - - trap_SetConfigstring( CS_PLAYERS+clientNum, s ); - - // this is not the userinfo, more like the configstring actually - G_LogPrintf( "ClientUserinfoChanged: %i %s\n", clientNum, s ); -} - - -/* -=========== -ClientConnect - -Called when a player begins connecting to the server. -Called again for every map change or tournement restart. - -The session information will be valid after exit. - -Return NULL if the client should be allowed, otherwise return -a string with the reason for denial. - -Otherwise, the client will be sent the current gamestate -and will eventually get to ClientBegin. - -firstTime will be qtrue the very first time a client connects -to the server machine, but qfalse on map changes and tournement -restarts. -============ -*/ -char *ClientConnect( int clientNum, qboolean firstTime, qboolean isBot ) { - char *value; -// char *areabits; - gclient_t *client; - char userinfo[MAX_INFO_STRING]; - gentity_t *ent; - - ent = &g_entities[ clientNum ]; - - trap_GetUserinfo( clientNum, userinfo, sizeof( userinfo ) ); - - // IP filtering - // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=500 - // recommanding PB based IP / GUID banning, the builtin system is pretty limited - // check to see if they are on the banned IP list - value = Info_ValueForKey (userinfo, "ip"); - if ( G_FilterPacket( value ) ) { - return "You are banned from this server."; - } - - // we don't check password for bots and local client - // NOTE: local client <-> "ip" "localhost" - // this means this client is not running in our current process - if ( !( ent->r.svFlags & SVF_BOT ) && (strcmp(value, "localhost") != 0)) { - // check for a password - value = Info_ValueForKey (userinfo, "password"); - if ( g_password.string[0] && Q_stricmp( g_password.string, "none" ) && - strcmp( g_password.string, value) != 0) { - return "Invalid password"; - } - } - - // they can connect - ent->client = level.clients + clientNum; - client = ent->client; - -// areabits = client->areabits; - - memset( client, 0, sizeof(*client) ); - - client->pers.connected = CON_CONNECTING; - - // read or initialize the session data - if ( firstTime || level.newSession ) { - G_InitSessionData( client, userinfo ); - } - G_ReadSessionData( client ); - - if( isBot ) { - ent->r.svFlags |= SVF_BOT; - ent->inuse = qtrue; - if( !G_BotConnect( clientNum, !firstTime ) ) { - return "BotConnectfailed"; - } - } - - // get and distribute relevent paramters - G_LogPrintf( "ClientConnect: %i\n", clientNum ); - ClientUserinfoChanged( clientNum ); - - // don't do the "xxx connected" messages if they were caried over from previous level - if ( firstTime ) { - trap_SendServerCommand( -1, va("print \"%s" S_COLOR_WHITE " connected\n\"", client->pers.netname) ); - } - - if ( g_gametype.integer >= GT_TEAM && - client->sess.sessionTeam != TEAM_SPECTATOR ) { - BroadcastTeamChange( client, -1 ); - } - - // count current clients and rank for scoreboard - CalculateRanks(); - - // for statistics -// client->areabits = areabits; -// if ( !client->areabits ) -// client->areabits = G_Alloc( (trap_AAS_PointReachabilityAreaIndex( NULL ) + 7) / 8 ); - - return NULL; -} - -/* -=========== -ClientBegin - -called when a client has finished connecting, and is ready -to be placed into the level. This will happen every level load, -and on transition between teams, but doesn't happen on respawns -============ -*/ -void ClientBegin( int clientNum ) { - gentity_t *ent; - gclient_t *client; - gentity_t *tent; - int flags; - - ent = g_entities + clientNum; - - client = level.clients + clientNum; - - if ( ent->r.linked ) { - trap_UnlinkEntity( ent ); - } - G_InitGentity( ent ); - ent->touch = 0; - ent->pain = 0; - ent->client = client; - - client->pers.connected = CON_CONNECTED; - client->pers.enterTime = level.time; - client->pers.teamState.state = TEAM_BEGIN; - - // save eflags around this, because changing teams will - // cause this to happen with a valid entity, and we - // want to make sure the teleport bit is set right - // so the viewpoint doesn't interpolate through the - // world to the new position - flags = client->ps.eFlags; - memset( &client->ps, 0, sizeof( client->ps ) ); - client->ps.eFlags = flags; - - // locate ent at a spawn point - ClientSpawn( ent ); - - if ( client->sess.sessionTeam != TEAM_SPECTATOR ) { - // send event - tent = G_TempEntity( ent->client->ps.origin, EV_PLAYER_TELEPORT_IN ); - tent->s.clientNum = ent->s.clientNum; - - if ( g_gametype.integer != GT_TOURNAMENT ) { - trap_SendServerCommand( -1, va("print \"%s" S_COLOR_WHITE " entered the game\n\"", client->pers.netname) ); - } - } - G_LogPrintf( "ClientBegin: %i\n", clientNum ); - - // count current clients and rank for scoreboard - CalculateRanks(); -} - -/* -=========== -ClientSpawn - -Called every time a client is placed fresh in the world: -after the first ClientBegin, and after each respawn -Initializes all non-persistant parts of playerState -============ -*/ -void ClientSpawn(gentity_t *ent) { - int index; - vec3_t spawn_origin, spawn_angles; - gclient_t *client; - int i; - clientPersistant_t saved; - clientSession_t savedSess; - int persistant[MAX_PERSISTANT]; - gentity_t *spawnPoint; - int flags; - int savedPing; -// char *savedAreaBits; - int accuracy_hits, accuracy_shots; - int eventSequence; - char userinfo[MAX_INFO_STRING]; - - index = ent - g_entities; - client = ent->client; - - // find a spawn point - // do it before setting health back up, so farthest - // ranging doesn't count this client - if ( client->sess.sessionTeam == TEAM_SPECTATOR ) { - spawnPoint = SelectSpectatorSpawnPoint ( - spawn_origin, spawn_angles); - } else if (g_gametype.integer >= GT_CTF ) { - // all base oriented team games use the CTF spawn points - spawnPoint = SelectCTFSpawnPoint ( - client->sess.sessionTeam, - client->pers.teamState.state, - spawn_origin, spawn_angles); - } else { - do { - // the first spawn should be at a good looking spot - if ( !client->pers.initialSpawn && client->pers.localClient ) { - client->pers.initialSpawn = qtrue; - spawnPoint = SelectInitialSpawnPoint( spawn_origin, spawn_angles ); - } else { - // don't spawn near existing origin if possible - spawnPoint = SelectSpawnPoint ( - client->ps.origin, - spawn_origin, spawn_angles); - } - - // Tim needs to prevent bots from spawning at the initial point - // on q3dm0... - if ( ( spawnPoint->flags & FL_NO_BOTS ) && ( ent->r.svFlags & SVF_BOT ) ) { - continue; // try again - } - // just to be symetric, we have a nohumans option... - if ( ( spawnPoint->flags & FL_NO_HUMANS ) && !( ent->r.svFlags & SVF_BOT ) ) { - continue; // try again - } - - break; - - } while ( 1 ); - } - client->pers.teamState.state = TEAM_ACTIVE; - - // always clear the kamikaze flag - ent->s.eFlags &= ~EF_KAMIKAZE; - - // toggle the teleport bit so the client knows to not lerp - // and never clear the voted flag - flags = ent->client->ps.eFlags & (EF_TELEPORT_BIT | EF_VOTED | EF_TEAMVOTED); - flags ^= EF_TELEPORT_BIT; - - // clear everything but the persistant data - - saved = client->pers; - savedSess = client->sess; - savedPing = client->ps.ping; -// savedAreaBits = client->areabits; - accuracy_hits = client->accuracy_hits; - accuracy_shots = client->accuracy_shots; - for ( i = 0 ; i < MAX_PERSISTANT ; i++ ) { - persistant[i] = client->ps.persistant[i]; - } - eventSequence = client->ps.eventSequence; - - memset (client, 0, sizeof(*client)); // bk FIXME: Com_Memset? - - client->pers = saved; - client->sess = savedSess; - client->ps.ping = savedPing; -// client->areabits = savedAreaBits; - client->accuracy_hits = accuracy_hits; - client->accuracy_shots = accuracy_shots; - client->lastkilled_client = -1; - - for ( i = 0 ; i < MAX_PERSISTANT ; i++ ) { - client->ps.persistant[i] = persistant[i]; - } - client->ps.eventSequence = eventSequence; - // increment the spawncount so the client will detect the respawn - client->ps.persistant[PERS_SPAWN_COUNT]++; - client->ps.persistant[PERS_TEAM] = client->sess.sessionTeam; - - client->airOutTime = level.time + 12000; - - trap_GetUserinfo( index, userinfo, sizeof(userinfo) ); - // set max health - client->pers.maxHealth = atoi( Info_ValueForKey( userinfo, "handicap" ) ); - if ( client->pers.maxHealth < 1 || client->pers.maxHealth > 100 ) { - client->pers.maxHealth = 100; - } - // clear entity values - client->ps.stats[STAT_MAX_HEALTH] = client->pers.maxHealth; - client->ps.eFlags = flags; - - ent->s.groundEntityNum = ENTITYNUM_NONE; - ent->client = &level.clients[index]; - ent->takedamage = qtrue; - ent->inuse = qtrue; - ent->classname = "player"; - ent->r.contents = CONTENTS_BODY; - ent->clipmask = MASK_PLAYERSOLID; - ent->die = player_die; - ent->waterlevel = 0; - ent->watertype = 0; - ent->flags = 0; - - VectorCopy (playerMins, ent->r.mins); - VectorCopy (playerMaxs, ent->r.maxs); - - client->ps.clientNum = index; - - client->ps.stats[STAT_WEAPONS] = ( 1 << WP_MACHINEGUN ); - if ( g_gametype.integer == GT_TEAM ) { - client->ps.ammo[WP_MACHINEGUN] = 50; - } else { - client->ps.ammo[WP_MACHINEGUN] = 100; - } - - client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_GAUNTLET ); - client->ps.ammo[WP_GAUNTLET] = -1; - client->ps.ammo[WP_GRAPPLING_HOOK] = -1; - - // health will count down towards max_health - ent->health = client->ps.stats[STAT_HEALTH] = client->ps.stats[STAT_MAX_HEALTH] + 25; - - G_SetOrigin( ent, spawn_origin ); - VectorCopy( spawn_origin, client->ps.origin ); - - // the respawned flag will be cleared after the attack and jump keys come up - client->ps.pm_flags |= PMF_RESPAWNED; - - trap_GetUsercmd( client - level.clients, &ent->client->pers.cmd ); - SetClientViewAngle( ent, spawn_angles ); - - if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) { - - } else { - G_KillBox( ent ); - trap_LinkEntity (ent); - - // force the base weapon up - client->ps.weapon = WP_MACHINEGUN; - client->ps.weaponstate = WEAPON_READY; - - } - - // don't allow full run speed for a bit - client->ps.pm_flags |= PMF_TIME_KNOCKBACK; - client->ps.pm_time = 100; - - client->respawnTime = level.time; - client->inactivityTime = level.time + g_inactivity.integer * 1000; - client->latched_buttons = 0; - - // set default animations - client->ps.torsoAnim = TORSO_STAND; - client->ps.legsAnim = LEGS_IDLE; - - if ( level.intermissiontime ) { - MoveClientToIntermission( ent ); - } else { - // fire the targets of the spawn point - G_UseTargets( spawnPoint, ent ); - - // select the highest weapon number available, after any - // spawn given items have fired - client->ps.weapon = 1; - for ( i = WP_NUM_WEAPONS - 1 ; i > 0 ; i-- ) { - if ( client->ps.stats[STAT_WEAPONS] & ( 1 << i ) ) { - client->ps.weapon = i; - break; - } - } - } - - // run a client frame to drop exactly to the floor, - // initialize animations and other things - client->ps.commandTime = level.time - 100; - ent->client->pers.cmd.serverTime = level.time; - ClientThink( ent-g_entities ); - - // positively link the client, even if the command times are weird - if ( ent->client->sess.sessionTeam != TEAM_SPECTATOR ) { - BG_PlayerStateToEntityState( &client->ps, &ent->s, qtrue ); - VectorCopy( ent->client->ps.origin, ent->r.currentOrigin ); - trap_LinkEntity( ent ); - } - - // run the presend to set anything else - ClientEndFrame( ent ); - - // clear entity state values - BG_PlayerStateToEntityState( &client->ps, &ent->s, qtrue ); -} - - -/* -=========== -ClientDisconnect - -Called when a player drops from the server. -Will not be called between levels. - -This should NOT be called directly by any game logic, -call trap_DropClient(), which will call this and do -server system housekeeping. -============ -*/ -void ClientDisconnect( int clientNum ) { - gentity_t *ent; - gentity_t *tent; - int i; - - // cleanup if we are kicking a bot that - // hasn't spawned yet - G_RemoveQueuedBotBegin( clientNum ); - - ent = g_entities + clientNum; - if ( !ent->client ) { - return; - } - - // stop any following clients - for ( i = 0 ; i < level.maxclients ; i++ ) { - if ( level.clients[i].sess.sessionTeam == TEAM_SPECTATOR - && level.clients[i].sess.spectatorState == SPECTATOR_FOLLOW - && level.clients[i].sess.spectatorClient == clientNum ) { - StopFollowing( &g_entities[i] ); - } - } - - // send effect if they were completely connected - if ( ent->client->pers.connected == CON_CONNECTED - && ent->client->sess.sessionTeam != TEAM_SPECTATOR ) { - tent = G_TempEntity( ent->client->ps.origin, EV_PLAYER_TELEPORT_OUT ); - tent->s.clientNum = ent->s.clientNum; - - // They don't get to take powerups with them! - // Especially important for stuff like CTF flags - TossClientItems( ent ); -#ifdef MISSIONPACK - TossClientPersistantPowerups( ent ); - if( g_gametype.integer == GT_HARVESTER ) { - TossClientCubes( ent ); - } -#endif - - } - - G_LogPrintf( "ClientDisconnect: %i\n", clientNum ); - - // if we are playing in tourney mode and losing, give a win to the other player - if ( (g_gametype.integer == GT_TOURNAMENT ) - && !level.intermissiontime - && !level.warmupTime && level.sortedClients[1] == clientNum ) { - level.clients[ level.sortedClients[0] ].sess.wins++; - ClientUserinfoChanged( level.sortedClients[0] ); - } - - trap_UnlinkEntity (ent); - ent->s.modelindex = 0; - ent->inuse = qfalse; - ent->classname = "disconnected"; - ent->client->pers.connected = CON_DISCONNECTED; - ent->client->ps.persistant[PERS_TEAM] = TEAM_FREE; - ent->client->sess.sessionTeam = TEAM_FREE; - - trap_SetConfigstring( CS_PLAYERS + clientNum, ""); - - CalculateRanks(); - - if ( ent->r.svFlags & SVF_BOT ) { - BotAIShutdownClient( clientNum, qfalse ); - } -} - - +/* +=========================================================================== +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 Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// +#include "g_local.h" + +// g_client.c -- client functions that don't happen every frame + +static vec3_t playerMins = {-15, -15, -24}; +static vec3_t playerMaxs = {15, 15, 32}; + +/*QUAKED info_player_deathmatch (1 0 1) (-16 -16 -24) (16 16 32) initial +potential spawning position for deathmatch games. +The first time a player enters the game, they will be at an 'initial' spot. +Targets will be fired when someone spawns in on them. +"nobots" will prevent bots from using this spot. +"nohumans" will prevent non-bots from using this spot. +*/ +void SP_info_player_deathmatch( gentity_t *ent ) { + int i; + + G_SpawnInt( "nobots", "0", &i); + if ( i ) { + ent->flags |= FL_NO_BOTS; + } + G_SpawnInt( "nohumans", "0", &i ); + if ( i ) { + ent->flags |= FL_NO_HUMANS; + } +} + +/*QUAKED info_player_start (1 0 0) (-16 -16 -24) (16 16 32) +equivelant to info_player_deathmatch +*/ +void SP_info_player_start(gentity_t *ent) { + ent->classname = "info_player_deathmatch"; + SP_info_player_deathmatch( ent ); +} + +/*QUAKED info_player_intermission (1 0 1) (-16 -16 -24) (16 16 32) +The intermission will be viewed from this point. Target an info_notnull for the view direction. +*/ +void SP_info_player_intermission( gentity_t *ent ) { + +} + + + +/* +======================================================================= + + SelectSpawnPoint + +======================================================================= +*/ + +/* +================ +SpotWouldTelefrag + +================ +*/ +qboolean SpotWouldTelefrag( gentity_t *spot ) { + int i, num; + int touch[MAX_GENTITIES]; + gentity_t *hit; + vec3_t mins, maxs; + + VectorAdd( spot->s.origin, playerMins, mins ); + VectorAdd( spot->s.origin, playerMaxs, maxs ); + num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES ); + + for (i=0 ; iclient && hit->client->ps.stats[STAT_HEALTH] > 0 ) { + if ( hit->client) { + return qtrue; + } + + } + + return qfalse; +} + +/* +================ +SelectNearestDeathmatchSpawnPoint + +Find the spot that we DON'T want to use +================ +*/ +#define MAX_SPAWN_POINTS 128 +gentity_t *SelectNearestDeathmatchSpawnPoint( vec3_t from ) { + gentity_t *spot; + vec3_t delta; + float dist, nearestDist; + gentity_t *nearestSpot; + + nearestDist = 999999; + nearestSpot = NULL; + spot = NULL; + + while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL) { + + VectorSubtract( spot->s.origin, from, delta ); + dist = VectorLength( delta ); + if ( dist < nearestDist ) { + nearestDist = dist; + nearestSpot = spot; + } + } + + return nearestSpot; +} + + +/* +================ +SelectRandomDeathmatchSpawnPoint + +go to a random point that doesn't telefrag +================ +*/ +#define MAX_SPAWN_POINTS 128 +gentity_t *SelectRandomDeathmatchSpawnPoint( void ) { + gentity_t *spot; + int count; + int selection; + gentity_t *spots[MAX_SPAWN_POINTS]; + + count = 0; + spot = NULL; + + while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL) { + if ( SpotWouldTelefrag( spot ) ) { + continue; + } + spots[ count ] = spot; + count++; + } + + if ( !count ) { // no spots that won't telefrag + return G_Find( NULL, FOFS(classname), "info_player_deathmatch"); + } + + selection = rand() % count; + return spots[ selection ]; +} + +/* +=========== +SelectRandomFurthestSpawnPoint + +Chooses a player start, deathmatch start, etc +============ +*/ +gentity_t *SelectRandomFurthestSpawnPoint ( vec3_t avoidPoint, vec3_t origin, vec3_t angles ) { + gentity_t *spot; + vec3_t delta; + float dist; + float list_dist[64]; + gentity_t *list_spot[64]; + int numSpots, rnd, i, j; + + numSpots = 0; + spot = NULL; + + while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL) { + if ( SpotWouldTelefrag( spot ) ) { + continue; + } + VectorSubtract( spot->s.origin, avoidPoint, delta ); + dist = VectorLength( delta ); + for (i = 0; i < numSpots; i++) { + if ( dist > list_dist[i] ) { + if ( numSpots >= 64 ) + numSpots = 64-1; + for (j = numSpots; j > i; j--) { + list_dist[j] = list_dist[j-1]; + list_spot[j] = list_spot[j-1]; + } + list_dist[i] = dist; + list_spot[i] = spot; + numSpots++; + if (numSpots > 64) + numSpots = 64; + break; + } + } + if (i >= numSpots && numSpots < 64) { + list_dist[numSpots] = dist; + list_spot[numSpots] = spot; + numSpots++; + } + } + if (!numSpots) { + spot = G_Find( NULL, FOFS(classname), "info_player_deathmatch"); + if (!spot) + G_Error( "Couldn't find a spawn point" ); + VectorCopy (spot->s.origin, origin); + origin[2] += 9; + VectorCopy (spot->s.angles, angles); + return spot; + } + + // select a random spot from the spawn points furthest away + rnd = random() * (numSpots / 2); + + VectorCopy (list_spot[rnd]->s.origin, origin); + origin[2] += 9; + VectorCopy (list_spot[rnd]->s.angles, angles); + + return list_spot[rnd]; +} + +/* +=========== +SelectSpawnPoint + +Chooses a player start, deathmatch start, etc +============ +*/ +gentity_t *SelectSpawnPoint ( vec3_t avoidPoint, vec3_t origin, vec3_t angles ) { + return SelectRandomFurthestSpawnPoint( avoidPoint, origin, angles ); + + /* + gentity_t *spot; + gentity_t *nearestSpot; + + nearestSpot = SelectNearestDeathmatchSpawnPoint( avoidPoint ); + + spot = SelectRandomDeathmatchSpawnPoint ( ); + if ( spot == nearestSpot ) { + // roll again if it would be real close to point of death + spot = SelectRandomDeathmatchSpawnPoint ( ); + if ( spot == nearestSpot ) { + // last try + spot = SelectRandomDeathmatchSpawnPoint ( ); + } + } + + // find a single player start spot + if (!spot) { + G_Error( "Couldn't find a spawn point" ); + } + + VectorCopy (spot->s.origin, origin); + origin[2] += 9; + VectorCopy (spot->s.angles, angles); + + return spot; + */ +} + +/* +=========== +SelectInitialSpawnPoint + +Try to find a spawn point marked 'initial', otherwise +use normal spawn selection. +============ +*/ +gentity_t *SelectInitialSpawnPoint( vec3_t origin, vec3_t angles ) { + gentity_t *spot; + + spot = NULL; + while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL) { + if ( spot->spawnflags & 1 ) { + break; + } + } + + if ( !spot || SpotWouldTelefrag( spot ) ) { + return SelectSpawnPoint( vec3_origin, origin, angles ); + } + + VectorCopy (spot->s.origin, origin); + origin[2] += 9; + VectorCopy (spot->s.angles, angles); + + return spot; +} + +/* +=========== +SelectSpectatorSpawnPoint + +============ +*/ +gentity_t *SelectSpectatorSpawnPoint( vec3_t origin, vec3_t angles ) { + FindIntermissionPoint(); + + VectorCopy( level.intermission_origin, origin ); + VectorCopy( level.intermission_angle, angles ); + + return NULL; +} + +/* +======================================================================= + +BODYQUE + +======================================================================= +*/ + +/* +=============== +InitBodyQue +=============== +*/ +void InitBodyQue (void) { + int i; + gentity_t *ent; + + level.bodyQueIndex = 0; + for (i=0; iclassname = "bodyque"; + ent->neverFree = qtrue; + level.bodyQue[i] = ent; + } +} + +/* +============= +BodySink + +After sitting around for five seconds, fall into the ground and dissapear +============= +*/ +void BodySink( gentity_t *ent ) { + if ( level.time - ent->timestamp > 6500 ) { + // the body ques are never actually freed, they are just unlinked + trap_UnlinkEntity( ent ); + ent->physicsObject = qfalse; + return; + } + ent->nextthink = level.time + 100; + ent->s.pos.trBase[2] -= 1; +} + +/* +============= +CopyToBodyQue + +A player is respawning, so make an entity that looks +just like the existing corpse to leave behind. +============= +*/ +void CopyToBodyQue( gentity_t *ent ) { +#ifdef MISSIONPACK + gentity_t *e; + int i; +#endif + gentity_t *body; + int contents; + + trap_UnlinkEntity (ent); + + // if client is in a nodrop area, don't leave the body + contents = trap_PointContents( ent->s.origin, -1 ); + if ( contents & CONTENTS_NODROP ) { + return; + } + + // grab a body que and cycle to the next one + body = level.bodyQue[ level.bodyQueIndex ]; + level.bodyQueIndex = (level.bodyQueIndex + 1) % BODY_QUEUE_SIZE; + + trap_UnlinkEntity (body); + + body->s = ent->s; + body->s.eFlags = EF_DEAD; // clear EF_TALK, etc +#ifdef MISSIONPACK + if ( ent->s.eFlags & EF_KAMIKAZE ) { + body->s.eFlags |= EF_KAMIKAZE; + + // check if there is a kamikaze timer around for this owner + for (i = 0; i < MAX_GENTITIES; i++) { + e = &g_entities[i]; + if (!e->inuse) + continue; + if (e->activator != ent) + continue; + if (strcmp(e->classname, "kamikaze timer")) + continue; + e->activator = body; + break; + } + } +#endif + body->s.powerups = 0; // clear powerups + body->s.loopSound = 0; // clear lava burning + body->s.number = body - g_entities; + body->timestamp = level.time; + body->physicsObject = qtrue; + body->physicsBounce = 0; // don't bounce + if ( body->s.groundEntityNum == ENTITYNUM_NONE ) { + body->s.pos.trType = TR_GRAVITY; + body->s.pos.trTime = level.time; + VectorCopy( ent->client->ps.velocity, body->s.pos.trDelta ); + } else { + body->s.pos.trType = TR_STATIONARY; + } + body->s.event = 0; + + // change the animation to the last-frame only, so the sequence + // doesn't repeat anew for the body + switch ( body->s.legsAnim & ~ANIM_TOGGLEBIT ) { + case BOTH_DEATH1: + case BOTH_DEAD1: + body->s.torsoAnim = body->s.legsAnim = BOTH_DEAD1; + break; + case BOTH_DEATH2: + case BOTH_DEAD2: + body->s.torsoAnim = body->s.legsAnim = BOTH_DEAD2; + break; + case BOTH_DEATH3: + case BOTH_DEAD3: + default: + body->s.torsoAnim = body->s.legsAnim = BOTH_DEAD3; + break; + } + + body->r.svFlags = ent->r.svFlags; + VectorCopy (ent->r.mins, body->r.mins); + VectorCopy (ent->r.maxs, body->r.maxs); + VectorCopy (ent->r.absmin, body->r.absmin); + VectorCopy (ent->r.absmax, body->r.absmax); + + body->clipmask = CONTENTS_SOLID | CONTENTS_PLAYERCLIP; + body->r.contents = CONTENTS_CORPSE; + body->r.ownerNum = ent->s.number; + + body->nextthink = level.time + 5000; + body->think = BodySink; + + body->die = body_die; + + // don't take more damage if already gibbed + if ( ent->health <= GIB_HEALTH ) { + body->takedamage = qfalse; + } else { + body->takedamage = qtrue; + } + + + VectorCopy ( body->s.pos.trBase, body->r.currentOrigin ); + trap_LinkEntity (body); +} + +//====================================================================== + + +/* +================== +SetClientViewAngle + +================== +*/ +void SetClientViewAngle( gentity_t *ent, vec3_t angle ) { + int i; + + // set the delta angle + for (i=0 ; i<3 ; i++) { + int cmdAngle; + + cmdAngle = ANGLE2SHORT(angle[i]); + ent->client->ps.delta_angles[i] = cmdAngle - ent->client->pers.cmd.angles[i]; + } + VectorCopy( angle, ent->s.angles ); + VectorCopy (ent->s.angles, ent->client->ps.viewangles); +} + +/* +================ +respawn +================ +*/ +void respawn( gentity_t *ent ) { + gentity_t *tent; + + CopyToBodyQue (ent); + ClientSpawn(ent); + + // add a teleportation effect + tent = G_TempEntity( ent->client->ps.origin, EV_PLAYER_TELEPORT_IN ); + tent->s.clientNum = ent->s.clientNum; +} + +/* +================ +TeamCount + +Returns number of players on a team +================ +*/ +team_t TeamCount( int ignoreClientNum, int team ) { + int i; + int count = 0; + + for ( i = 0 ; i < level.maxclients ; i++ ) { + if ( i == ignoreClientNum ) { + continue; + } + if ( level.clients[i].pers.connected == CON_DISCONNECTED ) { + continue; + } + if ( level.clients[i].sess.sessionTeam == team ) { + count++; + } + } + + return count; +} + +/* +================ +TeamLeader + +Returns the client number of the team leader +================ +*/ +int TeamLeader( int team ) { + int i; + + for ( i = 0 ; i < level.maxclients ; i++ ) { + if ( level.clients[i].pers.connected == CON_DISCONNECTED ) { + continue; + } + if ( level.clients[i].sess.sessionTeam == team ) { + if ( level.clients[i].sess.teamLeader ) + return i; + } + } + + return -1; +} + + +/* +================ +PickTeam + +================ +*/ +team_t PickTeam( int ignoreClientNum ) { + int counts[TEAM_NUM_TEAMS]; + + counts[TEAM_BLUE] = TeamCount( ignoreClientNum, TEAM_BLUE ); + counts[TEAM_RED] = TeamCount( ignoreClientNum, TEAM_RED ); + + if ( counts[TEAM_BLUE] > counts[TEAM_RED] ) { + return TEAM_RED; + } + if ( counts[TEAM_RED] > counts[TEAM_BLUE] ) { + return TEAM_BLUE; + } + // equal team count, so join the team with the lowest score + if ( level.teamScores[TEAM_BLUE] > level.teamScores[TEAM_RED] ) { + return TEAM_RED; + } + return TEAM_BLUE; +} + +/* +=========== +ForceClientSkin + +Forces a client's skin (for teamplay) +=========== +*/ +/* +static void ForceClientSkin( gclient_t *client, char *model, const char *skin ) { + char *p; + + if ((p = Q_strrchr(model, '/')) != 0) { + *p = 0; + } + + Q_strcat(model, MAX_QPATH, "/"); + Q_strcat(model, MAX_QPATH, skin); +} +*/ + +/* +=========== +ClientCheckName +============ +*/ +static void ClientCleanName( const char *in, char *out, int outSize ) { + int len, colorlessLen; + char ch; + char *p; + int spaces; + + //save room for trailing null byte + outSize--; + + len = 0; + colorlessLen = 0; + p = out; + *p = 0; + spaces = 0; + + while( 1 ) { + ch = *in++; + if( !ch ) { + break; + } + + // don't allow leading spaces + if( !*p && ch == ' ' ) { + continue; + } + + // check colors + if( ch == Q_COLOR_ESCAPE ) { + // solo trailing carat is not a color prefix + if( !*in ) { + break; + } + + // don't allow black in a name, period + if( ColorIndex(*in) == 0 ) { + in++; + continue; + } + + // make sure room in dest for both chars + if( len > outSize - 2 ) { + break; + } + + *out++ = ch; + *out++ = *in++; + len += 2; + continue; + } + + // don't allow too many consecutive spaces + if( ch == ' ' ) { + spaces++; + if( spaces > 3 ) { + continue; + } + } + else { + spaces = 0; + } + + if( len > outSize - 1 ) { + break; + } + + *out++ = ch; + colorlessLen++; + len++; + } + *out = 0; + + // don't allow empty names + if( *p == 0 || colorlessLen == 0 ) { + Q_strncpyz( p, "UnnamedPlayer", outSize ); + } +} + + +/* +=========== +ClientUserInfoChanged + +Called from ClientConnect when the player first connects and +directly by the server system when the player updates a userinfo variable. + +The game can override any of the settings and call trap_SetUserinfo +if desired. +============ +*/ +void ClientUserinfoChanged( int clientNum ) { + gentity_t *ent; + int teamTask, teamLeader, team, health; + char *s; + char model[MAX_QPATH]; + char headModel[MAX_QPATH]; + char oldname[MAX_STRING_CHARS]; + gclient_t *client; + char c1[MAX_INFO_STRING]; + char c2[MAX_INFO_STRING]; + char redTeam[MAX_INFO_STRING]; + char blueTeam[MAX_INFO_STRING]; + char userinfo[MAX_INFO_STRING]; + + ent = g_entities + clientNum; + client = ent->client; + + trap_GetUserinfo( clientNum, userinfo, sizeof( userinfo ) ); + + // check for malformed or illegal info strings + if ( !Info_Validate(userinfo) ) { + strcpy (userinfo, "\\name\\badinfo"); + } + + // check for local client + s = Info_ValueForKey( userinfo, "ip" ); + if ( !strcmp( s, "localhost" ) ) { + client->pers.localClient = qtrue; + } + + // check the item prediction + s = Info_ValueForKey( userinfo, "cg_predictItems" ); + if ( !atoi( s ) ) { + client->pers.predictItemPickup = qfalse; + } else { + client->pers.predictItemPickup = qtrue; + } + + // set name + Q_strncpyz ( oldname, client->pers.netname, sizeof( oldname ) ); + s = Info_ValueForKey (userinfo, "name"); + ClientCleanName( s, client->pers.netname, sizeof(client->pers.netname) ); + + if ( client->sess.sessionTeam == TEAM_SPECTATOR ) { + if ( client->sess.spectatorState == SPECTATOR_SCOREBOARD ) { + Q_strncpyz( client->pers.netname, "scoreboard", sizeof(client->pers.netname) ); + } + } + + if ( client->pers.connected == CON_CONNECTED ) { + if ( strcmp( oldname, client->pers.netname ) ) { + trap_SendServerCommand( -1, va("print \"%s" S_COLOR_WHITE " renamed to %s\n\"", oldname, + client->pers.netname) ); + } + } + + // set max health +#ifdef MISSIONPACK + if (client->ps.powerups[PW_GUARD]) { + client->pers.maxHealth = 200; + } else { + health = atoi( Info_ValueForKey( userinfo, "handicap" ) ); + client->pers.maxHealth = health; + if ( client->pers.maxHealth < 1 || client->pers.maxHealth > 100 ) { + client->pers.maxHealth = 100; + } + } +#else + health = atoi( Info_ValueForKey( userinfo, "handicap" ) ); + client->pers.maxHealth = health; + if ( client->pers.maxHealth < 1 || client->pers.maxHealth > 100 ) { + client->pers.maxHealth = 100; + } +#endif + client->ps.stats[STAT_MAX_HEALTH] = client->pers.maxHealth; + + // set model + if( g_gametype.integer >= GT_TEAM ) { + Q_strncpyz( model, Info_ValueForKey (userinfo, "team_model"), sizeof( model ) ); + Q_strncpyz( headModel, Info_ValueForKey (userinfo, "team_headmodel"), sizeof( headModel ) ); + } else { + Q_strncpyz( model, Info_ValueForKey (userinfo, "model"), sizeof( model ) ); + Q_strncpyz( headModel, Info_ValueForKey (userinfo, "headmodel"), sizeof( headModel ) ); + } + + // bots set their team a few frames later + if (g_gametype.integer >= GT_TEAM && g_entities[clientNum].r.svFlags & SVF_BOT) { + s = Info_ValueForKey( userinfo, "team" ); + if ( !Q_stricmp( s, "red" ) || !Q_stricmp( s, "r" ) ) { + team = TEAM_RED; + } else if ( !Q_stricmp( s, "blue" ) || !Q_stricmp( s, "b" ) ) { + team = TEAM_BLUE; + } else { + // pick the team with the least number of players + team = PickTeam( clientNum ); + } + } + else { + team = client->sess.sessionTeam; + } + +/* NOTE: all client side now + + // team + switch( team ) { + case TEAM_RED: + ForceClientSkin(client, model, "red"); +// ForceClientSkin(client, headModel, "red"); + break; + case TEAM_BLUE: + ForceClientSkin(client, model, "blue"); +// ForceClientSkin(client, headModel, "blue"); + break; + } + // don't ever use a default skin in teamplay, it would just waste memory + // however bots will always join a team but they spawn in as spectator + if ( g_gametype.integer >= GT_TEAM && team == TEAM_SPECTATOR) { + ForceClientSkin(client, model, "red"); +// ForceClientSkin(client, headModel, "red"); + } +*/ + +#ifdef MISSIONPACK + if (g_gametype.integer >= GT_TEAM) { + client->pers.teamInfo = qtrue; + } else { + s = Info_ValueForKey( userinfo, "teamoverlay" ); + if ( ! *s || atoi( s ) != 0 ) { + client->pers.teamInfo = qtrue; + } else { + client->pers.teamInfo = qfalse; + } + } +#else + // teamInfo + s = Info_ValueForKey( userinfo, "teamoverlay" ); + if ( ! *s || atoi( s ) != 0 ) { + client->pers.teamInfo = qtrue; + } else { + client->pers.teamInfo = qfalse; + } +#endif + /* + s = Info_ValueForKey( userinfo, "cg_pmove_fixed" ); + if ( !*s || atoi( s ) == 0 ) { + client->pers.pmoveFixed = qfalse; + } + else { + client->pers.pmoveFixed = qtrue; + } + */ + + // team task (0 = none, 1 = offence, 2 = defence) + teamTask = atoi(Info_ValueForKey(userinfo, "teamtask")); + // team Leader (1 = leader, 0 is normal player) + teamLeader = client->sess.teamLeader; + + // colors + strcpy(c1, Info_ValueForKey( userinfo, "color1" )); + strcpy(c2, Info_ValueForKey( userinfo, "color2" )); + + strcpy(redTeam, Info_ValueForKey( userinfo, "g_redteam" )); + strcpy(blueTeam, Info_ValueForKey( userinfo, "g_blueteam" )); + + // send over a subset of the userinfo keys so other clients can + // print scoreboards, display models, and play custom sounds + if ( ent->r.svFlags & SVF_BOT ) { + s = va("n\\%s\\t\\%i\\model\\%s\\hmodel\\%s\\c1\\%s\\c2\\%s\\hc\\%i\\w\\%i\\l\\%i\\skill\\%s\\tt\\%d\\tl\\%d", + client->pers.netname, team, model, headModel, c1, c2, + client->pers.maxHealth, client->sess.wins, client->sess.losses, + Info_ValueForKey( userinfo, "skill" ), teamTask, teamLeader ); + } else { + s = va("n\\%s\\t\\%i\\model\\%s\\hmodel\\%s\\g_redteam\\%s\\g_blueteam\\%s\\c1\\%s\\c2\\%s\\hc\\%i\\w\\%i\\l\\%i\\tt\\%d\\tl\\%d", + client->pers.netname, client->sess.sessionTeam, model, headModel, redTeam, blueTeam, c1, c2, + client->pers.maxHealth, client->sess.wins, client->sess.losses, teamTask, teamLeader); + } + + trap_SetConfigstring( CS_PLAYERS+clientNum, s ); + + // this is not the userinfo, more like the configstring actually + G_LogPrintf( "ClientUserinfoChanged: %i %s\n", clientNum, s ); +} + + +/* +=========== +ClientConnect + +Called when a player begins connecting to the server. +Called again for every map change or tournement restart. + +The session information will be valid after exit. + +Return NULL if the client should be allowed, otherwise return +a string with the reason for denial. + +Otherwise, the client will be sent the current gamestate +and will eventually get to ClientBegin. + +firstTime will be qtrue the very first time a client connects +to the server machine, but qfalse on map changes and tournement +restarts. +============ +*/ +char *ClientConnect( int clientNum, qboolean firstTime, qboolean isBot ) { + char *value; +// char *areabits; + gclient_t *client; + char userinfo[MAX_INFO_STRING]; + gentity_t *ent; + + ent = &g_entities[ clientNum ]; + + trap_GetUserinfo( clientNum, userinfo, sizeof( userinfo ) ); + + // IP filtering + // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=500 + // recommanding PB based IP / GUID banning, the builtin system is pretty limited + // check to see if they are on the banned IP list + value = Info_ValueForKey (userinfo, "ip"); + if ( G_FilterPacket( value ) ) { + return "You are banned from this server."; + } + + // we don't check password for bots and local client + // NOTE: local client <-> "ip" "localhost" + // this means this client is not running in our current process + if ( !( ent->r.svFlags & SVF_BOT ) && (strcmp(value, "localhost") != 0)) { + // check for a password + value = Info_ValueForKey (userinfo, "password"); + if ( g_password.string[0] && Q_stricmp( g_password.string, "none" ) && + strcmp( g_password.string, value) != 0) { + return "Invalid password"; + } + } + + // they can connect + ent->client = level.clients + clientNum; + client = ent->client; + +// areabits = client->areabits; + + memset( client, 0, sizeof(*client) ); + + client->pers.connected = CON_CONNECTING; + + // read or initialize the session data + if ( firstTime || level.newSession ) { + G_InitSessionData( client, userinfo ); + } + G_ReadSessionData( client ); + + if( isBot ) { + ent->r.svFlags |= SVF_BOT; + ent->inuse = qtrue; + if( !G_BotConnect( clientNum, !firstTime ) ) { + return "BotConnectfailed"; + } + } + + // get and distribute relevent paramters + G_LogPrintf( "ClientConnect: %i\n", clientNum ); + ClientUserinfoChanged( clientNum ); + + // don't do the "xxx connected" messages if they were caried over from previous level + if ( firstTime ) { + trap_SendServerCommand( -1, va("print \"%s" S_COLOR_WHITE " connected\n\"", client->pers.netname) ); + } + + if ( g_gametype.integer >= GT_TEAM && + client->sess.sessionTeam != TEAM_SPECTATOR ) { + BroadcastTeamChange( client, -1 ); + } + + // count current clients and rank for scoreboard + CalculateRanks(); + + // for statistics +// client->areabits = areabits; +// if ( !client->areabits ) +// client->areabits = G_Alloc( (trap_AAS_PointReachabilityAreaIndex( NULL ) + 7) / 8 ); + + return NULL; +} + +/* +=========== +ClientBegin + +called when a client has finished connecting, and is ready +to be placed into the level. This will happen every level load, +and on transition between teams, but doesn't happen on respawns +============ +*/ +void ClientBegin( int clientNum ) { + gentity_t *ent; + gclient_t *client; + gentity_t *tent; + int flags; + + ent = g_entities + clientNum; + + client = level.clients + clientNum; + + if ( ent->r.linked ) { + trap_UnlinkEntity( ent ); + } + G_InitGentity( ent ); + ent->touch = 0; + ent->pain = 0; + ent->client = client; + + client->pers.connected = CON_CONNECTED; + client->pers.enterTime = level.time; + client->pers.teamState.state = TEAM_BEGIN; + + // save eflags around this, because changing teams will + // cause this to happen with a valid entity, and we + // want to make sure the teleport bit is set right + // so the viewpoint doesn't interpolate through the + // world to the new position + flags = client->ps.eFlags; + memset( &client->ps, 0, sizeof( client->ps ) ); + client->ps.eFlags = flags; + + // locate ent at a spawn point + ClientSpawn( ent ); + + if ( client->sess.sessionTeam != TEAM_SPECTATOR ) { + // send event + tent = G_TempEntity( ent->client->ps.origin, EV_PLAYER_TELEPORT_IN ); + tent->s.clientNum = ent->s.clientNum; + + if ( g_gametype.integer != GT_TOURNAMENT ) { + trap_SendServerCommand( -1, va("print \"%s" S_COLOR_WHITE " entered the game\n\"", client->pers.netname) ); + } + } + G_LogPrintf( "ClientBegin: %i\n", clientNum ); + + // count current clients and rank for scoreboard + CalculateRanks(); +} + +/* +=========== +ClientSpawn + +Called every time a client is placed fresh in the world: +after the first ClientBegin, and after each respawn +Initializes all non-persistant parts of playerState +============ +*/ +void ClientSpawn(gentity_t *ent) { + int index; + vec3_t spawn_origin, spawn_angles; + gclient_t *client; + int i; + clientPersistant_t saved; + clientSession_t savedSess; + int persistant[MAX_PERSISTANT]; + gentity_t *spawnPoint; + int flags; + int savedPing; +// char *savedAreaBits; + int accuracy_hits, accuracy_shots; + int eventSequence; + char userinfo[MAX_INFO_STRING]; + + index = ent - g_entities; + client = ent->client; + + // find a spawn point + // do it before setting health back up, so farthest + // ranging doesn't count this client + if ( client->sess.sessionTeam == TEAM_SPECTATOR ) { + spawnPoint = SelectSpectatorSpawnPoint ( + spawn_origin, spawn_angles); + } else if (g_gametype.integer >= GT_CTF ) { + // all base oriented team games use the CTF spawn points + spawnPoint = SelectCTFSpawnPoint ( + client->sess.sessionTeam, + client->pers.teamState.state, + spawn_origin, spawn_angles); + } else { + do { + // the first spawn should be at a good looking spot + if ( !client->pers.initialSpawn && client->pers.localClient ) { + client->pers.initialSpawn = qtrue; + spawnPoint = SelectInitialSpawnPoint( spawn_origin, spawn_angles ); + } else { + // don't spawn near existing origin if possible + spawnPoint = SelectSpawnPoint ( + client->ps.origin, + spawn_origin, spawn_angles); + } + + // Tim needs to prevent bots from spawning at the initial point + // on q3dm0... + if ( ( spawnPoint->flags & FL_NO_BOTS ) && ( ent->r.svFlags & SVF_BOT ) ) { + continue; // try again + } + // just to be symetric, we have a nohumans option... + if ( ( spawnPoint->flags & FL_NO_HUMANS ) && !( ent->r.svFlags & SVF_BOT ) ) { + continue; // try again + } + + break; + + } while ( 1 ); + } + client->pers.teamState.state = TEAM_ACTIVE; + + // always clear the kamikaze flag + ent->s.eFlags &= ~EF_KAMIKAZE; + + // toggle the teleport bit so the client knows to not lerp + // and never clear the voted flag + flags = ent->client->ps.eFlags & (EF_TELEPORT_BIT | EF_VOTED | EF_TEAMVOTED); + flags ^= EF_TELEPORT_BIT; + + // clear everything but the persistant data + + saved = client->pers; + savedSess = client->sess; + savedPing = client->ps.ping; +// savedAreaBits = client->areabits; + accuracy_hits = client->accuracy_hits; + accuracy_shots = client->accuracy_shots; + for ( i = 0 ; i < MAX_PERSISTANT ; i++ ) { + persistant[i] = client->ps.persistant[i]; + } + eventSequence = client->ps.eventSequence; + + memset (client, 0, sizeof(*client)); // bk FIXME: Com_Memset? + + client->pers = saved; + client->sess = savedSess; + client->ps.ping = savedPing; +// client->areabits = savedAreaBits; + client->accuracy_hits = accuracy_hits; + client->accuracy_shots = accuracy_shots; + client->lastkilled_client = -1; + + for ( i = 0 ; i < MAX_PERSISTANT ; i++ ) { + client->ps.persistant[i] = persistant[i]; + } + client->ps.eventSequence = eventSequence; + // increment the spawncount so the client will detect the respawn + client->ps.persistant[PERS_SPAWN_COUNT]++; + client->ps.persistant[PERS_TEAM] = client->sess.sessionTeam; + + client->airOutTime = level.time + 12000; + + trap_GetUserinfo( index, userinfo, sizeof(userinfo) ); + // set max health + client->pers.maxHealth = atoi( Info_ValueForKey( userinfo, "handicap" ) ); + if ( client->pers.maxHealth < 1 || client->pers.maxHealth > 100 ) { + client->pers.maxHealth = 100; + } + // clear entity values + client->ps.stats[STAT_MAX_HEALTH] = client->pers.maxHealth; + client->ps.eFlags = flags; + + ent->s.groundEntityNum = ENTITYNUM_NONE; + ent->client = &level.clients[index]; + ent->takedamage = qtrue; + ent->inuse = qtrue; + ent->classname = "player"; + ent->r.contents = CONTENTS_BODY; + ent->clipmask = MASK_PLAYERSOLID; + ent->die = player_die; + ent->waterlevel = 0; + ent->watertype = 0; + ent->flags = 0; + + VectorCopy (playerMins, ent->r.mins); + VectorCopy (playerMaxs, ent->r.maxs); + + client->ps.clientNum = index; + + client->ps.stats[STAT_WEAPONS] = ( 1 << WP_MACHINEGUN ); + if ( g_gametype.integer == GT_TEAM ) { + client->ps.ammo[WP_MACHINEGUN] = 50; + } else { + client->ps.ammo[WP_MACHINEGUN] = 100; + } + + client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_GAUNTLET ); + client->ps.ammo[WP_GAUNTLET] = -1; + client->ps.ammo[WP_GRAPPLING_HOOK] = -1; + + // health will count down towards max_health + ent->health = client->ps.stats[STAT_HEALTH] = client->ps.stats[STAT_MAX_HEALTH] + 25; + + G_SetOrigin( ent, spawn_origin ); + VectorCopy( spawn_origin, client->ps.origin ); + + // the respawned flag will be cleared after the attack and jump keys come up + client->ps.pm_flags |= PMF_RESPAWNED; + + trap_GetUsercmd( client - level.clients, &ent->client->pers.cmd ); + SetClientViewAngle( ent, spawn_angles ); + + if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) { + + } else { + G_KillBox( ent ); + trap_LinkEntity (ent); + + // force the base weapon up + client->ps.weapon = WP_MACHINEGUN; + client->ps.weaponstate = WEAPON_READY; + + } + + // don't allow full run speed for a bit + client->ps.pm_flags |= PMF_TIME_KNOCKBACK; + client->ps.pm_time = 100; + + client->respawnTime = level.time; + client->inactivityTime = level.time + g_inactivity.integer * 1000; + client->latched_buttons = 0; + + // set default animations + client->ps.torsoAnim = TORSO_STAND; + client->ps.legsAnim = LEGS_IDLE; + + if ( level.intermissiontime ) { + MoveClientToIntermission( ent ); + } else { + // fire the targets of the spawn point + G_UseTargets( spawnPoint, ent ); + + // select the highest weapon number available, after any + // spawn given items have fired + client->ps.weapon = 1; + for ( i = WP_NUM_WEAPONS - 1 ; i > 0 ; i-- ) { + if ( client->ps.stats[STAT_WEAPONS] & ( 1 << i ) ) { + client->ps.weapon = i; + break; + } + } + } + + // run a client frame to drop exactly to the floor, + // initialize animations and other things + client->ps.commandTime = level.time - 100; + ent->client->pers.cmd.serverTime = level.time; + ClientThink( ent-g_entities ); + + // positively link the client, even if the command times are weird + if ( ent->client->sess.sessionTeam != TEAM_SPECTATOR ) { + BG_PlayerStateToEntityState( &client->ps, &ent->s, qtrue ); + VectorCopy( ent->client->ps.origin, ent->r.currentOrigin ); + trap_LinkEntity( ent ); + } + + // run the presend to set anything else + ClientEndFrame( ent ); + + // clear entity state values + BG_PlayerStateToEntityState( &client->ps, &ent->s, qtrue ); +} + + +/* +=========== +ClientDisconnect + +Called when a player drops from the server. +Will not be called between levels. + +This should NOT be called directly by any game logic, +call trap_DropClient(), which will call this and do +server system housekeeping. +============ +*/ +void ClientDisconnect( int clientNum ) { + gentity_t *ent; + gentity_t *tent; + int i; + + // cleanup if we are kicking a bot that + // hasn't spawned yet + G_RemoveQueuedBotBegin( clientNum ); + + ent = g_entities + clientNum; + if ( !ent->client ) { + return; + } + + // stop any following clients + for ( i = 0 ; i < level.maxclients ; i++ ) { + if ( level.clients[i].sess.sessionTeam == TEAM_SPECTATOR + && level.clients[i].sess.spectatorState == SPECTATOR_FOLLOW + && level.clients[i].sess.spectatorClient == clientNum ) { + StopFollowing( &g_entities[i] ); + } + } + + // send effect if they were completely connected + if ( ent->client->pers.connected == CON_CONNECTED + && ent->client->sess.sessionTeam != TEAM_SPECTATOR ) { + tent = G_TempEntity( ent->client->ps.origin, EV_PLAYER_TELEPORT_OUT ); + tent->s.clientNum = ent->s.clientNum; + + // They don't get to take powerups with them! + // Especially important for stuff like CTF flags + TossClientItems( ent ); +#ifdef MISSIONPACK + TossClientPersistantPowerups( ent ); + if( g_gametype.integer == GT_HARVESTER ) { + TossClientCubes( ent ); + } +#endif + + } + + G_LogPrintf( "ClientDisconnect: %i\n", clientNum ); + + // if we are playing in tourney mode and losing, give a win to the other player + if ( (g_gametype.integer == GT_TOURNAMENT ) + && !level.intermissiontime + && !level.warmupTime && level.sortedClients[1] == clientNum ) { + level.clients[ level.sortedClients[0] ].sess.wins++; + ClientUserinfoChanged( level.sortedClients[0] ); + } + + trap_UnlinkEntity (ent); + ent->s.modelindex = 0; + ent->inuse = qfalse; + ent->classname = "disconnected"; + ent->client->pers.connected = CON_DISCONNECTED; + ent->client->ps.persistant[PERS_TEAM] = TEAM_FREE; + ent->client->sess.sessionTeam = TEAM_FREE; + + trap_SetConfigstring( CS_PLAYERS + clientNum, ""); + + CalculateRanks(); + + if ( ent->r.svFlags & SVF_BOT ) { + BotAIShutdownClient( clientNum, qfalse ); + } +} + + diff --git a/code/game/g_cmds.c b/code/game/g_cmds.c index e72c80e..8773676 100755 --- a/code/game/g_cmds.c +++ b/code/game/g_cmds.c @@ -1,1701 +1,1701 @@ -/* -=========================================================================== -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 Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// -#include "g_local.h" - -#include "../../ui/menudef.h" // for the voice chats - -/* -================== -DeathmatchScoreboardMessage - -================== -*/ -void DeathmatchScoreboardMessage( gentity_t *ent ) { - char entry[1024]; - char string[1400]; - int stringlength; - int i, j; - gclient_t *cl; - int numSorted, scoreFlags, accuracy, perfect; - - // send the latest information on all clients - string[0] = 0; - stringlength = 0; - scoreFlags = 0; - - numSorted = level.numConnectedClients; - - for (i=0 ; i < numSorted ; i++) { - int ping; - - cl = &level.clients[level.sortedClients[i]]; - - if ( cl->pers.connected == CON_CONNECTING ) { - ping = -1; - } else { - ping = cl->ps.ping < 999 ? cl->ps.ping : 999; - } - - if( cl->accuracy_shots ) { - accuracy = cl->accuracy_hits * 100 / cl->accuracy_shots; - } - else { - accuracy = 0; - } - perfect = ( cl->ps.persistant[PERS_RANK] == 0 && cl->ps.persistant[PERS_KILLED] == 0 ) ? 1 : 0; - - Com_sprintf (entry, sizeof(entry), - " %i %i %i %i %i %i %i %i %i %i %i %i %i %i", level.sortedClients[i], - cl->ps.persistant[PERS_SCORE], ping, (level.time - cl->pers.enterTime)/60000, - scoreFlags, g_entities[level.sortedClients[i]].s.powerups, accuracy, - cl->ps.persistant[PERS_IMPRESSIVE_COUNT], - cl->ps.persistant[PERS_EXCELLENT_COUNT], - cl->ps.persistant[PERS_GAUNTLET_FRAG_COUNT], - cl->ps.persistant[PERS_DEFEND_COUNT], - cl->ps.persistant[PERS_ASSIST_COUNT], - perfect, - cl->ps.persistant[PERS_CAPTURES]); - j = strlen(entry); - if (stringlength + j > 1024) - break; - strcpy (string + stringlength, entry); - stringlength += j; - } - - trap_SendServerCommand( ent-g_entities, va("scores %i %i %i%s", i, - level.teamScores[TEAM_RED], level.teamScores[TEAM_BLUE], - string ) ); -} - - -/* -================== -Cmd_Score_f - -Request current scoreboard information -================== -*/ -void Cmd_Score_f( gentity_t *ent ) { - DeathmatchScoreboardMessage( ent ); -} - - - -/* -================== -CheatsOk -================== -*/ -qboolean CheatsOk( gentity_t *ent ) { - if ( !g_cheats.integer ) { - trap_SendServerCommand( ent-g_entities, va("print \"Cheats are not enabled on this server.\n\"")); - return qfalse; - } - if ( ent->health <= 0 ) { - trap_SendServerCommand( ent-g_entities, va("print \"You must be alive to use this command.\n\"")); - return qfalse; - } - return qtrue; -} - - -/* -================== -ConcatArgs -================== -*/ -char *ConcatArgs( int start ) { - int i, c, tlen; - static char line[MAX_STRING_CHARS]; - int len; - char arg[MAX_STRING_CHARS]; - - len = 0; - c = trap_Argc(); - for ( i = start ; i < c ; i++ ) { - trap_Argv( i, arg, sizeof( arg ) ); - tlen = strlen( arg ); - if ( len + tlen >= MAX_STRING_CHARS - 1 ) { - break; - } - memcpy( line + len, arg, tlen ); - len += tlen; - if ( i != c - 1 ) { - line[len] = ' '; - len++; - } - } - - line[len] = 0; - - return line; -} - -/* -================== -SanitizeString - -Remove case and control characters -================== -*/ -void SanitizeString( char *in, char *out ) { - while ( *in ) { - if ( *in == 27 ) { - in += 2; // skip color code - continue; - } - if ( *in < 32 ) { - in++; - continue; - } - *out++ = tolower( *in++ ); - } - - *out = 0; -} - -/* -================== -ClientNumberFromString - -Returns a player number for either a number or name string -Returns -1 if invalid -================== -*/ -int ClientNumberFromString( gentity_t *to, char *s ) { - gclient_t *cl; - int idnum; - char s2[MAX_STRING_CHARS]; - char n2[MAX_STRING_CHARS]; - - // numeric values are just slot numbers - if (s[0] >= '0' && s[0] <= '9') { - idnum = atoi( s ); - if ( idnum < 0 || idnum >= level.maxclients ) { - trap_SendServerCommand( to-g_entities, va("print \"Bad client slot: %i\n\"", idnum)); - return -1; - } - - cl = &level.clients[idnum]; - if ( cl->pers.connected != CON_CONNECTED ) { - trap_SendServerCommand( to-g_entities, va("print \"Client %i is not active\n\"", idnum)); - return -1; - } - return idnum; - } - - // check for a name match - SanitizeString( s, s2 ); - for ( idnum=0,cl=level.clients ; idnum < level.maxclients ; idnum++,cl++ ) { - if ( cl->pers.connected != CON_CONNECTED ) { - continue; - } - SanitizeString( cl->pers.netname, n2 ); - if ( !strcmp( n2, s2 ) ) { - return idnum; - } - } - - trap_SendServerCommand( to-g_entities, va("print \"User %s is not on the server\n\"", s)); - return -1; -} - -/* -================== -Cmd_Give_f - -Give items to a client -================== -*/ -void Cmd_Give_f (gentity_t *ent) -{ - char *name; - gitem_t *it; - int i; - qboolean give_all; - gentity_t *it_ent; - trace_t trace; - - if ( !CheatsOk( ent ) ) { - return; - } - - name = ConcatArgs( 1 ); - - if (Q_stricmp(name, "all") == 0) - give_all = qtrue; - else - give_all = qfalse; - - if (give_all || Q_stricmp( name, "health") == 0) - { - ent->health = ent->client->ps.stats[STAT_MAX_HEALTH]; - if (!give_all) - return; - } - - if (give_all || Q_stricmp(name, "weapons") == 0) - { - ent->client->ps.stats[STAT_WEAPONS] = (1 << WP_NUM_WEAPONS) - 1 - - ( 1 << WP_GRAPPLING_HOOK ) - ( 1 << WP_NONE ); - if (!give_all) - return; - } - - if (give_all || Q_stricmp(name, "ammo") == 0) - { - for ( i = 0 ; i < MAX_WEAPONS ; i++ ) { - ent->client->ps.ammo[i] = 999; - } - if (!give_all) - return; - } - - if (give_all || Q_stricmp(name, "armor") == 0) - { - ent->client->ps.stats[STAT_ARMOR] = 200; - - if (!give_all) - return; - } - - if (Q_stricmp(name, "excellent") == 0) { - ent->client->ps.persistant[PERS_EXCELLENT_COUNT]++; - return; - } - if (Q_stricmp(name, "impressive") == 0) { - ent->client->ps.persistant[PERS_IMPRESSIVE_COUNT]++; - return; - } - if (Q_stricmp(name, "gauntletaward") == 0) { - ent->client->ps.persistant[PERS_GAUNTLET_FRAG_COUNT]++; - return; - } - if (Q_stricmp(name, "defend") == 0) { - ent->client->ps.persistant[PERS_DEFEND_COUNT]++; - return; - } - if (Q_stricmp(name, "assist") == 0) { - ent->client->ps.persistant[PERS_ASSIST_COUNT]++; - return; - } - - // spawn a specific item right on the player - if ( !give_all ) { - it = BG_FindItem (name); - if (!it) { - return; - } - - it_ent = G_Spawn(); - VectorCopy( ent->r.currentOrigin, it_ent->s.origin ); - it_ent->classname = it->classname; - G_SpawnItem (it_ent, it); - FinishSpawningItem(it_ent ); - memset( &trace, 0, sizeof( trace ) ); - Touch_Item (it_ent, ent, &trace); - if (it_ent->inuse) { - G_FreeEntity( it_ent ); - } - } -} - - -/* -================== -Cmd_God_f - -Sets client to godmode - -argv(0) god -================== -*/ -void Cmd_God_f (gentity_t *ent) -{ - char *msg; - - if ( !CheatsOk( ent ) ) { - return; - } - - ent->flags ^= FL_GODMODE; - if (!(ent->flags & FL_GODMODE) ) - msg = "godmode OFF\n"; - else - msg = "godmode ON\n"; - - trap_SendServerCommand( ent-g_entities, va("print \"%s\"", msg)); -} - - -/* -================== -Cmd_Notarget_f - -Sets client to notarget - -argv(0) notarget -================== -*/ -void Cmd_Notarget_f( gentity_t *ent ) { - char *msg; - - if ( !CheatsOk( ent ) ) { - return; - } - - ent->flags ^= FL_NOTARGET; - if (!(ent->flags & FL_NOTARGET) ) - msg = "notarget OFF\n"; - else - msg = "notarget ON\n"; - - trap_SendServerCommand( ent-g_entities, va("print \"%s\"", msg)); -} - - -/* -================== -Cmd_Noclip_f - -argv(0) noclip -================== -*/ -void Cmd_Noclip_f( gentity_t *ent ) { - char *msg; - - if ( !CheatsOk( ent ) ) { - return; - } - - if ( ent->client->noclip ) { - msg = "noclip OFF\n"; - } else { - msg = "noclip ON\n"; - } - ent->client->noclip = !ent->client->noclip; - - trap_SendServerCommand( ent-g_entities, va("print \"%s\"", msg)); -} - - -/* -================== -Cmd_LevelShot_f - -This is just to help generate the level pictures -for the menus. It goes to the intermission immediately -and sends over a command to the client to resize the view, -hide the scoreboard, and take a special screenshot -================== -*/ -void Cmd_LevelShot_f( gentity_t *ent ) { - if ( !CheatsOk( ent ) ) { - return; - } - - // doesn't work in single player - if ( g_gametype.integer != 0 ) { - trap_SendServerCommand( ent-g_entities, - "print \"Must be in g_gametype 0 for levelshot\n\"" ); - return; - } - - BeginIntermission(); - trap_SendServerCommand( ent-g_entities, "clientLevelShot" ); -} - - -/* -================== -Cmd_LevelShot_f - -This is just to help generate the level pictures -for the menus. It goes to the intermission immediately -and sends over a command to the client to resize the view, -hide the scoreboard, and take a special screenshot -================== -*/ -void Cmd_TeamTask_f( gentity_t *ent ) { - char userinfo[MAX_INFO_STRING]; - char arg[MAX_TOKEN_CHARS]; - int task; - int client = ent->client - level.clients; - - if ( trap_Argc() != 2 ) { - return; - } - trap_Argv( 1, arg, sizeof( arg ) ); - task = atoi( arg ); - - trap_GetUserinfo(client, userinfo, sizeof(userinfo)); - Info_SetValueForKey(userinfo, "teamtask", va("%d", task)); - trap_SetUserinfo(client, userinfo); - ClientUserinfoChanged(client); -} - - - -/* -================= -Cmd_Kill_f -================= -*/ -void Cmd_Kill_f( gentity_t *ent ) { - if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) { - return; - } - if (ent->health <= 0) { - return; - } - ent->flags &= ~FL_GODMODE; - ent->client->ps.stats[STAT_HEALTH] = ent->health = -999; - player_die (ent, ent, ent, 100000, MOD_SUICIDE); -} - -/* -================= -BroadCastTeamChange - -Let everyone know about a team change -================= -*/ -void BroadcastTeamChange( gclient_t *client, int oldTeam ) -{ - if ( client->sess.sessionTeam == TEAM_RED ) { - trap_SendServerCommand( -1, va("cp \"%s" S_COLOR_WHITE " joined the red team.\n\"", - client->pers.netname) ); - } else if ( client->sess.sessionTeam == TEAM_BLUE ) { - trap_SendServerCommand( -1, va("cp \"%s" S_COLOR_WHITE " joined the blue team.\n\"", - client->pers.netname)); - } else if ( client->sess.sessionTeam == TEAM_SPECTATOR && oldTeam != TEAM_SPECTATOR ) { - trap_SendServerCommand( -1, va("cp \"%s" S_COLOR_WHITE " joined the spectators.\n\"", - client->pers.netname)); - } else if ( client->sess.sessionTeam == TEAM_FREE ) { - trap_SendServerCommand( -1, va("cp \"%s" S_COLOR_WHITE " joined the battle.\n\"", - client->pers.netname)); - } -} - -/* -================= -SetTeam -================= -*/ -void SetTeam( gentity_t *ent, char *s ) { - int team, oldTeam; - gclient_t *client; - int clientNum; - spectatorState_t specState; - int specClient; - int teamLeader; - - // - // see what change is requested - // - client = ent->client; - - clientNum = client - level.clients; - specClient = 0; - specState = SPECTATOR_NOT; - if ( !Q_stricmp( s, "scoreboard" ) || !Q_stricmp( s, "score" ) ) { - team = TEAM_SPECTATOR; - specState = SPECTATOR_SCOREBOARD; - } else if ( !Q_stricmp( s, "follow1" ) ) { - team = TEAM_SPECTATOR; - specState = SPECTATOR_FOLLOW; - specClient = -1; - } else if ( !Q_stricmp( s, "follow2" ) ) { - team = TEAM_SPECTATOR; - specState = SPECTATOR_FOLLOW; - specClient = -2; - } else if ( !Q_stricmp( s, "spectator" ) || !Q_stricmp( s, "s" ) ) { - team = TEAM_SPECTATOR; - specState = SPECTATOR_FREE; - } else if ( g_gametype.integer >= GT_TEAM ) { - // if running a team game, assign player to one of the teams - specState = SPECTATOR_NOT; - if ( !Q_stricmp( s, "red" ) || !Q_stricmp( s, "r" ) ) { - team = TEAM_RED; - } else if ( !Q_stricmp( s, "blue" ) || !Q_stricmp( s, "b" ) ) { - team = TEAM_BLUE; - } else { - // pick the team with the least number of players - team = PickTeam( clientNum ); - } - - if ( g_teamForceBalance.integer ) { - int counts[TEAM_NUM_TEAMS]; - - counts[TEAM_BLUE] = TeamCount( ent->client->ps.clientNum, TEAM_BLUE ); - counts[TEAM_RED] = TeamCount( ent->client->ps.clientNum, TEAM_RED ); - - // We allow a spread of two - if ( team == TEAM_RED && counts[TEAM_RED] - counts[TEAM_BLUE] > 1 ) { - trap_SendServerCommand( ent->client->ps.clientNum, - "cp \"Red team has too many players.\n\"" ); - return; // ignore the request - } - if ( team == TEAM_BLUE && counts[TEAM_BLUE] - counts[TEAM_RED] > 1 ) { - trap_SendServerCommand( ent->client->ps.clientNum, - "cp \"Blue team has too many players.\n\"" ); - return; // ignore the request - } - - // It's ok, the team we are switching to has less or same number of players - } - - } else { - // force them to spectators if there aren't any spots free - team = TEAM_FREE; - } - - // override decision if limiting the players - if ( (g_gametype.integer == GT_TOURNAMENT) - && level.numNonSpectatorClients >= 2 ) { - team = TEAM_SPECTATOR; - } else if ( g_maxGameClients.integer > 0 && - level.numNonSpectatorClients >= g_maxGameClients.integer ) { - team = TEAM_SPECTATOR; - } - - // - // decide if we will allow the change - // - oldTeam = client->sess.sessionTeam; - if ( team == oldTeam && team != TEAM_SPECTATOR ) { - return; - } - - // - // execute the team change - // - - // if the player was dead leave the body - if ( client->ps.stats[STAT_HEALTH] <= 0 ) { - CopyToBodyQue(ent); - } - - // he starts at 'base' - client->pers.teamState.state = TEAM_BEGIN; - if ( oldTeam != TEAM_SPECTATOR ) { - // Kill him (makes sure he loses flags, etc) - ent->flags &= ~FL_GODMODE; - ent->client->ps.stats[STAT_HEALTH] = ent->health = 0; - player_die (ent, ent, ent, 100000, MOD_SUICIDE); - - } - // they go to the end of the line for tournements - if ( team == TEAM_SPECTATOR ) { - client->sess.spectatorTime = level.time; - } - - client->sess.sessionTeam = team; - client->sess.spectatorState = specState; - client->sess.spectatorClient = specClient; - - client->sess.teamLeader = qfalse; - if ( team == TEAM_RED || team == TEAM_BLUE ) { - teamLeader = TeamLeader( team ); - // if there is no team leader or the team leader is a bot and this client is not a bot - if ( teamLeader == -1 || ( !(g_entities[clientNum].r.svFlags & SVF_BOT) && (g_entities[teamLeader].r.svFlags & SVF_BOT) ) ) { - SetLeader( team, clientNum ); - } - } - // make sure there is a team leader on the team the player came from - if ( oldTeam == TEAM_RED || oldTeam == TEAM_BLUE ) { - CheckTeamLeader( oldTeam ); - } - - BroadcastTeamChange( client, oldTeam ); - - // get and distribute relevent paramters - ClientUserinfoChanged( clientNum ); - - ClientBegin( clientNum ); -} - -/* -================= -StopFollowing - -If the client being followed leaves the game, or you just want to drop -to free floating spectator mode -================= -*/ -void StopFollowing( gentity_t *ent ) { - ent->client->ps.persistant[ PERS_TEAM ] = TEAM_SPECTATOR; - ent->client->sess.sessionTeam = TEAM_SPECTATOR; - ent->client->sess.spectatorState = SPECTATOR_FREE; - ent->client->ps.pm_flags &= ~PMF_FOLLOW; - ent->r.svFlags &= ~SVF_BOT; - ent->client->ps.clientNum = ent - g_entities; -} - -/* -================= -Cmd_Team_f -================= -*/ -void Cmd_Team_f( gentity_t *ent ) { - int oldTeam; - char s[MAX_TOKEN_CHARS]; - - if ( trap_Argc() != 2 ) { - oldTeam = ent->client->sess.sessionTeam; - switch ( oldTeam ) { - case TEAM_BLUE: - trap_SendServerCommand( ent-g_entities, "print \"Blue team\n\"" ); - break; - case TEAM_RED: - trap_SendServerCommand( ent-g_entities, "print \"Red team\n\"" ); - break; - case TEAM_FREE: - trap_SendServerCommand( ent-g_entities, "print \"Free team\n\"" ); - break; - case TEAM_SPECTATOR: - trap_SendServerCommand( ent-g_entities, "print \"Spectator team\n\"" ); - break; - } - return; - } - - if ( ent->client->switchTeamTime > level.time ) { - trap_SendServerCommand( ent-g_entities, "print \"May not switch teams more than once per 5 seconds.\n\"" ); - return; - } - - // if they are playing a tournement game, count as a loss - if ( (g_gametype.integer == GT_TOURNAMENT ) - && ent->client->sess.sessionTeam == TEAM_FREE ) { - ent->client->sess.losses++; - } - - trap_Argv( 1, s, sizeof( s ) ); - - SetTeam( ent, s ); - - ent->client->switchTeamTime = level.time + 5000; -} - - -/* -================= -Cmd_Follow_f -================= -*/ -void Cmd_Follow_f( gentity_t *ent ) { - int i; - char arg[MAX_TOKEN_CHARS]; - - if ( trap_Argc() != 2 ) { - if ( ent->client->sess.spectatorState == SPECTATOR_FOLLOW ) { - StopFollowing( ent ); - } - return; - } - - trap_Argv( 1, arg, sizeof( arg ) ); - i = ClientNumberFromString( ent, arg ); - if ( i == -1 ) { - return; - } - - // can't follow self - if ( &level.clients[ i ] == ent->client ) { - return; - } - - // can't follow another spectator - if ( level.clients[ i ].sess.sessionTeam == TEAM_SPECTATOR ) { - return; - } - - // if they are playing a tournement game, count as a loss - if ( (g_gametype.integer == GT_TOURNAMENT ) - && ent->client->sess.sessionTeam == TEAM_FREE ) { - ent->client->sess.losses++; - } - - // first set them to spectator - if ( ent->client->sess.sessionTeam != TEAM_SPECTATOR ) { - SetTeam( ent, "spectator" ); - } - - ent->client->sess.spectatorState = SPECTATOR_FOLLOW; - ent->client->sess.spectatorClient = i; -} - -/* -================= -Cmd_FollowCycle_f -================= -*/ -void Cmd_FollowCycle_f( gentity_t *ent, int dir ) { - int clientnum; - int original; - - // if they are playing a tournement game, count as a loss - if ( (g_gametype.integer == GT_TOURNAMENT ) - && ent->client->sess.sessionTeam == TEAM_FREE ) { - ent->client->sess.losses++; - } - // first set them to spectator - if ( ent->client->sess.spectatorState == SPECTATOR_NOT ) { - SetTeam( ent, "spectator" ); - } - - if ( dir != 1 && dir != -1 ) { - G_Error( "Cmd_FollowCycle_f: bad dir %i", dir ); - } - - clientnum = ent->client->sess.spectatorClient; - original = clientnum; - do { - clientnum += dir; - if ( clientnum >= level.maxclients ) { - clientnum = 0; - } - if ( clientnum < 0 ) { - clientnum = level.maxclients - 1; - } - - // can only follow connected clients - if ( level.clients[ clientnum ].pers.connected != CON_CONNECTED ) { - continue; - } - - // can't follow another spectator - if ( level.clients[ clientnum ].sess.sessionTeam == TEAM_SPECTATOR ) { - continue; - } - - // this is good, we can use it - ent->client->sess.spectatorClient = clientnum; - ent->client->sess.spectatorState = SPECTATOR_FOLLOW; - return; - } while ( clientnum != original ); - - // leave it where it was -} - - -/* -================== -G_Say -================== -*/ - -static void G_SayTo( gentity_t *ent, gentity_t *other, int mode, int color, const char *name, const char *message ) { - if (!other) { - return; - } - if (!other->inuse) { - return; - } - if (!other->client) { - return; - } - if ( other->client->pers.connected != CON_CONNECTED ) { - return; - } - if ( mode == SAY_TEAM && !OnSameTeam(ent, other) ) { - return; - } - // no chatting to players in tournements - if ( (g_gametype.integer == GT_TOURNAMENT ) - && other->client->sess.sessionTeam == TEAM_FREE - && ent->client->sess.sessionTeam != TEAM_FREE ) { - return; - } - - trap_SendServerCommand( other-g_entities, va("%s \"%s%c%c%s\"", - mode == SAY_TEAM ? "tchat" : "chat", - name, Q_COLOR_ESCAPE, color, message)); -} - -#define EC "\x19" - -void G_Say( gentity_t *ent, gentity_t *target, int mode, const char *chatText ) { - int j; - gentity_t *other; - int color; - char name[64]; - // don't let text be too long for malicious reasons - char text[MAX_SAY_TEXT]; - char location[64]; - - if ( g_gametype.integer < GT_TEAM && mode == SAY_TEAM ) { - mode = SAY_ALL; - } - - switch ( mode ) { - default: - case SAY_ALL: - G_LogPrintf( "say: %s: %s\n", ent->client->pers.netname, chatText ); - Com_sprintf (name, sizeof(name), "%s%c%c"EC": ", ent->client->pers.netname, Q_COLOR_ESCAPE, COLOR_WHITE ); - color = COLOR_GREEN; - break; - case SAY_TEAM: - G_LogPrintf( "sayteam: %s: %s\n", ent->client->pers.netname, chatText ); - if (Team_GetLocationMsg(ent, location, sizeof(location))) - Com_sprintf (name, sizeof(name), EC"(%s%c%c"EC") (%s)"EC": ", - ent->client->pers.netname, Q_COLOR_ESCAPE, COLOR_WHITE, location); - else - Com_sprintf (name, sizeof(name), EC"(%s%c%c"EC")"EC": ", - ent->client->pers.netname, Q_COLOR_ESCAPE, COLOR_WHITE ); - color = COLOR_CYAN; - break; - case SAY_TELL: - if (target && g_gametype.integer >= GT_TEAM && - target->client->sess.sessionTeam == ent->client->sess.sessionTeam && - Team_GetLocationMsg(ent, location, sizeof(location))) - Com_sprintf (name, sizeof(name), EC"[%s%c%c"EC"] (%s)"EC": ", ent->client->pers.netname, Q_COLOR_ESCAPE, COLOR_WHITE, location ); - else - Com_sprintf (name, sizeof(name), EC"[%s%c%c"EC"]"EC": ", ent->client->pers.netname, Q_COLOR_ESCAPE, COLOR_WHITE ); - color = COLOR_MAGENTA; - break; - } - - Q_strncpyz( text, chatText, sizeof(text) ); - - if ( target ) { - G_SayTo( ent, target, mode, color, name, text ); - return; - } - - // echo the text to the console - if ( g_dedicated.integer ) { - G_Printf( "%s%s\n", name, text); - } - - // send it to all the apropriate clients - for (j = 0; j < level.maxclients; j++) { - other = &g_entities[j]; - G_SayTo( ent, other, mode, color, name, text ); - } -} - - -/* -================== -Cmd_Say_f -================== -*/ -static void Cmd_Say_f( gentity_t *ent, int mode, qboolean arg0 ) { - char *p; - - if ( trap_Argc () < 2 && !arg0 ) { - return; - } - - if (arg0) - { - p = ConcatArgs( 0 ); - } - else - { - p = ConcatArgs( 1 ); - } - - G_Say( ent, NULL, mode, p ); -} - -/* -================== -Cmd_Tell_f -================== -*/ -static void Cmd_Tell_f( gentity_t *ent ) { - int targetNum; - gentity_t *target; - char *p; - char arg[MAX_TOKEN_CHARS]; - - if ( trap_Argc () < 2 ) { - return; - } - - trap_Argv( 1, arg, sizeof( arg ) ); - targetNum = atoi( arg ); - if ( targetNum < 0 || targetNum >= level.maxclients ) { - return; - } - - target = &g_entities[targetNum]; - if ( !target || !target->inuse || !target->client ) { - return; - } - - p = ConcatArgs( 2 ); - - G_LogPrintf( "tell: %s to %s: %s\n", ent->client->pers.netname, target->client->pers.netname, p ); - G_Say( ent, target, SAY_TELL, p ); - // don't tell to the player self if it was already directed to this player - // also don't send the chat back to a bot - if ( ent != target && !(ent->r.svFlags & SVF_BOT)) { - G_Say( ent, ent, SAY_TELL, p ); - } -} - - -static void G_VoiceTo( gentity_t *ent, gentity_t *other, int mode, const char *id, qboolean voiceonly ) { - int color; - char *cmd; - - if (!other) { - return; - } - if (!other->inuse) { - return; - } - if (!other->client) { - return; - } - if ( mode == SAY_TEAM && !OnSameTeam(ent, other) ) { - return; - } - // no chatting to players in tournements - if ( (g_gametype.integer == GT_TOURNAMENT )) { - return; - } - - if (mode == SAY_TEAM) { - color = COLOR_CYAN; - cmd = "vtchat"; - } - else if (mode == SAY_TELL) { - color = COLOR_MAGENTA; - cmd = "vtell"; - } - else { - color = COLOR_GREEN; - cmd = "vchat"; - } - - trap_SendServerCommand( other-g_entities, va("%s %d %d %d %s", cmd, voiceonly, ent->s.number, color, id)); -} - -void G_Voice( gentity_t *ent, gentity_t *target, int mode, const char *id, qboolean voiceonly ) { - int j; - gentity_t *other; - - if ( g_gametype.integer < GT_TEAM && mode == SAY_TEAM ) { - mode = SAY_ALL; - } - - if ( target ) { - G_VoiceTo( ent, target, mode, id, voiceonly ); - return; - } - - // echo the text to the console - if ( g_dedicated.integer ) { - G_Printf( "voice: %s %s\n", ent->client->pers.netname, id); - } - - // send it to all the apropriate clients - for (j = 0; j < level.maxclients; j++) { - other = &g_entities[j]; - G_VoiceTo( ent, other, mode, id, voiceonly ); - } -} - -/* -================== -Cmd_Voice_f -================== -*/ -static void Cmd_Voice_f( gentity_t *ent, int mode, qboolean arg0, qboolean voiceonly ) { - char *p; - - if ( trap_Argc () < 2 && !arg0 ) { - return; - } - - if (arg0) - { - p = ConcatArgs( 0 ); - } - else - { - p = ConcatArgs( 1 ); - } - - G_Voice( ent, NULL, mode, p, voiceonly ); -} - -/* -================== -Cmd_VoiceTell_f -================== -*/ -static void Cmd_VoiceTell_f( gentity_t *ent, qboolean voiceonly ) { - int targetNum; - gentity_t *target; - char *id; - char arg[MAX_TOKEN_CHARS]; - - if ( trap_Argc () < 2 ) { - return; - } - - trap_Argv( 1, arg, sizeof( arg ) ); - targetNum = atoi( arg ); - if ( targetNum < 0 || targetNum >= level.maxclients ) { - return; - } - - target = &g_entities[targetNum]; - if ( !target || !target->inuse || !target->client ) { - return; - } - - id = ConcatArgs( 2 ); - - G_LogPrintf( "vtell: %s to %s: %s\n", ent->client->pers.netname, target->client->pers.netname, id ); - G_Voice( ent, target, SAY_TELL, id, voiceonly ); - // don't tell to the player self if it was already directed to this player - // also don't send the chat back to a bot - if ( ent != target && !(ent->r.svFlags & SVF_BOT)) { - G_Voice( ent, ent, SAY_TELL, id, voiceonly ); - } -} - - -/* -================== -Cmd_VoiceTaunt_f -================== -*/ -static void Cmd_VoiceTaunt_f( gentity_t *ent ) { - gentity_t *who; - int i; - - if (!ent->client) { - return; - } - - // insult someone who just killed you - if (ent->enemy && ent->enemy->client && ent->enemy->client->lastkilled_client == ent->s.number) { - // i am a dead corpse - if (!(ent->enemy->r.svFlags & SVF_BOT)) { - G_Voice( ent, ent->enemy, SAY_TELL, VOICECHAT_DEATHINSULT, qfalse ); - } - if (!(ent->r.svFlags & SVF_BOT)) { - G_Voice( ent, ent, SAY_TELL, VOICECHAT_DEATHINSULT, qfalse ); - } - ent->enemy = NULL; - return; - } - // insult someone you just killed - if (ent->client->lastkilled_client >= 0 && ent->client->lastkilled_client != ent->s.number) { - who = g_entities + ent->client->lastkilled_client; - if (who->client) { - // who is the person I just killed - if (who->client->lasthurt_mod == MOD_GAUNTLET) { - if (!(who->r.svFlags & SVF_BOT)) { - G_Voice( ent, who, SAY_TELL, VOICECHAT_KILLGAUNTLET, qfalse ); // and I killed them with a gauntlet - } - if (!(ent->r.svFlags & SVF_BOT)) { - G_Voice( ent, ent, SAY_TELL, VOICECHAT_KILLGAUNTLET, qfalse ); - } - } else { - if (!(who->r.svFlags & SVF_BOT)) { - G_Voice( ent, who, SAY_TELL, VOICECHAT_KILLINSULT, qfalse ); // and I killed them with something else - } - if (!(ent->r.svFlags & SVF_BOT)) { - G_Voice( ent, ent, SAY_TELL, VOICECHAT_KILLINSULT, qfalse ); - } - } - ent->client->lastkilled_client = -1; - return; - } - } - - if (g_gametype.integer >= GT_TEAM) { - // praise a team mate who just got a reward - for(i = 0; i < MAX_CLIENTS; i++) { - who = g_entities + i; - if (who->client && who != ent && who->client->sess.sessionTeam == ent->client->sess.sessionTeam) { - if (who->client->rewardTime > level.time) { - if (!(who->r.svFlags & SVF_BOT)) { - G_Voice( ent, who, SAY_TELL, VOICECHAT_PRAISE, qfalse ); - } - if (!(ent->r.svFlags & SVF_BOT)) { - G_Voice( ent, ent, SAY_TELL, VOICECHAT_PRAISE, qfalse ); - } - return; - } - } - } - } - - // just say something - G_Voice( ent, NULL, SAY_ALL, VOICECHAT_TAUNT, qfalse ); -} - - - -static char *gc_orders[] = { - "hold your position", - "hold this position", - "come here", - "cover me", - "guard location", - "search and destroy", - "report" -}; - -void Cmd_GameCommand_f( gentity_t *ent ) { - int player; - int order; - char str[MAX_TOKEN_CHARS]; - - trap_Argv( 1, str, sizeof( str ) ); - player = atoi( str ); - trap_Argv( 2, str, sizeof( str ) ); - order = atoi( str ); - - if ( player < 0 || player >= MAX_CLIENTS ) { - return; - } - if ( order < 0 || order > sizeof(gc_orders)/sizeof(char *) ) { - return; - } - G_Say( ent, &g_entities[player], SAY_TELL, gc_orders[order] ); - G_Say( ent, ent, SAY_TELL, gc_orders[order] ); -} - -/* -================== -Cmd_Where_f -================== -*/ -void Cmd_Where_f( gentity_t *ent ) { - trap_SendServerCommand( ent-g_entities, va("print \"%s\n\"", vtos( ent->s.origin ) ) ); -} - -static const char *gameNames[] = { - "Free For All", - "Tournament", - "Single Player", - "Team Deathmatch", - "Capture the Flag", - "One Flag CTF", - "Overload", - "Harvester" -}; - -/* -================== -Cmd_CallVote_f -================== -*/ -void Cmd_CallVote_f( gentity_t *ent ) { - int i; - char arg1[MAX_STRING_TOKENS]; - char arg2[MAX_STRING_TOKENS]; - - if ( !g_allowVote.integer ) { - trap_SendServerCommand( ent-g_entities, "print \"Voting not allowed here.\n\"" ); - return; - } - - if ( level.voteTime ) { - trap_SendServerCommand( ent-g_entities, "print \"A vote is already in progress.\n\"" ); - return; - } - if ( ent->client->pers.voteCount >= MAX_VOTE_COUNT ) { - trap_SendServerCommand( ent-g_entities, "print \"You have called the maximum number of votes.\n\"" ); - return; - } - if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) { - trap_SendServerCommand( ent-g_entities, "print \"Not allowed to call a vote as spectator.\n\"" ); - return; - } - - // make sure it is a valid command to vote on - trap_Argv( 1, arg1, sizeof( arg1 ) ); - trap_Argv( 2, arg2, sizeof( arg2 ) ); - - if( strchr( arg1, ';' ) || strchr( arg2, ';' ) ) { - trap_SendServerCommand( ent-g_entities, "print \"Invalid vote string.\n\"" ); - return; - } - - if ( !Q_stricmp( arg1, "map_restart" ) ) { - } else if ( !Q_stricmp( arg1, "nextmap" ) ) { - } else if ( !Q_stricmp( arg1, "map" ) ) { - } else if ( !Q_stricmp( arg1, "g_gametype" ) ) { - } else if ( !Q_stricmp( arg1, "kick" ) ) { - } else if ( !Q_stricmp( arg1, "clientkick" ) ) { - } else if ( !Q_stricmp( arg1, "g_doWarmup" ) ) { - } else if ( !Q_stricmp( arg1, "timelimit" ) ) { - } else if ( !Q_stricmp( arg1, "fraglimit" ) ) { - } else { - trap_SendServerCommand( ent-g_entities, "print \"Invalid vote string.\n\"" ); - trap_SendServerCommand( ent-g_entities, "print \"Vote commands are: map_restart, nextmap, map , g_gametype , kick , clientkick , g_doWarmup, timelimit