thirtyflightsofloving/game/acebot_items.c

773 lines
21 KiB
C

/*
===========================================================================
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;i<num_players;i++)
if(ent == players[i])
pos = i;
// decrement
for(i=pos;i<num_players-1;i++)
players[i] = players[i+1];
num_players--;
}
///////////////////////////////////////////////////////////////////////
// Can we get there?
///////////////////////////////////////////////////////////////////////
qboolean ACEIT_IsReachable(edict_t *self, vec3_t goal)
{
trace_t trace;
vec3_t v;
VectorCopy(self->mins,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;i<numnodes;i++)
{
if(nodes[i].type == NODE_ITEM ||
nodes[i].type == NODE_PLATFORM ||
nodes[i].type == NODE_TELEPORTER) // valid types
{
VectorCopy(items->s.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
}