mirror of
https://github.com/Q3Rally-Team/q3rally.git
synced 2024-11-25 13:21:08 +00:00
96a9e2a9aa
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
1828 lines
52 KiB
C
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
|