/* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. Copyright (C) 2000-2006 Tim Angus This file is part of Tremulous. Tremulous 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. Tremulous 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 Tremulous; 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) (int)&(((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]; // bk001206 - FIXME: init? //item configuration itemconfig_t *itemconfig = NULL; // bk001206 - init //level items levelitem_t *levelitemheap = NULL; // bk001206 - init levelitem_t *freelevelitems = NULL; // bk001206 - init levelitem_t *levelitems = NULL; // bk001206 - init int numlevelitems = 0; //map locations maplocation_t *maplocations = NULL; // bk001206 - init //camp spots campspot_t *campspots = NULL; // bk001206 - init //the game type int g_gametype = 0; // bk001206 - init //additional dropped item weight libvar_t *droppedweight = NULL; // bk001206 - init //======================================================================== // // 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); 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); //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); 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_PATH]; 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" ); } strncpy( path, filename, MAX_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\n", 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); FreeMemory(source); return NULL; } //end if StripDoubleQuotes(token.string); strncpy(ii->classname, token.string, sizeof(ii->classname)-1); 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\n", 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 (bot_developer) { 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; //update the modelindexes of the item info for (i = 0; i < ic->numiteminfo; i++) { //ic->iteminfo[i].modelindex = AAS_IndexFromModel(ic->iteminfo[i].model); 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) { strncpy(name, itemconfig->iteminfo[li->iteminfo].name, size-1); name[size-1] = '\0'; return; } //end for } //end for strcpy(name, ""); return; } //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; //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); 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); 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 a 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