1 /*
2 Copyright (C) 1997-2001 Id Software, Inc.
3
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12
13 See the GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18
19 */
20
21 #include "../g_local.h"
22 #include "ai_local.h"
23
24 //ACE
25
26
27
28 //==========================================
29 // Some CTF stuff
30 //==========================================
31 static gitem_t *redflag;
32 static gitem_t *blueflag;
33
34
35 //==========================================
36 // BOT_DMclass_Move
37 // DMClass is generic bot class
38 //==========================================
BOT_DMclass_Move(edict_t * self,usercmd_t * ucmd)39 void BOT_DMclass_Move(edict_t *self, usercmd_t *ucmd)
40 {
41 int current_node_flags = 0;
42 int next_node_flags = 0;
43 int current_link_type = 0;
44 int i;
45
46 current_node_flags = nodes[self->ai->current_node].flags;
47 next_node_flags = nodes[self->ai->next_node].flags;
48 if( AI_PlinkExists( self->ai->current_node, self->ai->next_node ))
49 {
50 current_link_type = AI_PlinkMoveType( self->ai->current_node, self->ai->next_node );
51 //Com_Printf("%s\n", AI_LinkString( current_link_type ));
52 }
53
54 // Platforms
55 if( current_link_type == LINK_PLATFORM )
56 {
57 // Move to the center
58 self->ai->move_vector[2] = 0; // kill z movement
59 if(VectorLength(self->ai->move_vector) > 10)
60 ucmd->forwardmove = 200; // walk to center
61
62 AI_ChangeAngle(self);
63
64 return; // No move, riding elevator
65 }
66 else if( next_node_flags & NODEFLAGS_PLATFORM )
67 {
68 // is lift down?
69 for(i=0;i<nav.num_ents;i++){
70 if( nav.ents[i].node == self->ai->next_node )
71 {
72 //testing line
73 //vec3_t tPoint;
74 //int j;
75 //for(j=0; j<3; j++)//center of the ent
76 // tPoint[j] = nav.ents[i].ent->s.origin[j] + 0.5*(nav.ents[i].ent->mins[j] + nav.ents[i].ent->maxs[j]);
77 //tPoint[2] = nav.ents[i].ent->s.origin[2] + nav.ents[i].ent->maxs[2];
78 //tPoint[2] += 8;
79 //AITools_DrawLine( self->s.origin, tPoint );
80
81 //if not reachable, wait for it (only height matters)
82 if( ((nav.ents[i].ent->s.origin[2] + nav.ents[i].ent->maxs[2])
83 > (self->s.origin[2] + self->mins[2] + AI_JUMPABLE_HEIGHT) ) &&
84 nav.ents[i].ent->moveinfo.state != STATE_BOTTOM) //jabot092(2)
85 return; //wait for elevator
86 }
87 }
88 }
89
90 // Ladder movement
91 if( self->is_ladder )
92 {
93 ucmd->forwardmove = 70;
94 ucmd->upmove = 200;
95 ucmd->sidemove = 0;
96 return;
97 }
98
99 // Falling off ledge
100 if(!self->groundentity && !self->is_step && !self->is_swim )
101 {
102 AI_ChangeAngle(self);
103 if (current_link_type == LINK_JUMPPAD ) {
104 ucmd->forwardmove = 100;
105 } else if( current_link_type == LINK_JUMP ) {
106 self->velocity[0] = self->ai->move_vector[0] * 280;
107 self->velocity[1] = self->ai->move_vector[1] * 280;
108 } else {
109 self->velocity[0] = self->ai->move_vector[0] * 160;
110 self->velocity[1] = self->ai->move_vector[1] * 160;
111 }
112 return;
113 }
114
115 // jumping over (keep fall before this)
116 if( current_link_type == LINK_JUMP && self->groundentity)
117 {
118 trace_t trace;
119 vec3_t v1, v2;
120 //check floor in front, if there's none... Jump!
121 VectorCopy( self->s.origin, v1 );
122 VectorCopy( self->ai->move_vector, v2 );
123 VectorNormalize( v2 );
124 VectorMA( v1, 12, v2, v1 );
125 v1[2] += self->mins[2];
126 trace = gi.trace( v1, tv(-2, -2, -AI_JUMPABLE_HEIGHT), tv(2, 2, 0), v1, self, MASK_AISOLID );
127 if( !trace.startsolid && trace.fraction == 1.0 )
128 {
129 //jump!
130 ucmd->forwardmove = 400;
131 //prevent double jumping on crates
132 VectorCopy( self->s.origin, v1 );
133 v1[2] += self->mins[2];
134 trace = gi.trace( v1, tv(-12, -12, -8), tv(12, 12, 0), v1, self, MASK_AISOLID );
135 if( trace.startsolid )
136 ucmd->upmove = 400;
137 return;
138 }
139 }
140
141 // Move To Short Range goal (not following paths)
142 // plats, grapple, etc have higher priority than SR Goals, cause the bot will
143 // drop from them and have to repeat the process from the beginning
144 if (AI_MoveToGoalEntity(self,ucmd))
145 return;
146
147 // swimming
148 if( self->is_swim )
149 {
150 // We need to be pointed up/down
151 AI_ChangeAngle(self);
152
153 if( !(gi.pointcontents(nodes[self->ai->next_node].origin) & MASK_WATER) ) // Exit water
154 ucmd->upmove = 400;
155
156 ucmd->forwardmove = 300;
157 return;
158 }
159
160 // Check to see if stuck, and if so try to free us
161 if(VectorLength(self->velocity) < 37)
162 {
163 // Keep a random factor just in case....
164 if( random() > 0.1 && AI_SpecialMove(self, ucmd) ) //jumps, crouches, turns...
165 return;
166
167 self->s.angles[YAW] += random() * 180 - 90;
168
169 AI_ChangeAngle(self);
170
171 ucmd->forwardmove = 400;
172
173 return;
174 }
175
176
177 AI_ChangeAngle(self);
178
179 // Otherwise move as fast as we can...
180 ucmd->forwardmove = 400;
181 }
182
183
184 //==========================================
185 // BOT_DMclass_Wander
186 // Wandering code (based on old ACE movement code)
187 //==========================================
BOT_DMclass_Wander(edict_t * self,usercmd_t * ucmd)188 void BOT_DMclass_Wander(edict_t *self, usercmd_t *ucmd)
189 {
190 vec3_t temp;
191
192 // Do not move
193 if(self->ai->next_move_time > level.time)
194 return;
195
196 if (self->deadflag)
197 return;
198
199 // Special check for elevators, stand still until the ride comes to a complete stop.
200 if(self->groundentity != NULL && self->groundentity->use == Use_Plat)
201 {
202 if(self->groundentity->moveinfo.state == STATE_UP ||
203 self->groundentity->moveinfo.state == STATE_DOWN)
204 {
205 self->velocity[0] = 0;
206 self->velocity[1] = 0;
207 self->velocity[2] = 0;
208 self->ai->next_move_time = level.time + 0.5;
209 return;
210 }
211 }
212
213 // Move To Goal (Short Range Goal, not following paths)
214 if (AI_MoveToGoalEntity(self,ucmd))
215 return;
216
217 // Swimming?
218 VectorCopy(self->s.origin,temp);
219 temp[2]+=24;
220
221 // if(trap_PointContents (temp) & MASK_WATER)
222 if( gi.pointcontents (temp) & MASK_WATER)
223 {
224 // If drowning and no node, move up
225 if( self->client && self->client->next_drown_time > 0 ) //jalfixme: client references must pass into botStatus
226 {
227 ucmd->upmove = 100;
228 self->s.angles[PITCH] = -45;
229 }
230 else
231 ucmd->upmove = 15;
232
233 ucmd->forwardmove = 300;
234 }
235 // else self->client->next_drown_time = 0; // probably shound not be messing with this, but
236
237
238 // Lava?
239 temp[2]-=48;
240 //if(trap_PointContents(temp) & (CONTENTS_LAVA|CONTENTS_SLIME))
241 if( gi.pointcontents(temp) & (CONTENTS_LAVA|CONTENTS_SLIME) )
242 {
243 self->s.angles[YAW] += random() * 360 - 180;
244 ucmd->forwardmove = 400;
245 if(self->groundentity)
246 ucmd->upmove = 400;
247 else
248 ucmd->upmove = 0;
249 return;
250 }
251
252
253 // Check for special movement
254 if(VectorLength(self->velocity) < 37)
255 {
256 if(random() > 0.1 && AI_SpecialMove(self,ucmd)) //jumps, crouches, turns...
257 return;
258
259 self->s.angles[YAW] += random() * 180 - 90;
260
261 if (!self->is_step)// if there is ground continue otherwise wait for next move
262 ucmd->forwardmove = 0; //0
263 else if( AI_CanMove( self, BOT_MOVE_FORWARD))
264 ucmd->forwardmove = 100;
265
266 return;
267 }
268
269
270 // Otherwise move slowly, walking wondering what's going on
271 if( AI_CanMove( self, BOT_MOVE_FORWARD))
272 ucmd->forwardmove = 100;
273 else
274 ucmd->forwardmove = -100;
275 }
276
277
278 //==========================================
279 // BOT_DMclass_CombatMovement
280 //
281 // NOTE: Very simple for now, just a basic move about avoidance.
282 // Change this routine for more advanced attack movement.
283 //==========================================
BOT_DMclass_CombatMovement(edict_t * self,usercmd_t * ucmd)284 void BOT_DMclass_CombatMovement( edict_t *self, usercmd_t *ucmd )
285 {
286 float c;
287 vec3_t attackvector;
288 float dist;
289
290 //jalToDo: Convert CombatMovement to a BOT_STATE, allowing
291 //it to dodge, but still follow paths, chasing enemy or
292 //running away... hmmm... maybe it will need 2 different BOT_STATEs
293
294 if(!self->enemy) {
295
296 //do whatever (tmp move wander)
297 if( AI_FollowPath(self) )
298 BOT_DMclass_Move(self, ucmd);
299 return;
300 }
301
302 // Randomly choose a movement direction
303 c = random();
304
305 if(c < 0.2 && AI_CanMove(self,BOT_MOVE_LEFT))
306 ucmd->sidemove -= 400;
307 else if(c < 0.4 && AI_CanMove(self,BOT_MOVE_RIGHT))
308 ucmd->sidemove += 400;
309 else if(c < 0.6 && AI_CanMove(self,BOT_MOVE_FORWARD))
310 ucmd->forwardmove += 400;
311 else if(c < 0.8 && AI_CanMove(self,BOT_MOVE_BACK))
312 ucmd->forwardmove -= 400;
313
314
315 VectorSubtract( self->s.origin, self->enemy->s.origin, attackvector);
316 dist = VectorLength( attackvector);
317
318 if(dist < 75)
319 ucmd->forwardmove -= 200;
320 }
321
322
323 //==========================================
324 // BOT_DMclass_CheckShot
325 // Checks if shot is blocked or if too far to shoot
326 //==========================================
BOT_DMclass_CheckShot(edict_t * ent,vec3_t point)327 qboolean BOT_DMclass_CheckShot(edict_t *ent, vec3_t point)
328 {
329 trace_t tr;
330 vec3_t start, forward, right, offset;
331
332 AngleVectors (ent->client->v_angle, forward, right, NULL);
333
334 VectorSet(offset, 8, 8, ent->viewheight-8);
335 P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start);
336
337 //bloqued, don't shoot
338 tr = gi.trace( start, vec3_origin, vec3_origin, point, ent, MASK_AISOLID);
339 // trap_Trace( &tr, self->s.origin, vec3_origin, vec3_origin, point, self, MASK_AISOLID);
340 if (tr.fraction < 0.3) //just enough to prevent self damage (by now)
341 return false;
342
343 return true;
344 }
345
346
347 //==========================================
348 // BOT_DMclass_FindEnemy
349 // Scan for enemy (simplifed for now to just pick any visible enemy)
350 //==========================================
BOT_DMclass_FindEnemy(edict_t * self)351 qboolean BOT_DMclass_FindEnemy(edict_t *self)
352 {
353 int i;
354
355 edict_t *bestenemy = NULL;
356 float bestweight = 99999;
357 float weight;
358 vec3_t dist;
359
360 // we already set up an enemy this frame (reacting to attacks)
361 if(self->enemy != NULL)
362 return true;
363
364 // Find Enemy
365 for(i=0;i<num_AIEnemies;i++)
366 {
367 if( AIEnemies[i] == NULL || AIEnemies[i] == self
368 || AIEnemies[i]->solid == SOLID_NOT)
369 continue;
370
371 //Ignore players with 0 weight (was set at botstatus)
372 if(self->ai->status.playersWeights[i] == 0)
373 continue;
374
375 if( !AIEnemies[i]->deadflag && visible(self, AIEnemies[i]) &&
376 //trap_inPVS (self->s.origin, players[i]->s.origin))
377 gi.inPVS(self->s.origin, AIEnemies[i]->s.origin))
378 {
379 //(weight enemies from fusionbot) Is enemy visible, or is it too close to ignore
380 VectorSubtract(self->s.origin, AIEnemies[i]->s.origin, dist);
381 weight = VectorLength( dist );
382
383 //modify weight based on precomputed player weights
384 weight *= (1.0 - self->ai->status.playersWeights[i]);
385
386 if( infront( self, AIEnemies[i] ) ||
387 (weight < 300 ) )
388 {
389 // Check if best target, or better than current target
390 if (weight < bestweight)
391 {
392 bestweight = weight;
393 bestenemy = AIEnemies[i];
394 }
395 }
396 }
397 }
398
399 // If best enemy, set up
400 if(bestenemy)
401 {
402 // if (AIDevel.debugChased && bot_showcombat->value && bestenemy->ai->is_bot)
403 // G_PrintMsg (AIDevel.chaseguy, PRINT_HIGH, "%s: selected %s as enemy.\n",
404 // self->ai->pers.netname,
405 // bestenemy->ai->pers.netname);
406
407 self->enemy = bestenemy;
408 return true;
409 }
410
411 return false; // NO enemy
412 }
413
414
415 //==========================================
416 // BOT_DMClass_ChangeWeapon
417 //==========================================
BOT_DMClass_ChangeWeapon(edict_t * ent,gitem_t * item)418 qboolean BOT_DMClass_ChangeWeapon (edict_t *ent, gitem_t *item)
419 {
420 int ammo_index;
421 gitem_t *ammo_item;
422
423 // see if we're already using it
424 if (!item || item == ent->client->pers.weapon)
425 return true;
426
427 // Has not picked up weapon yet
428 if(!ent->client->pers.inventory[ITEM_INDEX(item)])
429 return false;
430
431 // Do we have ammo for it?
432 if (item->ammo)
433 {
434 ammo_item = FindItem(item->ammo);
435 ammo_index = ITEM_INDEX(ammo_item);
436 if ( !ent->client->pers.inventory[ammo_index] && !g_select_empty->value )
437 return false;
438 }
439
440 // Change to this weapon
441 ent->client->newweapon = item;
442 ent->ai->changeweapon_timeout = level.time + 6.0;
443
444 return true;
445 }
446
447
448 //==========================================
449 // BOT_DMclass_ChooseWeapon
450 // Choose weapon based on range & weights
451 //==========================================
BOT_DMclass_ChooseWeapon(edict_t * self)452 void BOT_DMclass_ChooseWeapon(edict_t *self)
453 {
454 float dist;
455 vec3_t v;
456 int i;
457 float best_weight = 0.0;
458 gitem_t *best_weapon = NULL;
459 int weapon_range = 0;
460
461 // if no enemy, then what are we doing here?
462 if(!self->enemy)
463 return;
464
465 if( self->ai->changeweapon_timeout > level.time )
466 return;
467
468 // Base weapon selection on distance:
469 VectorSubtract (self->s.origin, self->enemy->s.origin, v);
470 dist = VectorLength(v);
471
472 if(dist < 150)
473 weapon_range = AIWEAP_MELEE_RANGE;
474
475 else if(dist < 500) //Medium range limit is Grenade Laucher range
476 weapon_range = AIWEAP_SHORT_RANGE;
477
478 else if(dist < 900)
479 weapon_range = AIWEAP_MEDIUM_RANGE;
480
481 else
482 weapon_range = AIWEAP_LONG_RANGE;
483
484
485 for(i=0; i<WEAP_TOTAL; i++)
486 {
487 if (!AIWeapons[i].weaponItem)
488 continue;
489
490 //ignore those we don't have
491 if (!self->client->pers.inventory[ITEM_INDEX(AIWeapons[i].weaponItem)] )
492 continue;
493
494 //ignore those we don't have ammo for
495 if (AIWeapons[i].ammoItem != NULL //excepting for those not using ammo
496 && !self->client->pers.inventory[ITEM_INDEX(AIWeapons[i].ammoItem)] )
497 continue;
498
499 //compare range weights
500 if (AIWeapons[i].RangeWeight[weapon_range] > best_weight) {
501 best_weight = AIWeapons[i].RangeWeight[weapon_range];
502 best_weapon = AIWeapons[i].weaponItem;
503 }
504 //jal: enable randomnes later
505 //else if (AIWeapons[i].RangeWeight[weapon_range] == best_weight && random() > 0.2) { //allow some random for equal weights
506 // best_weight = AIWeapons[i].RangeWeight[weapon_range];
507 // best_weapon = AIWeapons[i].weaponItem;
508 //}
509 }
510
511 //do the change (same weapon, or null best_weapon is covered at ChangeWeapon)
512 BOT_DMClass_ChangeWeapon( self, best_weapon );
513
514 return;
515 }
516
517
518 //==========================================
519 // BOT_DMclass_FireWeapon
520 // Fire if needed
521 //==========================================
BOT_DMclass_FireWeapon(edict_t * self,usercmd_t * ucmd)522 void BOT_DMclass_FireWeapon (edict_t *self, usercmd_t *ucmd)
523 {
524 //float c;
525 float firedelay;
526 vec3_t target;
527 vec3_t angles;
528 int weapon;
529 //vec3_t attackvector;
530 //float dist;
531
532 if (!self->enemy)
533 return;
534
535 //weapon = self->s.skinnum & 0xff;
536 if (self->client->pers.weapon)
537 weapon = (self->client->pers.weapon->weapmodel & 0xff);
538 else
539 weapon = 0;
540
541 //jalToDo: Add different aiming types (explosive aim to legs, hitscan aim to body)
542
543 //was find range. I might use it later
544 //VectorSubtract( self->s.origin, self->enemy->s.origin, attackvector);
545 //dist = VectorLength( attackvector);
546
547
548 // Aim
549 VectorCopy(self->enemy->s.origin,target);
550
551 // find out our weapon AIM style
552 if( AIWeapons[weapon].aimType == AI_AIMSTYLE_PREDICTION_EXPLOSIVE )
553 {
554 //aim to the feets when enemy isn't higher
555 if( self->s.origin[2] + self->viewheight > target[2] + (self->enemy->mins[2] * 0.8) )
556 target[2] += self->enemy->mins[2];
557 }
558 else if ( AIWeapons[weapon].aimType == AI_AIMSTYLE_PREDICTION )
559 {
560 //jalToDo
561
562 }
563 else if ( AIWeapons[weapon].aimType == AI_AIMSTYLE_DROP )
564 {
565 //jalToDo
566
567 } else { //AI_AIMSTYLE_INSTANTHIT
568
569 }
570
571 // modify attack angles based on accuracy (mess this up to make the bot's aim not so deadly)
572 target[0] += (random()-0.5) * ((MAX_BOT_SKILL - self->ai->pers.skillLevel) *2);
573 target[1] += (random()-0.5) * ((MAX_BOT_SKILL - self->ai->pers.skillLevel) *2);
574
575 // Set direction
576 VectorSubtract (target, self->s.origin, self->ai->move_vector);
577 vectoangles (self->ai->move_vector, angles);
578 VectorCopy(angles,self->s.angles);
579
580
581 // Set the attack
582 firedelay = random()*(MAX_BOT_SKILL*1.8);
583 if (firedelay > (MAX_BOT_SKILL - self->ai->pers.skillLevel) && BOT_DMclass_CheckShot(self, target))
584 ucmd->buttons = BUTTON_ATTACK;
585
586 //if(AIDevel.debugChased && bot_showcombat->integer)
587 // G_PrintMsg (AIDevel.devguy, PRINT_HIGH, "%s: attacking %s\n",self->bot.pers.netname ,self->enemy->r.client->pers.netname);
588 }
589
590
591 //==========================================
592 // BOT_DMclass_WeightPlayers
593 // weight players based on game state
594 //==========================================
BOT_DMclass_WeightPlayers(edict_t * self)595 void BOT_DMclass_WeightPlayers(edict_t *self)
596 {
597 int i;
598
599 //clear
600 memset(self->ai->status.playersWeights, 0, sizeof (self->ai->status.playersWeights));
601
602 for( i=0; i<num_AIEnemies; i++ )
603 {
604 if( AIEnemies[i] == NULL )
605 continue;
606
607 if( AIEnemies[i] == self )
608 continue;
609
610 //ignore spectators and dead players
611 if( AIEnemies[i]->svflags & SVF_NOCLIENT || AIEnemies[i]->deadflag ) {
612 self->ai->status.playersWeights[i] = 0.0f;
613 continue;
614 }
615
616 if( ctf->value )
617 {
618 if( AIEnemies[i]->client->resp.ctf_team != self->client->resp.ctf_team )
619 {
620 //being at enemy team gives a small weight, but weight afterall
621 self->ai->status.playersWeights[i] = 0.2;
622
623 //enemy has redflag
624 if( redflag && AIEnemies[i]->client->pers.inventory[ITEM_INDEX(redflag)]
625 && (self->client->resp.ctf_team == CTF_TEAM1) )
626 {
627 if( !self->client->pers.inventory[ITEM_INDEX(blueflag)] ) //don't hunt if you have the other flag, let others do
628 self->ai->status.playersWeights[i] = 0.9;
629 }
630
631 //enemy has blueflag
632 if( blueflag && AIEnemies[i]->client->pers.inventory[ITEM_INDEX(blueflag)]
633 && (self->client->resp.ctf_team == CTF_TEAM2) )
634 {
635 if( !self->client->pers.inventory[ITEM_INDEX(redflag)] ) //don't hunt if you have the other flag, let others do
636 self->ai->status.playersWeights[i] = 0.9;
637 }
638 }
639 }
640 else //if not at ctf every player has some value
641 self->ai->status.playersWeights[i] = 0.3;
642
643 }
644 }
645
646
647 //==========================================
648 // BOT_DMclass_WantedFlag
649 // find needed flag
650 //==========================================
BOT_DMclass_WantedFlag(edict_t * self)651 gitem_t *BOT_DMclass_WantedFlag (edict_t *self)
652 {
653 qboolean hasflag;
654
655 if (!ctf->value)
656 return NULL;
657
658 if (!self->client || !self->client->resp.ctf_team)
659 return NULL;
660
661 //find out if the player has a flag, and what flag is it
662 if (redflag && self->client->pers.inventory[ITEM_INDEX(redflag)])
663 hasflag = true;
664 else if (blueflag && self->client->pers.inventory[ITEM_INDEX(blueflag)])
665 hasflag = true;
666 else
667 hasflag = false;
668
669 //jalToDo: see if our flag is at base
670
671 if (!hasflag)//if we don't have a flag we want other's team flag
672 {
673 if (self->client->resp.ctf_team == CTF_TEAM1)
674 return blueflag;
675 else
676 return redflag;
677 }
678 else //we have a flag
679 {
680 if (self->client->resp.ctf_team == CTF_TEAM1)
681 return redflag;
682 else
683 return blueflag;
684 }
685
686 return NULL;
687 }
688
689
690 //==========================================
691 // BOT_DMclass_WeightInventory
692 // weight items up or down based on bot needs
693 //==========================================
BOT_DMclass_WeightInventory(edict_t * self)694 void BOT_DMclass_WeightInventory(edict_t *self)
695 {
696 float LowNeedFactor = 0.5;
697 gclient_t *client;
698 int i;
699
700 client = self->client;
701
702 //reset with persistant values
703 memcpy(self->ai->status.inventoryWeights, self->ai->pers.inventoryWeights, sizeof(self->ai->pers.inventoryWeights));
704
705
706 //weight ammo down if bot doesn't have the weapon for it,
707 //or denny weight for it, if bot is packed up.
708 //------------------------------------------------------
709
710 //AMMO_BULLETS
711
712 if (!AI_CanPick_Ammo (self, AIWeapons[WEAP_MACHINEGUN].ammoItem) )
713 self->ai->status.inventoryWeights[ITEM_INDEX(AIWeapons[WEAP_MACHINEGUN].ammoItem)] = 0.0;
714 //find out if it has a weapon for this amno
715 else if (!client->pers.inventory[ITEM_INDEX(AIWeapons[WEAP_CHAINGUN].weaponItem)]
716 && !client->pers.inventory[ITEM_INDEX(AIWeapons[WEAP_MACHINEGUN].weaponItem)] )
717 self->ai->status.inventoryWeights[ITEM_INDEX(AIWeapons[WEAP_MACHINEGUN].ammoItem)] *= LowNeedFactor;
718
719 //AMMO_SHELLS:
720
721 //find out if it's packed up
722 if (!AI_CanPick_Ammo (self, AIWeapons[WEAP_SHOTGUN].ammoItem) )
723 self->ai->status.inventoryWeights[ITEM_INDEX(AIWeapons[WEAP_SHOTGUN].ammoItem)] = 0.0;
724 //find out if it has a weapon for this amno
725 else if (!client->pers.inventory[ITEM_INDEX(AIWeapons[WEAP_SHOTGUN].weaponItem)]
726 && !client->pers.inventory[ITEM_INDEX(AIWeapons[WEAP_SUPERSHOTGUN].weaponItem)] )
727 self->ai->status.inventoryWeights[ITEM_INDEX(AIWeapons[WEAP_SHOTGUN].ammoItem)] *= LowNeedFactor;
728
729 //AMMO_ROCKETS:
730
731 //find out if it's packed up
732 if (!AI_CanPick_Ammo (self, AIWeapons[WEAP_ROCKETLAUNCHER].ammoItem))
733 self->ai->status.inventoryWeights[ITEM_INDEX(AIWeapons[WEAP_ROCKETLAUNCHER].ammoItem)] = 0.0;
734 //find out if it has a weapon for this amno
735 else if (!client->pers.inventory[ITEM_INDEX(AIWeapons[WEAP_ROCKETLAUNCHER].weaponItem)] )
736 self->ai->status.inventoryWeights[ITEM_INDEX(AIWeapons[WEAP_ROCKETLAUNCHER].ammoItem)] *= LowNeedFactor;
737
738 //AMMO_GRENADES:
739
740 //find if it's packed up
741 if (!AI_CanPick_Ammo (self, AIWeapons[WEAP_GRENADES].ammoItem))
742 self->ai->status.inventoryWeights[ITEM_INDEX(AIWeapons[WEAP_GRENADES].ammoItem)] = 0.0;
743 //grenades are also weapons, and are weighted down by LowNeedFactor in weapons group
744
745 //AMMO_CELLS:
746
747 //find out if it's packed up
748 if (!AI_CanPick_Ammo (self, AIWeapons[WEAP_HYPERBLASTER].ammoItem))
749 self->ai->status.inventoryWeights[ITEM_INDEX(AIWeapons[WEAP_HYPERBLASTER].ammoItem)] = 0.0;
750 //find out if it has a weapon for this amno
751 else if (!client->pers.inventory[ITEM_INDEX(AIWeapons[WEAP_HYPERBLASTER].weaponItem)]
752 && !client->pers.inventory[ITEM_INDEX(AIWeapons[WEAP_BFG].weaponItem)]
753 && !client->pers.inventory[ITEM_INDEX(FindItemByClassname("item_power_shield"))])
754 self->ai->status.inventoryWeights[ITEM_INDEX(AIWeapons[WEAP_HYPERBLASTER].ammoItem)] *= LowNeedFactor;
755
756 //AMMO_SLUGS:
757
758 //find out if it's packed up
759 if (!AI_CanPick_Ammo (self, AIWeapons[WEAP_RAILGUN].ammoItem))
760 self->ai->status.inventoryWeights[ITEM_INDEX(AIWeapons[WEAP_RAILGUN].ammoItem)] = 0.0;
761 //find out if it has a weapon for this amno
762 else if (!client->pers.inventory[ITEM_INDEX(AIWeapons[WEAP_RAILGUN].weaponItem)] )
763 self->ai->status.inventoryWeights[ITEM_INDEX(AIWeapons[WEAP_RAILGUN].ammoItem)] *= LowNeedFactor;
764
765
766 //WEAPONS
767 //-----------------------------------------------------
768
769 //weight weapon down if bot already has it
770 for (i=0; i<WEAP_TOTAL; i++) {
771 if ( AIWeapons[i].weaponItem && client->pers.inventory[ITEM_INDEX(AIWeapons[i].weaponItem)])
772 self->ai->status.inventoryWeights[ITEM_INDEX(AIWeapons[i].weaponItem)] *= LowNeedFactor;
773 }
774
775 //ARMOR
776 //-----------------------------------------------------
777 //shards are ALWAYS accepted but still...
778 if (!AI_CanUseArmor ( FindItemByClassname("item_armor_shard"), self ))
779 self->ai->status.inventoryWeights[ITEM_INDEX(FindItemByClassname("item_armor_shard"))] = 0.0;
780
781 if (!AI_CanUseArmor ( FindItemByClassname("item_armor_jacket"), self ))
782 self->ai->status.inventoryWeights[ITEM_INDEX(FindItemByClassname("item_armor_jacket"))] = 0.0;
783
784 if (!AI_CanUseArmor ( FindItemByClassname("item_armor_combat"), self ))
785 self->ai->status.inventoryWeights[ITEM_INDEX(FindItemByClassname("item_armor_combat"))] = 0.0;
786
787 if (!AI_CanUseArmor ( FindItemByClassname("item_armor_body"), self ))
788 self->ai->status.inventoryWeights[ITEM_INDEX(FindItemByClassname("item_armor_body"))] = 0.0;
789
790
791 //TECH :
792 //-----------------------------------------------------
793 if ( self->client->pers.inventory[ITEM_INDEX( FindItemByClassname("item_tech1"))]
794 || self->client->pers.inventory[ITEM_INDEX( FindItemByClassname("item_tech2"))]
795 || self->client->pers.inventory[ITEM_INDEX( FindItemByClassname("item_tech3"))]
796 || self->client->pers.inventory[ITEM_INDEX( FindItemByClassname("item_tech4"))] )
797 {
798 self->ai->status.inventoryWeights[ITEM_INDEX( FindItemByClassname("item_tech1"))] = 0.0;
799 self->ai->status.inventoryWeights[ITEM_INDEX( FindItemByClassname("item_tech2"))] = 0.0;
800 self->ai->status.inventoryWeights[ITEM_INDEX( FindItemByClassname("item_tech3"))] = 0.0;
801 self->ai->status.inventoryWeights[ITEM_INDEX( FindItemByClassname("item_tech4"))] = 0.0;
802 }
803
804 //CTF:
805 //-----------------------------------------------------
806 if( ctf->value )
807 {
808 gitem_t *wantedFlag;
809
810 wantedFlag = BOT_DMclass_WantedFlag( self ); //Returns the flag gitem_t
811
812 //flags have weights defined inside persistant inventory. Remove weight from the unwanted one/s.
813 if (blueflag && blueflag != wantedFlag)
814 self->ai->status.inventoryWeights[ITEM_INDEX(blueflag)] = 0.0;
815 if (redflag && redflag != wantedFlag)
816 self->ai->status.inventoryWeights[ITEM_INDEX(redflag)] = 0.0;
817 }
818 }
819
820
821 //==========================================
822 // BOT_DMclass_UpdateStatus
823 // update ai->status values based on bot state,
824 // so ai can decide based on these settings
825 //==========================================
BOT_DMclass_UpdateStatus(edict_t * self)826 void BOT_DMclass_UpdateStatus( edict_t *self )
827 {
828 self->enemy = NULL;
829 self->movetarget = NULL;
830
831 // Set up for new client movement: jalfixme
832 VectorCopy(self->client->ps.viewangles,self->s.angles);
833 VectorSet (self->client->ps.pmove.delta_angles, 0, 0, 0);
834
835 //JALFIXMEQ2
836 /*
837 if (self->client->jumppad_time)
838 self->ai->status.jumpadReached = true; //jumpad time from client to botStatus
839 else
840 self->ai->status.jumpadReached = false;
841 */
842 if (self->client->ps.pmove.pm_flags & PMF_TIME_TELEPORT)
843 self->ai->status.TeleportReached = true;
844 else
845 self->ai->status.TeleportReached = false;
846
847 //set up AI status for the upcoming AI_frame
848 BOT_DMclass_WeightInventory( self ); //weight items
849 BOT_DMclass_WeightPlayers( self ); //weight players
850 }
851
852
853 //==========================================
854 // BOT_DMClass_BloquedTimeout
855 // the bot has been bloqued for too long
856 //==========================================
BOT_DMClass_BloquedTimeout(edict_t * self)857 void BOT_DMClass_BloquedTimeout( edict_t *self )
858 {
859 self->health = 0;
860 self->ai->bloqued_timeout = level.time + 15.0;
861 self->die(self, self, self, 100000, vec3_origin);
862 self->nextthink = level.time + FRAMETIME;
863 }
864
865
866 //==========================================
867 // BOT_DMclass_DeadFrame
868 // ent is dead = run this think func
869 //==========================================
BOT_DMclass_DeadFrame(edict_t * self)870 void BOT_DMclass_DeadFrame( edict_t *self )
871 {
872 usercmd_t ucmd;
873
874 // ask for respawn
875 self->client->buttons = 0;
876 ucmd.buttons = BUTTON_ATTACK;
877 ClientThink (self, &ucmd);
878 self->nextthink = level.time + FRAMETIME;
879 }
880
881
882 //==========================================
883 // BOT_DMclass_RunFrame
884 // States Machine & call client movement
885 //==========================================
BOT_DMclass_RunFrame(edict_t * self)886 void BOT_DMclass_RunFrame( edict_t *self )
887 {
888 usercmd_t ucmd;
889 memset( &ucmd, 0, sizeof(ucmd) );
890
891 // Look for enemies
892 if( BOT_DMclass_FindEnemy(self) )
893 {
894 BOT_DMclass_ChooseWeapon( self );
895 BOT_DMclass_FireWeapon( self, &ucmd );
896 self->ai->state = BOT_STATE_ATTACK;
897 self->ai->state_combat_timeout = level.time + 1.0;
898
899 } else if( self->ai->state == BOT_STATE_ATTACK &&
900 level.time > self->ai->state_combat_timeout)
901 {
902 //Jalfixme: change to: AI_SetUpStateMove(self);
903 self->ai->state = BOT_STATE_MOVE;
904 }
905
906 // Execute the move, or wander
907 if( self->ai->state == BOT_STATE_MOVE )
908 BOT_DMclass_Move( self, &ucmd );
909
910 else if(self->ai->state == BOT_STATE_ATTACK)
911 BOT_DMclass_CombatMovement( self, &ucmd );
912
913 else if ( self->ai->state == BOT_STATE_WANDER )
914 BOT_DMclass_Wander( self, &ucmd );
915
916
917
918 //set up for pmove
919 ucmd.angles[PITCH] = ANGLE2SHORT(self->s.angles[PITCH]);
920 ucmd.angles[YAW] = ANGLE2SHORT(self->s.angles[YAW]);
921 ucmd.angles[ROLL] = ANGLE2SHORT(self->s.angles[ROLL]);
922
923 // set approximate ping and show values
924 ucmd.msec = 75 + floor (random () * 25) + 1;
925 self->client->ping = ucmd.msec;
926
927 // send command through id's code
928 ClientThink( self, &ucmd );
929 self->nextthink = level.time + FRAMETIME;
930 }
931
932
933 //==========================================
934 // BOT_DMclass_InitPersistant
935 // Persistant after respawns.
936 //==========================================
BOT_DMclass_InitPersistant(edict_t * self)937 void BOT_DMclass_InitPersistant(edict_t *self)
938 {
939 self->classname = "dmbot";
940
941 //copy name
942 if (self->client->pers.netname)
943 self->ai->pers.netname = self->client->pers.netname;
944 else
945 self->ai->pers.netname = "dmBot";
946
947 //set 'class' functions
948 self->ai->pers.RunFrame = BOT_DMclass_RunFrame;
949 self->ai->pers.UpdateStatus = BOT_DMclass_UpdateStatus;
950 self->ai->pers.bloquedTimeout = BOT_DMClass_BloquedTimeout;
951 self->ai->pers.deadFrame = BOT_DMclass_DeadFrame;
952
953 //available moveTypes for this class
954 self->ai->pers.moveTypesMask = (LINK_MOVE|LINK_STAIRS|LINK_FALL|LINK_WATER|LINK_WATERJUMP|LINK_JUMPPAD|LINK_PLATFORM|LINK_TELEPORT|LINK_LADDER|LINK_JUMP|LINK_CROUCH);
955
956 //Persistant Inventory Weights (0 = can not pick)
957 memset(self->ai->pers.inventoryWeights, 0, sizeof (self->ai->pers.inventoryWeights));
958
959 //weapons
960 self->ai->pers.inventoryWeights[ITEM_INDEX(AIWeapons[WEAP_BLASTER].weaponItem)] = 0.0;
961 //self->bot.pers.inventoryWeights[ITEM_INDEX(FindItemByClassname("weapon_blaster"))] = 0.0; //it's the same thing
962 self->ai->pers.inventoryWeights[ITEM_INDEX(AIWeapons[WEAP_SHOTGUN].weaponItem)] = 0.5;
963 self->ai->pers.inventoryWeights[ITEM_INDEX(AIWeapons[WEAP_SUPERSHOTGUN].weaponItem)] = 0.7;
964 self->ai->pers.inventoryWeights[ITEM_INDEX(AIWeapons[WEAP_MACHINEGUN].weaponItem)] = 0.5;
965 self->ai->pers.inventoryWeights[ITEM_INDEX(AIWeapons[WEAP_CHAINGUN].weaponItem)] = 0.7;
966 self->ai->pers.inventoryWeights[ITEM_INDEX(AIWeapons[WEAP_GRENADES].weaponItem)] = 0.5;
967 self->ai->pers.inventoryWeights[ITEM_INDEX(AIWeapons[WEAP_GRENADELAUNCHER].weaponItem)] = 0.6;
968 self->ai->pers.inventoryWeights[ITEM_INDEX(AIWeapons[WEAP_ROCKETLAUNCHER].weaponItem)] = 0.8;
969 self->ai->pers.inventoryWeights[ITEM_INDEX(AIWeapons[WEAP_HYPERBLASTER].weaponItem)] = 0.7;
970 self->ai->pers.inventoryWeights[ITEM_INDEX(AIWeapons[WEAP_RAILGUN].weaponItem)] = 0.8;
971 self->ai->pers.inventoryWeights[ITEM_INDEX(AIWeapons[WEAP_BFG].weaponItem)] = 0.5;
972
973 //ammo
974 self->ai->pers.inventoryWeights[ITEM_INDEX(FindItemByClassname("ammo_shells"))] = 0.5;
975 self->ai->pers.inventoryWeights[ITEM_INDEX(FindItemByClassname("ammo_bullets"))] = 0.5;
976 self->ai->pers.inventoryWeights[ITEM_INDEX(FindItemByClassname("ammo_cells"))] = 0.5;
977 self->ai->pers.inventoryWeights[ITEM_INDEX(FindItemByClassname("ammo_rockets"))] = 0.5;
978 self->ai->pers.inventoryWeights[ITEM_INDEX(FindItemByClassname("ammo_slugs"))] = 0.5;
979 self->ai->pers.inventoryWeights[ITEM_INDEX(FindItemByClassname("ammo_grenades"))] = 0.5;
980
981 //armor
982 self->ai->pers.inventoryWeights[ITEM_INDEX(FindItemByClassname("item_armor_body"))] = 0.9;
983 self->ai->pers.inventoryWeights[ITEM_INDEX(FindItemByClassname("item_armor_combat"))] = 0.8;
984 self->ai->pers.inventoryWeights[ITEM_INDEX(FindItemByClassname("item_armor_jacket"))] = 0.5;
985 self->ai->pers.inventoryWeights[ITEM_INDEX(FindItemByClassname("item_armor_shard"))] = 0.2;
986
987 //techs
988 self->ai->pers.inventoryWeights[ITEM_INDEX(FindItemByClassname("item_tech1"))] = 0.5;
989 self->ai->pers.inventoryWeights[ITEM_INDEX(FindItemByClassname("item_tech2"))] = 0.5;
990 self->ai->pers.inventoryWeights[ITEM_INDEX(FindItemByClassname("item_tech3"))] = 0.5;
991 self->ai->pers.inventoryWeights[ITEM_INDEX(FindItemByClassname("item_tech4"))] = 0.5;
992
993 if( ctf->value ) {
994 redflag = FindItemByClassname("item_flag_team1"); // store pointers to flags gitem_t, for
995 blueflag = FindItemByClassname("item_flag_team2");// simpler comparisons inside this archive
996 self->ai->pers.inventoryWeights[ITEM_INDEX(FindItemByClassname("item_flag_team1"))] = 3.0;
997 self->ai->pers.inventoryWeights[ITEM_INDEX(FindItemByClassname("item_flag_team2"))] = 3.0;
998 }
999 }
1000
1001
1002