1 /*
2 Copyright (C) 1997-2001 Id Software, Inc.
3 Copyright (C) 1998 Steve Yeager
4 Copyright (C) 2010 COR Entertainment, LLC.
5
6 See below for Steve Yeager's original copyright notice.
7 Modified to GPL in 2002.
8
9 This program is free software; you can redistribute it and/or
10 modify it under the terms of the GNU General Public License
11 as published by the Free Software Foundation; either version 2
12 of the License, or (at your option) any later version.
13
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
17
18 See the GNU General Public License for more details.
19
20 You should have received a copy of the GNU General Public License along
21 with this program; if not, write to the Free Software Foundation, Inc.,
22 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23 */
24 ///////////////////////////////////////////////////////////////////////
25 //
26 // ACE - Quake II Bot Base Code
27 //
28 // Version 1.0
29 //
30 // This file is Copyright(c), Steve Yeager 1998, All Rights Reserved
31 //
32 //
33 // All other files are Copyright(c) Id Software, Inc.
34 //
35 // Please see liscense.txt in the source directory for the copyright
36 // information regarding those files belonging to Id Software, Inc.
37 //
38 // Should you decide to release a modified version of ACE, you MUST
39 // include the following text (minus the BEGIN and END lines) in the
40 // documentation for your modification.
41 //
42 // --- BEGIN ---
43 //
44 // The ACE Bot is a product of Steve Yeager, and is available from
45 // the ACE Bot homepage, at http://www.axionfx.com/ace.
46 //
47 // This program is a modification of the ACE Bot, and is therefore
48 // in NO WAY supported by Steve Yeager.
49
50 // This program MUST NOT be sold in ANY form. If you have paid for
51 // this product, you should contact Steve Yeager immediately, via
52 // the ACE Bot homepage.
53 //
54 // --- END ---
55 //
56 // I, Steve Yeager, hold no responsibility for any harm caused by the
57 // use of this source code, especially to small children and animals.
58 // It is provided as-is with no implied warranty or support.
59 //
60 // I also wish to thank and acknowledge the great work of others
61 // that has helped me to develop this code.
62 //
63 // John Cricket - For ideas and swapping code.
64 // Ryan Feltrin - For ideas and swapping code.
65 // SABIN - For showing how to do true client based movement.
66 // BotEpidemic - For keeping us up to date.
67 // Telefragged.com - For giving ACE a home.
68 // Microsoft - For giving us such a wonderful crash free OS.
69 // id - Need I say more.
70 //
71 // And to all the other testers, pathers, and players and people
72 // who I can't remember who the heck they were, but helped out.
73 //
74 ///////////////////////////////////////////////////////////////////////
75
76 ///////////////////////////////////////////////////////////////////////
77 // acebot_movement.c - This file contains all of the
78 // movement routines for the ACE bot
79 //
80 ///////////////////////////////////////////////////////////////////////
81
82 #ifdef HAVE_CONFIG_H
83 #include "config.h"
84 #endif
85
86 #include "game/g_local.h"
87 #include "acebot.h"
88
89 ///////////////////////////////////////////////////////////////////////
90 // Checks if bot can move (really just checking the ground)
91 // Also, this is not a real accurate check, but does a
92 // pretty good job and looks for lava/slime.
93 ///////////////////////////////////////////////////////////////////////
ACEMV_CanMove(edict_t * self,int direction)94 qboolean ACEMV_CanMove(edict_t *self, int direction)
95 {
96 vec3_t forward, right;
97 vec3_t offset,start,end;
98 vec3_t angles;
99 trace_t tr;
100 gitem_t *vehicle;
101
102 vehicle = FindItemByClassname("item_bomber");
103
104 if (self->client->pers.inventory[ITEM_INDEX(vehicle)]) {
105 return true; // yup, can move, we are in an air vehicle
106 }
107 vehicle = FindItemByClassname("item_strafer");
108
109 if (self->client->pers.inventory[ITEM_INDEX(vehicle)]) {
110 return true; // yup, can move, we are in an air vehicle
111 }
112
113 // Now check to see if move will move us off an edge
114 VectorCopy(self->s.angles,angles);
115
116 if(direction == MOVE_LEFT)
117 angles[1] += 90;
118 else if(direction == MOVE_RIGHT)
119 angles[1] -= 90;
120 else if(direction == MOVE_BACK)
121 angles[1] -=180;
122
123 // Set up the vectors
124 AngleVectors (angles, forward, right, NULL);
125
126 VectorSet(offset, 36, 0, 24);
127 G_ProjectSource (self->s.origin, offset, forward, right, start);
128
129 VectorSet(offset, 36, 0, -400);
130 G_ProjectSource (self->s.origin, offset, forward, right, end);
131
132 tr = gi.trace(start, NULL, NULL, end, self,
133 (CONTENTS_SOLID|CONTENTS_SLIME|CONTENTS_MIST|CONTENTS_LAVA|CONTENTS_WINDOW));
134 if(tr.fraction > 0.3 || (tr.contents & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_MIST)) )
135 {
136 if(debug_mode)
137 debug_printf("%s: move blocked\n",self->client->pers.netname);
138 if(self->groundentity)
139 self->s.angles[YAW] += random() * 180 - 90; //blocked, so try something else
140 return false;
141 }
142
143 return true; // yup, can move
144 }
145
146 ///////////////////////////////////////////////////////////////////////
147 // Handle special cases of crouch/jump
148 //
149 // If the move is resolved here, this function returns
150 // true.
151 ///////////////////////////////////////////////////////////////////////
ACEMV_SpecialMove(edict_t * self,usercmd_t * ucmd)152 qboolean ACEMV_SpecialMove(edict_t *self, usercmd_t *ucmd)
153 {
154 vec3_t dir,forward,right,start,end,offset;
155 vec3_t top;
156 trace_t tr;
157 short mSpeed;
158
159 if(g_tactical->integer)
160 mSpeed = 200;
161 else
162 mSpeed = 400;
163
164 // Get current direction
165 VectorCopy(self->client->ps.viewangles,dir);
166 dir[YAW] = self->s.angles[YAW];
167 AngleVectors (dir, forward, right, NULL);
168
169 VectorSet(offset, 18, 0, 0);
170 G_ProjectSource (self->s.origin, offset, forward, right, start);
171 offset[0] += 18;
172 G_ProjectSource (self->s.origin, offset, forward, right, end);
173
174 // trace it
175 start[2] += 18; // so they are not jumping all the time
176 end[2] += 18;
177 tr = gi.trace (start, self->mins, self->maxs, end, self, MASK_MONSTERSOLID);
178
179 if(tr.allsolid)
180 {
181 // Check for crouching
182 start[2] -= 14;
183 end[2] -= 14;
184
185 // Set up for crouching check
186 VectorCopy(self->maxs,top);
187 top[2] = 0.0; // crouching height
188 tr = gi.trace (start, self->mins, top, end, self, MASK_PLAYERSOLID);
189
190 // Crouch
191 if(!tr.allsolid)
192 {
193 if(ACEMV_CanMove(self, MOVE_FORWARD))
194 ucmd->forwardmove = mSpeed;
195 else if(ACEMV_CanMove(self, MOVE_BACK))
196 ucmd->forwardmove = -mSpeed;
197 ucmd->upmove = -400;
198 return true;
199 }
200
201 // Check for jump
202 start[2] += 32;
203 end[2] += 32;
204 tr = gi.trace (start, self->mins, self->maxs, end, self, MASK_MONSTERSOLID);
205
206 if(!tr.allsolid)
207 {
208 if(ACEMV_CanMove(self, MOVE_FORWARD))
209 ucmd->forwardmove = mSpeed;
210 else if(ACEMV_CanMove(self, MOVE_BACK))
211 ucmd->forwardmove = -mSpeed;
212 ucmd->upmove = 400;
213 return true;
214 }
215 }
216
217 return false; // We did not resolve a move here
218 }
219
220
221 ///////////////////////////////////////////////////////////////////////
222 // Checks for obstructions in front of bot
223 //
224 // This is a function I created origianlly for ACE that
225 // tries to help steer the bot around obstructions.
226 //
227 // If the move is resolved here, this function returns true.
228 ///////////////////////////////////////////////////////////////////////
ACEMV_CheckEyes(edict_t * self,usercmd_t * ucmd)229 qboolean ACEMV_CheckEyes(edict_t *self, usercmd_t *ucmd)
230 {
231 vec3_t forward, right;
232 vec3_t leftstart, rightstart,focalpoint;
233 vec3_t upstart,upend;
234 vec3_t dir,offset;
235 short mSpeed;
236 trace_t traceRight,traceLeft,traceUp, traceFront; // for eyesight
237
238 if(g_tactical->integer)
239 mSpeed = 200;
240 else
241 mSpeed = 400;
242
243 // Get current angle and set up "eyes"
244
245 /* make sure bot's "eyes" are straight ahead
246 * something is going wrong after rocket jump, and "eyes" are always down
247 * and bot runs in circle.
248 */
249 self->s.angles[PITCH] = 0.0f;
250
251 VectorCopy(self->s.angles,dir);
252 AngleVectors (dir, forward, right, NULL);
253
254 // Let them move to targets by walls
255 if(!self->movetarget)
256 VectorSet(offset,200,0,4); // focalpoint
257 else
258 VectorSet(offset,36,0,4); // focalpoint
259
260 G_ProjectSource (self->s.origin, offset, forward, right, focalpoint);
261
262 // Check from self to focalpoint
263 // Ladder code
264 VectorSet(offset,36,0,0); // set as high as possible
265 G_ProjectSource (self->s.origin, offset, forward, right, upend);
266 traceFront = gi.trace(self->s.origin, self->mins, self->maxs, upend, self, BOTMASK_OPAQUE);
267
268 if ( traceFront.contents & CONTENTS_LADDER )
269 {
270 ucmd->upmove = 400;
271 if(ACEMV_CanMove(self, MOVE_FORWARD))
272 ucmd->forwardmove = mSpeed;
273 return true;
274 }
275
276 // If this check fails we need to continue on with more detailed checks
277 if ( traceFront.fraction >= 1.0f )
278 {
279 if ( ACEMV_CanMove( self, MOVE_FORWARD ) )
280 ucmd->forwardmove = mSpeed; //only try forward, bot should be looking to move in direction of eyes
281 return true;
282 }
283
284 VectorSet(offset, 0, 18, 4);
285 G_ProjectSource (self->s.origin, offset, forward, right, leftstart);
286
287 offset[1] -= 36; // want to make sure this is correct
288 //VectorSet(offset, 0, -18, 4);
289 G_ProjectSource (self->s.origin, offset, forward, right, rightstart);
290
291 traceRight = gi.trace(rightstart, NULL, NULL, focalpoint, self, BOTMASK_OPAQUE);
292 traceLeft = gi.trace(leftstart, NULL, NULL, focalpoint, self, BOTMASK_OPAQUE);
293
294 // Wall checking code, this will degenerate progressivly so the least cost
295 // check will be done first.
296
297 // If open space move ok
298 if(traceRight.fraction != 1 || traceLeft.fraction != 1 || strcmp(traceLeft.ent->classname,"func_door")!=0)
299 {
300 // Special uppoint logic to check for slopes/stairs/jumping etc.
301 VectorSet(offset, 0, 18, 24);
302 G_ProjectSource (self->s.origin, offset, forward, right, upstart);
303
304 VectorSet(offset,0,0,200); // scan for height above head
305 G_ProjectSource (self->s.origin, offset, forward, right, upend);
306 traceUp = gi.trace(upstart, NULL, NULL, upend, self, BOTMASK_OPAQUE);
307
308 VectorSet(offset,200,0,200*traceUp.fraction-5); // set as high as possible
309 G_ProjectSource (self->s.origin, offset, forward, right, upend);
310 traceUp = gi.trace(upstart, NULL, NULL, upend, self, BOTMASK_OPAQUE);
311
312 // If the upper trace is not open, we need to turn.
313 if(traceUp.fraction < 1.0f )
314 {
315 if(traceRight.fraction > traceLeft.fraction)
316 self->s.angles[YAW] += (1.0 - traceLeft.fraction) * 45.0;
317 else
318 self->s.angles[YAW] += -(1.0 - traceRight.fraction) * 45.0;
319 if(ACEMV_CanMove(self, MOVE_FORWARD))
320 ucmd->forwardmove = mSpeed;
321 return true;
322 }
323 }
324
325 return false;
326 }
327
328 ///////////////////////////////////////////////////////////////////////
329 // Make the change in angles a little more gradual, not so snappy
330 // Subtle, but noticeable.
331 //
332 // Modified from the original id ChangeYaw code...
333 ///////////////////////////////////////////////////////////////////////
ACEMV_ChangeBotAngle(edict_t * ent)334 void ACEMV_ChangeBotAngle (edict_t *ent)
335 {
336 float ideal_yaw;
337 float ideal_pitch;
338 float current_yaw;
339 float current_pitch;
340 float move;
341 float speed;
342 vec3_t ideal_angle;
343
344 // Normalize the move angle first
345 VectorNormalize(ent->move_vector);
346
347 current_yaw = anglemod(ent->s.angles[YAW]);
348 current_pitch = anglemod(ent->s.angles[PITCH]);
349
350 vectoangles (ent->move_vector, ideal_angle);
351
352 ideal_yaw = anglemod(ideal_angle[YAW]);
353 ideal_pitch = anglemod(ideal_angle[PITCH]);
354
355 // Yaw
356 if (current_yaw != ideal_yaw)
357 {
358 move = ideal_yaw - current_yaw;
359 speed = ent->yaw_speed;
360 if (ideal_yaw > current_yaw)
361 {
362 if (move >= 180.0f)
363 move = move - 360.0f;
364 }
365 else
366 {
367 if (move <= -180.0f)
368 move = move + 360.0f;
369 }
370 if (move > 0.0f)
371 {
372 if (move > speed)
373 move = speed;
374 }
375 else
376 {
377 if (move < -speed)
378 move = -speed;
379 }
380 ent->s.angles[YAW] = anglemod (current_yaw + move);
381 }
382
383 // Pitch
384 if (current_pitch != ideal_pitch)
385 {
386 move = ideal_pitch - current_pitch;
387 speed = ent->yaw_speed;
388 if (ideal_pitch > current_pitch)
389 {
390 if (move >= 180.0f)
391 move = move - 360.0f;
392 }
393 else
394 {
395 if (move <= -180.0f)
396 move = move + 360.0f;
397 }
398 if (move > 0.0f)
399 {
400 if (move > speed)
401 move = speed;
402 }
403 else
404 {
405 if (move < -speed)
406 move = -speed;
407 }
408 ent->s.angles[PITCH] = anglemod (current_pitch + move);
409 }
410 }
411
412 ///////////////////////////////////////////////////////////////////////
413 // Set bot to move to it's movetarget. (following node path)
414 ///////////////////////////////////////////////////////////////////////
ACEMV_MoveToGoal(edict_t * self,usercmd_t * ucmd)415 void ACEMV_MoveToGoal(edict_t *self, usercmd_t *ucmd)
416 {
417 short mSpeed;
418
419 if(g_tactical->integer)
420 mSpeed = 200;
421 else
422 mSpeed = 400;
423
424 // If a rocket or grenade is around deal with it
425 // Simple, but effective (could be rewritten to be more accurate)
426 if(strcmp(self->movetarget->classname,"rocket") == 0 ||
427 strcmp(self->movetarget->classname,"grenade") == 0 ||
428 strcmp(self->movetarget->classname,"seeker") == 0 )
429 {
430 if(debug_mode)
431 debug_printf("%s: Oh crap a rocket!\n",self->client->pers.netname);
432
433 if(strcmp(self->movetarget->classname,"seeker") == 0)
434 {
435 VectorSubtract (self->s.origin, self->movetarget->s.origin, self->move_vector);
436 ACEMV_ChangeBotAngle(self);
437
438 //turn and run like hell away from it
439 //to do - needs improvement - bot should run when getting hit by seeker as well
440 if(ACEMV_CanMove(self, MOVE_FORWARD))
441 ucmd->forwardmove = mSpeed;
442 }
443 else
444 {
445 VectorSubtract (self->movetarget->s.origin, self->s.origin, self->move_vector);
446 ACEMV_ChangeBotAngle(self);
447
448 // strafe left/right
449 if(rand()%1 && ACEMV_CanMove(self, MOVE_LEFT))
450 ucmd->sidemove = -mSpeed;
451 else if(ACEMV_CanMove(self, MOVE_RIGHT))
452 ucmd->sidemove = mSpeed;
453 }
454 return;
455
456 }
457 else
458 {
459 // Set bot's movement direction
460 VectorSubtract (self->movetarget->s.origin, self->s.origin, self->move_vector);
461 ACEMV_ChangeBotAngle(self);
462
463 //try moving forward, if blocked, strafe around it if possible.
464 if(ACEMV_CanMove(self, MOVE_FORWARD))
465 ucmd->forwardmove = mSpeed;
466 else if(ACEMV_CanMove(self, MOVE_BACK))
467 ucmd->forwardmove = -mSpeed;
468 else if(ACEMV_CanMove(self, MOVE_RIGHT))
469 ucmd->sidemove = mSpeed;
470 else if(ACEMV_CanMove(self, MOVE_LEFT))
471 ucmd->sidemove = -mSpeed;
472 return;
473 }
474 }
475
476 ///////////////////////////////////////////////////////////////////////
477 // Main movement code. (following node path)
478 ///////////////////////////////////////////////////////////////////////
ACEMV_Move(edict_t * self,usercmd_t * ucmd)479 void ACEMV_Move(edict_t *self, usercmd_t *ucmd)
480 {
481 vec3_t dist;
482 int current_node_type=-1;
483 int next_node_type=-1;
484 int i;
485 float c;
486 short mSpeed;
487
488 if(g_tactical->integer)
489 mSpeed = 200;
490 else
491 mSpeed = 400;
492
493 // Get current and next node back from nav code.
494 if(!ACEND_FollowPath(self))
495 {
496 self->state = STATE_WANDER;
497 self->wander_timeout = level.time + 1.0;
498 return;
499 }
500
501 if(!self->groundentity)
502 return;
503
504 current_node_type = nodes[self->current_node].type;
505 next_node_type = nodes[self->next_node].type;
506
507 ///////////////////////////
508 // Move To Goal
509 ///////////////////////////
510 if (self->movetarget)
511 ACEMV_MoveToGoal(self,ucmd);
512
513 ////////////////////////////////////////////////////////
514 // Platforms
515 ///////////////////////////////////////////////////////
516 if(current_node_type != NODE_PLATFORM && next_node_type == NODE_PLATFORM)
517 {
518 // check to see if lift is down?
519 for(i=0;i<num_items;i++)
520 if(item_table[i].node == self->next_node)
521 if(item_table[i].ent->moveinfo.state != STATE_BOTTOM)
522 return; // Wait for elevator
523 }
524 if(current_node_type == NODE_PLATFORM && next_node_type == NODE_PLATFORM)
525 {
526 // Move to the center
527 self->move_vector[2] = 0; // kill z movement
528 if(VectorLength(self->move_vector) > 10)
529 ucmd->forwardmove = 200; // walk to center
530
531 ACEMV_ChangeBotAngle(self);
532
533 return; // No move, riding elevator
534 }
535
536 ////////////////////////////////////////////////////////
537 // Jumpto Nodes
538 ///////////////////////////////////////////////////////
539 if(next_node_type == NODE_JUMP ||
540 (current_node_type == NODE_JUMP && next_node_type != NODE_ITEM && nodes[self->next_node].origin[2] > self->s.origin[2]))
541 {
542 // Set up a jump move
543 ucmd->forwardmove = mSpeed;
544 ucmd->upmove = 400;
545
546 ACEMV_ChangeBotAngle(self);
547
548 VectorCopy(self->move_vector, dist);
549 VectorScale(dist, 440, self->velocity);
550
551 return;
552 }
553
554 ////////////////////////////////////////////////////////
555 // Ladder Nodes
556 ///////////////////////////////////////////////////////
557 if(next_node_type == NODE_LADDER && nodes[self->next_node].origin[2] > self->s.origin[2])
558 {
559 // Otherwise move as fast as we can
560 ucmd->forwardmove = mSpeed;
561 self->velocity[2] = 320;
562
563 ACEMV_ChangeBotAngle(self);
564
565 return;
566
567 }
568 // If getting off the ladder
569 if(current_node_type == NODE_LADDER && next_node_type != NODE_LADDER &&
570 nodes[self->next_node].origin[2] > self->s.origin[2])
571 {
572 ucmd->forwardmove = mSpeed;
573 ucmd->upmove = 200;
574 self->velocity[2] = 200;
575 ACEMV_ChangeBotAngle(self);
576 return;
577 }
578
579 ////////////////////////////////////////////////////////
580 // Water Nodes
581 ///////////////////////////////////////////////////////
582 if(current_node_type == NODE_WATER)
583 {
584 // We need to be pointed up/down
585 ACEMV_ChangeBotAngle(self);
586
587 // If the next node is not in the water, then move up to get out.
588 if(next_node_type != NODE_WATER && !(gi.pointcontents(nodes[self->next_node].origin) & MASK_WATER)) // Exit water
589 ucmd->upmove = 400;
590
591 ucmd->forwardmove = 300;
592
593 return;
594 }
595
596 // Falling off ledge?
597 if(!self->groundentity)
598 {
599 ACEMV_ChangeBotAngle(self);
600
601 self->velocity[0] = self->move_vector[0] * 360;
602 self->velocity[1] = self->move_vector[1] * 360;
603
604 return;
605 }
606
607 // Check to see if stuck, and if so try to free us
608 // Also handles crouching
609 if(VectorLength(self->velocity) < 37)
610 {
611 // Keep a random factor just in case....
612 if(random() > 0.1 && ACEMV_SpecialMove(self, ucmd))
613 return;
614
615 self->s.angles[YAW] += random() * 180 - 90;
616 // Try moving foward, if not, try to strafe around obstacle
617 if(ACEMV_CanMove(self, MOVE_FORWARD))
618 ucmd->forwardmove = mSpeed;
619 else if(ACEMV_CanMove(self, MOVE_BACK))
620 ucmd->forwardmove = -mSpeed;
621 else if(ACEMV_CanMove(self, MOVE_RIGHT))
622 ucmd->sidemove = mSpeed;
623 else if(ACEMV_CanMove(self, MOVE_LEFT))
624 ucmd->sidemove = -mSpeed;
625 return;
626 }
627
628 // Otherwise move as fast as we can
629 if(ACEMV_CanMove(self, MOVE_FORWARD))
630 ucmd->forwardmove = mSpeed;
631
632 if(self->skill == 3)
633 { //ultra skill level(will be 3)
634 c = random();
635
636 if(!self->in_deathball && grapple->value && c <= .7)
637 { //use the grapple once in awhile to pull itself around
638
639 if(self->client->ctf_grapplestate == CTF_GRAPPLE_STATE_HANG)
640 {
641 CTFPlayerResetGrapple(self);
642 ACEMV_ChangeBotAngle(self);
643 return;
644 }
645
646 ACEMV_ChangeBotAngle(self);
647 ACEIT_ChangeWeapon(self,FindItem("grapple"));
648 ucmd->buttons = BUTTON_ATTACK;
649 ACEMV_ChangeBotAngle(self);
650 return;
651 }
652 else if(self->client->ctf_grapplestate != CTF_GRAPPLE_STATE_PULL &&
653 self->client->ctf_grapplestate != CTF_GRAPPLE_STATE_HANG)
654 { //don't interrupt a pull
655 float weight;
656 int strafeJump = false;
657
658 //Strafejumping should only occur now if a bot is far enough from a node
659 //and not wandering. We really don't want them to jump around when wandering
660 //as it seems to hinder locating a goal.
661
662 VectorSubtract(self->s.origin, nodes[self->current_node].origin, dist);
663 weight = VectorLength( dist );
664 if(weight > 300)
665 strafeJump = true;
666
667 if(strafeJump)
668 {
669 if(c > .7)
670 ucmd->upmove = 400; //jump around the level
671
672 if(c > 0.9 && ACEMV_CanMove(self, MOVE_LEFT))
673 ucmd->sidemove = -200; //strafejump left(was -400)
674
675 else if(c > 0.8 && ACEMV_CanMove(self, MOVE_RIGHT))
676 ucmd->sidemove = 200; //strafejump right(was 400)
677 }
678 }
679
680 //Now if we have the Alien Smartgun, drop some prox mines :)
681 if (self->client->pers.weapon == FindItem("alien smartgun") && c < 0.2)
682 ucmd->buttons = BUTTON_ATTACK2;
683 }
684
685 ACEMV_ChangeBotAngle(self);
686 }
687
688
689 ///////////////////////////////////////////////////////////////////////
690 // Wandering code (based on old ACE movement code)
691 ///////////////////////////////////////////////////////////////////////
ACEMV_Wander(edict_t * self,usercmd_t * ucmd)692 void ACEMV_Wander(edict_t *self, usercmd_t *ucmd)
693 {
694 vec3_t temp;
695 float c;
696 short mSpeed;
697
698 if(g_tactical->integer)
699 mSpeed = 200;
700 else
701 mSpeed = 400;
702
703 // Do not move
704 if(self->next_move_time > level.time)
705 return;
706
707 // Special check for elevators, stand still until the ride comes to a complete stop.
708 if(self->groundentity != NULL && self->groundentity->use == Use_Plat)
709 if(self->groundentity->moveinfo.state == STATE_UP ||
710 self->groundentity->moveinfo.state == STATE_DOWN) // only move when platform not
711 {
712 self->velocity[0] = 0;
713 self->velocity[1] = 0;
714 self->velocity[2] = 0;
715 self->next_move_time = level.time + 0.5;
716 return;
717 }
718
719 // Is there a target to move to
720 if (self->movetarget)
721 ACEMV_MoveToGoal(self,ucmd);
722
723 ////////////////////////////////
724 // Swimming?
725 ////////////////////////////////
726 VectorCopy(self->s.origin,temp);
727 temp[2]+=24;
728
729 if(gi.pointcontents (temp) & MASK_WATER)
730 {
731 // If drowning and no node, move up
732 if(self->client->next_drown_time > 0)
733 {
734 ucmd->upmove = 1;
735 self->s.angles[PITCH] = -45;
736 }
737 else
738 ucmd->upmove = 15;
739
740 ucmd->forwardmove = 300;
741 }
742 else
743 self->client->next_drown_time = 0; // probably shound not be messing with this, but
744
745 ////////////////////////////////
746 // Lava?
747 ////////////////////////////////
748 temp[2]-=48;
749 if(gi.pointcontents(temp) & (CONTENTS_LAVA|CONTENTS_SLIME))
750 {
751 // safe_bprintf(PRINT_MEDIUM,"lava jump\n");
752 self->s.angles[YAW] += random() * 360 - 180;
753 ucmd->forwardmove = mSpeed;
754 ucmd->upmove = 400;
755 return;
756 }
757
758 if(ACEMV_CheckEyes(self,ucmd))
759 return;
760
761 // Check for special movement if we have a normal move (have to test)
762 if(VectorLength(self->velocity) < 37)
763 {
764 /*if(random() > 0.1 && ACEMV_SpecialMove(self,ucmd))
765 return;*/ //removed this because when wandering, the last thing you want is bots jumping
766 //over things and going off ledges. It's better for them to just bounce around the map.
767
768 self->s.angles[YAW] += random() * 180 - 90;
769
770 // Try to move forward, if blocked, try to strafe around obstacle
771 if(ACEMV_CanMove(self, MOVE_FORWARD))
772 ucmd->forwardmove = mSpeed;
773 else if(ACEMV_CanMove(self, MOVE_BACK))
774 ucmd->forwardmove = -mSpeed;
775 else if(ACEMV_CanMove(self, MOVE_RIGHT))
776 ucmd->sidemove = mSpeed;
777 else if(ACEMV_CanMove(self, MOVE_LEFT))
778 ucmd->sidemove = -mSpeed;
779
780 if(!M_CheckBottom(self) || !self->groundentity) // if there is ground continue, otherwise wait for next move.
781 {
782 if(ACEMV_CanMove(self, MOVE_FORWARD))
783 ucmd->forwardmove = mSpeed;
784 }
785
786 return;
787 }
788
789 if(ACEMV_CanMove(self, MOVE_FORWARD))
790 ucmd->forwardmove = mSpeed;
791
792 if(self->skill == 3)
793 { //ultra skill level(will be 3)
794 c = random();
795
796 //If we have the Alien Smartgun, drop some prox mines :)
797 if (self->client->pers.weapon == FindItem("alien smartgun") && c < 0.2)
798 ucmd->buttons = BUTTON_ATTACK2;
799 }
800 }
801
802 /**
803 * @brief Apply some imprecision to an attacking bot's aim
804 *
805 * @detail Uses a randomly oriented X,Y vector with a random length
806 * plus an offset, which leaves a hole in the center of the
807 * target area.
808 *
809 * @param self entity for the bot
810 * @param pdx x for targeted enemy's origin
811 * @param pdy y for targeted enemy's origin
812 *
813 * @return *pdx and *pdy altered
814 */
815 /*
816 * constants for target fuzzification
817 */
818 static const float ktgt_scale = 250.0f; //scaling factor
819 static const float ktgt_ofs = 20.0f; //radius of hole
820 /* scaling for weap skill for bot cfg skill 0, 1, 2 & 3 */
821 static const float ktgt_skill[4] =
822 { 0.7f, 0.8f, 0.9f, 1.0f }; //accuracy scaling by skill
823 //accuracy factor = ktgt_skill[0] * self->accuracy minimum clamp
824 static const float ktgt_acc = 0.35;
825
fuzzy_target(edict_t * self,float * pdx,float * pdy)826 static void fuzzy_target( edict_t *self, float *pdx, float *pdy )
827 {
828 float accuracy;
829 float random_r;
830 float radius;
831 float angle;
832 float ca,sa;
833 float dx,dy;
834
835 /*
836 * self->accuracy is weapon specific accuracy from bot .cfg file
837 */
838 accuracy = self->accuracy;
839 if ( accuracy < 0.5f )
840 accuracy = 0.5f;
841 else if ( accuracy > 1.0f )
842 accuracy = 1.0f;
843
844 //calc weap accuracy factor with scaling on bot skill
845 switch ( self->skill )
846 {
847 case 0:
848 accuracy *= ktgt_skill[0];
849 break;
850 case 1:
851 default:
852 accuracy *= ktgt_skill[1];
853 break;
854 case 2:
855 accuracy *= ktgt_skill[2];
856 break;
857 case 3:
858 //override 3->2 for CTF
859 accuracy *= (ctf->integer) ? ktgt_skill[2] : ktgt_skill[3];
860 break;
861 }
862
863 //radius calc
864 random_r = ktgt_scale * crandom();
865 radius = (random_r * ( ktgt_acc / accuracy)) + ktgt_ofs;
866 //angle calc
867 angle = random() * 2.0f * M_PI;
868 fast_sincosf( angle, &sa, &ca );
869 //apply delta to target
870 *pdx += dx = ca * radius;
871 *pdy += dy = sa * radius;
872
873 // if ( debug_mode )
874 // {
875 // gi.dprintf("{\t%0.2f\t%0.2f\tacc%0.1f\t%i}\n",
876 // dx, dy, accuracy, self->skill );
877 // }
878
879 }
880
881 ///////////////////////////////////////////////////////////////////////
882 // Attack movement routine
883 ///////////////////////////////////////////////////////////////////////
ACEMV_Attack(edict_t * self,usercmd_t * ucmd)884 void ACEMV_Attack (edict_t *self, usercmd_t *ucmd)
885 {
886 float c, d;
887 vec3_t target;
888 vec3_t angles;
889 vec3_t down;
890 int strafespeed;
891 float jump_thresh;
892 float crouch_thresh;
893 gitem_t *vehicle;
894 float range = 0.0f;
895 vec3_t v;
896 qboolean use_fuzzy_aim;
897 short mSpeed;
898
899 if(g_tactical->integer)
900 mSpeed = 200;
901 else
902 mSpeed = 400;
903
904 ucmd->buttons = 0;
905 use_fuzzy_aim = true; // unless overridden by special cases
906 if ( dmflags->integer & DF_BOT_FUZZYAIM )
907 { // when bit is set it means fuzzy aim is disabled. for testing mostly.
908 use_fuzzy_aim = false;
909 }
910
911 vehicle = FindItemByClassname("item_bomber");
912
913 if (self->client->pers.inventory[ITEM_INDEX(vehicle)])
914 {
915 //if we are too low, don't shoot, and move up. Should be fairly simple, right?
916 if(self->enemy->s.origin[2] >= self->s.origin[2] - 128) { //we want to be well above our target
917 ucmd->upmove += 400;
918 // face the enemy while moving up
919 VectorCopy(self->enemy->s.origin,target);
920 VectorSubtract (target, self->s.origin, self->move_vector);
921 vectoangles (self->move_vector, angles);
922 VectorCopy(angles,self->s.angles);
923 return;
924 }
925 }
926
927 switch ( self->skill )
928 { // set up the skill levels
929 case 0:
930 strafespeed = 300;
931 jump_thresh = 1.0f; // never jump
932 crouch_thresh = 0.0f; // never crouch
933 break;
934
935 case 1:
936 default:
937 strafespeed = 400;
938 jump_thresh = 0.95f;
939 crouch_thresh = 0.85f; // crouch much
940 break;
941
942 case 2:
943 case 3:
944 strafespeed = 800;
945 if ( !joustmode->integer )
946 {
947 jump_thresh = 0.8f;
948 crouch_thresh = 0.7f;
949 }
950 else
951 {
952 jump_thresh = 0.5f; // want to jump a whole lot more in joust mode
953 crouch_thresh = 0.4f; // and crouch less
954 }
955 break;
956 }
957
958 d = random(); // for skill 0 movement
959 c = random(); // for strafing, jumping, crouching
960
961 // violator attack
962 if ( self->client->pers.weapon == FindItem( "Violator" ) )
963 {
964 use_fuzzy_aim = false; // avoid potential odd melee attack behaviour
965 if ( ACEMV_CanMove( self, MOVE_FORWARD ) )
966 ucmd->forwardmove += 400; //lunge at enemy
967 goto attack;
968 }
969
970 //machinegun/blaster/beamgun strafing for level 2/3 bots
971 if ( !joustmode->value
972 && self->skill >= 2
973 && (self->client->pers.weapon == FindItem( "Blaster" )
974 || self->client->pers.weapon == FindItem( "Alien Blaster" )
975 || self->client->pers.weapon == FindItem( "Pulse Rifle" )
976 || self->client->pers.weapon == FindItem( "Disruptor" )))
977 {
978 //strafe no matter what
979 if ( c < 0.5f && ACEMV_CanMove( self, MOVE_LEFT ) )
980 {
981 ucmd->sidemove -= mSpeed;
982 }
983 else if ( c < 1.0f && ACEMV_CanMove( self, MOVE_RIGHT ) )
984 {
985 ucmd->sidemove += mSpeed;
986 }
987 else
988 { //don't want high skill level bots just standing around
989 goto standardmove;
990 }
991
992 //allow for some circle strafing
993 if ( self->health < 50 && ACEMV_CanMove( self, MOVE_BACK ) )
994 {
995 ucmd->forwardmove -= mSpeed;
996 }
997 else if ( c < 0.6f && ACEMV_CanMove( self, MOVE_FORWARD ) )
998 { //keep this at default, not make them TOO hard
999 ucmd->forwardmove += mSpeed;
1000 }
1001 else if ( c < 0.8f && ACEMV_CanMove( self, MOVE_BACK ) )
1002 {
1003 ucmd->forwardmove -= mSpeed;
1004 }
1005 goto attack;
1006 //skip any jumping or crouching
1007 }
1008
1009 if ( self->skill == 0 && d < 0.9f )
1010 goto attack; //skill 0 bots will barely move while firing
1011
1012 standardmove:
1013
1014 if ( c < 0.2f && ACEMV_CanMove( self, MOVE_LEFT ) )
1015 {
1016 ucmd->sidemove -= strafespeed;
1017 //300 for low skill 800 for hardest(3 levels?)
1018 }
1019 else if ( c < 0.4f && ACEMV_CanMove( self, MOVE_RIGHT ) )
1020 {
1021 ucmd->sidemove += strafespeed;
1022 }
1023
1024 if ( self->health < 50 && ACEMV_CanMove( self, MOVE_BACK ) )
1025 { //run away if wounded
1026 ucmd->forwardmove -= mSpeed;
1027 }
1028 else if ( c < 0.6f && ACEMV_CanMove( self, MOVE_FORWARD ) )
1029 { //keep this at default, not make them TOO hard
1030 ucmd->forwardmove += mSpeed;
1031 }
1032 else if ( c < 0.8f && ACEMV_CanMove( self, MOVE_BACK ) )
1033 {
1034 ucmd->forwardmove -= mSpeed;
1035 }
1036
1037 c = random(); //really mix this up some
1038 if ( self->health >= 50 && c < crouch_thresh )
1039 {
1040 ucmd->upmove -= 200;
1041 }
1042 else if ( c > jump_thresh )
1043 {
1044 #ifndef ALTERIA
1045 c = random();
1046 if ( self->health >= 70
1047 && self->skill >= 2
1048 && !self->in_vehicle && !self->in_deathball
1049 && ACEIT_ChangeWeapon( self, FindItem( "Rocket Launcher" ) )
1050 && c < 0.6f )
1051 { // Rocket Jump
1052 self->s.angles[PITCH] = 90.0f;
1053 AngleVectors( self->s.angles, down, NULL, NULL );
1054 fire_rocket( self, self->s.origin, down, 200, 650, 120, 120 );
1055 ucmd->upmove += 200;
1056 self->s.angles[PITCH] = 0.0f;
1057 if ( (!(dmflags->integer & DF_INFINITE_AMMO))
1058 && !rocket_arena->integer && !insta_rockets->integer )
1059 {
1060 self->client->pers.inventory[self->client->ammo_index]--;
1061 }
1062 return;
1063 }
1064 else
1065 #endif
1066 { // Normal Jump
1067 ucmd->upmove += 200;
1068 }
1069 }
1070
1071 attack:
1072 // Set the attack
1073 if(ACEAI_CheckShot(self))
1074 {
1075 //bot is taking a shot, lose spawn protection
1076 // and calculate distance to enemy
1077 range = 0.0f;
1078 if(self->enemy)
1079 {
1080 self->client->spawnprotected = false;
1081 VectorSubtract (self->s.origin, self->enemy->s.origin, v);
1082 range = VectorLength(v);
1083 if ( range < 32.0f )
1084 { // point blank range, avoid potentially odd behaviour
1085 use_fuzzy_aim = false;
1086 }
1087 }
1088
1089 if(self->skill >= 2)
1090 { //skill 2/3 bots can use alt-fires!
1091
1092 // Base selection on distance.
1093 ucmd->buttons = BUTTON_ATTACK;
1094
1095 if (self->client->pers.weapon == FindItem("Blaster") || self->client->pers.weapon == FindItem("Alien Blaster"))
1096 {
1097 if( range > 500)
1098 ucmd->buttons = BUTTON_ATTACK2;
1099 else
1100 ucmd->buttons = BUTTON_ATTACK;
1101 }
1102
1103 if (self->client->pers.weapon == FindItem("Alien Disruptor"))
1104 {
1105 if(range > 1000) {
1106 ucmd->buttons = BUTTON_ATTACK2;
1107 use_fuzzy_aim = false;
1108 //make it more accurate, since he's sniping
1109 }
1110 else
1111 ucmd->buttons = BUTTON_ATTACK;
1112 }
1113
1114 if (self->client->pers.weapon == FindItem("Flame Thrower"))
1115 {
1116 if(range < 500)
1117 ucmd->buttons = BUTTON_ATTACK;
1118 else
1119 ucmd->buttons = BUTTON_ATTACK2;
1120 }
1121
1122 if (self->client->pers.weapon == FindItem("Pulse Rifle"))
1123 {
1124 if(range < 200)
1125 ucmd->buttons = BUTTON_ATTACK2;
1126 else
1127 ucmd->buttons = BUTTON_ATTACK;
1128 }
1129
1130 if (self->client->pers.weapon == FindItem("Disruptor"))
1131 {
1132 if(range < 300)
1133 ucmd->buttons = BUTTON_ATTACK2;
1134 else
1135 ucmd->buttons = BUTTON_ATTACK;
1136 }
1137
1138 if (self->client->pers.weapon == FindItem("Alien Vaporizer"))
1139 {
1140 if(range < 300)
1141 ucmd->buttons = BUTTON_ATTACK2;
1142 else
1143 ucmd->buttons = BUTTON_ATTACK;
1144 }
1145
1146 if (self->client->pers.weapon == FindItem("Minderaser"))
1147 {
1148 if(range < 400)
1149 ucmd->buttons = BUTTON_ATTACK;
1150 else
1151 ucmd->buttons = BUTTON_ATTACK2;
1152 }
1153
1154 //vehicle alt-fires
1155 if (self->client->pers.weapon == FindItem("bomber")
1156 || self->client->pers.weapon == FindItem("strafer"))
1157 {
1158 if(range > 500)
1159 ucmd->buttons = BUTTON_ATTACK2;
1160 else
1161 ucmd->buttons = BUTTON_ATTACK;
1162 }
1163 if (self->client->pers.weapon == FindItem("hover"))
1164 {
1165 if(range < 300)
1166 ucmd->buttons = BUTTON_ATTACK2;
1167 else
1168 ucmd->buttons = BUTTON_ATTACK;
1169 }
1170 }
1171 else
1172 {
1173 ucmd->buttons = BUTTON_ATTACK;
1174 }
1175 }
1176 else
1177 { // not taking a shot
1178 ucmd->buttons = 0;
1179 use_fuzzy_aim = false;
1180 }
1181
1182 // Aim
1183 VectorCopy(self->enemy->s.origin,target);
1184 if ( use_fuzzy_aim )
1185 {
1186 fuzzy_target( self, &target[0], &target[1] );
1187 }
1188 // Set movement direction toward targeted enemy
1189 VectorSubtract (target, self->s.origin, self->move_vector);
1190 vectoangles (self->move_vector, angles);
1191 VectorCopy(angles,self->s.angles);
1192 }
1193