q3rally/engine/code/botlib/be_ai_goal.c
zturtleman 96a9e2a9aa ioquake3 resync to revision 3511 from 3444.
This updates from SDL 2.0.4 to SDL 2.0.8.

Fix nullptr dereference in front of nullptr check in FS_CheckPak0
Fix undefined behaviour due to shifting signed in snd_mem.c
Fix shifting bits out of byte in tr_font.c
Fix shift into sign in cl_cin.c
Fix signed bit operations in MSG_ReadBits
Add missing address operator in cm_polylib.c
OpenGL1: Decay float[8] to float * in tr_marks.c
Avoid srcList[-1] in snd_openal.c
Fix the behaviour of CVAR_LATCH|CVAR_CHEAT cvars
Maximize cURL buffer size
Fix mouse grab after toggling fullscreen
Fix q3history buffer not cleared between mods and OOB-access
Revert "Removed "Color Depth" from q3_ui system settings, it didn't control anything."
Fix displayed color/depth/stencil bits values
Restore setting r_colorbits in q3_ui
Make setting r_stencilbits more consistent in Team Arena UI
Fix map list in Team Arena start server menu after entering SP menu
Support SDL audio devices that require float32 samples.
sdl_snd.c should just initialize SDL audio without checking SDL_WasInit().
There's no need to SDL_PauseAudio(1) before calling SDL_CloseAudio().
Added audio capture support to SDL backend.
Use the SDL2 audio device interface instead of the legacy 1.2 API.
Disable SDL audio capture until prebuilt SDL libraries are updated to 2.0.8.
Update SDL2 to 2.0.8
Add SDL 2.0.1 headers for macOS PPC
Make macOS Universal Bundle target 10.6 for x86 and x86_64
Fix possible bot goal state NULL pointer dereference
Fix uninitialized bot_goal_t fields
Remove unnecessary NULL pointer check in Cmd_RemoveCommand
Make UI_DrawProportionalString handle NULL string
Fix compiling against macOS system OpenAL and SDL2 frameworks
Fix array index in CanDamage() function - discovered by MARTY
Fix compiling Makefile (broke in macOS frameworks commit)
Fix clearing keys for control in Team Arena UI
Make s_useOpenAL be CVAR_LATCH
Improvements for dedicated camera followers (team follow1/2)
Fix not closing description.txt and fix path seperator
Fix duplicate bots displayed in Team Arena ingame add bot menu
OpenGL2: Fix parsing specularScale in shaders
Don't allow SDL audio capture using pulseaudio
Isolate the Altivec code so non-Altivec PPC targets can use the same binary.
Limit -maltivec to specific source files on OpenBSD too (untested)
Use SDL 2.0.1 headers for macOS ppc64
Fix console offset while Team Arena voiceMenu is open
OpenGL2: Readd r_deluxeSpecular.
Fix client kicked as unpure when missing the latest cgame/ui pk3s
Don't create multiple windows when GL context creation fails
Require OpenGL 1.2 for GL_CLAMP_TO_EDGE
Fix Linux uninstaller requiring Bash
Fix Linux uninstaller redirecting stderr to stdout in preuninstall.sh
Reported by @illwieckz.
Fix in_restart causing fatal error while video is shutdown
Allow pkg-config binary to be overridden with PKG_CONFIG
Make testgun command without argument disable test gun model
Remove unused renderer_buffer variable
Don't upload 8 bit grayscale images as 16 bit luminance
OpenGL1: Use RE_UploadCinematic() instead of duplicate code
Don't load non-core GL functions for OpenGL 3.2 core context
Load OpenGL ES 2.0 function procs
Don't check fixed function GL extensions when using shader pipeline
OpenGL2: Fix world VAO cache drawing when glIndex_t is unsigned short
OpenGL2: Misc fixes and cleanup
Fix IQM root joint backlerp when joint number is more than 0
Improve IQM loading
Improve IQM CPU vertex skinning performance
OpenGL2: Add GPU vertex skinning for IQM models
2018-07-30 11:35:12 +00:00

1828 lines
52 KiB
C

/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.
This file is part of Quake III Arena source code.
Quake III Arena source code is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
Quake III Arena source code is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Quake III Arena source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
===========================================================================
*/
/*****************************************************************************
* name: be_ai_goal.c
*
* desc: goal AI
*
* $Archive: /MissionPack/code/botlib/be_ai_goal.c $
*
*****************************************************************************/
#include "../qcommon/q_shared.h"
#include "l_utils.h"
#include "l_libvar.h"
#include "l_memory.h"
#include "l_log.h"
#include "l_script.h"
#include "l_precomp.h"
#include "l_struct.h"
#include "aasfile.h"
#include "botlib.h"
#include "be_aas.h"
#include "be_aas_funcs.h"
#include "be_interface.h"
#include "be_ai_weight.h"
#include "be_ai_goal.h"
#include "be_ai_move.h"
//#define DEBUG_AI_GOAL
#ifdef RANDOMIZE
#define UNDECIDEDFUZZY
#endif //RANDOMIZE
#define DROPPEDWEIGHT
//minimum avoid goal time
#define AVOID_MINIMUM_TIME 10
//default avoid goal time
#define AVOID_DEFAULT_TIME 30
//avoid dropped goal time
#define AVOID_DROPPED_TIME 10
//
#define TRAVELTIME_SCALE 0.01
//item flags
#define IFL_NOTFREE 1 //not in free for all
#define IFL_NOTTEAM 2 //not in team play
#define IFL_NOTSINGLE 4 //not in single player
#define IFL_NOTBOT 8 //bot should never go for this
#define IFL_ROAM 16 //bot roam goal
//location in the map "target_location"
typedef struct maplocation_s
{
vec3_t origin;
int areanum;
char name[MAX_EPAIRKEY];
struct maplocation_s *next;
} maplocation_t;
//camp spots "info_camp"
typedef struct campspot_s
{
vec3_t origin;
int areanum;
char name[MAX_EPAIRKEY];
float range;
float weight;
float wait;
float random;
struct campspot_s *next;
} campspot_t;
//FIXME: these are game specific
typedef enum {
GT_FFA, // free for all
GT_TOURNAMENT, // one on one tournament
GT_SINGLE_PLAYER, // single player tournament
//-- team games go after this --
GT_TEAM, // team deathmatch
GT_CTF, // capture the flag
#ifdef MISSIONPACK
GT_1FCTF,
GT_OBELISK,
GT_HARVESTER,
#endif
GT_MAX_GAME_TYPE
} gametype_t;
typedef struct levelitem_s
{
int number; //number of the level item
int iteminfo; //index into the item info
int flags; //item flags
float weight; //fixed roam weight
vec3_t origin; //origin of the item
int goalareanum; //area the item is in
vec3_t goalorigin; //goal origin within the area
int entitynum; //entity number
float timeout; //item is removed after this time
struct levelitem_s *prev, *next;
} levelitem_t;
typedef struct iteminfo_s
{
char classname[32]; //classname of the item
char name[MAX_STRINGFIELD]; //name of the item
char model[MAX_STRINGFIELD]; //model of the item
int modelindex; //model index
int type; //item type
int index; //index in the inventory
float respawntime; //respawn time
vec3_t mins; //mins of the item
vec3_t maxs; //maxs of the item
int number; //number of the item info
} iteminfo_t;
#define ITEMINFO_OFS(x) (size_t)&(((iteminfo_t *)0)->x)
fielddef_t iteminfo_fields[] =
{
{"name", ITEMINFO_OFS(name), FT_STRING},
{"model", ITEMINFO_OFS(model), FT_STRING},
{"modelindex", ITEMINFO_OFS(modelindex), FT_INT},
{"type", ITEMINFO_OFS(type), FT_INT},
{"index", ITEMINFO_OFS(index), FT_INT},
{"respawntime", ITEMINFO_OFS(respawntime), FT_FLOAT},
{"mins", ITEMINFO_OFS(mins), FT_FLOAT|FT_ARRAY, 3},
{"maxs", ITEMINFO_OFS(maxs), FT_FLOAT|FT_ARRAY, 3},
{NULL, 0, 0}
};
structdef_t iteminfo_struct =
{
sizeof(iteminfo_t), iteminfo_fields
};
typedef struct itemconfig_s
{
int numiteminfo;
iteminfo_t *iteminfo;
} itemconfig_t;
//goal state
typedef struct bot_goalstate_s
{
struct weightconfig_s *itemweightconfig; //weight config
int *itemweightindex; //index from item to weight
//
int client; //client using this goal state
int lastreachabilityarea; //last area with reachabilities the bot was in
//
bot_goal_t goalstack[MAX_GOALSTACK]; //goal stack
int goalstacktop; //the top of the goal stack
//
int avoidgoals[MAX_AVOIDGOALS]; //goals to avoid
float avoidgoaltimes[MAX_AVOIDGOALS]; //times to avoid the goals
} bot_goalstate_t;
bot_goalstate_t *botgoalstates[MAX_CLIENTS + 1]; // FIXME: init?
//item configuration
itemconfig_t *itemconfig = NULL;
//level items
levelitem_t *levelitemheap = NULL;
levelitem_t *freelevelitems = NULL;
levelitem_t *levelitems = NULL;
int numlevelitems = 0;
//map locations
maplocation_t *maplocations = NULL;
//camp spots
campspot_t *campspots = NULL;
//the game type
int g_gametype = 0;
//additional dropped item weight
libvar_t *droppedweight = NULL;
//========================================================================
//
// Parameter: -
// Returns: -
// Changes Globals: -
//========================================================================
bot_goalstate_t *BotGoalStateFromHandle(int handle)
{
if (handle <= 0 || handle > MAX_CLIENTS)
{
botimport.Print(PRT_FATAL, "goal state handle %d out of range\n", handle);
return NULL;
} //end if
if (!botgoalstates[handle])
{
botimport.Print(PRT_FATAL, "invalid goal state %d\n", handle);
return NULL;
} //end if
return botgoalstates[handle];
} //end of the function BotGoalStateFromHandle
//===========================================================================
//
// Parameter: -
// Returns: -
// Changes Globals: -
//===========================================================================
void BotInterbreedGoalFuzzyLogic(int parent1, int parent2, int child)
{
bot_goalstate_t *p1, *p2, *c;
p1 = BotGoalStateFromHandle(parent1);
p2 = BotGoalStateFromHandle(parent2);
c = BotGoalStateFromHandle(child);
if (!p1 || !p2 || !c)
return;
InterbreedWeightConfigs(p1->itemweightconfig, p2->itemweightconfig,
c->itemweightconfig);
} //end of the function BotInterbreedingGoalFuzzyLogic
//===========================================================================
//
// Parameter: -
// Returns: -
// Changes Globals: -
//===========================================================================
void BotSaveGoalFuzzyLogic(int goalstate, char *filename)
{
//bot_goalstate_t *gs;
//gs = BotGoalStateFromHandle(goalstate);
//if (!gs) return;
//WriteWeightConfig(filename, gs->itemweightconfig);
} //end of the function BotSaveGoalFuzzyLogic
//===========================================================================
//
// Parameter: -
// Returns: -
// Changes Globals: -
//===========================================================================
void BotMutateGoalFuzzyLogic(int goalstate, float range)
{
bot_goalstate_t *gs;
gs = BotGoalStateFromHandle(goalstate);
if (!gs) return;
EvolveWeightConfig(gs->itemweightconfig);
} //end of the function BotMutateGoalFuzzyLogic
//===========================================================================
//
// Parameter: -
// Returns: -
// Changes Globals: -
//===========================================================================
itemconfig_t *LoadItemConfig(char *filename)
{
int max_iteminfo;
token_t token;
char path[MAX_QPATH];
source_t *source;
itemconfig_t *ic;
iteminfo_t *ii;
max_iteminfo = (int) LibVarValue("max_iteminfo", "256");
if (max_iteminfo < 0)
{
botimport.Print(PRT_ERROR, "max_iteminfo = %d\n", max_iteminfo);
max_iteminfo = 256;
LibVarSet( "max_iteminfo", "256" );
}
Q_strncpyz(path, filename, sizeof(path));
PC_SetBaseFolder(BOTFILESBASEFOLDER);
source = LoadSourceFile( path );
if( !source ) {
botimport.Print( PRT_ERROR, "counldn't load %s\n", path );
return NULL;
} //end if
//initialize item config
ic = (itemconfig_t *) GetClearedHunkMemory(sizeof(itemconfig_t) +
max_iteminfo * sizeof(iteminfo_t));
ic->iteminfo = (iteminfo_t *) ((char *) ic + sizeof(itemconfig_t));
ic->numiteminfo = 0;
//parse the item config file
while(PC_ReadToken(source, &token))
{
if (!strcmp(token.string, "iteminfo"))
{
if (ic->numiteminfo >= max_iteminfo)
{
SourceError(source, "more than %d item info defined", max_iteminfo);
FreeMemory(ic);
FreeSource(source);
return NULL;
} //end if
ii = &ic->iteminfo[ic->numiteminfo];
Com_Memset(ii, 0, sizeof(iteminfo_t));
if (!PC_ExpectTokenType(source, TT_STRING, 0, &token))
{
FreeMemory(ic);
FreeSource(source);
return NULL;
} //end if
StripDoubleQuotes(token.string);
Q_strncpyz(ii->classname, token.string, sizeof(ii->classname));
if (!ReadStructure(source, &iteminfo_struct, (char *) ii))
{
FreeMemory(ic);
FreeSource(source);
return NULL;
} //end if
ii->number = ic->numiteminfo;
ic->numiteminfo++;
} //end if
else
{
SourceError(source, "unknown definition %s", token.string);
FreeMemory(ic);
FreeSource(source);
return NULL;
} //end else
} //end while
FreeSource(source);
//
if (!ic->numiteminfo) botimport.Print(PRT_WARNING, "no item info loaded\n");
botimport.Print(PRT_MESSAGE, "loaded %s\n", path);
return ic;
} //end of the function LoadItemConfig
//===========================================================================
// index to find the weight function of an iteminfo
//
// Parameter: -
// Returns: -
// Changes Globals: -
//===========================================================================
int *ItemWeightIndex(weightconfig_t *iwc, itemconfig_t *ic)
{
int *index, i;
//initialize item weight index
index = (int *) GetClearedMemory(sizeof(int) * ic->numiteminfo);
for (i = 0; i < ic->numiteminfo; i++)
{
index[i] = FindFuzzyWeight(iwc, ic->iteminfo[i].classname);
if (index[i] < 0)
{
Log_Write("item info %d \"%s\" has no fuzzy weight\r\n", i, ic->iteminfo[i].classname);
} //end if
} //end for
return index;
} //end of the function ItemWeightIndex
//===========================================================================
//
// Parameter: -
// Returns: -
// Changes Globals: -
//===========================================================================
void InitLevelItemHeap(void)
{
int i, max_levelitems;
if (levelitemheap) FreeMemory(levelitemheap);
max_levelitems = (int) LibVarValue("max_levelitems", "256");
levelitemheap = (levelitem_t *) GetClearedMemory(max_levelitems * sizeof(levelitem_t));
for (i = 0; i < max_levelitems-1; i++)
{
levelitemheap[i].next = &levelitemheap[i + 1];
} //end for
levelitemheap[max_levelitems-1].next = NULL;
//
freelevelitems = levelitemheap;
} //end of the function InitLevelItemHeap
//===========================================================================
//
// Parameter: -
// Returns: -
// Changes Globals: -
//===========================================================================
levelitem_t *AllocLevelItem(void)
{
levelitem_t *li;
li = freelevelitems;
if (!li)
{
botimport.Print(PRT_FATAL, "out of level items\n");
return NULL;
} //end if
//
freelevelitems = freelevelitems->next;
Com_Memset(li, 0, sizeof(levelitem_t));
return li;
} //end of the function AllocLevelItem
//===========================================================================
//
// Parameter: -
// Returns: -
// Changes Globals: -
//===========================================================================
void FreeLevelItem(levelitem_t *li)
{
li->next = freelevelitems;
freelevelitems = li;
} //end of the function FreeLevelItem
//===========================================================================
//
// Parameter: -
// Returns: -
// Changes Globals: -
//===========================================================================
void AddLevelItemToList(levelitem_t *li)
{
if (levelitems) levelitems->prev = li;
li->prev = NULL;
li->next = levelitems;
levelitems = li;
} //end of the function AddLevelItemToList
//===========================================================================
//
// Parameter: -
// Returns: -
// Changes Globals: -
//===========================================================================
void RemoveLevelItemFromList(levelitem_t *li)
{
if (li->prev) li->prev->next = li->next;
else levelitems = li->next;
if (li->next) li->next->prev = li->prev;
} //end of the function RemoveLevelItemFromList
//===========================================================================
//
// Parameter: -
// Returns: -
// Changes Globals: -
//===========================================================================
void BotFreeInfoEntities(void)
{
maplocation_t *ml, *nextml;
campspot_t *cs, *nextcs;
for (ml = maplocations; ml; ml = nextml)
{
nextml = ml->next;
FreeMemory(ml);
} //end for
maplocations = NULL;
for (cs = campspots; cs; cs = nextcs)
{
nextcs = cs->next;
FreeMemory(cs);
} //end for
campspots = NULL;
} //end of the function BotFreeInfoEntities
//===========================================================================
//
// Parameter: -
// Returns: -
// Changes Globals: -
//===========================================================================
void BotInitInfoEntities(void)
{
char classname[MAX_EPAIRKEY];
maplocation_t *ml;
campspot_t *cs;
int ent, numlocations, numcampspots;
BotFreeInfoEntities();
//
numlocations = 0;
numcampspots = 0;
for (ent = AAS_NextBSPEntity(0); ent; ent = AAS_NextBSPEntity(ent))
{
if (!AAS_ValueForBSPEpairKey(ent, "classname", classname, MAX_EPAIRKEY)) continue;
//map locations
if (!strcmp(classname, "target_location"))
{
ml = (maplocation_t *) GetClearedMemory(sizeof(maplocation_t));
AAS_VectorForBSPEpairKey(ent, "origin", ml->origin);
AAS_ValueForBSPEpairKey(ent, "message", ml->name, sizeof(ml->name));
ml->areanum = AAS_PointAreaNum(ml->origin);
ml->next = maplocations;
maplocations = ml;
numlocations++;
} //end if
//camp spots
else if (!strcmp(classname, "info_camp"))
{
cs = (campspot_t *) GetClearedMemory(sizeof(campspot_t));
AAS_VectorForBSPEpairKey(ent, "origin", cs->origin);
//cs->origin[2] += 16;
AAS_ValueForBSPEpairKey(ent, "message", cs->name, sizeof(cs->name));
AAS_FloatForBSPEpairKey(ent, "range", &cs->range);
AAS_FloatForBSPEpairKey(ent, "weight", &cs->weight);
AAS_FloatForBSPEpairKey(ent, "wait", &cs->wait);
AAS_FloatForBSPEpairKey(ent, "random", &cs->random);
cs->areanum = AAS_PointAreaNum(cs->origin);
if (!cs->areanum)
{
botimport.Print(PRT_MESSAGE, "camp spot at %1.1f %1.1f %1.1f in solid\n", cs->origin[0], cs->origin[1], cs->origin[2]);
FreeMemory(cs);
continue;
} //end if
cs->next = campspots;
campspots = cs;
//AAS_DrawPermanentCross(cs->origin, 4, LINECOLOR_YELLOW);
numcampspots++;
} //end else if
} //end for
if (botDeveloper)
{
botimport.Print(PRT_MESSAGE, "%d map locations\n", numlocations);
botimport.Print(PRT_MESSAGE, "%d camp spots\n", numcampspots);
} //end if
} //end of the function BotInitInfoEntities
//===========================================================================
//
// Parameter: -
// Returns: -
// Changes Globals: -
//===========================================================================
void BotInitLevelItems(void)
{
int i, spawnflags, value;
char classname[MAX_EPAIRKEY];
vec3_t origin, end;
int ent, goalareanum;
itemconfig_t *ic;
levelitem_t *li;
bsp_trace_t trace;
//initialize the map locations and camp spots
BotInitInfoEntities();
//initialize the level item heap
InitLevelItemHeap();
levelitems = NULL;
numlevelitems = 0;
//
ic = itemconfig;
if (!ic) return;
//if there's no AAS file loaded
if (!AAS_Loaded()) return;
//validate the modelindexes of the item info
for (i = 0; i < ic->numiteminfo; i++)
{
if (!ic->iteminfo[i].modelindex)
{
Log_Write("item %s has modelindex 0", ic->iteminfo[i].classname);
} //end if
} //end for
for (ent = AAS_NextBSPEntity(0); ent; ent = AAS_NextBSPEntity(ent))
{
if (!AAS_ValueForBSPEpairKey(ent, "classname", classname, MAX_EPAIRKEY)) continue;
//
spawnflags = 0;
AAS_IntForBSPEpairKey(ent, "spawnflags", &spawnflags);
//
for (i = 0; i < ic->numiteminfo; i++)
{
if (!strcmp(classname, ic->iteminfo[i].classname)) break;
} //end for
if (i >= ic->numiteminfo)
{
Log_Write("entity %s unknown item\r\n", classname);
continue;
} //end if
//get the origin of the item
if (!AAS_VectorForBSPEpairKey(ent, "origin", origin))
{
botimport.Print(PRT_ERROR, "item %s without origin\n", classname);
continue;
} //end else
//
goalareanum = 0;
//if it is a floating item
if (spawnflags & 1)
{
//if the item is not floating in water
if (!(AAS_PointContents(origin) & CONTENTS_WATER))
{
VectorCopy(origin, end);
end[2] -= 32;
trace = AAS_Trace(origin, ic->iteminfo[i].mins, ic->iteminfo[i].maxs, end, -1, CONTENTS_SOLID|CONTENTS_PLAYERCLIP);
//if the item not near the ground
if (trace.fraction >= 1)
{
//if the item is not reachable from a jumppad
goalareanum = AAS_BestReachableFromJumpPadArea(origin, ic->iteminfo[i].mins, ic->iteminfo[i].maxs);
Log_Write("item %s reachable from jumppad area %d\r\n", ic->iteminfo[i].classname, goalareanum);
//botimport.Print(PRT_MESSAGE, "item %s reachable from jumppad area %d\r\n", ic->iteminfo[i].classname, goalareanum);
if (!goalareanum) continue;
} //end if
} //end if
} //end if
li = AllocLevelItem();
if (!li) return;
//
li->number = ++numlevelitems;
li->timeout = 0;
li->entitynum = 0;
//
li->flags = 0;
AAS_IntForBSPEpairKey(ent, "notfree", &value);
if (value) li->flags |= IFL_NOTFREE;
AAS_IntForBSPEpairKey(ent, "notteam", &value);
if (value) li->flags |= IFL_NOTTEAM;
AAS_IntForBSPEpairKey(ent, "notsingle", &value);
if (value) li->flags |= IFL_NOTSINGLE;
AAS_IntForBSPEpairKey(ent, "notbot", &value);
if (value) li->flags |= IFL_NOTBOT;
if (!strcmp(classname, "item_botroam"))
{
li->flags |= IFL_ROAM;
AAS_FloatForBSPEpairKey(ent, "weight", &li->weight);
} //end if
//if not a stationary item
if (!(spawnflags & 1))
{
if (!AAS_DropToFloor(origin, ic->iteminfo[i].mins, ic->iteminfo[i].maxs))
{
botimport.Print(PRT_MESSAGE, "%s in solid at (%1.1f %1.1f %1.1f)\n",
classname, origin[0], origin[1], origin[2]);
} //end if
} //end if
//item info of the level item
li->iteminfo = i;
//origin of the item
VectorCopy(origin, li->origin);
//
if (goalareanum)
{
li->goalareanum = goalareanum;
VectorCopy(origin, li->goalorigin);
} //end if
else
{
//get the item goal area and goal origin
li->goalareanum = AAS_BestReachableArea(origin,
ic->iteminfo[i].mins, ic->iteminfo[i].maxs,
li->goalorigin);
if (!li->goalareanum)
{
botimport.Print(PRT_MESSAGE, "%s not reachable for bots at (%1.1f %1.1f %1.1f)\n",
classname, origin[0], origin[1], origin[2]);
} //end if
} //end else
//
AddLevelItemToList(li);
} //end for
botimport.Print(PRT_MESSAGE, "found %d level items\n", numlevelitems);
} //end of the function BotInitLevelItems
//===========================================================================
//
// Parameter: -
// Returns: -
// Changes Globals: -
//===========================================================================
void BotGoalName(int number, char *name, int size)
{
levelitem_t *li;
if (!itemconfig) return;
//
for (li = levelitems; li; li = li->next)
{
if (li->number == number)
{
Q_strncpyz(name, itemconfig->iteminfo[li->iteminfo].name, size);
return;
} //end for
} //end for
strcpy(name, "");
} //end of the function BotGoalName
//===========================================================================
//
// Parameter: -
// Returns: -
// Changes Globals: -
//===========================================================================
void BotResetAvoidGoals(int goalstate)
{
bot_goalstate_t *gs;
gs = BotGoalStateFromHandle(goalstate);
if (!gs) return;
Com_Memset(gs->avoidgoals, 0, MAX_AVOIDGOALS * sizeof(int));
Com_Memset(gs->avoidgoaltimes, 0, MAX_AVOIDGOALS * sizeof(float));
} //end of the function BotResetAvoidGoals
//===========================================================================
//
// Parameter: -
// Returns: -
// Changes Globals: -
//===========================================================================
void BotDumpAvoidGoals(int goalstate)
{
int i;
bot_goalstate_t *gs;
char name[32];
gs = BotGoalStateFromHandle(goalstate);
if (!gs) return;
for (i = 0; i < MAX_AVOIDGOALS; i++)
{
if (gs->avoidgoaltimes[i] >= AAS_Time())
{
BotGoalName(gs->avoidgoals[i], name, 32);
Log_Write("avoid goal %s, number %d for %f seconds", name,
gs->avoidgoals[i], gs->avoidgoaltimes[i] - AAS_Time());
} //end if
} //end for
} //end of the function BotDumpAvoidGoals
//===========================================================================
//
// Parameter: -
// Returns: -
// Changes Globals: -
//===========================================================================
void BotAddToAvoidGoals(bot_goalstate_t *gs, int number, float avoidtime)
{
int i;
for (i = 0; i < MAX_AVOIDGOALS; i++)
{
//if the avoid goal is already stored
if (gs->avoidgoals[i] == number)
{
gs->avoidgoals[i] = number;
gs->avoidgoaltimes[i] = AAS_Time() + avoidtime;
return;
} //end if
} //end for
for (i = 0; i < MAX_AVOIDGOALS; i++)
{
//if this avoid goal has expired
if (gs->avoidgoaltimes[i] < AAS_Time())
{
gs->avoidgoals[i] = number;
gs->avoidgoaltimes[i] = AAS_Time() + avoidtime;
return;
} //end if
} //end for
} //end of the function BotAddToAvoidGoals
//===========================================================================
//
// Parameter: -
// Returns: -
// Changes Globals: -
//===========================================================================
void BotRemoveFromAvoidGoals(int goalstate, int number)
{
int i;
bot_goalstate_t *gs;
gs = BotGoalStateFromHandle(goalstate);
if (!gs) return;
//don't use the goals the bot wants to avoid
for (i = 0; i < MAX_AVOIDGOALS; i++)
{
if (gs->avoidgoals[i] == number && gs->avoidgoaltimes[i] >= AAS_Time())
{
gs->avoidgoaltimes[i] = 0;
return;
} //end if
} //end for
} //end of the function BotRemoveFromAvoidGoals
//===========================================================================
//
// Parameter: -
// Returns: -
// Changes Globals: -
//===========================================================================
float BotAvoidGoalTime(int goalstate, int number)
{
int i;
bot_goalstate_t *gs;
gs = BotGoalStateFromHandle(goalstate);
if (!gs) return 0;
//don't use the goals the bot wants to avoid
for (i = 0; i < MAX_AVOIDGOALS; i++)
{
if (gs->avoidgoals[i] == number && gs->avoidgoaltimes[i] >= AAS_Time())
{
return gs->avoidgoaltimes[i] - AAS_Time();
} //end if
} //end for
return 0;
} //end of the function BotAvoidGoalTime
//===========================================================================
//
// Parameter: -
// Returns: -
// Changes Globals: -
//===========================================================================
void BotSetAvoidGoalTime(int goalstate, int number, float avoidtime)
{
bot_goalstate_t *gs;
levelitem_t *li;
gs = BotGoalStateFromHandle(goalstate);
if (!gs)
return;
if (avoidtime < 0)
{
if (!itemconfig)
return;
//
for (li = levelitems; li; li = li->next)
{
if (li->number == number)
{
avoidtime = itemconfig->iteminfo[li->iteminfo].respawntime;
if (!avoidtime)
avoidtime = AVOID_DEFAULT_TIME;
if (avoidtime < AVOID_MINIMUM_TIME)
avoidtime = AVOID_MINIMUM_TIME;
BotAddToAvoidGoals(gs, number, avoidtime);
return;
} //end for
} //end for
return;
} //end if
else
{
BotAddToAvoidGoals(gs, number, avoidtime);
} //end else
} //end of the function BotSetAvoidGoalTime
//===========================================================================
//
// Parameter: -
// Returns: -
// Changes Globals: -
//===========================================================================
int BotGetLevelItemGoal(int index, char *name, bot_goal_t *goal)
{
levelitem_t *li;
if (!itemconfig) return -1;
li = levelitems;
if (index >= 0)
{
for (; li; li = li->next)
{
if (li->number == index)
{
li = li->next;
break;
} //end if
} //end for
} //end for
for (; li; li = li->next)
{
//
if (g_gametype == GT_SINGLE_PLAYER) {
if (li->flags & IFL_NOTSINGLE) continue;
}
else if (g_gametype >= GT_TEAM) {
if (li->flags & IFL_NOTTEAM) continue;
}
else {
if (li->flags & IFL_NOTFREE) continue;
}
if (li->flags & IFL_NOTBOT) continue;
//
if (!Q_stricmp(name, itemconfig->iteminfo[li->iteminfo].name))
{
goal->areanum = li->goalareanum;
VectorCopy(li->goalorigin, goal->origin);
goal->entitynum = li->entitynum;
VectorCopy(itemconfig->iteminfo[li->iteminfo].mins, goal->mins);
VectorCopy(itemconfig->iteminfo[li->iteminfo].maxs, goal->maxs);
goal->number = li->number;
goal->flags = GFL_ITEM;
if (li->timeout) goal->flags |= GFL_DROPPED;
goal->iteminfo = li->iteminfo;
//botimport.Print(PRT_MESSAGE, "found li %s\n", itemconfig->iteminfo[li->iteminfo].name);
return li->number;
} //end if
} //end for
return -1;
} //end of the function BotGetLevelItemGoal
//===========================================================================
//
// Parameter: -
// Returns: -
// Changes Globals: -
//===========================================================================
int BotGetMapLocationGoal(char *name, bot_goal_t *goal)
{
maplocation_t *ml;
vec3_t mins = {-8, -8, -8}, maxs = {8, 8, 8};
for (ml = maplocations; ml; ml = ml->next)
{
if (!Q_stricmp(ml->name, name))
{
goal->areanum = ml->areanum;
VectorCopy(ml->origin, goal->origin);
goal->entitynum = 0;
VectorCopy(mins, goal->mins);
VectorCopy(maxs, goal->maxs);
goal->number = 0;
goal->flags = 0;
goal->iteminfo = 0;
return qtrue;
} //end if
} //end for
return qfalse;
} //end of the function BotGetMapLocationGoal
//===========================================================================
//
// Parameter: -
// Returns: -
// Changes Globals: -
//===========================================================================
int BotGetNextCampSpotGoal(int num, bot_goal_t *goal)
{
int i;
campspot_t *cs;
vec3_t mins = {-8, -8, -8}, maxs = {8, 8, 8};
if (num < 0) num = 0;
i = num;
for (cs = campspots; cs; cs = cs->next)
{
if (--i < 0)
{
goal->areanum = cs->areanum;
VectorCopy(cs->origin, goal->origin);
goal->entitynum = 0;
VectorCopy(mins, goal->mins);
VectorCopy(maxs, goal->maxs);
goal->number = 0;
goal->flags = 0;
goal->iteminfo = 0;
return num+1;
} //end if
} //end for
return 0;
} //end of the function BotGetNextCampSpotGoal
//===========================================================================
//
// Parameter: -
// Returns: -
// Changes Globals: -
//===========================================================================
void BotFindEntityForLevelItem(levelitem_t *li)
{
int ent, modelindex;
itemconfig_t *ic;
aas_entityinfo_t entinfo;
vec3_t dir;
ic = itemconfig;
if (!itemconfig) return;
for (ent = AAS_NextEntity(0); ent; ent = AAS_NextEntity(ent))
{
//get the model index of the entity
modelindex = AAS_EntityModelindex(ent);
//
if (!modelindex) continue;
//get info about the entity
AAS_EntityInfo(ent, &entinfo);
//if the entity is still moving
if (entinfo.origin[0] != entinfo.lastvisorigin[0] ||
entinfo.origin[1] != entinfo.lastvisorigin[1] ||
entinfo.origin[2] != entinfo.lastvisorigin[2]) continue;
//
if (ic->iteminfo[li->iteminfo].modelindex == modelindex)
{
//check if the entity is very close
VectorSubtract(li->origin, entinfo.origin, dir);
if (VectorLength(dir) < 30)
{
//found an entity for this level item
li->entitynum = ent;
} //end if
} //end if
} //end for
} //end of the function BotFindEntityForLevelItem
//===========================================================================
//
// Parameter: -
// Returns: -
// Changes Globals: -
//===========================================================================
//NOTE: enum entityType_t in bg_public.h
#define ET_ITEM 2
void BotUpdateEntityItems(void)
{
int ent, i, modelindex;
vec3_t dir;
levelitem_t *li, *nextli;
aas_entityinfo_t entinfo;
itemconfig_t *ic;
//timeout current entity items if necessary
for (li = levelitems; li; li = nextli)
{
nextli = li->next;
//if it is an item that will time out
if (li->timeout)
{
//timeout the item
if (li->timeout < AAS_Time())
{
RemoveLevelItemFromList(li);
FreeLevelItem(li);
} //end if
} //end if
} //end for
//find new entity items
ic = itemconfig;
if (!itemconfig) return;
//
for (ent = AAS_NextEntity(0); ent; ent = AAS_NextEntity(ent))
{
if (AAS_EntityType(ent) != ET_ITEM) continue;
//get the model index of the entity
modelindex = AAS_EntityModelindex(ent);
//
if (!modelindex) continue;
//get info about the entity
AAS_EntityInfo(ent, &entinfo);
//FIXME: don't do this
//skip all floating items for now
//if (entinfo.groundent != ENTITYNUM_WORLD) continue;
//if the entity is still moving
if (entinfo.origin[0] != entinfo.lastvisorigin[0] ||
entinfo.origin[1] != entinfo.lastvisorigin[1] ||
entinfo.origin[2] != entinfo.lastvisorigin[2]) continue;
//check if the entity is already stored as a level item
for (li = levelitems; li; li = li->next)
{
//if the level item is linked to an entity
if (li->entitynum && li->entitynum == ent)
{
//the entity is re-used if the models are different
if (ic->iteminfo[li->iteminfo].modelindex != modelindex)
{
//remove this level item
RemoveLevelItemFromList(li);
FreeLevelItem(li);
li = NULL;
break;
} //end if
else
{
if (entinfo.origin[0] != li->origin[0] ||
entinfo.origin[1] != li->origin[1] ||
entinfo.origin[2] != li->origin[2])
{
VectorCopy(entinfo.origin, li->origin);
//also update the goal area number
li->goalareanum = AAS_BestReachableArea(li->origin,
ic->iteminfo[li->iteminfo].mins, ic->iteminfo[li->iteminfo].maxs,
li->goalorigin);
} //end if
break;
} //end else
} //end if
} //end for
if (li) continue;
//try to link the entity to a level item
for (li = levelitems; li; li = li->next)
{
//if this level item is already linked
if (li->entitynum) continue;
//
if (g_gametype == GT_SINGLE_PLAYER) {
if (li->flags & IFL_NOTSINGLE) continue;
}
else if (g_gametype >= GT_TEAM) {
if (li->flags & IFL_NOTTEAM) continue;
}
else {
if (li->flags & IFL_NOTFREE) continue;
}
//if the model of the level item and the entity are the same
if (ic->iteminfo[li->iteminfo].modelindex == modelindex)
{
//check if the entity is very close
VectorSubtract(li->origin, entinfo.origin, dir);
if (VectorLength(dir) < 30)
{
//found an entity for this level item
li->entitynum = ent;
//if the origin is different
if (entinfo.origin[0] != li->origin[0] ||
entinfo.origin[1] != li->origin[1] ||
entinfo.origin[2] != li->origin[2])
{
//update the level item origin
VectorCopy(entinfo.origin, li->origin);
//also update the goal area number
li->goalareanum = AAS_BestReachableArea(li->origin,
ic->iteminfo[li->iteminfo].mins, ic->iteminfo[li->iteminfo].maxs,
li->goalorigin);
} //end if
#ifdef DEBUG
Log_Write("linked item %s to an entity", ic->iteminfo[li->iteminfo].classname);
#endif //DEBUG
break;
} //end if
} //end else
} //end for
if (li) continue;
//check if the model is from a known item
for (i = 0; i < ic->numiteminfo; i++)
{
if (ic->iteminfo[i].modelindex == modelindex)
{
break;
} //end if
} //end for
//if the model is not from a known item
if (i >= ic->numiteminfo) continue;
//allocate a new level item
li = AllocLevelItem();
//
if (!li) continue;
//entity number of the level item
li->entitynum = ent;
//number for the level item
li->number = numlevelitems + ent;
//set the item info index for the level item
li->iteminfo = i;
//origin of the item
VectorCopy(entinfo.origin, li->origin);
//get the item goal area and goal origin
li->goalareanum = AAS_BestReachableArea(li->origin,
ic->iteminfo[i].mins, ic->iteminfo[i].maxs,
li->goalorigin);
//never go for items dropped into jumppads
if (AAS_AreaJumpPad(li->goalareanum))
{
FreeLevelItem(li);
continue;
} //end if
//time this item out after 30 seconds
//dropped items disappear after 30 seconds
li->timeout = AAS_Time() + 30;
//add the level item to the list
AddLevelItemToList(li);
//botimport.Print(PRT_MESSAGE, "found new level item %s\n", ic->iteminfo[i].classname);
} //end for
/*
for (li = levelitems; li; li = li->next)
{
if (!li->entitynum)
{
BotFindEntityForLevelItem(li);
} //end if
} //end for*/
} //end of the function BotUpdateEntityItems
//===========================================================================
//
// Parameter: -
// Returns: -
// Changes Globals: -
//===========================================================================
void BotDumpGoalStack(int goalstate)
{
int i;
bot_goalstate_t *gs;
char name[32];
gs = BotGoalStateFromHandle(goalstate);
if (!gs) return;
for (i = 1; i <= gs->goalstacktop; i++)
{
BotGoalName(gs->goalstack[i].number, name, 32);
Log_Write("%d: %s", i, name);
} //end for
} //end of the function BotDumpGoalStack
//===========================================================================
//
// Parameter: -
// Returns: -
// Changes Globals: -
//===========================================================================
void BotPushGoal(int goalstate, bot_goal_t *goal)
{
bot_goalstate_t *gs;
gs = BotGoalStateFromHandle(goalstate);
if (!gs) return;
if (gs->goalstacktop >= MAX_GOALSTACK-1)
{
botimport.Print(PRT_ERROR, "goal heap overflow\n");
BotDumpGoalStack(goalstate);
return;
} //end if
gs->goalstacktop++;
Com_Memcpy(&gs->goalstack[gs->goalstacktop], goal, sizeof(bot_goal_t));
} //end of the function BotPushGoal
//===========================================================================
//
// Parameter: -
// Returns: -
// Changes Globals: -
//===========================================================================
void BotPopGoal(int goalstate)
{
bot_goalstate_t *gs;
gs = BotGoalStateFromHandle(goalstate);
if (!gs) return;
if (gs->goalstacktop > 0) gs->goalstacktop--;
} //end of the function BotPopGoal
//===========================================================================
//
// Parameter: -
// Returns: -
// Changes Globals: -
//===========================================================================
void BotEmptyGoalStack(int goalstate)
{
bot_goalstate_t *gs;
gs = BotGoalStateFromHandle(goalstate);
if (!gs) return;
gs->goalstacktop = 0;
} //end of the function BotEmptyGoalStack
//===========================================================================
//
// Parameter: -
// Returns: -
// Changes Globals: -
//===========================================================================
int BotGetTopGoal(int goalstate, bot_goal_t *goal)
{
bot_goalstate_t *gs;
gs = BotGoalStateFromHandle(goalstate);
if (!gs) return qfalse;
if (!gs->goalstacktop) return qfalse;
Com_Memcpy(goal, &gs->goalstack[gs->goalstacktop], sizeof(bot_goal_t));
return qtrue;
} //end of the function BotGetTopGoal
//===========================================================================
//
// Parameter: -
// Returns: -
// Changes Globals: -
//===========================================================================
int BotGetSecondGoal(int goalstate, bot_goal_t *goal)
{
bot_goalstate_t *gs;
gs = BotGoalStateFromHandle(goalstate);
if (!gs) return qfalse;
if (gs->goalstacktop <= 1) return qfalse;
Com_Memcpy(goal, &gs->goalstack[gs->goalstacktop-1], sizeof(bot_goal_t));
return qtrue;
} //end of the function BotGetSecondGoal
//===========================================================================
// pops a new long term goal on the goal stack in the goalstate
//
// Parameter: -
// Returns: -
// Changes Globals: -
//===========================================================================
int BotChooseLTGItem(int goalstate, vec3_t origin, int *inventory, int travelflags)
{
int areanum, t, weightnum;
float weight, bestweight, avoidtime;
iteminfo_t *iteminfo;
itemconfig_t *ic;
levelitem_t *li, *bestitem;
bot_goal_t goal;
bot_goalstate_t *gs;
gs = BotGoalStateFromHandle(goalstate);
if (!gs)
return qfalse;
if (!gs->itemweightconfig)
return qfalse;
//get the area the bot is in
areanum = BotReachabilityArea(origin, gs->client);
//if the bot is in solid or if the area the bot is in has no reachability links
if (!areanum || !AAS_AreaReachability(areanum))
{
//use the last valid area the bot was in
areanum = gs->lastreachabilityarea;
} //end if
//remember the last area with reachabilities the bot was in
gs->lastreachabilityarea = areanum;
//if still in solid
if (!areanum)
return qfalse;
//the item configuration
ic = itemconfig;
if (!itemconfig)
return qfalse;
//best weight and item so far
bestweight = 0;
bestitem = NULL;
Com_Memset(&goal, 0, sizeof(bot_goal_t));
//go through the items in the level
for (li = levelitems; li; li = li->next)
{
if (g_gametype == GT_SINGLE_PLAYER) {
if (li->flags & IFL_NOTSINGLE)
continue;
}
else if (g_gametype >= GT_TEAM) {
if (li->flags & IFL_NOTTEAM)
continue;
}
else {
if (li->flags & IFL_NOTFREE)
continue;
}
if (li->flags & IFL_NOTBOT)
continue;
//if the item is not in a possible goal area
if (!li->goalareanum)
continue;
//FIXME: is this a good thing? added this for items that never spawned into the game (f.i. CTF flags in obelisk)
if (!li->entitynum && !(li->flags & IFL_ROAM))
continue;
//get the fuzzy weight function for this item
iteminfo = &ic->iteminfo[li->iteminfo];
weightnum = gs->itemweightindex[iteminfo->number];
if (weightnum < 0)
continue;
#ifdef UNDECIDEDFUZZY
weight = FuzzyWeightUndecided(inventory, gs->itemweightconfig, weightnum);
#else
weight = FuzzyWeight(inventory, gs->itemweightconfig, weightnum);
#endif //UNDECIDEDFUZZY
#ifdef DROPPEDWEIGHT
//HACK: to make dropped items more attractive
if (li->timeout)
weight += droppedweight->value;
#endif //DROPPEDWEIGHT
//use weight scale for item_botroam
if (li->flags & IFL_ROAM) weight *= li->weight;
//
if (weight > 0)
{
//get the travel time towards the goal area
t = AAS_AreaTravelTimeToGoalArea(areanum, origin, li->goalareanum, travelflags);
//if the goal is reachable
if (t > 0)
{
//if this item won't respawn before we get there
avoidtime = BotAvoidGoalTime(goalstate, li->number);
if (avoidtime - t * 0.009 > 0)
continue;
//
weight /= (float) t * TRAVELTIME_SCALE;
//
if (weight > bestweight)
{
bestweight = weight;
bestitem = li;
} //end if
} //end if
} //end if
} //end for
//if no goal item found
if (!bestitem)
{
/*
//if not in lava or slime
if (!AAS_AreaLava(areanum) && !AAS_AreaSlime(areanum))
{
if (AAS_RandomGoalArea(areanum, travelflags, &goal.areanum, goal.origin))
{
VectorSet(goal.mins, -15, -15, -15);
VectorSet(goal.maxs, 15, 15, 15);
goal.entitynum = 0;
goal.number = 0;
goal.flags = GFL_ROAM;
goal.iteminfo = 0;
//push the goal on the stack
BotPushGoal(goalstate, &goal);
//
#ifdef DEBUG
botimport.Print(PRT_MESSAGE, "chosen roam goal area %d\n", goal.areanum);
#endif //DEBUG
return qtrue;
} //end if
} //end if
*/
return qfalse;
} //end if
//create a bot goal for this item
iteminfo = &ic->iteminfo[bestitem->iteminfo];
VectorCopy(bestitem->goalorigin, goal.origin);
VectorCopy(iteminfo->mins, goal.mins);
VectorCopy(iteminfo->maxs, goal.maxs);
goal.areanum = bestitem->goalareanum;
goal.entitynum = bestitem->entitynum;
goal.number = bestitem->number;
goal.flags = GFL_ITEM;
if (bestitem->timeout)
goal.flags |= GFL_DROPPED;
if (bestitem->flags & IFL_ROAM)
goal.flags |= GFL_ROAM;
goal.iteminfo = bestitem->iteminfo;
//if it's a dropped item
if (bestitem->timeout)
{
avoidtime = AVOID_DROPPED_TIME;
} //end if
else
{
avoidtime = iteminfo->respawntime;
if (!avoidtime)
avoidtime = AVOID_DEFAULT_TIME;
if (avoidtime < AVOID_MINIMUM_TIME)
avoidtime = AVOID_MINIMUM_TIME;
} //end else
//add the chosen goal to the goals to avoid for a while
BotAddToAvoidGoals(gs, bestitem->number, avoidtime);
//push the goal on the stack
BotPushGoal(goalstate, &goal);
//
return qtrue;
} //end of the function BotChooseLTGItem
//===========================================================================
//
// Parameter: -
// Returns: -
// Changes Globals: -
//===========================================================================
int BotChooseNBGItem(int goalstate, vec3_t origin, int *inventory, int travelflags,
bot_goal_t *ltg, float maxtime)
{
int areanum, t, weightnum, ltg_time;
float weight, bestweight, avoidtime;
iteminfo_t *iteminfo;
itemconfig_t *ic;
levelitem_t *li, *bestitem;
bot_goal_t goal;
bot_goalstate_t *gs;
gs = BotGoalStateFromHandle(goalstate);
if (!gs)
return qfalse;
if (!gs->itemweightconfig)
return qfalse;
//get the area the bot is in
areanum = BotReachabilityArea(origin, gs->client);
//if the bot is in solid or if the area the bot is in has no reachability links
if (!areanum || !AAS_AreaReachability(areanum))
{
//use the last valid area the bot was in
areanum = gs->lastreachabilityarea;
} //end if
//remember the last area with reachabilities the bot was in
gs->lastreachabilityarea = areanum;
//if still in solid
if (!areanum)
return qfalse;
//
if (ltg) ltg_time = AAS_AreaTravelTimeToGoalArea(areanum, origin, ltg->areanum, travelflags);
else ltg_time = 99999;
//the item configuration
ic = itemconfig;
if (!itemconfig)
return qfalse;
//best weight and item so far
bestweight = 0;
bestitem = NULL;
Com_Memset(&goal, 0, sizeof(bot_goal_t));
//go through the items in the level
for (li = levelitems; li; li = li->next)
{
if (g_gametype == GT_SINGLE_PLAYER) {
if (li->flags & IFL_NOTSINGLE)
continue;
}
else if (g_gametype >= GT_TEAM) {
if (li->flags & IFL_NOTTEAM)
continue;
}
else {
if (li->flags & IFL_NOTFREE)
continue;
}
if (li->flags & IFL_NOTBOT)
continue;
//if the item is in a possible goal area
if (!li->goalareanum)
continue;
//FIXME: is this a good thing? added this for items that never spawned into the game (f.i. CTF flags in obelisk)
if (!li->entitynum && !(li->flags & IFL_ROAM))
continue;
//get the fuzzy weight function for this item
iteminfo = &ic->iteminfo[li->iteminfo];
weightnum = gs->itemweightindex[iteminfo->number];
if (weightnum < 0)
continue;
//
#ifdef UNDECIDEDFUZZY
weight = FuzzyWeightUndecided(inventory, gs->itemweightconfig, weightnum);
#else
weight = FuzzyWeight(inventory, gs->itemweightconfig, weightnum);
#endif //UNDECIDEDFUZZY
#ifdef DROPPEDWEIGHT
//HACK: to make dropped items more attractive
if (li->timeout)
weight += droppedweight->value;
#endif //DROPPEDWEIGHT
//use weight scale for item_botroam
if (li->flags & IFL_ROAM) weight *= li->weight;
//
if (weight > 0)
{
//get the travel time towards the goal area
t = AAS_AreaTravelTimeToGoalArea(areanum, origin, li->goalareanum, travelflags);
//if the goal is reachable
if (t > 0 && t < maxtime)
{
//if this item won't respawn before we get there
avoidtime = BotAvoidGoalTime(goalstate, li->number);
if (avoidtime - t * 0.009 > 0)
continue;
//
weight /= (float) t * TRAVELTIME_SCALE;
//
if (weight > bestweight)
{
t = 0;
if (ltg && !li->timeout)
{
//get the travel time from the goal to the long term goal
t = AAS_AreaTravelTimeToGoalArea(li->goalareanum, li->goalorigin, ltg->areanum, travelflags);
} //end if
//if the travel back is possible and doesn't take too long
if (t <= ltg_time)
{
bestweight = weight;
bestitem = li;
} //end if
} //end if
} //end if
} //end if
} //end for
//if no goal item found
if (!bestitem)
return qfalse;
//create a bot goal for this item
iteminfo = &ic->iteminfo[bestitem->iteminfo];
VectorCopy(bestitem->goalorigin, goal.origin);
VectorCopy(iteminfo->mins, goal.mins);
VectorCopy(iteminfo->maxs, goal.maxs);
goal.areanum = bestitem->goalareanum;
goal.entitynum = bestitem->entitynum;
goal.number = bestitem->number;
goal.flags = GFL_ITEM;
if (bestitem->timeout)
goal.flags |= GFL_DROPPED;
if (bestitem->flags & IFL_ROAM)
goal.flags |= GFL_ROAM;
goal.iteminfo = bestitem->iteminfo;
//if it's a dropped item
if (bestitem->timeout)
{
avoidtime = AVOID_DROPPED_TIME;
} //end if
else
{
avoidtime = iteminfo->respawntime;
if (!avoidtime)
avoidtime = AVOID_DEFAULT_TIME;
if (avoidtime < AVOID_MINIMUM_TIME)
avoidtime = AVOID_MINIMUM_TIME;
} //end else
//add the chosen goal to the goals to avoid for a while
BotAddToAvoidGoals(gs, bestitem->number, avoidtime);
//push the goal on the stack
BotPushGoal(goalstate, &goal);
//
return qtrue;
} //end of the function BotChooseNBGItem
//===========================================================================
//
// Parameter: -
// Returns: -
// Changes Globals: -
//===========================================================================
int BotTouchingGoal(vec3_t origin, bot_goal_t *goal)
{
int i;
vec3_t boxmins, boxmaxs;
vec3_t absmins, absmaxs;
vec3_t safety_maxs = {0, 0, 0}; //{4, 4, 10};
vec3_t safety_mins = {0, 0, 0}; //{-4, -4, 0};
AAS_PresenceTypeBoundingBox(PRESENCE_NORMAL, boxmins, boxmaxs);
VectorSubtract(goal->mins, boxmaxs, absmins);
VectorSubtract(goal->maxs, boxmins, absmaxs);
VectorAdd(absmins, goal->origin, absmins);
VectorAdd(absmaxs, goal->origin, absmaxs);
//make the box a little smaller for safety
VectorSubtract(absmaxs, safety_maxs, absmaxs);
VectorSubtract(absmins, safety_mins, absmins);
for (i = 0; i < 3; i++)
{
if (origin[i] < absmins[i] || origin[i] > absmaxs[i]) return qfalse;
} //end for
return qtrue;
} //end of the function BotTouchingGoal
//===========================================================================
//
// Parameter: -
// Returns: -
// Changes Globals: -
//===========================================================================
int BotItemGoalInVisButNotVisible(int viewer, vec3_t eye, vec3_t viewangles, bot_goal_t *goal)
{
aas_entityinfo_t entinfo;
bsp_trace_t trace;
vec3_t middle;
if (!(goal->flags & GFL_ITEM)) return qfalse;
//
VectorAdd(goal->mins, goal->mins, middle);
VectorScale(middle, 0.5, middle);
VectorAdd(goal->origin, middle, middle);
//
trace = AAS_Trace(eye, NULL, NULL, middle, viewer, CONTENTS_SOLID);
//if the goal middle point is visible
if (trace.fraction >= 1)
{
//the goal entity number doesn't have to be valid
//just assume it's valid
if (goal->entitynum <= 0)
return qfalse;
//
//if the entity data isn't valid
AAS_EntityInfo(goal->entitynum, &entinfo);
//NOTE: for some wacko reason entities are sometimes
// not updated
//if (!entinfo.valid) return qtrue;
if (entinfo.ltime < AAS_Time() - 0.5)
return qtrue;
} //end if
return qfalse;
} //end of the function BotItemGoalInVisButNotVisible
//===========================================================================
//
// Parameter: -
// Returns: -
// Changes Globals: -
//===========================================================================
void BotResetGoalState(int goalstate)
{
bot_goalstate_t *gs;
gs = BotGoalStateFromHandle(goalstate);
if (!gs) return;
Com_Memset(gs->goalstack, 0, MAX_GOALSTACK * sizeof(bot_goal_t));
gs->goalstacktop = 0;
BotResetAvoidGoals(goalstate);
} //end of the function BotResetGoalState
//===========================================================================
//
// Parameter: -
// Returns: -
// Changes Globals: -
//===========================================================================
int BotLoadItemWeights(int goalstate, char *filename)
{
bot_goalstate_t *gs;
gs = BotGoalStateFromHandle(goalstate);
if (!gs) return BLERR_CANNOTLOADITEMWEIGHTS;
//load the weight configuration
gs->itemweightconfig = ReadWeightConfig(filename);
if (!gs->itemweightconfig)
{
botimport.Print(PRT_FATAL, "couldn't load weights\n");
return BLERR_CANNOTLOADITEMWEIGHTS;
} //end if
//if there's no item configuration
if (!itemconfig) return BLERR_CANNOTLOADITEMWEIGHTS;
//create the item weight index
gs->itemweightindex = ItemWeightIndex(gs->itemweightconfig, itemconfig);
//everything went ok
return BLERR_NOERROR;
} //end of the function BotLoadItemWeights
//===========================================================================
//
// Parameter: -
// Returns: -
// Changes Globals: -
//===========================================================================
void BotFreeItemWeights(int goalstate)
{
bot_goalstate_t *gs;
gs = BotGoalStateFromHandle(goalstate);
if (!gs) return;
if (gs->itemweightconfig) FreeWeightConfig(gs->itemweightconfig);
if (gs->itemweightindex) FreeMemory(gs->itemweightindex);
} //end of the function BotFreeItemWeights
//===========================================================================
//
// Parameter: -
// Returns: -
// Changes Globals: -
//===========================================================================
int BotAllocGoalState(int client)
{
int i;
for (i = 1; i <= MAX_CLIENTS; i++)
{
if (!botgoalstates[i])
{
botgoalstates[i] = GetClearedMemory(sizeof(bot_goalstate_t));
botgoalstates[i]->client = client;
return i;
} //end if
} //end for
return 0;
} //end of the function BotAllocGoalState
//========================================================================
//
// Parameter: -
// Returns: -
// Changes Globals: -
//========================================================================
void BotFreeGoalState(int handle)
{
if (handle <= 0 || handle > MAX_CLIENTS)
{
botimport.Print(PRT_FATAL, "goal state handle %d out of range\n", handle);
return;
} //end if
if (!botgoalstates[handle])
{
botimport.Print(PRT_FATAL, "invalid goal state handle %d\n", handle);
return;
} //end if
BotFreeItemWeights(handle);
FreeMemory(botgoalstates[handle]);
botgoalstates[handle] = NULL;
} //end of the function BotFreeGoalState
//===========================================================================
//
// Parameter: -
// Returns: -
// Changes Globals: -
//===========================================================================
int BotSetupGoalAI(void)
{
char *filename;
//check if teamplay is on
g_gametype = LibVarValue("g_gametype", "0");
//item configuration file
filename = LibVarString("itemconfig", "items.c");
//load the item configuration
itemconfig = LoadItemConfig(filename);
if (!itemconfig)
{
botimport.Print(PRT_FATAL, "couldn't load item config\n");
return BLERR_CANNOTLOADITEMCONFIG;
} //end if
//
droppedweight = LibVar("droppedweight", "1000");
//everything went ok
return BLERR_NOERROR;
} //end of the function BotSetupGoalAI
//===========================================================================
//
// Parameter: -
// Returns: -
// Changes Globals: -
//===========================================================================
void BotShutdownGoalAI(void)
{
int i;
if (itemconfig) FreeMemory(itemconfig);
itemconfig = NULL;
if (levelitemheap) FreeMemory(levelitemheap);
levelitemheap = NULL;
freelevelitems = NULL;
levelitems = NULL;
numlevelitems = 0;
BotFreeInfoEntities();
for (i = 1; i <= MAX_CLIENTS; i++)
{
if (botgoalstates[i])
{
BotFreeGoalState(i);
} //end if
} //end for
} //end of the function BotShutdownGoalAI