1 ///////////////////////////////////////////////////////////////////////
2 //
3 // ACE - Quake II Bot Base Code
4 //
5 // Version 1.0
6 //
7 // This file is Copyright(c), Steve Yeager 1998, All Rights Reserved
8 //
9 //
10 // All other files are Copyright(c) Id Software, Inc.
11 //
12 // Please see liscense.txt in the source directory for the copyright
13 // information regarding those files belonging to Id Software, Inc.
14 //
15 // Should you decide to release a modified version of ACE, you MUST
16 // include the following text (minus the BEGIN and END lines) in the
17 // documentation for your modification.
18 //
19 // --- BEGIN ---
20 //
21 // The ACE Bot is a product of Steve Yeager, and is available from
22 // the ACE Bot homepage, at http://www.axionfx.com/ace.
23 //
24 // This program is a modification of the ACE Bot, and is therefore
25 // in NO WAY supported by Steve Yeager.
26
27 // This program MUST NOT be sold in ANY form. If you have paid for
28 // this product, you should contact Steve Yeager immediately, via
29 // the ACE Bot homepage.
30 //
31 // --- END ---
32 //
33 // I, Steve Yeager, hold no responsibility for any harm caused by the
34 // use of this source code, especially to small children and animals.
35 // It is provided as-is with no implied warranty or support.
36 //
37 // I also wish to thank and acknowledge the great work of others
38 // that has helped me to develop this code.
39 //
40 // John Cricket - For ideas and swapping code.
41 // Ryan Feltrin - For ideas and swapping code.
42 // SABIN - For showing how to do true client based movement.
43 // BotEpidemic - For keeping us up to date.
44 // Telefragged.com - For giving ACE a home.
45 // Microsoft - For giving us such a wonderful crash free OS.
46 // id - Need I say more.
47 //
48 // And to all the other testers, pathers, and players and people
49 // who I can't remember who the heck they were, but helped out.
50 //
51 ///////////////////////////////////////////////////////////////////////
52
53 ///////////////////////////////////////////////////////////////////////
54 //
55 // acebot_items.c - This file contains all of the
56 // item handling routines for the
57 // ACE bot, including fact table support
58 //
59 ///////////////////////////////////////////////////////////////////////
60
61 #include "../g_local.h"
62 #include "acebot.h"
63
64 int num_players = 0;
65 int num_items = 0;
66 item_table_t item_table[MAX_EDICTS];
67 edict_t *players[MAX_CLIENTS]; // pointers to all players in the game
68
69 ///////////////////////////////////////////////////////////////////////
70 // Add the player to our list
71 ///////////////////////////////////////////////////////////////////////
ACEIT_PlayerAdded(edict_t * ent)72 void ACEIT_PlayerAdded(edict_t *ent)
73 {
74 players[num_players++] = ent;
75 }
76
77 ///////////////////////////////////////////////////////////////////////
78 // Remove player from list
79 ///////////////////////////////////////////////////////////////////////
ACEIT_PlayerRemoved(edict_t * ent)80 void ACEIT_PlayerRemoved(edict_t *ent)
81 {
82 int i;
83 int pos;
84
85 // watch for 0 players
86 if(num_players == 0)
87 return;
88
89 // special cas for only one player
90 if(num_players == 1)
91 {
92 num_players = 0;
93 return;
94 }
95
96 // Find the player
97 for(i=0;i<num_players;i++)
98 if(ent == players[i])
99 pos = i;
100
101 // decrement
102 for(i=pos;i<num_players-1;i++)
103 players[i] = players[i+1];
104
105 num_players--;
106 }
107
108 ///////////////////////////////////////////////////////////////////////
109 // Can we get there?
110 ///////////////////////////////////////////////////////////////////////
ACEIT_IsReachable(edict_t * self,vec3_t goal)111 qboolean ACEIT_IsReachable(edict_t *self, vec3_t goal)
112 {
113 trace_t trace;
114 vec3_t v;
115
116 VectorCopy(self->mins,v);
117 v[2] += 18; // Stepsize
118
119 trace = gi.trace (self->s.origin, v, self->maxs, goal, self, MASK_OPAQUE);
120
121 // Yes we can see it
122 if (trace.fraction == 1.0)
123 return true;
124 else
125 return false;
126
127 }
128
129 ///////////////////////////////////////////////////////////////////////
130 // Visiblilty check
131 ///////////////////////////////////////////////////////////////////////
ACEIT_IsVisible(edict_t * self,vec3_t goal)132 qboolean ACEIT_IsVisible(edict_t *self, vec3_t goal)
133 {
134 trace_t trace;
135
136 trace = gi.trace (self->s.origin, vec3_origin, vec3_origin, goal, self, MASK_OPAQUE);
137
138 // Yes we can see it
139 if (trace.fraction == 1.0)
140 return true;
141 else
142 return false;
143
144 }
145
146 ///////////////////////////////////////////////////////////////////////
147 // Weapon changing support
148 ///////////////////////////////////////////////////////////////////////
ACEIT_ChangeWeapon(edict_t * ent,gitem_t * item)149 qboolean ACEIT_ChangeWeapon (edict_t *ent, gitem_t *item)
150 {
151 int ammo_index;
152 gitem_t *ammo_item;
153
154 // see if we're already using it
155 if (item == ent->client->pers.weapon)
156 return true;
157
158 // Has not picked up weapon yet
159 if(!ent->client->pers.inventory[ITEM_INDEX(item)])
160 return false;
161
162 // Do we have ammo for it?
163 if (item->ammo)
164 {
165 ammo_item = FindItem(item->ammo);
166 ammo_index = ITEM_INDEX(ammo_item);
167 if (!ent->client->pers.inventory[ammo_index] && !g_select_empty->value)
168 return false;
169 }
170
171 // Change to this weapon
172 ent->client->newweapon = item;
173
174 return true;
175 }
176
177
178 extern gitem_armor_t jacketarmor_info;
179 extern gitem_armor_t combatarmor_info;
180 extern gitem_armor_t bodyarmor_info;
181
182 ///////////////////////////////////////////////////////////////////////
183 // Check if we can use the armor
184 ///////////////////////////////////////////////////////////////////////
ACEIT_CanUseArmor(gitem_t * item,edict_t * other)185 qboolean ACEIT_CanUseArmor (gitem_t *item, edict_t *other)
186 {
187 int old_armor_index;
188 gitem_armor_t *oldinfo;
189 gitem_armor_t *newinfo;
190 int newcount;
191 float salvage;
192 int salvagecount;
193
194 // get info on new armor
195 newinfo = (gitem_armor_t *)item->info;
196
197 old_armor_index = ArmorIndex (other);
198
199 // handle armor shards specially
200 if (item->tag == ARMOR_SHARD)
201 return true;
202
203 // get info on old armor
204 if (old_armor_index == ITEM_INDEX(FindItem("Jacket Armor")))
205 oldinfo = &jacketarmor_info;
206 else if (old_armor_index == ITEM_INDEX(FindItem("Combat Armor")))
207 oldinfo = &combatarmor_info;
208 else // (old_armor_index == body_armor_index)
209 oldinfo = &bodyarmor_info;
210
211 if (newinfo->normal_protection <= oldinfo->normal_protection)
212 {
213 // calc new armor values
214 salvage = newinfo->normal_protection / oldinfo->normal_protection;
215 salvagecount = salvage * newinfo->base_count;
216 newcount = other->client->pers.inventory[old_armor_index] + salvagecount;
217
218 if (newcount > oldinfo->max_count)
219 newcount = oldinfo->max_count;
220
221 // if we're already maxed out then we don't need the new armor
222 if (other->client->pers.inventory[old_armor_index] >= newcount)
223 return false;
224
225 }
226
227 return true;
228 }
229
230
231 ///////////////////////////////////////////////////////////////////////
232 // Determins the NEED for an item
233 //
234 // This function can be modified to support new items to pick up
235 // Any other logic that needs to be added for custom decision making
236 // can be added here. For now it is very simple.
237 ///////////////////////////////////////////////////////////////////////
ACEIT_ItemNeed(edict_t * self,int item)238 float ACEIT_ItemNeed(edict_t *self, int item)
239 {
240
241 // Make sure item is at least close to being valid
242 if(item < 0 || item > 100)
243 return 0.0;
244
245 switch(item)
246 {
247 // Health
248 case ITEMLIST_HEALTH_SMALL:
249 case ITEMLIST_HEALTH_MEDIUM:
250 case ITEMLIST_HEALTH_LARGE:
251 case ITEMLIST_HEALTH_MEGA:
252 if(self->health < 100)
253 return 1.0 - (float)self->health/100.0f; // worse off, higher priority
254 else
255 return 0.0;
256
257 case ITEMLIST_AMMOPACK:
258 case ITEMLIST_QUADDAMAGE:
259 case ITEMLIST_INVULNERABILITY:
260 case ITEMLIST_SILENCER:
261 // case ITEMLIST_REBREATHER
262 // case ITEMLIST_ENVIRONMENTSUIT
263 case ITEMLIST_ADRENALINE:
264 case ITEMLIST_BANDOLIER:
265 return 0.6;
266
267 // Weapons
268 case ITEMLIST_ROCKETLAUNCHER:
269 case ITEMLIST_RAILGUN:
270 case ITEMLIST_MACHINEGUN:
271 case ITEMLIST_CHAINGUN:
272 case ITEMLIST_SHOTGUN:
273 case ITEMLIST_SUPERSHOTGUN:
274 case ITEMLIST_BFG10K:
275 case ITEMLIST_GRENADELAUNCHER:
276 case ITEMLIST_HYPERBLASTER:
277 if(!self->client->pers.inventory[item])
278 return 0.7;
279 else
280 return 0.0;
281
282 // Ammo
283 case ITEMLIST_SLUGS:
284 if(self->client->pers.inventory[ITEMLIST_SLUGS] < self->client->pers.max_slugs)
285 return 0.4;
286 else
287 return 0.0;
288
289 case ITEMLIST_BULLETS:
290 if(self->client->pers.inventory[ITEMLIST_BULLETS] < self->client->pers.max_bullets)
291 return 0.3;
292 else
293 return 0.0;
294
295 case ITEMLIST_SHELLS:
296 if(self->client->pers.inventory[ITEMLIST_SHELLS] < self->client->pers.max_shells)
297 return 0.3;
298 else
299 return 0.0;
300
301 case ITEMLIST_CELLS:
302 if(self->client->pers.inventory[ITEMLIST_CELLS] < self->client->pers.max_cells)
303 return 0.3;
304 else
305 return 0.0;
306
307 case ITEMLIST_ROCKETS:
308 if(self->client->pers.inventory[ITEMLIST_ROCKETS] < self->client->pers.max_rockets)
309 return 1.5;
310 else
311 return 0.0;
312
313 case ITEMLIST_GRENADES:
314 if(self->client->pers.inventory[ITEMLIST_GRENADES] < self->client->pers.max_grenades)
315 return 0.3;
316 else
317 return 0.0;
318
319 case ITEMLIST_BODYARMOR:
320 if(ACEIT_CanUseArmor (FindItem("Body Armor"), self))
321 return 0.6;
322 else
323 return 0.0;
324
325 case ITEMLIST_COMBATARMOR:
326 if(ACEIT_CanUseArmor (FindItem("Combat Armor"), self))
327 return 0.6;
328 else
329 return 0.0;
330
331 case ITEMLIST_JACKETARMOR:
332 if(ACEIT_CanUseArmor (FindItem("Jacket Armor"), self))
333 return 0.6;
334 else
335 return 0.0;
336
337 case ITEMLIST_POWERSCREEN:
338 case ITEMLIST_POWERSHIELD:
339 return 0.5;
340 /*
341 case ITEMLIST_FLAG1:
342 // If I am on team one, I want team two's flag
343 if(!self->client->pers.inventory[item] && self->client->resp.ctf_team == CTF_TEAM2)
344 return 10.0;
345 else
346 return 0.0;
347
348 case ITEMLIST_FLAG2:
349 if(!self->client->pers.inventory[item] && self->client->resp.ctf_team == CTF_TEAM1)
350 return 10.0;
351 else
352 return 0.0;
353 */
354 case ITEMLIST_RESISTANCETECH:
355 case ITEMLIST_STRENGTHTECH:
356 case ITEMLIST_HASTETECH:
357 case ITEMLIST_REGENERATIONTECH:
358 // Check for other tech
359 if(!self->client->pers.inventory[ITEMLIST_RESISTANCETECH] &&
360 !self->client->pers.inventory[ITEMLIST_STRENGTHTECH] &&
361 !self->client->pers.inventory[ITEMLIST_HASTETECH] &&
362 !self->client->pers.inventory[ITEMLIST_REGENERATIONTECH])
363 return 0.4;
364 else
365 return 0.0;
366
367 default:
368 return 0.0;
369
370 }
371
372 }
373
374 ///////////////////////////////////////////////////////////////////////
375 // Convert a classname to its index value
376 //
377 // I prefer to use integers/defines for simplicity sake. This routine
378 // can lead to some slowdowns I guess, but makes the rest of the code
379 // easier to deal with.
380 ///////////////////////////////////////////////////////////////////////
ACEIT_ClassnameToIndex(char * classname)381 int ACEIT_ClassnameToIndex(char *classname)
382 {
383 if(strcmp(classname,"item_armor_body")==0)
384 return ITEMLIST_BODYARMOR;
385
386 if(strcmp(classname,"item_armor_combat")==0)
387 return ITEMLIST_COMBATARMOR;
388
389 if(strcmp(classname,"item_armor_jacket")==0)
390 return ITEMLIST_JACKETARMOR;
391
392 if(strcmp(classname,"item_armor_shard")==0)
393 return ITEMLIST_ARMORSHARD;
394
395 if(strcmp(classname,"item_power_screen")==0)
396 return ITEMLIST_POWERSCREEN;
397
398 if(strcmp(classname,"item_power_shield")==0)
399 return ITEMLIST_POWERSHIELD;
400
401 if(strcmp(classname,"weapon_grapple")==0)
402 return ITEMLIST_GRAPPLE;
403
404 if(strcmp(classname,"weapon_blaster")==0)
405 return ITEMLIST_BLASTER;
406
407 if(strcmp(classname,"weapon_shotgun")==0)
408 return ITEMLIST_SHOTGUN;
409
410 if(strcmp(classname,"weapon_supershotgun")==0)
411 return ITEMLIST_SUPERSHOTGUN;
412
413 if(strcmp(classname,"weapon_machinegun")==0)
414 return ITEMLIST_MACHINEGUN;
415
416 if(strcmp(classname,"weapon_chaingun")==0)
417 return ITEMLIST_CHAINGUN;
418
419 if(strcmp(classname,"weapon_chaingun")==0)
420 return ITEMLIST_CHAINGUN;
421
422 if(strcmp(classname,"ammo_grenades")==0)
423 return ITEMLIST_GRENADES;
424
425 if(strcmp(classname,"weapon_grenadelauncher")==0)
426 return ITEMLIST_GRENADELAUNCHER;
427
428 if(strcmp(classname,"weapon_rocketlauncher")==0)
429 return ITEMLIST_ROCKETLAUNCHER;
430
431 if(strcmp(classname,"weapon_hyperblaster")==0)
432 return ITEMLIST_HYPERBLASTER;
433
434 if(strcmp(classname,"weapon_boomer")==0)
435 return ITEMLIST_BOOMER;
436
437 if(strcmp(classname,"weapon_railgun")==0)
438 return ITEMLIST_RAILGUN;
439
440 if(strcmp(classname,"weapon_phalanx")==0)
441 return ITEMLIST_PHALANX;
442
443 if(strcmp(classname,"weapon_bfg10k")==0)
444 return ITEMLIST_BFG10K;
445
446 if(strcmp(classname,"ammo_shells")==0)
447 return ITEMLIST_SHELLS;
448
449 if(strcmp(classname,"ammo_bullets")==0)
450 return ITEMLIST_BULLETS;
451
452 if(strcmp(classname,"ammo_cells")==0)
453 return ITEMLIST_CELLS;
454
455 if(strcmp(classname,"ammo_rockets")==0)
456 return ITEMLIST_ROCKETS;
457
458 if(strcmp(classname,"ammo_slugs")==0)
459 return ITEMLIST_SLUGS;
460
461 if(strcmp(classname,"item_quad")==0)
462 return ITEMLIST_QUADDAMAGE;
463
464 if(strcmp(classname,"item_invunerability")==0)
465 return ITEMLIST_INVULNERABILITY;
466
467 if(strcmp(classname,"item_silencer")==0)
468 return ITEMLIST_SILENCER;
469
470 if(strcmp(classname,"item_rebreather")==0)
471 return ITEMLIST_REBREATHER;
472
473 if(strcmp(classname,"item_enviornmentsuit")==0)
474 return ITEMLIST_ENVIRONMENTSUIT;
475
476 if(strcmp(classname,"item_ancienthead")==0)
477 return ITEMLIST_ANCIENTHEAD;
478
479 if(strcmp(classname,"item_adrenaline")==0)
480 return ITEMLIST_ADRENALINE;
481
482 if(strcmp(classname,"item_bandolier")==0)
483 return ITEMLIST_BANDOLIER;
484
485 if(strcmp(classname,"item_pack")==0)
486 return ITEMLIST_AMMOPACK;
487
488 if(strcmp(classname,"item_datacd")==0)
489 return ITEMLIST_DATACD;
490
491 if(strcmp(classname,"item_powercube")==0)
492 return ITEMLIST_POWERCUBE;
493
494 if(strcmp(classname,"item_pyramidkey")==0)
495 return ITEMLIST_PYRAMIDKEY;
496
497 if(strcmp(classname,"item_dataspinner")==0)
498 return ITEMLIST_DATASPINNER;
499
500 if(strcmp(classname,"item_securitypass")==0)
501 return ITEMLIST_SECURITYPASS;
502
503 if(strcmp(classname,"item_bluekey")==0)
504 return ITEMLIST_BLUEKEY;
505
506 if(strcmp(classname,"item_redkey")==0)
507 return ITEMLIST_REDKEY;
508
509 if(strcmp(classname,"item_commandershead")==0)
510 return ITEMLIST_COMMANDERSHEAD;
511
512 if(strcmp(classname,"item_airstrikemarker")==0)
513 return ITEMLIST_AIRSTRIKEMARKER;
514
515 if(strcmp(classname,"item_health")==0) // ??
516 return ITEMLIST_HEALTH;
517
518 if(strcmp(classname,"item_flag_team1")==0)
519 return ITEMLIST_FLAG1;
520
521 if(strcmp(classname,"item_flag_team2")==0)
522 return ITEMLIST_FLAG2;
523
524 if(strcmp(classname,"item_tech1")==0)
525 return ITEMLIST_RESISTANCETECH;
526
527 if(strcmp(classname,"item_tech2")==0)
528 return ITEMLIST_STRENGTHTECH;
529
530 if(strcmp(classname,"item_tech3")==0)
531 return ITEMLIST_HASTETECH;
532
533 if(strcmp(classname,"item_tech4")==0)
534 return ITEMLIST_REGENERATIONTECH;
535
536 if(strcmp(classname,"item_health_small")==0)
537 return ITEMLIST_HEALTH_SMALL;
538
539 if(strcmp(classname,"item_health_medium")==0)
540 return ITEMLIST_HEALTH_MEDIUM;
541
542 if(strcmp(classname,"item_health_large")==0)
543 return ITEMLIST_HEALTH_LARGE;
544
545 if(strcmp(classname,"item_health_mega")==0)
546 return ITEMLIST_HEALTH_MEGA;
547
548 return INVALID;
549 }
550
551
552 ///////////////////////////////////////////////////////////////////////
553 // Only called once per level, when saved will not be called again
554 //
555 // Downside of the routine is that items can not move about. If the level
556 // has been saved before and reloaded, it could cause a problem if there
557 // are items that spawn at random locations.
558 //
559 //#define DEBUG // uncomment to write out items to a file.
560 ///////////////////////////////////////////////////////////////////////
ACEIT_BuildItemNodeTable(qboolean rebuild)561 void ACEIT_BuildItemNodeTable (qboolean rebuild)
562 {
563 edict_t *items;
564 int i,item_index;
565 vec3_t v,v1,v2;
566
567 #ifdef DEBUG
568 FILE *pOut; // for testing
569 if((pOut = fopen("items.txt","wt"))==NULL)
570 return;
571 #endif
572
573 num_items = 0;
574
575 // Add game items
576 for(items = g_edicts; items < &g_edicts[globals.num_edicts]; items++)
577 {
578 // filter out crap
579 if(items->solid == SOLID_NOT)
580 continue;
581
582 if(!items->classname)
583 continue;
584
585 /////////////////////////////////////////////////////////////////
586 // Items
587 /////////////////////////////////////////////////////////////////
588 item_index = ACEIT_ClassnameToIndex(items->classname);
589
590 ////////////////////////////////////////////////////////////////
591 // SPECIAL NAV NODE DROPPING CODE
592 ////////////////////////////////////////////////////////////////
593 // Special node dropping for platforms
594 if(strcmp(items->classname,"func_plat")==0)
595 {
596 if(!rebuild)
597 ACEND_AddNode(items,NODE_PLATFORM);
598 item_index = 99; // to allow to pass the item index test
599 }
600
601 // Special node dropping for teleporters
602 if(strcmp(items->classname,"misc_teleporter_dest")==0 || strcmp(items->classname,"misc_teleporter")==0)
603 {
604 if(!rebuild)
605 ACEND_AddNode(items,NODE_TELEPORTER);
606 item_index = 99;
607 }
608
609 #ifdef DEBUG
610 if(item_index == INVALID)
611 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]);
612 else
613 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]);
614 #endif
615
616 if(item_index == INVALID)
617 continue;
618
619 // add a pointer to the item entity
620 item_table[num_items].ent = items;
621 item_table[num_items].item = item_index;
622
623 // If new, add nodes for items
624 if(!rebuild)
625 {
626 // Add a new node at the item's location.
627 item_table[num_items].node = ACEND_AddNode(items,NODE_ITEM);
628 num_items++;
629 }
630 else // Now if rebuilding, just relink ent structures
631 {
632 // Find stored location
633 for(i=0;i<numnodes;i++)
634 {
635 if(nodes[i].type == NODE_ITEM ||
636 nodes[i].type == NODE_PLATFORM ||
637 nodes[i].type == NODE_TELEPORTER) // valid types
638 {
639 VectorCopy(items->s.origin,v);
640
641 // Add 16 to item type nodes
642 if(nodes[i].type == NODE_ITEM)
643 v[2] += 16;
644
645 // Add 32 to teleporter
646 if(nodes[i].type == NODE_TELEPORTER)
647 v[2] += 32;
648
649 if(nodes[i].type == NODE_PLATFORM)
650 {
651 VectorCopy(items->maxs,v1);
652 VectorCopy(items->mins,v2);
653
654 // To get the center
655 v[0] = (v1[0] - v2[0]) / 2 + v2[0];
656 v[1] = (v1[1] - v2[1]) / 2 + v2[1];
657 v[2] = items->mins[2]+64;
658 }
659
660 if(v[0] == nodes[i].origin[0] &&
661 v[1] == nodes[i].origin[1] &&
662 v[2] == nodes[i].origin[2])
663 {
664 // found a match now link to facts
665 item_table[num_items].node = i;
666
667 #ifdef DEBUG
668 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]);
669 #endif
670 num_items++;
671 }
672 }
673 }
674 }
675
676
677 }
678
679 #ifdef DEBUG
680 fclose(pOut);
681 #endif
682
683 }
684
685