/* =========================================================================== Copyright (C) 1998 Steve Yeager This file is part of ACE Bot source code. ACE Bot 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. ACE Bot 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 ACE Bot source code; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ /////////////////////////////////////////////////////////////////////// // // ACE - Quake II Bot Base Code // // Version 1.0 // // This file is Copyright(c), Steve Yeager 1998, All Rights Reserved // // // All other files are Copyright(c) Id Software, Inc. // // Please see liscense.txt in the source directory for the copyright // information regarding those files belonging to Id Software, Inc. // // Should you decide to release a modified version of ACE, you MUST // include the following text (minus the BEGIN and END lines) in the // documentation for your modification. // // --- BEGIN --- // // The ACE Bot is a product of Steve Yeager, and is available from // the ACE Bot homepage, at http://www.axionfx.com/ace. // // This program is a modification of the ACE Bot, and is therefore // in NO WAY supported by Steve Yeager. // // --- END --- // // I, Steve Yeager, hold no responsibility for any harm caused by the // use of this source code, especially to small children and animals. // It is provided as-is with no implied warranty or support. // // I also wish to thank and acknowledge the great work of others // that has helped me to develop this code. // // John Cricket - For ideas and swapping code. // Ryan Feltrin - For ideas and swapping code. // SABIN - For showing how to do true client based movement. // BotEpidemic - For keeping us up to date. // Telefragged.com - For giving ACE a home. // Microsoft - For giving us such a wonderful crash free OS. // id - Need I say more. // // And to all the other testers, pathers, and players and people // who I can't remember who the heck they were, but helped out. // /////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////// // // acebot_items.c - This file contains all of the // item handling routines for the // ACE bot, including fact table support // /////////////////////////////////////////////////////////////////////// #include "g_local.h" #include "acebot.h" int num_players = 0; int num_items = 0; item_table_t item_table[MAX_NODES]; // was MAX_EDICTS edict_t *players[MAX_CLIENTS]; // pointers to all players in the game /////////////////////////////////////////////////////////////////////// // Add the player to our list /////////////////////////////////////////////////////////////////////// void ACEIT_PlayerAdded(edict_t *ent) { players[num_players++] = ent; } /////////////////////////////////////////////////////////////////////// // Remove player from list /////////////////////////////////////////////////////////////////////// void ACEIT_PlayerRemoved(edict_t *ent) { int i; int pos; // watch for 0 players if(num_players == 0) return; // special cas for only one player if(num_players == 1) { num_players = 0; return; } // Find the player for(i=0;imins,v); v[2] += 18; // Stepsize trace = gi.trace (self->s.origin, v, self->maxs, goal, self, MASK_OPAQUE); // Yes we can see it if (trace.fraction == 1.0) return true; else return false; } /////////////////////////////////////////////////////////////////////// // Visiblilty check /////////////////////////////////////////////////////////////////////// qboolean ACEIT_IsVisible(edict_t *self, vec3_t goal) { trace_t trace; trace = gi.trace (self->s.origin, vec3_origin, vec3_origin, goal, self, MASK_OPAQUE); // Yes we can see it if (trace.fraction == 1.0) return true; else return false; } /////////////////////////////////////////////////////////////////////// // Weapon changing support /////////////////////////////////////////////////////////////////////// qboolean ACEIT_ChangeWeapon (edict_t *ent, gitem_t *item) { int ammo_index; gitem_t *ammo_item; // see if we're already using it if (item == ent->client->pers.weapon) return true; // Has not picked up weapon yet if(!ent->client->pers.inventory[ITEM_INDEX(item)]) return false; // Do we have ammo for it? if (item->ammo) { ammo_item = FindItem(item->ammo); ammo_index = ITEM_INDEX(ammo_item); if (!ent->client->pers.inventory[ammo_index] && !g_select_empty->value) return false; } // Change to this weapon ent->client->newweapon = item; return true; } extern gitem_armor_t jacketarmor_info; extern gitem_armor_t combatarmor_info; extern gitem_armor_t bodyarmor_info; /////////////////////////////////////////////////////////////////////// // Check if we can use the armor /////////////////////////////////////////////////////////////////////// qboolean ACEIT_CanUseArmor (gitem_t *item, edict_t *other) { int old_armor_index; gitem_armor_t *oldinfo; gitem_armor_t *newinfo; int newcount; float salvage; int salvagecount; // get info on new armor newinfo = (gitem_armor_t *)item->info; old_armor_index = ArmorIndex (other); // handle armor shards specially if (item->tag == ARMOR_SHARD) return true; // get info on old armor if (old_armor_index == ITEM_INDEX(FindItem("Jacket Armor"))) oldinfo = &jacketarmor_info; else if (old_armor_index == ITEM_INDEX(FindItem("Combat Armor"))) oldinfo = &combatarmor_info; else // (old_armor_index == body_armor_index) oldinfo = &bodyarmor_info; if (newinfo->normal_protection <= oldinfo->normal_protection) { // calc new armor values salvage = newinfo->normal_protection / oldinfo->normal_protection; salvagecount = salvage * newinfo->base_count; newcount = other->client->pers.inventory[old_armor_index] + salvagecount; if (newcount > oldinfo->max_count) newcount = oldinfo->max_count; // if we're already maxed out then we don't need the new armor if (other->client->pers.inventory[old_armor_index] >= newcount) return false; } return true; } /////////////////////////////////////////////////////////////////////// // Determines the NEED for an item // // This function can be modified to support new items to pick up // Any other logic that needs to be added for custom decision making // can be added here. For now it is very simple. /////////////////////////////////////////////////////////////////////// float ACEIT_ItemNeed(edict_t *self, int item) { // Make sure item is at least close to being valid if(item < 0 || item > 100) return 0.0; switch(item) { // Health case ITEMLIST_HEALTH_SMALL: case ITEMLIST_HEALTH_MEDIUM: case ITEMLIST_HEALTH_LARGE: case ITEMLIST_HEALTH_MEGA: if(self->health < 100) return 1.0 - (float)self->health/100.0f; // worse off, higher priority else return 0.0; case ITEMLIST_AMMOPACK: case ITEMLIST_QUADDAMAGE: case ITEMLIST_INVULNERABILITY: case ITEMLIST_SILENCER: case ITEMLIST_REBREATHER: case ITEMLIST_ENVIRONMENTSUIT: case ITEMLIST_ADRENALINE: case ITEMLIST_BANDOLIER: case ITEMLIST_FLASHLIGHT: // Knightmare added case ITEMLIST_STASIS: // Knightmare added case ITEMLIST_JETPACK: // Knightmare added return 0.6; // Weapons case ITEMLIST_ROCKETLAUNCHER: case ITEMLIST_RAILGUN: case ITEMLIST_MACHINEGUN: case ITEMLIST_CHAINGUN: case ITEMLIST_SHOTGUN: case ITEMLIST_SUPERSHOTGUN: case ITEMLIST_BFG10K: case ITEMLIST_GRENADELAUNCHER: case ITEMLIST_HYPERBLASTER: if(!self->client->pers.inventory[item]) return 0.7; else return 0.0; // Ammo case ITEMLIST_SLUGS: if(self->client->pers.inventory[ITEMLIST_SLUGS] < self->client->pers.max_slugs) return 0.4; else return 0.0; case ITEMLIST_BULLETS: if(self->client->pers.inventory[ITEMLIST_BULLETS] < self->client->pers.max_bullets) return 0.3; else return 0.0; case ITEMLIST_SHELLS: if(self->client->pers.inventory[ITEMLIST_SHELLS] < self->client->pers.max_shells) return 0.3; else return 0.0; case ITEMLIST_CELLS: if(self->client->pers.inventory[ITEMLIST_CELLS] < self->client->pers.max_cells) return 0.3; else return 0.0; case ITEMLIST_ROCKETS: if(self->client->pers.inventory[ITEMLIST_ROCKETS] < self->client->pers.max_rockets) return 1.5; else return 0.0; // Knightmare added case ITEMLIST_HOMINGROCKETS: if(self->client->pers.inventory[ITEMLIST_HOMINGROCKETS] < self->client->pers.max_homing_rockets) return 1.5; else return 0.0; case ITEMLIST_GRENADES: if(self->client->pers.inventory[ITEMLIST_GRENADES] < self->client->pers.max_grenades) return 0.3; else return 0.0; // Knightmare added case ITEMLIST_FUEL: if(self->client->pers.inventory[ITEMLIST_FUEL] < self->client->pers.max_fuel) return 0.3; else return 0.0; // Knightmare added case ITEMLIST_AMMOGENPACK: return 0.5; case ITEMLIST_BODYARMOR: if(ACEIT_CanUseArmor (FindItem("Body Armor"), self)) return 0.6; else return 0.0; case ITEMLIST_COMBATARMOR: if(ACEIT_CanUseArmor (FindItem("Combat Armor"), self)) return 0.6; else return 0.0; case ITEMLIST_JACKETARMOR: if(ACEIT_CanUseArmor (FindItem("Jacket Armor"), self)) return 0.6; else return 0.0; case ITEMLIST_POWERSCREEN: case ITEMLIST_POWERSHIELD: return 0.5; case ITEMLIST_FLAG1: // If I am on team one or three, I want team two's flag if(!self->client->pers.inventory[item] && self->client->resp.ctf_team == CTF_TEAM2 || self->client->resp.ctf_team == CTF_TEAM3) return 10.0; else return 0.0; case ITEMLIST_FLAG2: if(!self->client->pers.inventory[item] && self->client->resp.ctf_team == CTF_TEAM1 || self->client->resp.ctf_team == CTF_TEAM3) return 10.0; else return 0.0; case ITEMLIST_FLAG3: if(!self->client->pers.inventory[item] && self->client->resp.ctf_team == CTF_TEAM1 || self->client->resp.ctf_team == CTF_TEAM2) return 10.0; else return 0.0; case ITEMLIST_RESISTANCETECH: case ITEMLIST_STRENGTHTECH: case ITEMLIST_HASTETECH: case ITEMLIST_REGENERATIONTECH: case ITEMLIST_VAMPIRETECH: // Knightmare added case ITEMLIST_AMMOGENTECH: // Knightamre added // Check for other tech if(!self->client->pers.inventory[ITEMLIST_RESISTANCETECH] && !self->client->pers.inventory[ITEMLIST_STRENGTHTECH] && !self->client->pers.inventory[ITEMLIST_HASTETECH] && !self->client->pers.inventory[ITEMLIST_VAMPIRETECH] && !self->client->pers.inventory[ITEMLIST_AMMOGENTECH] && !self->client->pers.inventory[ITEMLIST_REGENERATIONTECH]) return 0.4; else return 0.0; default: return 0.0; } } /////////////////////////////////////////////////////////////////////// // Convert a classname to its index value // // I prefer to use integers/defines for simplicity sake. This routine // can lead to some slowdowns I guess, but makes the rest of the code // easier to deal with. /////////////////////////////////////////////////////////////////////// int ACEIT_ClassnameToIndex(char *classname) { if(strcmp(classname,"item_armor_body")==0) return ITEMLIST_BODYARMOR; if(strcmp(classname,"item_armor_combat")==0) return ITEMLIST_COMBATARMOR; if(strcmp(classname,"item_armor_jacket")==0) return ITEMLIST_JACKETARMOR; if(strcmp(classname,"item_armor_shard")==0) return ITEMLIST_ARMORSHARD; // Knightmare added if(strcmp(classname,"item_armor_shard_flat")==0) return ITEMLIST_ARMORSHARD_FLAT; if(strcmp(classname,"item_power_screen")==0) return ITEMLIST_POWERSCREEN; if(strcmp(classname,"item_power_shield")==0) return ITEMLIST_POWERSHIELD; if(strcmp(classname,"weapon_grapple")==0) return ITEMLIST_GRAPPLE; if(strcmp(classname,"weapon_blaster")==0) return ITEMLIST_BLASTER; if(strcmp(classname,"weapon_shotgun")==0) return ITEMLIST_SHOTGUN; if(strcmp(classname,"weapon_supershotgun")==0) return ITEMLIST_SUPERSHOTGUN; if(strcmp(classname,"weapon_machinegun")==0) return ITEMLIST_MACHINEGUN; if(strcmp(classname,"weapon_chaingun")==0) return ITEMLIST_CHAINGUN; if(strcmp(classname,"ammo_grenades")==0) return ITEMLIST_GRENADES; if(strcmp(classname,"weapon_grenadelauncher")==0) return ITEMLIST_GRENADELAUNCHER; if(strcmp(classname,"weapon_rocketlauncher")==0) return ITEMLIST_ROCKETLAUNCHER; if(strcmp(classname,"weapon_hyperblaster")==0) return ITEMLIST_HYPERBLASTER; if(strcmp(classname,"weapon_railgun")==0) return ITEMLIST_RAILGUN; if(strcmp(classname,"weapon_bfg10k")==0) return ITEMLIST_BFG10K; //if(strcmp(classname,"weapon_hml")==0) // return ITEMLIST_HML; if(strcmp(classname,"ammo_shells")==0) return ITEMLIST_SHELLS; if(strcmp(classname,"ammo_bullets")==0) return ITEMLIST_BULLETS; if(strcmp(classname,"ammo_cells")==0) return ITEMLIST_CELLS; if(strcmp(classname,"ammo_rockets")==0) return ITEMLIST_ROCKETS; // Knightmare added if(strcmp(classname,"ammo_homing_missiles")==0) return ITEMLIST_HOMINGROCKETS; if(strcmp(classname,"ammo_slugs")==0) return ITEMLIST_SLUGS; // Knightmare added if(strcmp(classname,"ammo_fuel")==0) return ITEMLIST_FUEL; if(strcmp(classname,"item_quad")==0) return ITEMLIST_QUADDAMAGE; if(strcmp(classname,"item_invunerability")==0) return ITEMLIST_INVULNERABILITY; if(strcmp(classname,"item_silencer")==0) return ITEMLIST_SILENCER; if(strcmp(classname,"item_rebreather")==0) return ITEMLIST_REBREATHER; if(strcmp(classname,"item_enviornmentsuit")==0) return ITEMLIST_ENVIRONMENTSUIT; if(strcmp(classname,"item_ancienthead")==0) return ITEMLIST_ANCIENTHEAD; if(strcmp(classname,"item_adrenaline")==0) return ITEMLIST_ADRENALINE; if(strcmp(classname,"item_bandolier")==0) return ITEMLIST_BANDOLIER; if(strcmp(classname,"item_pack")==0) return ITEMLIST_AMMOPACK; // Knightmare added if(strcmp(classname,"item_flashlight")==0) return ITEMLIST_FLASHLIGHT; if(strcmp(classname,"item_jetpack")==0) return ITEMLIST_JETPACK; if(strcmp(classname,"item_freeze")==0) return ITEMLIST_STASIS; //end Knightmare if(strcmp(classname,"item_datacd")==0) return ITEMLIST_DATACD; if(strcmp(classname,"item_powercube")==0) return ITEMLIST_POWERCUBE; if(strcmp(classname,"item_pyramidkey")==0) return ITEMLIST_PYRAMIDKEY; if(strcmp(classname,"item_dataspinner")==0) return ITEMLIST_DATASPINNER; if(strcmp(classname,"item_securitypass")==0) return ITEMLIST_SECURITYPASS; if(strcmp(classname,"item_bluekey")==0) return ITEMLIST_BLUEKEY; if(strcmp(classname,"item_redkey")==0) return ITEMLIST_REDKEY; if(strcmp(classname,"item_commandershead")==0) return ITEMLIST_COMMANDERSHEAD; if(strcmp(classname,"item_airstrikemarker")==0) return ITEMLIST_AIRSTRIKEMARKER; //if(strcmp(classname,"item_health")==0) // ?? // return ITEMLIST_HEALTH; if(strcmp(classname,"item_health_small")==0) return ITEMLIST_HEALTH_SMALL; if(strcmp(classname,"item_health")==0) return ITEMLIST_HEALTH_MEDIUM; if(strcmp(classname,"item_health_large")==0) return ITEMLIST_HEALTH_LARGE; if(strcmp(classname,"item_health_mega")==0) return ITEMLIST_HEALTH_MEGA; if(strcmp(classname,"item_flag_team1")==0) return ITEMLIST_FLAG1; if(strcmp(classname,"item_flag_team2")==0) return ITEMLIST_FLAG2; // Knightmare added if(strcmp(classname,"item_flag_team3")==0) return ITEMLIST_FLAG3; if(strcmp(classname,"item_tech1")==0) return ITEMLIST_RESISTANCETECH; if(strcmp(classname,"item_tech2")==0) return ITEMLIST_STRENGTHTECH; if(strcmp(classname,"item_tech3")==0) return ITEMLIST_HASTETECH; if(strcmp(classname,"item_tech4")==0) return ITEMLIST_REGENERATIONTECH; // Knightmare added if(strcmp(classname,"item_tech5")==0) return ITEMLIST_VAMPIRETECH; if(strcmp(classname,"item_tech6")==0) return ITEMLIST_AMMOGENTECH; if(strcmp(classname,"item_ammogen_pack")==0) return ITEMLIST_AMMOGENPACK; // end Knightmare return INVALID; } /////////////////////////////////////////////////////////////////////// // Only called once per level, when saved will not be called again // // Downside of the routine is that items can not move about. If the level // has been saved before and reloaded, it could cause a problem if there // are items that spawn at random locations. // //#define DEBUG // uncomment to write out items to a file. /////////////////////////////////////////////////////////////////////// void ACEIT_BuildItemNodeTable (qboolean rebuild) { edict_t *items; int i,item_index; vec3_t v,v1,v2; int j; #ifdef DEBUG FILE *pOut; // for testing if((pOut = fopen("items.txt","wt"))==NULL) return; #endif num_items = 0; // Add game items //for (items = g_edicts; items < &g_edicts[globals.num_edicts]; items++) items = g_edicts+1; // skip the worldspawn for (j = 1; j < globals.num_edicts; j++, items++) { // filter out crap if (!items->inuse) // Knightmare added continue; if (!items->classname) continue; if (items->solid == SOLID_NOT) continue; ///////////////////////////////////////////////////////////////// // Items ///////////////////////////////////////////////////////////////// item_index = ACEIT_ClassnameToIndex(items->classname); //////////////////////////////////////////////////////////////// // SPECIAL NAV NODE DROPPING CODE //////////////////////////////////////////////////////////////// // Special node dropping for platforms if(strcmp(items->classname,"func_plat")==0) { if(!rebuild) ACEND_AddNode(items,NODE_PLATFORM); item_index = 99; // to allow to pass the item index test } // Special node dropping for teleporters if(strcmp(items->classname,"misc_teleporter_dest")==0 || strcmp(items->classname,"misc_teleporter")==0) { if(!rebuild) ACEND_AddNode(items,NODE_TELEPORTER); item_index = 99; } #ifdef DEBUG if(item_index == INVALID) fprintf(pOut,"Rejected item: %s node: %d pos: %f %f %f\n",items->classname,item_table[num_items].node,items->s.origin[0],items->s.origin[1],items->s.origin[2]); else fprintf(pOut,"item: %s node: %d pos: %f %f %f\n",items->classname,item_table[num_items].node,items->s.origin[0],items->s.origin[1],items->s.origin[2]); #endif if (item_index == INVALID) continue; // add a pointer to the item entity item_table[num_items].ent = items; item_table[num_items].item = item_index; // If new, add nodes for items if(!rebuild) { // Add a new node at the item's location. item_table[num_items].node = ACEND_AddNode(items,NODE_ITEM); num_items++; } else // Now if rebuilding, just relink ent structures { // Find stored location for(i=0;is.origin,v); // Add 16 to item type nodes if(nodes[i].type == NODE_ITEM) v[2] += 16; // Add 32 to teleporter if(nodes[i].type == NODE_TELEPORTER) v[2] += 32; if(nodes[i].type == NODE_PLATFORM) { VectorCopy(items->maxs,v1); VectorCopy(items->mins,v2); // To get the center v[0] = (v1[0] - v2[0]) / 2 + v2[0]; v[1] = (v1[1] - v2[1]) / 2 + v2[1]; v[2] = items->mins[2]+64; } if(v[0] == nodes[i].origin[0] && v[1] == nodes[i].origin[1] && v[2] == nodes[i].origin[2]) { // found a match now link to facts item_table[num_items].node = i; #ifdef DEBUG fprintf(pOut,"Relink item: %s node: %d pos: %f %f %f\n",items->classname,item_table[num_items].node,items->s.origin[0],items->s.origin[1],items->s.origin[2]); #endif num_items++; } } } } } #ifdef DEBUG fclose(pOut); #endif }