532 lines
16 KiB
C
532 lines
16 KiB
C
|
/*****************************************************************
|
||
|
|
||
|
Eraser Bot source code - by Ryan Feltrin, Added to by Acrid-
|
||
|
|
||
|
..............................................................
|
||
|
|
||
|
This file is Copyright(c) 1998, Ryan Feltrin, 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 Eraser, you MUST
|
||
|
include the following text (minus the BEGIN and END lines) in the
|
||
|
documentation for your modification.
|
||
|
|
||
|
--- BEGIN ---
|
||
|
|
||
|
The Eraser Bot is a product of Ryan Feltrin, and is available from
|
||
|
the Eraser Bot homepage, at http://impact.frag.com.
|
||
|
|
||
|
This program is a modification of the Eraser Bot, and is therefore
|
||
|
in NO WAY supported by Ryan Feltrin.
|
||
|
|
||
|
This program MUST NOT be sold in ANY form. If you have paid for
|
||
|
this product, you should contact Ryan Feltrin immediately, via
|
||
|
the Eraser Bot homepage.
|
||
|
|
||
|
--- END ---
|
||
|
|
||
|
..............................................................
|
||
|
|
||
|
You will find p_trail.c has not been included with the Eraser
|
||
|
source code release. This is NOT an error. I am unable to
|
||
|
distribute this file because it contains code that is bound by
|
||
|
legal documents, and signed by myself, never to be released
|
||
|
to the public. Sorry guys, but law is law.
|
||
|
|
||
|
I have therefore include the compiled version of these files
|
||
|
in .obj form in the src\Release and src\Debug directories.
|
||
|
So while you cannot edit and debug code within these files,
|
||
|
you can still compile this source as-is. Although these will only
|
||
|
work in MSVC v5.0, linux versions can be made available upon
|
||
|
request.
|
||
|
|
||
|
NOTE: When compiling this source, you will get a warning
|
||
|
message from the compiler, regarding the missing p_trail.c
|
||
|
file. Just ignore it, it will still compile fine.
|
||
|
|
||
|
..............................................................
|
||
|
|
||
|
I, Ryan Feltrin/Acrid-, hold no responsibility for any harm caused by the
|
||
|
use of this source code. I also am NOT willing to provide any form
|
||
|
of help or support for this source code. It is provided as-is,
|
||
|
as a service by me, with no documentation, other then the comments
|
||
|
contained within the code. If you have any queries, I suggest you
|
||
|
visit the "official" Eraser source web-board, at
|
||
|
http://www.telefragged.com/epidemic/. I will stop by there from
|
||
|
time to time, to answer questions and help with any problems that
|
||
|
may arise.
|
||
|
|
||
|
Otherwise, have fun, and I look forward to seeing what can be done
|
||
|
with this.
|
||
|
|
||
|
-Ryan Feltrin
|
||
|
-Acrid-
|
||
|
|
||
|
*****************************************************************/
|
||
|
#include "g_local.h"
|
||
|
#include "g_items.h"
|
||
|
#include "p_trail.h"
|
||
|
#include "bot_procs.h"
|
||
|
|
||
|
extern gitem_t *titems[4];
|
||
|
|
||
|
void botSetWant(edict_t *self, int dist_divide)
|
||
|
{
|
||
|
if (dist_divide <= 1)
|
||
|
self->movetarget_want = WANT_KINDA;
|
||
|
else if (dist_divide <= 3)
|
||
|
self->movetarget_want = WANT_YEH_OK;
|
||
|
else
|
||
|
self->movetarget_want = WANT_SHITYEAH;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
=============
|
||
|
RoamFindBestItem
|
||
|
|
||
|
Searches for the best reachable item, in list_head
|
||
|
set check_paths to enable reaching items that aren't visible
|
||
|
** be careful not to call this too often, it will slow things down
|
||
|
=============
|
||
|
*/
|
||
|
int RoamFindBestItem(edict_t *self, edict_t *list_head, int check_paths)
|
||
|
{
|
||
|
float closest_dist=999999, this_dist;
|
||
|
edict_t *best=NULL, *node, *trav;
|
||
|
int si; // node closest to self
|
||
|
int dist_divide=1; // divide distance by this, simulates wieghts for better targets
|
||
|
int best_divide;
|
||
|
|
||
|
if (!list_head) // nothing to look for
|
||
|
return -1;
|
||
|
|
||
|
si = ClosestNodeToEnt(self, false, false);
|
||
|
|
||
|
for (trav = list_head ; trav ; trav = trav->node_target)
|
||
|
{
|
||
|
if (trav->solid != SOLID_TRIGGER)//crash here
|
||
|
continue;
|
||
|
|
||
|
this_dist = 0;
|
||
|
|
||
|
if (((trav->item->pickup != CTFPickup_Flag) && (trav->item->pickup != Pickup_Weapon))
|
||
|
&& (this_dist = entdist(self, trav)) > 2000) // too far away
|
||
|
continue;
|
||
|
|
||
|
// CTF, if guarding base, don't go too far away//FIXME LOOK HERE
|
||
|
if(!bot_melee->value)//fixme
|
||
|
if (self->target_ent && self->target_ent->item && (self->target_ent->item->pickup == CTFPickup_Flag)
|
||
|
&& (this_dist > BOT_GUARDING_RANGE))
|
||
|
continue;
|
||
|
// CTF
|
||
|
|
||
|
if (trav->ignore_time >= level.time)
|
||
|
continue;
|
||
|
// make sure we can pickup the ammo
|
||
|
if ((list_head == ammo_head) &&
|
||
|
(!((dist_divide = (2*botHasWeaponForAmmo(self->client, trav->item) + (this_dist < 256))) &&
|
||
|
(dist_divide *= 2*botCanPickupAmmo(self->client, trav->item)) &&
|
||
|
(dist_divide *= 3*(self->bot_fire == botBlaster))))) // no weapon to use this ammo, or ammo full
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
else if (list_head == bonus_head)
|
||
|
{
|
||
|
if (trav->item->pickup == Pickup_Armor)
|
||
|
{
|
||
|
if (!(dist_divide = botCanPickupArmor(self, trav)))
|
||
|
{ // can't pickup this armor item (already have better armor)
|
||
|
dist_divide = 1;
|
||
|
continue;
|
||
|
}
|
||
|
}
|
||
|
/////////////////////////////////////specials/////////////////////////////
|
||
|
else if (trav->item->pickup == Place_Special)
|
||
|
{
|
||
|
// dist_divide += 9999;
|
||
|
if (!(dist_divide = botCanPlaceSpecial(self, trav)))
|
||
|
{ //can't pickup already have a sentry placed or not enough cells
|
||
|
// botDebugPrint("sentry place 1\n");
|
||
|
dist_divide = 1;
|
||
|
continue;
|
||
|
}
|
||
|
}
|
||
|
////////////////////////////////specials end//////////////////////////////
|
||
|
|
||
|
else if (trav->item->pickup == Pickup_Pack)
|
||
|
{
|
||
|
if (!(dist_divide = botCanPickupPack(self, trav)))
|
||
|
{ // can't pickup this pack item (already full)
|
||
|
dist_divide = 1;
|
||
|
continue;
|
||
|
}
|
||
|
}
|
||
|
if (((strcmp(trav->classname, "item_flagreturn_team1") == 0) ||//$
|
||
|
(strcmp(trav->classname, "item_flagreturn_team2") == 0)) &&//$
|
||
|
!CarryingFlag(self))//$
|
||
|
{//$
|
||
|
continue;//$
|
||
|
}//$
|
||
|
///////////////////////////////////////////////////////////////////////////
|
||
|
else if (trav->item->pickup == CTFPickup_Flag)
|
||
|
{
|
||
|
//f dist_divide += 4;//FIXME ACRID WAS = 6 MAYBE ONE PROBLEM
|
||
|
|
||
|
//f if (self->bot_fire != botBlaster && self->bot_fire != botShotgun)
|
||
|
//f dist_divide += 100;
|
||
|
|
||
|
if ( ( (self->client->resp.ctf_team == CTF_TEAM1)
|
||
|
&& (self->client->pers.inventory[ITEM_INDEX(flag2_item)])
|
||
|
&& (trav == flag1_ent || trav == flagreturn1_ent))//flagreturn code
|
||
|
|| ( (self->client->resp.ctf_team == CTF_TEAM2)
|
||
|
&& (self->client->pers.inventory[ITEM_INDEX(flag1_item)])
|
||
|
&& (trav == flag2_ent || trav == flagreturn2_ent)))//flagreturn code
|
||
|
{ // we have their flag, so HEAD FOR OUR FLAG!
|
||
|
dist_divide += 9999;
|
||
|
|
||
|
}
|
||
|
else if ( ((self->client->resp.ctf_team == CTF_TEAM1) && (trav == flag1_ent) && (flag1_ent->solid = SOLID_TRIGGER))
|
||
|
|| ((self->client->resp.ctf_team == CTF_TEAM2) && (trav == flag2_ent) && (flag2_ent->solid = SOLID_TRIGGER)))
|
||
|
{ // flag is sitting at home, don't try and get it
|
||
|
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
else if (trav->item->pickup == CTFPickup_Tech)
|
||
|
{
|
||
|
qboolean has_tech=false;
|
||
|
|
||
|
if ( (self->client->pers.inventory[ITEM_INDEX(item_tech1)])
|
||
|
|| (self->client->pers.inventory[ITEM_INDEX(item_tech2)])
|
||
|
|| (self->client->pers.inventory[ITEM_INDEX(item_tech3)])
|
||
|
|| (self->client->pers.inventory[ITEM_INDEX(item_tech4)]))
|
||
|
{
|
||
|
has_tech = true;
|
||
|
}
|
||
|
|
||
|
if (has_tech) // already have a tech, so ignore
|
||
|
continue;
|
||
|
|
||
|
if (!self->client->ctf_has_tech)
|
||
|
dist_divide = 3;
|
||
|
}
|
||
|
else // item is a powerup
|
||
|
{
|
||
|
if (trav->item->use == Use_Quad)
|
||
|
{
|
||
|
dist_divide = 4;
|
||
|
|
||
|
if (skill->value > 1)
|
||
|
dist_divide += 4 * (skill->value - 1);
|
||
|
|
||
|
if (self->bot_stats->quad_freak)
|
||
|
dist_divide *= 2; // REALLY REALLY WANT THIS SUCKER!!
|
||
|
}
|
||
|
}
|
||
|
|
||
|
}
|
||
|
else if (list_head == weapons_head) // for weapons, apply weights for good weapons
|
||
|
{//fixme acrid use this for something else?
|
||
|
// don't go for a (non-droppped) weapon we already have, if in "weapons stay" mode
|
||
|
if ( ((int)(dmflags->value) & DF_WEAPONS_STAY)
|
||
|
&& (!(trav->spawnflags & (DROPPED_ITEM | DROPPED_PLAYER_ITEM) ))
|
||
|
&& (self->client->pers.inventory[ITEM_INDEX(trav->item)]))
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
else if ((trav->item == item_rocketlauncher) ||
|
||
|
(trav->item == item_chaingun) ||//42 Acrid note,3.4 code
|
||
|
(trav->item == item_railgun))
|
||
|
dist_divide = 4;
|
||
|
else if (trav->item == item_bfg10k)
|
||
|
{
|
||
|
dist_divide = 3;
|
||
|
|
||
|
if (num_players > 4) // BFG rocks with lots of people
|
||
|
dist_divide += 3;
|
||
|
}
|
||
|
|
||
|
if (trav->item == self->bot_stats->fav_weapon)
|
||
|
dist_divide += 3;
|
||
|
|
||
|
if (self->bot_fire == botBlaster) // we really need a weapon
|
||
|
dist_divide += 4;
|
||
|
}
|
||
|
else if (list_head == health_head)
|
||
|
{
|
||
|
if (trav->count == 100)
|
||
|
dist_divide = 4;
|
||
|
else if (self->health > 90) // don't need it
|
||
|
continue;
|
||
|
else if ((self->health > 50) && check_paths) // only check routes for health when really low
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if ((this_dist < 384) && visible_box(self, trav) && CanReach(self, trav)) // go for it!
|
||
|
{
|
||
|
if (trav != self->save_movetarget)
|
||
|
self->goalentity = trav;
|
||
|
else
|
||
|
self->goalentity = self->save_goalentity;
|
||
|
|
||
|
self->movetarget = trav;
|
||
|
|
||
|
botSetWant(self, dist_divide);
|
||
|
|
||
|
this_dist = this_dist/dist_divide;
|
||
|
|
||
|
// this_dist = this_dist/256; // always grab something close by
|
||
|
|
||
|
return this_dist;
|
||
|
}
|
||
|
|
||
|
if (!check_paths)
|
||
|
continue;
|
||
|
// can't see a node, so don't bother checking routes
|
||
|
if (si == -1)
|
||
|
continue;
|
||
|
|
||
|
// don't look for a path to shards
|
||
|
if (list_head == health_head)
|
||
|
{
|
||
|
if (trav->count < 10)
|
||
|
continue;
|
||
|
}
|
||
|
else if (list_head == bonus_head)
|
||
|
{
|
||
|
if (trav->item->tag == ARMOR_SHARD)
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// see if any of our visible nodes, has a route to one of the
|
||
|
// item's visible nodes //crash here
|
||
|
if (((this_dist = PathToEnt(trail[si], trav, false, false)) > -1)
|
||
|
&& ((this_dist / dist_divide) < closest_dist))
|
||
|
{
|
||
|
this_dist = this_dist / dist_divide;
|
||
|
closest_dist = this_dist;
|
||
|
best = trav;
|
||
|
node = trail[si]; // go for the nearest node first
|
||
|
best_divide = dist_divide;
|
||
|
|
||
|
if (this_dist < 128) // OPTIMIZE: go for this one!
|
||
|
{
|
||
|
if (node != self->save_movetarget)
|
||
|
self->goalentity = node;
|
||
|
else
|
||
|
self->goalentity = self->save_goalentity;
|
||
|
|
||
|
self->movetarget = best;
|
||
|
|
||
|
botSetWant(self, dist_divide);
|
||
|
|
||
|
return this_dist;
|
||
|
}
|
||
|
}
|
||
|
//ACO BOTH
|
||
|
botDebugPrint("Found item %s: %i away\n", trav->classname, (int) this_dist);
|
||
|
}
|
||
|
|
||
|
if (best)
|
||
|
{
|
||
|
botDebugPrint("Best item %s: %i away\n", best->classname, (int) closest_dist);
|
||
|
if (node != self->save_movetarget)
|
||
|
self->goalentity = node;
|
||
|
else
|
||
|
self->goalentity = self->save_goalentity;
|
||
|
|
||
|
self->movetarget = best;
|
||
|
|
||
|
botSetWant(self, best_divide);
|
||
|
|
||
|
return closest_dist;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return -1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//////////////////////////////Specials///////////////////////////////
|
||
|
// Similar to canuse
|
||
|
int botCanPlaceSpecial (edict_t *self, edict_t *ent)
|
||
|
{
|
||
|
gclient_t *client;
|
||
|
|
||
|
if (self->client)
|
||
|
client = self->client;
|
||
|
else
|
||
|
return false;
|
||
|
if (strcmp(ent->item->classname, "item_sentryspot") == 0)
|
||
|
{
|
||
|
if (!ent->owner && self->sentry)
|
||
|
return false;
|
||
|
//code problem still says owned by previous owner,see if added eos helps
|
||
|
if (ent->owner && ent->owner->sentry)
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (strcmp(ent->item->classname, "item_depotspot") == 0)
|
||
|
{
|
||
|
if (!ent->owner && self->supply)
|
||
|
return false;
|
||
|
//code problem still says owned by previous owner,see if added eos helps
|
||
|
if (ent->owner && ent->owner->supply)
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( (strcmp(ent->item->classname, "item_sentryspot") == 0)
|
||
|
&& ( (client->player_special & SPECIAL_SENTRY_GUN
|
||
|
&& client->pers.inventory[ITEM_INDEX(FindItem("Cells"))] >= 60) ||
|
||
|
(client->player_special & SPECIAL_BIOSENTRY
|
||
|
&& client->pers.inventory[ITEM_INDEX(FindItem("Cells"))] >= 50) ))
|
||
|
return 9999;//tried 4 but they almost totally ignore for enemy flag
|
||
|
|
||
|
else if ( (strcmp(ent->item->classname, "item_depotspot") == 0)
|
||
|
&& (client->player_special & SPECIAL_SUPPLY_DEPOT
|
||
|
|| client->player_special & SPECIAL_HEALING) )
|
||
|
return 9999;//tried 4 but they almost totally ignore for enemy flag
|
||
|
else
|
||
|
return false;
|
||
|
}//ent->client->player_special & SPECIAL_SUPPLY_DEPOT
|
||
|
///////////////////////////Specials end//////////////////////////////
|
||
|
//Acrid pack code
|
||
|
int botCanPickupPack (edict_t *self, edict_t *ent)
|
||
|
{
|
||
|
gclient_t *client;
|
||
|
|
||
|
if (self->client)
|
||
|
client = self->client;
|
||
|
else
|
||
|
return false;
|
||
|
|
||
|
|
||
|
//Team=9 means that any team can pick it up, but it will respawn in 5 seconds
|
||
|
if ((ent->wf_team) && (ent->wf_team != self->wf_team) && (ent->wf_team != 9))
|
||
|
return false;
|
||
|
//ref self->client->pers.inventory[self->client->ammo_index]
|
||
|
//ref client->pers.inventory[ITEM_INDEX(item_bullets)]
|
||
|
if ((client->pers.inventory[ITEM_INDEX(item_bullets)] != client->pers.max_bullets)||
|
||
|
(client->pers.inventory[ITEM_INDEX(item_shells)] != client->pers.max_shells) ||
|
||
|
(client->pers.inventory[ITEM_INDEX(item_rockets)] != client->pers.max_rockets)||
|
||
|
(client->pers.inventory[ITEM_INDEX(item_grenades)] != client->pers.max_grenades)||
|
||
|
(client->pers.inventory[ITEM_INDEX(item_cells)] != client->pers.max_cells ) ||
|
||
|
(client->pers.inventory[ITEM_INDEX(item_slugs)] != client->pers.max_slugs))
|
||
|
return 2;
|
||
|
else
|
||
|
return false;
|
||
|
|
||
|
}
|
||
|
// FIXME: these are in g_items.c also!!
|
||
|
extern gitem_armor_t jacketarmor_info;
|
||
|
extern gitem_armor_t combatarmor_info;
|
||
|
extern gitem_armor_t bodyarmor_info;
|
||
|
|
||
|
// returns 0 if this armour is of no use to the bot, a higher number is returned for more useful armour
|
||
|
int botCanPickupArmor (edict_t *self, edict_t *ent)
|
||
|
{
|
||
|
int old_armor_index;
|
||
|
gitem_armor_t *oldinfo;
|
||
|
gitem_armor_t *newinfo;
|
||
|
int newcount;
|
||
|
float salvage;
|
||
|
int salvagecount;
|
||
|
gclient_t *client;
|
||
|
qboolean canuse;
|
||
|
|
||
|
if (self->client)
|
||
|
client = self->client;
|
||
|
else
|
||
|
return false;
|
||
|
return false; //fixme
|
||
|
// get info on new armor
|
||
|
newinfo = (gitem_armor_t *)ent->item->info;
|
||
|
|
||
|
old_armor_index = ArmorIndex (self);
|
||
|
botDebugPrint("armor testing 1\n");
|
||
|
//ACRID WF ARMOR add the wfflags for playerclasses fixme
|
||
|
canuse = false;
|
||
|
if ((ent->item->tag == ARMOR_BODY) &&
|
||
|
(self->client->player_items & ITEM_BODYARMOR)){
|
||
|
canuse = true;}
|
||
|
|
||
|
if ((ent->item->tag == ARMOR_COMBAT) &&
|
||
|
(self->client->player_items & ITEM_COMBATARMOR))
|
||
|
{canuse = true;}
|
||
|
|
||
|
if ((ent->item->tag == ARMOR_JACKET) &&
|
||
|
(self->client->player_items & ITEM_JACKETARMOR)){
|
||
|
canuse = true;}
|
||
|
if (canuse == false)
|
||
|
{botDebugPrint("armor testing 2\n");
|
||
|
return false;
|
||
|
}
|
||
|
//ACRID WF ARMOR END
|
||
|
botDebugPrint("armor testing 3\n");
|
||
|
// handle armor shards specially
|
||
|
if (ent->item->tag == ARMOR_SHARD)
|
||
|
{
|
||
|
return 1;
|
||
|
}
|
||
|
// if bot has no armor, just use it
|
||
|
else if (!old_armor_index)
|
||
|
{
|
||
|
return 4; // any armour is FUCKING GOOD armour!//FIXME ACRID was 4
|
||
|
}
|
||
|
// use the better armor
|
||
|
else
|
||
|
{
|
||
|
// get info on old armor
|
||
|
if (old_armor_index == jacket_armor_index)
|
||
|
oldinfo = &jacketarmor_info;
|
||
|
else if (old_armor_index == combat_armor_index)
|
||
|
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 = oldinfo->normal_protection / newinfo->normal_protection;
|
||
|
salvagecount = salvage * client->pers.inventory[old_armor_index];
|
||
|
newcount = newinfo->base_count + salvagecount;
|
||
|
if (newcount > newinfo->max_count)
|
||
|
newcount = newinfo->max_count;
|
||
|
|
||
|
return (int) (((newcount - client->pers.inventory[old_armor_index]) / 50)*3 + 1);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// calc new armor values
|
||
|
salvage = newinfo->normal_protection / oldinfo->normal_protection;
|
||
|
salvagecount = salvage * newinfo->base_count;
|
||
|
newcount = 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 (client->pers.inventory[old_armor_index] >= newcount)
|
||
|
return false;
|
||
|
|
||
|
// armour is useful, return 4 if VERY useful :)
|
||
|
return (int) (((newcount - client->pers.inventory[old_armor_index]) / 50)*3 + 1);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|