// Bot_goal.cpp // #include "precompiled.h" #include "../Game_local.h" idCVar bot_droppedweight( "bot_droppedweight", "1000", CVAR_CHEAT | CVAR_INTEGER, "" ); idCVar bot_itemsfile( "bot_itemsfile", "items.c", CVAR_CHEAT, "" ); idBotGoalManager botGoalManager; static bool BotGoalItemNameContains( const iteminfo_t* itemInfo, const char* needle ) { if( itemInfo == NULL || needle == NULL ) { return false; } return strstr( itemInfo->classname.c_str(), needle ) != NULL || strstr( itemInfo->name.c_str(), needle ) != NULL; } static bool BotGoalHasUsableWeapon( const int* inventory ) { if( inventory == NULL ) { return false; } return ( inventory[INVENTORY_ROCKETLAUNCHER] > 0 && inventory[INVENTORY_ROCKETS] > 0 ) || ( inventory[INVENTORY_PLASMAGUN] > 0 && inventory[INVENTORY_CELLS] > 0 ) || ( inventory[INVENTORY_SHOTGUN] > 0 && inventory[INVENTORY_SHELLS] > 0 ) || ( inventory[INVENTORY_MACHINEGUN] > 0 && inventory[INVENTORY_BULLETS] > 0 ); } static bool BotGoalHasPowerWeapon( const int* inventory ) { if( inventory == NULL ) { return false; } return ( inventory[INVENTORY_ROCKETLAUNCHER] > 0 && inventory[INVENTORY_ROCKETS] > 0 ) || ( inventory[INVENTORY_PLASMAGUN] > 0 && inventory[INVENTORY_CELLS] > 0 ) || ( inventory[INVENTORY_SHOTGUN] > 0 && inventory[INVENTORY_SHELLS] > 0 ); } static bool BotGoalHasOnlyPistolOrDryWeapons( const int* inventory ) { if( inventory == NULL ) { return true; } const bool ownsNonPistolWeapon = inventory[INVENTORY_MACHINEGUN] > 0 || inventory[INVENTORY_SHOTGUN] > 0 || inventory[INVENTORY_PLASMAGUN] > 0 || inventory[INVENTORY_ROCKETLAUNCHER] > 0; if( !ownsNonPistolWeapon ) { return true; } if( !BotGoalHasUsableWeapon( inventory ) ) { return true; } const bool onlyMachinegun = inventory[INVENTORY_MACHINEGUN] > 0 && inventory[INVENTORY_SHOTGUN] <= 0 && inventory[INVENTORY_PLASMAGUN] <= 0 && inventory[INVENTORY_ROCKETLAUNCHER] <= 0; return onlyMachinegun && inventory[INVENTORY_BULLETS] < 25; } static float BotGoalCombatItemBias( const iteminfo_t* itemInfo, const int* inventory ) { if( itemInfo == NULL || inventory == NULL ) { return 0.0f; } const bool desperate = BotGoalHasOnlyPistolOrDryWeapons( inventory ); const bool lacksPowerWeapon = !BotGoalHasPowerWeapon( inventory ); float bias = 0.0f; // Weapons: route through these during a fight, especially if the bot is stuck // on pistol/machinegun or is out of usable ammo. if( BotGoalItemNameContains( itemInfo, "weapon_rocketlauncher" ) || BotGoalItemNameContains( itemInfo, "rocketlauncher" ) ) { if( inventory[INVENTORY_ROCKETLAUNCHER] <= 0 || desperate ) { bias += desperate ? 9000.0f : 4200.0f; } else if( inventory[INVENTORY_ROCKETS] <= 3 ) { bias += 2200.0f; } } else if( BotGoalItemNameContains( itemInfo, "weapon_plasmagun" ) || BotGoalItemNameContains( itemInfo, "plasmagun" ) ) { if( inventory[INVENTORY_PLASMAGUN] <= 0 || desperate ) { bias += desperate ? 8200.0f : 3800.0f; } else if( inventory[INVENTORY_CELLS] <= 18 ) { bias += 1800.0f; } } else if( BotGoalItemNameContains( itemInfo, "weapon_shotgun" ) || BotGoalItemNameContains( itemInfo, "shotgun" ) ) { if( inventory[INVENTORY_SHOTGUN] <= 0 || desperate || lacksPowerWeapon ) { bias += desperate ? 7600.0f : 3200.0f; } else if( inventory[INVENTORY_SHELLS] <= 6 ) { bias += 1600.0f; } } else if( BotGoalItemNameContains( itemInfo, "weapon_machinegun" ) || BotGoalItemNameContains( itemInfo, "machinegun" ) ) { if( inventory[INVENTORY_MACHINEGUN] <= 0 || desperate ) { bias += desperate ? 6200.0f : 1800.0f; } else if( inventory[INVENTORY_BULLETS] <= 30 ) { bias += 1200.0f; } } else if( BotGoalItemNameContains( itemInfo, "ammo_rockets" ) || BotGoalItemNameContains( itemInfo, "rocket_ammo" ) ) { if( inventory[INVENTORY_ROCKETLAUNCHER] > 0 && inventory[INVENTORY_ROCKETS] <= 5 ) { bias += inventory[INVENTORY_ROCKETS] <= 1 ? 4200.0f : 2400.0f; } } else if( BotGoalItemNameContains( itemInfo, "ammo_cells" ) || BotGoalItemNameContains( itemInfo, "cells" ) ) { if( inventory[INVENTORY_PLASMAGUN] > 0 && inventory[INVENTORY_CELLS] <= 25 ) { bias += inventory[INVENTORY_CELLS] <= 8 ? 3600.0f : 2000.0f; } } else if( BotGoalItemNameContains( itemInfo, "ammo_shells" ) || BotGoalItemNameContains( itemInfo, "shells" ) ) { if( inventory[INVENTORY_SHOTGUN] > 0 && inventory[INVENTORY_SHELLS] <= 10 ) { bias += inventory[INVENTORY_SHELLS] <= 3 ? 3200.0f : 1700.0f; } } else if( BotGoalItemNameContains( itemInfo, "ammo_clip" ) || BotGoalItemNameContains( itemInfo, "bullets" ) ) { if( inventory[INVENTORY_MACHINEGUN] > 0 && inventory[INVENTORY_BULLETS] <= 45 ) { bias += inventory[INVENTORY_BULLETS] <= 10 ? 2800.0f : 1300.0f; } } // Health/armor should also interrupt a fight, but only when it is tactically // worth the detour. This still lets fuzzy item weights handle normal roaming. if( BotGoalItemNameContains( itemInfo, "health" ) ) { if( inventory[INVENTORY_HEALTH] < 35 ) { bias += 5200.0f; } else if( inventory[INVENTORY_HEALTH] < 65 ) { bias += 2600.0f; } else if( inventory[INVENTORY_HEALTH] < 95 ) { bias += 900.0f; } } if( BotGoalItemNameContains( itemInfo, "armor" ) ) { if( inventory[INVENTORY_ARMOR] < 25 ) { bias += 2400.0f; } else if( inventory[INVENTORY_ARMOR] < 75 ) { bias += 1100.0f; } } return bias; } /* ================== idBotGoalManager::idBotGoalManager ================== */ idBotGoalManager::idBotGoalManager() { freelevelitems = NULL; levelitems = NULL; } /* ================== idBotGoalManager::BotGoalStateFromHandle ================== */ bot_goalstate_t* idBotGoalManager::BotGoalStateFromHandle( int handle ) { if( handle <= 0 || handle > MAX_CLIENTS ) { gameLocal.Error( "goal state handle %d out of range\n", handle ); return NULL; } if( !botgoalstates[handle].InUse() ) { gameLocal.Error( "invalid goal state %d\n", handle ); return NULL; } return &botgoalstates[handle]; } /* ================== idBotGoalManager::BotInterbreedGoalFuzzyLogic ================== */ void idBotGoalManager::BotInterbreedGoalFuzzyLogic( int parent1, int parent2, int child ) { bot_goalstate_t* p1, * p2, * c; p1 = BotGoalStateFromHandle( parent1 ); p2 = BotGoalStateFromHandle( parent2 ); c = BotGoalStateFromHandle( child ); botFuzzyWeightManager.InterbreedWeightConfigs( p1->itemweightconfig, p2->itemweightconfig, c->itemweightconfig ); } /* ======================== idBotGoalManager::BotSaveGoalFuzzyLogic ======================== */ void idBotGoalManager::BotSaveGoalFuzzyLogic( int goalstate, char* filename ) { bot_goalstate_t* gs; gs = BotGoalStateFromHandle( goalstate ); //WriteWeightConfig(filename, gs->itemweightconfig); } /* ======================== idBotGoalManager::BotMutateGoalFuzzyLogic ======================== */ void idBotGoalManager::BotMutateGoalFuzzyLogic( int goalstate, float range ) { bot_goalstate_t* gs; gs = BotGoalStateFromHandle( goalstate ); botFuzzyWeightManager.EvolveWeightConfig( gs->itemweightconfig ); } /* ======================== idBotGoalManager::ParseItemInfo ======================== */ void idBotGoalManager::ParseItemInfo( idParser& parser, iteminfo_t* itemInfo ) { idToken token; parser.ExpectTokenString( "{" ); while( true ) { if( !parser.ReadToken( &token ) ) { parser.Error( "Unexpected end of file found while parsing weaponinfo!" ); } if( token == "}" ) { break; } if( token == "name" ) { idToken valueToken; if( !parser.ReadToken( &valueToken ) ) { parser.Error( "Failed to read value token, encountered EOF parsing weapon info!" ); } itemInfo->name = valueToken; } else if( token == "model" ) { idToken valueToken; if( !parser.ReadToken( &valueToken ) ) { parser.Error( "Failed to read value token, encountered EOF parsing weapon info!" ); } itemInfo->model = valueToken; } else if( token == "modelindex" ) { idToken valueToken; if( !parser.ReadToken( &valueToken ) ) { parser.Error( "Failed to read value token, encountered EOF parsing weapon info!" ); } itemInfo->modelindex = valueToken.GetIntValue(); } else if( token == "type" ) { idToken valueToken; if( !parser.ReadToken( &valueToken ) ) { parser.Error( "Failed to read value token, encountered EOF parsing weapon info!" ); } itemInfo->type = valueToken.GetIntValue(); } else if( token == "index" ) { idToken valueToken; if( !parser.ReadToken( &valueToken ) ) { parser.Error( "Failed to read value token, encountered EOF parsing weapon info!" ); } itemInfo->index = valueToken.GetIntValue(); } else if( token == "respawntime" ) { idToken valueToken; if( !parser.ReadToken( &valueToken ) ) { parser.Error( "Failed to read value token, encountered EOF parsing weapon info!" ); } itemInfo->respawntime = valueToken.GetIntValue(); } else if( token == "mins" ) { float value[3]; if( !parser.Parse1DMatrixLegacy( 3, &value[0] ) ) { parser.Error( "Failed to read value token, encountered EOF parsing weapon info!" ); } itemInfo->mins.Set( value[0], value[1], value[2] ); } else if( token == "maxs" ) { float value[3]; if( !parser.Parse1DMatrixLegacy( 3, &value[0] ) ) { parser.Error( "Failed to read value token, encountered EOF parsing weapon info!" ); } itemInfo->maxs.Set( value[0], value[1], value[2] ); } else { parser.Error( "ParseProjectileInfo: Unexpected token %s\n", token.c_str() ); } } } /* ======================== idBotGoalManager::LoadItemConfig ======================== */ itemconfig_t* idBotGoalManager::LoadItemConfig( char* filename ) { idToken token; idStr path; itemconfig_t ic; iteminfo_t* ii; idParser source; path = filename; rvmScopedLexerBaseFolder scopedBaseFolder( BOTFILESBASEFOLDER ); if( !source.LoadFile( path ) ) { gameLocal.Error( "counldn't load %s\n", path.c_str() ); return NULL; } ic.Reset(); //parse the item config file while( source.ReadToken( &token ) ) { if( token == "iteminfo" ) { if( ic.numiteminfo >= MAX_BOT_ITEM_INFO ) { source.Error( "more than %d item info defined\n", MAX_BOT_ITEM_INFO ); return NULL; } ii = &ic.iteminfo[ic.numiteminfo]; if( !source.ExpectTokenType( TT_STRING, 0, &token ) ) { return NULL; } token.StripDoubleQuotes(); ii->classname = token; ParseItemInfo( source, ii ); ii->number = ic.numiteminfo; ic.numiteminfo++; } else { gameLocal.Error( "unknown definition %s\n", token.c_str() ); return NULL; } } if( !ic.numiteminfo ) { common->Warning( "no item info loaded\n" ); } common->DPrintf( "loaded %s\n", path ); itemconfiglocal = ic; itemconfig = &itemconfiglocal; return itemconfig; } /* ======================== idBotGoalManager::ItemWeightIndex ======================== */ static const char* Bot_ItemWeightAlias( const char* classname ) { if( classname == NULL || classname[0] == '\0' ) { return NULL; } struct itemWeightAlias_t { const char* missingName; const char* fallbackName; }; static const itemWeightAlias_t aliases[] = { { "weapon_grenadelauncher", "weapon_rocketlauncher" }, { "weapon_lightning", "weapon_plasmagun" }, { "weapon_railgun", "weapon_rocketlauncher" }, { "weapon_fists", "weapon_pistol" }, { "weapon_grapplinghook", "weapon_chainsaw" }, { "weapon_nailgun", "weapon_chaingun" }, { "weapon_prox_launcher", "weapon_rocketlauncher" }, { "team_redobelisk", "item_armor_shard" }, { "team_blueobelisk", "item_armor_shard" }, { "team_neutralobelisk", "item_armor_shard" }, { NULL, NULL } }; for( int i = 0; aliases[i].missingName != NULL; i++ ) { if( idStr::Icmp( classname, aliases[i].missingName ) == 0 ) { return aliases[i].fallbackName; } } return NULL; } static bool Bot_ItemClassnamesMatch( const char* entityClassname, const char* itemClassname ) { if( entityClassname == NULL || itemClassname == NULL ) { return false; } if( idStr::Icmp( entityClassname, itemClassname ) == 0 ) { return true; } idStr normalizedItem = itemClassname; if( idStr::Icmp( normalizedItem.Right( 3 ).c_str(), "_mp" ) == 0 ) { normalizedItem.StripTrailing( "_mp" ); if( idStr::Icmp( entityClassname, normalizedItem.c_str() ) == 0 ) { return true; } } const char* aliasName = Bot_ItemWeightAlias( itemClassname ); return aliasName != NULL && idStr::Icmp( entityClassname, aliasName ) == 0; } int* idBotGoalManager::ItemWeightIndex( weightconfig_t* iwc, itemconfig_t* ic ) { int* index; if( iwc == NULL || ic == NULL || ic->numiteminfo <= 0 ) { return NULL; } //initialize item weight index index = new int[ic->numiteminfo]; // jmarshall: Get this off of the heap! for( int i = 0; i < ic->numiteminfo; i++ ) { const char* classname = ic->iteminfo[i].classname.c_str(); index[i] = botFuzzyWeightManager.FindFuzzyWeight( iwc, ( char* )classname ); if( index[i] < 0 ) { const char* aliasName = Bot_ItemWeightAlias( classname ); if( aliasName != NULL ) { index[i] = botFuzzyWeightManager.FindFuzzyWeight( iwc, ( char* )aliasName ); } } if( index[i] < 0 ) { static const char* safeFallbacks[] = { "item_armor_shard", "item_armor", "ammo_clip", "weapon_machinegun", "weapon_shotgun", NULL }; for( int j = 0; safeFallbacks[j] != NULL; j++ ) { index[i] = botFuzzyWeightManager.FindFuzzyWeight( iwc, ( char* )safeFallbacks[j] ); if( index[i] >= 0 ) { break; } } } if( index[i] < 0 ) { common->Warning( "item info %d \"%s\" has no fuzzy weight\r\n", i, classname ); } } return index; } /* ======================== idBotGoalManager::InitLevelItemHeap ======================== */ void idBotGoalManager::InitLevelItemHeap( void ) { for( int i = 0; i < MAX_BOT_LEVEL_ITEMS - 1; i++ ) { levelitemheap[i].next = &levelitemheap[i + 1]; } levelitemheap[MAX_BOT_LEVEL_ITEMS - 1].next = NULL; freelevelitems = levelitemheap; } /* ======================== idBotGoalManager::AllocLevelItem ======================== */ levelitem_t* idBotGoalManager::AllocLevelItem( void ) { levelitem_t* li; li = freelevelitems; if( !li ) { gameLocal.Warning( "out of bot level items, increase MAX_BOT_LEVEL_ITEMS\n" ); return NULL; } freelevelitems = freelevelitems->next; //memset(li, 0, sizeof(levelitem_t)); li->Reset(); return li; } /* ======================== idBotGoalManager::FreeLevelItem ======================== */ void idBotGoalManager::FreeLevelItem( levelitem_t* li ) { li->next = freelevelitems; freelevelitems = li; } /* ======================== idBotGoalManager::AddLevelItemToList ======================== */ void idBotGoalManager::AddLevelItemToList( levelitem_t* li ) { if( levelitems ) { levelitems->prev = li; } li->prev = NULL; li->next = levelitems; levelitems = li; } /* ======================== idBotGoalManager::RemoveLevelItemFromList ======================== */ void idBotGoalManager::RemoveLevelItemFromList( levelitem_t* li ) { if( li->prev ) { li->prev->next = li->next; } else { levelitems = li->next; } if( li->next ) { li->next->prev = li->prev; } } /* ======================== idBotGoalManager::BotFreeInfoEntities ======================== */ void idBotGoalManager::BotFreeInfoEntities( void ) { maplocation_t* ml, * nextml; campspot_t* cs, * nextcs; // for (ml = maplocations; ml; ml = nextml) // { // nextml = ml->next; // // jmarshall // //FreeMemory(ml); // // jmarshall end // } maplocations.Clear(); //for (cs = campspots; cs; cs = nextcs) //{ // nextcs = cs->next; // // jmarshall // //FreeMemory(cs); // // jmarshall end //} campspots.Clear(); } /* ======================== idBotGoalManager::BotInitInfoEntities ======================== */ void idBotGoalManager::BotInitInfoEntities( void ) { //char classname[MAX_EPAIRKEY]; idStr classname; maplocation_t* ml; campspot_t* cs; BotFreeInfoEntities(); maplocations.Clear(); campspots.Clear(); for( int i = 0; i < gameLocal.num_entities; i++ ) { idEntity* ent = gameLocal.entities[i]; if( ent == NULL ) { continue; } classname = ent->spawnArgs.GetString( "classname" ); //map locations if( classname == "target_location" ) { maplocation_t ml; ml.origin = ent->GetPhysics()->GetOrigin(); ml.name = ent->GetKey( "message" ); maplocations.Append( ml ); } //camp spots else if( classname == "info_camp" ) { //cs = (campspot_t*)G_AllocClearedMemory(sizeof(campspot_t)); campspot_t cs; cs.origin = ent->GetPhysics()->GetOrigin(); cs.name = ent->GetKey( "message" ); cs.range = ent->GetFloat( "range" ); cs.weight = ent->GetFloat( "weight" ); cs.wait = ent->GetFloat( "wait" ); cs.random = ent->GetFloat( "random" ); campspots.Append( cs ); } } common->Printf( "%d map locations\n", maplocations.Num() ); common->Printf( "%d camp spots\n", campspots.Num() ); } /* ======================= idBotGoalManager::InitLevelItems ======================= */ void idBotGoalManager::InitLevelItems( void ) { int i, spawnflags, value; idStr classname; idVec3 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; } //update 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); // } //} //for (ent = NAV_NextBSPEntity(0); ent; ent = NAV_NextBSPEntity(ent)) for( int idx = 0; idx < gameLocal.num_entities; idx++ ) { if( gameLocal.entities[idx] == NULL ) { continue; } idItem* ent = gameLocal.entities[idx]->Cast(); if( ent == nullptr ) { continue; } spawnflags = ent->GetInt( "spawnflags" ); classname = ent->spawnArgs.GetString( "classname" ); for( i = 0; i < ic->numiteminfo; i++ ) { if( Bot_ItemClassnamesMatch( classname.c_str(), ic->iteminfo[i].classname.c_str() ) ) { break; } } if( i >= ic->numiteminfo ) { gameLocal.Warning( "entity %s unknown item\r\n", classname.c_str() ); continue; } //get the origin of the item. origin = ent->GetPhysics()->GetOrigin(); goalareanum = 0; // jmarshall - fix floating items //if it is a floating item //if (spawnflags & 1) //{ // //if the item is not floating in water // if (!(NAV_PointContents(origin) & CONTENTS_WATER)) // { // VectorCopy(origin, end); // end[2] -= 32; // trace = NAV_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 = NAV_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); // //G_Printf( "item %s reachable from jumppad area %d\r\n", ic->iteminfo[i].classname, goalareanum); // if (!goalareanum) continue; // } //end if // } //end if //} //end if // jmarshall end li = AllocLevelItem(); if( !li ) { return; } li->number = ++numlevelitems; li->timeout = 0; li->item = ent; li->name = classname; li->flags = 0; if( ent->GetBool( "notfree" ) ) { li->flags |= IFL_NOTFREE; } if( ent->GetBool( "notteam" ) ) { li->flags |= IFL_NOTTEAM; } if( ent->GetBool( "notsingle" ) ) { li->flags |= IFL_NOTSINGLE; } if( ent->GetBool( "notbot" ) ) { li->flags |= IFL_NOTBOT; } if( classname == "item_botroam" ) { li->flags |= IFL_ROAM; //NAV_FloatForBSPEpairKey(ent, "weight", &li->weight); li->weight = ent->GetFloat( "weight" ); } ////if not a stationary item //if (!(spawnflags & 1)) //{ // if (!NAV_DropToFloor(origin, ic->iteminfo[i].mins, ic->iteminfo[i].maxs)) // { // G_Printf( "%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 ); VectorCopy( origin, li->goalorigin ); AddLevelItemToList( li ); } gameLocal.Printf( "found %d level items\n", numlevelitems ); } /* ======================= idBotGoalManager::BotGoalName ======================= */ void idBotGoalManager::BotGoalName( int number, char* name, int size ) { levelitem_t* li; if( name == NULL || size <= 0 ) { return; } name[0] = '\0'; if( !itemconfig ) { return; } for( li = levelitems; li; li = li->next ) { if( li->number == number ) { idStr::Copynz( name, itemconfig->iteminfo[li->iteminfo].name, size ); return; } } } /* ======================= idBotGoalManager::BotResetAvoidGoals ======================= */ void idBotGoalManager::BotResetAvoidGoals( int goalstate ) { bot_goalstate_t* gs; gs = BotGoalStateFromHandle( goalstate ); if( !gs ) { return; } memset( gs->avoidgoals, 0, MAX_AVOIDGOALS * sizeof( int ) ); memset( gs->avoidgoaltimes, 0, MAX_AVOIDGOALS * sizeof( float ) ); } /* ======================= idBotGoalManager::BotResetAvoidGoals ======================= */ void idBotGoalManager::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] >= Bot_Time() ) { BotGoalName( gs->avoidgoals[i], name, 32 ); //Log_Write("avoid goal %s, number %d for %f seconds", name, // gs->avoidgoals[i], gs->avoidgoaltimes[i] - NAV_Time()); } } } /* ======================= idBotGoalManager::BotAddToAvoidGoals ======================= */ void idBotGoalManager::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] = Bot_Time() + avoidtime; return; } } for( i = 0; i < MAX_AVOIDGOALS; i++ ) { //if this avoid goal has expired if( gs->avoidgoaltimes[i] < Bot_Time() ) { gs->avoidgoals[i] = number; gs->avoidgoaltimes[i] = Bot_Time() + avoidtime; return; } } } /* ======================= idBotGoalManager::BotAddToAvoidGoals ======================= */ void idBotGoalManager::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] >= Bot_Time() ) { gs->avoidgoaltimes[i] = 0; return; } } } /* ======================= idBotGoalManager::BotAvoidGoalTime ======================= */ float idBotGoalManager::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] >= Bot_Time() ) { return gs->avoidgoaltimes[i] - Bot_Time(); } } return 0; } /* ======================= idBotGoalManager::BotSetAvoidGoalTime ======================= */ void idBotGoalManager::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; } } return; } else { BotAddToAvoidGoals( gs, number, avoidtime ); } } /* ======================= idBotGoalManager::BotGetLevelItemGoal ======================= */ int idBotGoalManager::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; } } } for( ; li; li = li->next ) { if( gameLocal.gameType == GAME_SP ) { if( li->flags & IFL_NOTSINGLE ) { continue; } } else if( gameLocal.gameType >= GAME_TDM ) { if( li->flags & IFL_NOTTEAM ) { continue; } } else { if( li->flags & IFL_NOTFREE ) { continue; } } if( li->flags & IFL_NOTBOT ) { continue; } if( itemconfig->iteminfo[li->iteminfo].name == name ) { // goal->areanum = li->goalareanum; goal->origin = li->goalorigin; goal->entitynum = li->item != NULL ? li->item->entityNumber : 0; goal->mins = itemconfig->iteminfo[li->iteminfo].mins; goal->maxs = itemconfig->iteminfo[li->iteminfo].maxs; goal->number = li->number; goal->flags = GFL_ITEM; if( li->timeout ) { goal->flags |= GFL_DROPPED; } //G_Printf( "found li %s\n", itemconfig->iteminfo[li->iteminfo].name); return li->number; } } return -1; } /* ========================= idBotGoalManager::BotGetMapLocationGoal ========================= */ int idBotGoalManager::BotGetMapLocationGoal( char* name, bot_goal_t* goal ) { maplocation_t* ml; idVec3 mins( -8, -8, -8 ); idVec3 maxs( 8, 8, 8 ); for( int i = 0; i < maplocations.Num(); i++ ) { ml = &maplocations[i]; if( ml->name == name ) { goal->areanum = ml->areanum; VectorCopy( ml->origin, goal->origin ); goal->entitynum = 0; VectorCopy( mins, goal->mins ); VectorCopy( maxs, goal->maxs ); return true; } } return false; } /* ========================= idBotGoalManager::BotGetNextCampSpotGoal ========================= */ int idBotGoalManager::BotGetNextCampSpotGoal( int num, bot_goal_t* goal ) { int i; campspot_t* cs; idVec3 mins( -8, -8, -8 ); idVec3 maxs( 8, 8, 8 ); if( num < 0 ) { num = 0; } i = num; for( int d = 0; d < campspots.Num(); d++ ) { cs = &campspots[d]; 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; } } return 0; } /* ============================ idBotGoalManager::BotFindEntityForLevelItem ============================ */ void idBotGoalManager::BotFindEntityForLevelItem( levelitem_t* li ) { int ent, modelindex; itemconfig_t* ic; //aas_entityinfo_t entinfo; idVec3 dir; ic = itemconfig; if( !itemconfig ) { return; } for( int idx = 0; idx < gameLocal.num_entities; idx++ ) { if( gameLocal.entities[idx] == NULL ) { continue; } idItem* ent = gameLocal.entities[idx]->Cast(); if( ent == nullptr ) { continue; } //get the model index of the entity modelindex = ent->GetModelIndex(); if( !modelindex ) { continue; } // jmarshall - eval moving objects //get info about the entity //NAV_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; // // jmarshall end if( ic->iteminfo[li->iteminfo].modelindex == modelindex ) { //check if the entity is very close idVec3 dir = li->origin - ent->GetPhysics()->GetOrigin(); if( dir.Length() < 30 ) { //found an entity for this level item li->item = ent; } } } } /* ======================== UpdateEntityItems ======================== */ //NOTE: enum entityType_t in bg_public.h #define ET_ITEM 2 void idBotGoalManager::UpdateEntityItems( void ) { int i, modelindex; idVec3 dir; levelitem_t* li, * nextli; 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 < Bot_Time() ) { RemoveLevelItemFromList( li ); FreeLevelItem( li ); } } } //find new entity items ic = itemconfig; if( !itemconfig ) { return; } // for( int idx = 0; idx < gameLocal.num_entities; idx++ ) { if( gameLocal.entities[idx] == NULL ) { continue; } idItem* ent = gameLocal.entities[idx]->Cast(); if( ent == nullptr ) { continue; } //get the model index of the entity modelindex = ent->GetModelIndex(); if( !modelindex ) { continue; } // jmarshall - eval floating items. ////get info about the entity //NAV_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; // jmarshall end //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->item && li->item == 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; } 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 = NAV_BestReachableArea(li->origin, // ic->iteminfo[li->iteminfo].mins, ic->iteminfo[li->iteminfo].maxs, // li->goalorigin); //} //end if li->origin = ent->GetPhysics()->GetOrigin(); break; } } } 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->item ) { continue; } // if( gameLocal.gameType == GAME_SP ) { if( li->flags & IFL_NOTSINGLE ) { continue; } } else if( gameLocal.gameType >= GAME_TDM ) { 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 dir = li->origin - ent->GetPhysics()->GetOrigin(); if( dir.Length() < 30 ) { //found an entity for this level item li->item = 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(ent->r.currentOrigin, li->origin); // //also update the goal area number // li->goalareanum = NAV_BestReachableArea(li->origin, // ic->iteminfo[li->iteminfo].mins, ic->iteminfo[li->iteminfo].maxs, // li->goalorigin); //} //end if //update the level item origin //VectorCopy(ent->r.currentOrigin, li->origin); li->origin = ent->GetPhysics()->GetOrigin(); break; } } } 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; } } //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->item = ent; //number for the level item li->number = ent->entityNumber; //set the item info index for the level item li->iteminfo = i; //origin of the item //VectorCopy(ent->r.currentOrigin, li->origin); li->origin = ent->GetPhysics()->GetOrigin(); // jmarshall - fix this, bots, jump pads, and droppable items, bad combo. //get the item goal area and goal origin //li->goalareanum = NAV_BestReachableArea(li->origin, // ic->iteminfo[i].mins, ic->iteminfo[i].maxs, // li->goalorigin); ////never go for items dropped into jumppads //if (NAV_AreaJumpPad(li->goalareanum)) //{ // FreeLevelItem(li); // continue; //} //end if // jmarshall end //time this item out after 30 seconds //dropped items disappear after 30 seconds li->timeout = Bot_Time() + 30; //add the level item to the list AddLevelItemToList( li ); //G_Printf( "found new level item %s\n", ic->iteminfo[i].classname); } } /* ==================== idBotGoalManager::BotDumpGoalStack ==================== */ void idBotGoalManager::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 ); common->Printf( "%d: %s", i, name ); } } /* ==================== BotPushGoal ==================== */ void idBotGoalManager::BotPushGoal( int goalstate, bot_goal_t* goal ) { bot_goalstate_t* gs; gs = BotGoalStateFromHandle( goalstate ); if( !gs ) { return; } if( gs->goalstacktop > 0 && gs->goalstack[gs->goalstacktop].number == goal->number ) { gs->goalstack[gs->goalstacktop] = *goal; return; } if( gs->goalstacktop >= MAX_GOALSTACK - 1 ) { gameLocal.Warning( "bot goal stack overflow, clearing stale goals\n" ); BotDumpGoalStack( goalstate ); gs->goalstacktop = 0; } gs->goalstacktop++; gs->goalstack[gs->goalstacktop] = *goal; } /* ==================== idBotGoalManager::BotPopGoal ==================== */ void idBotGoalManager::BotPopGoal( int goalstate ) { bot_goalstate_t* gs; gs = BotGoalStateFromHandle( goalstate ); if( !gs ) { return; } if( gs->goalstacktop > 0 ) { gs->goalstacktop--; } } /* ==================== idBotGoalManager::BotEmptyGoalStack ==================== */ void idBotGoalManager::BotEmptyGoalStack( int goalstate ) { bot_goalstate_t* gs; gs = BotGoalStateFromHandle( goalstate ); if( !gs ) { return; } gs->goalstacktop = 0; } /* ==================== idBotGoalManager::BotGetTopGoal ==================== */ int idBotGoalManager::BotGetTopGoal( int goalstate, bot_goal_t* goal ) { bot_goalstate_t* gs; gs = BotGoalStateFromHandle( goalstate ); if( !gs ) { return false; } if( !gs->goalstacktop ) { return false; } memcpy( goal, &gs->goalstack[gs->goalstacktop], sizeof( bot_goal_t ) ); return true; } /* ==================== idBotGoalManager::BotGetSecondGoal ==================== */ int idBotGoalManager::BotGetSecondGoal( int goalstate, bot_goal_t* goal ) { bot_goalstate_t* gs; gs = BotGoalStateFromHandle( goalstate ); if( !gs ) { return false; } if( gs->goalstacktop <= 1 ) { return false; } memcpy( goal, &gs->goalstack[gs->goalstacktop - 1], sizeof( bot_goal_t ) ); return true; } /* ==================== idBotGoalManager::BotChooseLTGItem ==================== */ int idBotGoalManager::BotChooseLTGItem( int goalstate, idVec3 origin, int* inventory, int travelflags ) { int 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 false; } if( !gs->itemweightconfig ) { return false; } //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 || !NAV_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 false; } //best weight and item so far bestweight = 0; bestitem = NULL; memset( &goal, 0, sizeof( bot_goal_t ) ); //go through the items in the level for( li = levelitems; li; li = li->next ) { if( gameLocal.gameType == GAME_SP ) { if( li->flags & IFL_NOTSINGLE ) { continue; } } else if( gameLocal.gameType >= GAME_TDM ) { 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->item && !( li->flags & IFL_ROAM ) ) { continue; } //get the fuzzy weight function for this item iteminfo = &ic->iteminfo[li->iteminfo]; const float tacticalBias = BotGoalCombatItemBias( iteminfo, inventory ); weightnum = gs->itemweightindex[iteminfo->number]; if( weightnum < 0 ) { if( tacticalBias <= 0.0f ) { continue; } weight = 0.0f; } else { #ifdef UNDECIDEDFUZZY weight = FuzzyWeightUndecided( inventory, gs->itemweightconfig, weightnum ); #else weight = botFuzzyWeightManager.FuzzyWeight( inventory, gs->itemweightconfig, weightnum ); #endif //UNDECIDEDFUZZY } weight += tacticalBias; #ifdef DROPPEDWEIGHT //HACK: to make dropped items more attractive if( li->timeout ) { weight += bot_droppedweight.GetFloat(); //GetValueFromLibVar(droppedweight); } #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 // jmarshall //t = NAV_AreaTravelTimeToGoalArea(areanum, origin, li->goalareanum, travelflags); t = gameLocal.TravelTimeToGoal( origin, li->goalorigin ); // jmarshall end //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; } } } } //if no goal item found if( !bestitem ) { /* //if not in lava or slime if (!NAV_AreaLava(areanum) && !NAV_AreaSlime(areanum)) { if (NAV_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 G_Printf( "chosen roam goal area %d\n", goal.areanum); #endif //DEBUG return qtrue; } //end if } //end if */ return false; } //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->item != NULL ? bestitem->item->entityNumber : 0; 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; } else { avoidtime = iteminfo->respawntime; if( !avoidtime ) { avoidtime = AVOID_DEFAULT_TIME; } if( avoidtime < AVOID_MINIMUM_TIME ) { avoidtime = AVOID_MINIMUM_TIME; } } //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 true; } /* ==================== idBotGoalManager::BotChooseNBGItem ==================== */ int idBotGoalManager::BotChooseNBGItem( int goalstate, idVec3 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 false; } if( !gs->itemweightconfig ) { return false; } //get the area the bot is in // jmarshall areanum = 1; // BotReachabilityArea(origin, gs->client); // jmarshall end //if the bot is in solid or if the area the bot is in has no reachability links //if (!areanum || !NAV_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 false; } // jmarshall if( ltg ) { //ltg_time = NAV_AreaTravelTimeToGoalArea(areanum, origin, ltg->areanum, travelflags); ltg_time = gameLocal.TravelTimeToGoal( origin, ltg->origin ); } else { ltg_time = 99999; } // jmarshall end //the item configuration ic = itemconfig; if( !itemconfig ) { return false; } //best weight and item so far bestweight = 0; bestitem = NULL; memset( &goal, 0, sizeof( bot_goal_t ) ); //go through the items in the level for( li = levelitems; li; li = li->next ) { if( gameLocal.gameType == GAME_SP ) { if( li->flags & IFL_NOTSINGLE ) { continue; } } else if( gameLocal.gameType >= GAME_TDM ) { 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->item && !( li->flags & IFL_ROAM ) ) { continue; } //get the fuzzy weight function for this item iteminfo = &ic->iteminfo[li->iteminfo]; const float tacticalBias = BotGoalCombatItemBias( iteminfo, inventory ); weightnum = gs->itemweightindex[iteminfo->number]; if( weightnum < 0 ) { if( tacticalBias <= 0.0f ) { continue; } weight = 0.0f; } else { #ifdef UNDECIDEDFUZZY weight = FuzzyWeightUndecided( inventory, gs->itemweightconfig, weightnum ); #else weight = botFuzzyWeightManager.FuzzyWeight( inventory, gs->itemweightconfig, weightnum ); #endif //UNDECIDEDFUZZY } weight += tacticalBias; #ifdef DROPPEDWEIGHT //HACK: to make dropped items more attractive if( li->timeout ) { weight += bot_droppedweight.GetFloat(); } #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 = NAV_AreaTravelTimeToGoalArea(areanum, origin, li->goalareanum, travelflags); t = gameLocal.TravelTimeToGoal( origin, li->goalorigin ); //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 // jmarshall //t = NAV_AreaTravelTimeToGoalArea(li->goalareanum, li->goalorigin, ltg->areanum, travelflags); t = gameLocal.TravelTimeToGoal( li->goalorigin, ltg->origin ); // jmarshall end } //end if // For ordinary detours, keep the original "on the way" check. For tactical // combat pickups such as a missing weapon, ammo, or low-health pickup, allow // a short off-route move because surviving the fight is more important than // staying glued to the enemy/chase goal. if( tacticalBias > 0.0f || 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 false; } //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->item != NULL ? bestitem->item->entityNumber : 0; 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 true; } //end of the function BotChooseNBGItem /* ==================== idBotGoalManager::BotTouchingGoal ==================== */ int idBotGoalManager::BotTouchingGoal( idVec3 origin, bot_goal_t* goal ) { int i; idVec3 boxmins, boxmaxs; idVec3 absmins, absmaxs; idVec3 safety_maxs( 0, 0, 0 ); //{4, 4, 10}; idVec3 safety_mins( 0, 0, 0 ); //{-4, -4, 0}; rvmBot::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 false; } } return true; } /* ======================== idBotGoalManager::BotItemGoalInVisButNotVisible ======================== */ int idBotGoalManager::BotItemGoalInVisButNotVisible( int viewer, idVec3 eye, idAngles viewangles, bot_goal_t* goal ) { //aas_entityinfo_t entinfo; trace_t trace; idVec3 middle; if( !( goal->flags & GFL_ITEM ) ) { return false; } VectorAdd( goal->mins, goal->maxs, middle ); VectorScale( middle, 0.5, middle ); VectorAdd( goal->origin, middle, middle ); //trap_Trace(&trace, eye, NULL, NULL, middle, viewer, CONTENTS_SOLID); idMat3 axis; axis.Identity(); gameLocal.clip.Translation( trace, eye, middle, NULL, axis, CONTENTS_SOLID, NULL ); //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 false; } // jmarshall - time delay thing here? // //if the entity data isn't valid //NAV_EntityInfo(goal->entitynum, &entinfo); //NOTE: for some wacko reason entities are sometimes // not updated //if (!entinfo.valid) return qtrue; //if (entinfo.ltime < NAV_Time() - 0.5) // return qtrue; // jmarshall end } return false; } /* ======================== BotResetGoalState ======================== */ void idBotGoalManager::BotResetGoalState( int goalstate ) { bot_goalstate_t* gs; gs = BotGoalStateFromHandle( goalstate ); if( !gs ) { return; } //memset(gs->goalstack, 0, MAX_GOALSTACK * sizeof(bot_goal_t)); for( int i = 0; i < MAX_GOALSTACK; i++ ) { gs->goalstack[i].Reset(); } gs->goalstacktop = 0; BotResetAvoidGoals( goalstate ); } /* ======================== idBotGoalManager::BotResetGoalState ======================== */ int idBotGoalManager::BotLoadItemWeights( int goalstate, char* filename ) { bot_goalstate_t* gs; gs = BotGoalStateFromHandle( goalstate ); if( !gs ) { return BLERR_CANNOTLOADITEMWEIGHTS; } //load the weight configuration gs->itemweightconfig = botFuzzyWeightManager.ReadWeightConfig( filename ); if( !gs->itemweightconfig ) { gameLocal.Error( "couldn't load weights\n" ); return BLERR_CANNOTLOADITEMWEIGHTS; } //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; } /* ======================== idBotGoalManager::BotResetGoalState ======================== */ void idBotGoalManager::BotFreeItemWeights( int goalstate ) { bot_goalstate_t* gs; gs = BotGoalStateFromHandle( goalstate ); if( !gs ) { return; } if( gs->itemweightconfig ) { botFuzzyWeightManager.FreeWeightConfig( gs->itemweightconfig ); gs->itemweightconfig = NULL; } if( gs->itemweightindex ) { delete[] gs->itemweightindex; gs->itemweightindex = NULL; } } /* ======================== idBotGoalManager::BotAllocGoalState ======================== */ int idBotGoalManager::BotAllocGoalState( int client ) { int i; for( i = 1; i <= MAX_CLIENTS; i++ ) { if( !botgoalstates[i].InUse() ) { botgoalstates[i].Reset(); botgoalstates[i].client = client; return i; } } return 0; } /* ======================== idBotGoalManager::BotFreeGoalState ======================== */ void idBotGoalManager::BotFreeGoalState( int handle ) { if( handle <= 0 || handle > MAX_CLIENTS ) { gameLocal.Error( "goal state handle %d out of range\n", handle ); return; } if( !botgoalstates[handle].InUse() ) { gameLocal.Error( "invalid goal state handle %d\n", handle ); return; } BotFreeItemWeights( handle ); // jmarshall //FreeMemory(botgoalstates[handle]); // jmarshall end botgoalstates[handle].Reset(); } /* ======================== idBotGoalManager::BotSetupGoalAI ======================== */ int idBotGoalManager::BotSetupGoalAI( void ) { //load the item configuration itemconfig = LoadItemConfig( ( char* )bot_itemsfile.GetString() ); if( !itemconfig ) { gameLocal.Error( "couldn't load item config\n" ); return BLERR_CANNOTLOADITEMCONFIG; } //everything went ok return BLERR_NOERROR; } /* ======================== idBotGoalManager::BotSetupGoalAI ======================== */ void idBotGoalManager::BotShutdownGoalAI( void ) { int i; // jmarshall //if (itemconfig) // FreeMemory(itemconfig); // jmarshall end itemconfig = NULL; // jmarshall //if (levelitemheap) // FreeMemory(levelitemheap); // jmarshall end //levelitemheap = NULL; for( int i = 0; i < MAX_BOT_LEVEL_ITEMS; i++ ) { levelitemheap[i].Reset(); } freelevelitems = NULL; levelitems = NULL; numlevelitems = 0; BotFreeInfoEntities(); for( i = 1; i <= MAX_CLIENTS; i++ ) { if( botgoalstates[i].InUse() ) { BotFreeGoalState( i ); } } } /* ======================= idBotGoalManager::BotNearGoal( ======================= */ bool idBotGoalManager::BotNearGoal( idVec3 p1, idVec3 p2 ) { idVec3 p1_z, p2_z; p1_z[0] = p1[0]; p1_z[1] = p1[1]; p1_z[2] = 0; p2_z[0] = p2[0]; p2_z[1] = p2[1]; p2_z[2] = 0; float distToGoal = idMath::Distance( p1_z, p2_z ); if( distToGoal <= 50 ) { return true; } return false; }