ai_dmq3.c (158046B)
1 /* 2 =========================================================================== 3 Copyright (C) 1999-2005 Id Software, Inc. 4 5 This file is part of Quake III Arena source code. 6 7 Quake III Arena source code is free software; you can redistribute it 8 and/or modify it under the terms of the GNU General Public License as 9 published by the Free Software Foundation; either version 2 of the License, 10 or (at your option) any later version. 11 12 Quake III Arena source code is distributed in the hope that it will be 13 useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 GNU General Public License for more details. 16 17 You should have received a copy of the GNU General Public License 18 along with Foobar; if not, write to the Free Software 19 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 20 =========================================================================== 21 */ 22 // 23 24 /***************************************************************************** 25 * name: ai_dmq3.c 26 * 27 * desc: Quake3 bot AI 28 * 29 * $Archive: /MissionPack/code/game/ai_dmq3.c $ 30 * 31 *****************************************************************************/ 32 33 34 #include "g_local.h" 35 #include "botlib.h" 36 #include "be_aas.h" 37 #include "be_ea.h" 38 #include "be_ai_char.h" 39 #include "be_ai_chat.h" 40 #include "be_ai_gen.h" 41 #include "be_ai_goal.h" 42 #include "be_ai_move.h" 43 #include "be_ai_weap.h" 44 // 45 #include "ai_main.h" 46 #include "ai_dmq3.h" 47 #include "ai_chat.h" 48 #include "ai_cmd.h" 49 #include "ai_dmnet.h" 50 #include "ai_team.h" 51 // 52 #include "chars.h" //characteristics 53 #include "inv.h" //indexes into the inventory 54 #include "syn.h" //synonyms 55 #include "match.h" //string matching types and vars 56 57 // for the voice chats 58 #include "../../ui/menudef.h" // sos001205 - for q3_ui also 59 60 // from aasfile.h 61 #define AREACONTENTS_MOVER 1024 62 #define AREACONTENTS_MODELNUMSHIFT 24 63 #define AREACONTENTS_MAXMODELNUM 0xFF 64 #define AREACONTENTS_MODELNUM (AREACONTENTS_MAXMODELNUM << AREACONTENTS_MODELNUMSHIFT) 65 66 #define IDEAL_ATTACKDIST 140 67 68 #define MAX_WAYPOINTS 128 69 // 70 bot_waypoint_t botai_waypoints[MAX_WAYPOINTS]; 71 bot_waypoint_t *botai_freewaypoints; 72 73 //NOTE: not using a cvars which can be updated because the game should be reloaded anyway 74 int gametype; //game type 75 int maxclients; //maximum number of clients 76 77 vmCvar_t bot_grapple; 78 vmCvar_t bot_rocketjump; 79 vmCvar_t bot_fastchat; 80 vmCvar_t bot_nochat; 81 vmCvar_t bot_testrchat; 82 vmCvar_t bot_challenge; 83 vmCvar_t bot_predictobstacles; 84 vmCvar_t g_spSkill; 85 86 extern vmCvar_t bot_developer; 87 88 vec3_t lastteleport_origin; //last teleport event origin 89 float lastteleport_time; //last teleport event time 90 int max_bspmodelindex; //maximum BSP model index 91 92 //CTF flag goals 93 bot_goal_t ctf_redflag; 94 bot_goal_t ctf_blueflag; 95 #ifdef MISSIONPACK 96 bot_goal_t ctf_neutralflag; 97 bot_goal_t redobelisk; 98 bot_goal_t blueobelisk; 99 bot_goal_t neutralobelisk; 100 #endif 101 102 #define MAX_ALTROUTEGOALS 32 103 104 int altroutegoals_setup; 105 aas_altroutegoal_t red_altroutegoals[MAX_ALTROUTEGOALS]; 106 int red_numaltroutegoals; 107 aas_altroutegoal_t blue_altroutegoals[MAX_ALTROUTEGOALS]; 108 int blue_numaltroutegoals; 109 110 111 /* 112 ================== 113 BotSetUserInfo 114 ================== 115 */ 116 void BotSetUserInfo(bot_state_t *bs, char *key, char *value) { 117 char userinfo[MAX_INFO_STRING]; 118 119 trap_GetUserinfo(bs->client, userinfo, sizeof(userinfo)); 120 Info_SetValueForKey(userinfo, key, value); 121 trap_SetUserinfo(bs->client, userinfo); 122 ClientUserinfoChanged( bs->client ); 123 } 124 125 /* 126 ================== 127 BotCTFCarryingFlag 128 ================== 129 */ 130 int BotCTFCarryingFlag(bot_state_t *bs) { 131 if (gametype != GT_CTF) return CTF_FLAG_NONE; 132 133 if (bs->inventory[INVENTORY_REDFLAG] > 0) return CTF_FLAG_RED; 134 else if (bs->inventory[INVENTORY_BLUEFLAG] > 0) return CTF_FLAG_BLUE; 135 return CTF_FLAG_NONE; 136 } 137 138 /* 139 ================== 140 BotTeam 141 ================== 142 */ 143 int BotTeam(bot_state_t *bs) { 144 char info[1024]; 145 146 if (bs->client < 0 || bs->client >= MAX_CLIENTS) { 147 //BotAI_Print(PRT_ERROR, "BotCTFTeam: client out of range\n"); 148 return qfalse; 149 } 150 trap_GetConfigstring(CS_PLAYERS+bs->client, info, sizeof(info)); 151 // 152 if (atoi(Info_ValueForKey(info, "t")) == TEAM_RED) return TEAM_RED; 153 else if (atoi(Info_ValueForKey(info, "t")) == TEAM_BLUE) return TEAM_BLUE; 154 return TEAM_FREE; 155 } 156 157 /* 158 ================== 159 BotOppositeTeam 160 ================== 161 */ 162 int BotOppositeTeam(bot_state_t *bs) { 163 switch(BotTeam(bs)) { 164 case TEAM_RED: return TEAM_BLUE; 165 case TEAM_BLUE: return TEAM_RED; 166 default: return TEAM_FREE; 167 } 168 } 169 170 /* 171 ================== 172 BotEnemyFlag 173 ================== 174 */ 175 bot_goal_t *BotEnemyFlag(bot_state_t *bs) { 176 if (BotTeam(bs) == TEAM_RED) { 177 return &ctf_blueflag; 178 } 179 else { 180 return &ctf_redflag; 181 } 182 } 183 184 /* 185 ================== 186 BotTeamFlag 187 ================== 188 */ 189 bot_goal_t *BotTeamFlag(bot_state_t *bs) { 190 if (BotTeam(bs) == TEAM_RED) { 191 return &ctf_redflag; 192 } 193 else { 194 return &ctf_blueflag; 195 } 196 } 197 198 199 /* 200 ================== 201 EntityIsDead 202 ================== 203 */ 204 qboolean EntityIsDead(aas_entityinfo_t *entinfo) { 205 playerState_t ps; 206 207 if (entinfo->number >= 0 && entinfo->number < MAX_CLIENTS) { 208 //retrieve the current client state 209 BotAI_GetClientState( entinfo->number, &ps ); 210 if (ps.pm_type != PM_NORMAL) return qtrue; 211 } 212 return qfalse; 213 } 214 215 /* 216 ================== 217 EntityCarriesFlag 218 ================== 219 */ 220 qboolean EntityCarriesFlag(aas_entityinfo_t *entinfo) { 221 if ( entinfo->powerups & ( 1 << PW_REDFLAG ) ) 222 return qtrue; 223 if ( entinfo->powerups & ( 1 << PW_BLUEFLAG ) ) 224 return qtrue; 225 #ifdef MISSIONPACK 226 if ( entinfo->powerups & ( 1 << PW_NEUTRALFLAG ) ) 227 return qtrue; 228 #endif 229 return qfalse; 230 } 231 232 /* 233 ================== 234 EntityIsInvisible 235 ================== 236 */ 237 qboolean EntityIsInvisible(aas_entityinfo_t *entinfo) { 238 // the flag is always visible 239 if (EntityCarriesFlag(entinfo)) { 240 return qfalse; 241 } 242 if (entinfo->powerups & (1 << PW_INVIS)) { 243 return qtrue; 244 } 245 return qfalse; 246 } 247 248 /* 249 ================== 250 EntityIsShooting 251 ================== 252 */ 253 qboolean EntityIsShooting(aas_entityinfo_t *entinfo) { 254 if (entinfo->flags & EF_FIRING) { 255 return qtrue; 256 } 257 return qfalse; 258 } 259 260 /* 261 ================== 262 EntityIsChatting 263 ================== 264 */ 265 qboolean EntityIsChatting(aas_entityinfo_t *entinfo) { 266 if (entinfo->flags & EF_TALK) { 267 return qtrue; 268 } 269 return qfalse; 270 } 271 272 /* 273 ================== 274 EntityHasQuad 275 ================== 276 */ 277 qboolean EntityHasQuad(aas_entityinfo_t *entinfo) { 278 if (entinfo->powerups & (1 << PW_QUAD)) { 279 return qtrue; 280 } 281 return qfalse; 282 } 283 284 #ifdef MISSIONPACK 285 /* 286 ================== 287 EntityHasKamikze 288 ================== 289 */ 290 qboolean EntityHasKamikaze(aas_entityinfo_t *entinfo) { 291 if (entinfo->flags & EF_KAMIKAZE) { 292 return qtrue; 293 } 294 return qfalse; 295 } 296 297 /* 298 ================== 299 EntityCarriesCubes 300 ================== 301 */ 302 qboolean EntityCarriesCubes(aas_entityinfo_t *entinfo) { 303 entityState_t state; 304 305 if (gametype != GT_HARVESTER) 306 return qfalse; 307 //FIXME: get this info from the aas_entityinfo_t ? 308 BotAI_GetEntityState(entinfo->number, &state); 309 if (state.generic1 > 0) 310 return qtrue; 311 return qfalse; 312 } 313 314 /* 315 ================== 316 Bot1FCTFCarryingFlag 317 ================== 318 */ 319 int Bot1FCTFCarryingFlag(bot_state_t *bs) { 320 if (gametype != GT_1FCTF) return qfalse; 321 322 if (bs->inventory[INVENTORY_NEUTRALFLAG] > 0) return qtrue; 323 return qfalse; 324 } 325 326 /* 327 ================== 328 BotHarvesterCarryingCubes 329 ================== 330 */ 331 int BotHarvesterCarryingCubes(bot_state_t *bs) { 332 if (gametype != GT_HARVESTER) return qfalse; 333 334 if (bs->inventory[INVENTORY_REDCUBE] > 0) return qtrue; 335 if (bs->inventory[INVENTORY_BLUECUBE] > 0) return qtrue; 336 return qfalse; 337 } 338 #endif 339 340 /* 341 ================== 342 BotRememberLastOrderedTask 343 ================== 344 */ 345 void BotRememberLastOrderedTask(bot_state_t *bs) { 346 if (!bs->ordered) { 347 return; 348 } 349 bs->lastgoal_decisionmaker = bs->decisionmaker; 350 bs->lastgoal_ltgtype = bs->ltgtype; 351 memcpy(&bs->lastgoal_teamgoal, &bs->teamgoal, sizeof(bot_goal_t)); 352 bs->lastgoal_teammate = bs->teammate; 353 } 354 355 /* 356 ================== 357 BotSetTeamStatus 358 ================== 359 */ 360 void BotSetTeamStatus(bot_state_t *bs) { 361 #ifdef MISSIONPACK 362 int teamtask; 363 aas_entityinfo_t entinfo; 364 365 teamtask = TEAMTASK_PATROL; 366 367 switch(bs->ltgtype) { 368 case LTG_TEAMHELP: 369 break; 370 case LTG_TEAMACCOMPANY: 371 BotEntityInfo(bs->teammate, &entinfo); 372 if ( ( (gametype == GT_CTF || gametype == GT_1FCTF) && EntityCarriesFlag(&entinfo)) 373 || ( gametype == GT_HARVESTER && EntityCarriesCubes(&entinfo)) ) { 374 teamtask = TEAMTASK_ESCORT; 375 } 376 else { 377 teamtask = TEAMTASK_FOLLOW; 378 } 379 break; 380 case LTG_DEFENDKEYAREA: 381 teamtask = TEAMTASK_DEFENSE; 382 break; 383 case LTG_GETFLAG: 384 teamtask = TEAMTASK_OFFENSE; 385 break; 386 case LTG_RUSHBASE: 387 teamtask = TEAMTASK_DEFENSE; 388 break; 389 case LTG_RETURNFLAG: 390 teamtask = TEAMTASK_RETRIEVE; 391 break; 392 case LTG_CAMP: 393 case LTG_CAMPORDER: 394 teamtask = TEAMTASK_CAMP; 395 break; 396 case LTG_PATROL: 397 teamtask = TEAMTASK_PATROL; 398 break; 399 case LTG_GETITEM: 400 teamtask = TEAMTASK_PATROL; 401 break; 402 case LTG_KILL: 403 teamtask = TEAMTASK_PATROL; 404 break; 405 case LTG_HARVEST: 406 teamtask = TEAMTASK_OFFENSE; 407 break; 408 case LTG_ATTACKENEMYBASE: 409 teamtask = TEAMTASK_OFFENSE; 410 break; 411 default: 412 teamtask = TEAMTASK_PATROL; 413 break; 414 } 415 BotSetUserInfo(bs, "teamtask", va("%d", teamtask)); 416 #endif 417 } 418 419 /* 420 ================== 421 BotSetLastOrderedTask 422 ================== 423 */ 424 int BotSetLastOrderedTask(bot_state_t *bs) { 425 426 if (gametype == GT_CTF) { 427 // don't go back to returning the flag if it's at the base 428 if ( bs->lastgoal_ltgtype == LTG_RETURNFLAG ) { 429 if ( BotTeam(bs) == TEAM_RED ) { 430 if ( bs->redflagstatus == 0 ) { 431 bs->lastgoal_ltgtype = 0; 432 } 433 } 434 else { 435 if ( bs->blueflagstatus == 0 ) { 436 bs->lastgoal_ltgtype = 0; 437 } 438 } 439 } 440 } 441 442 if ( bs->lastgoal_ltgtype ) { 443 bs->decisionmaker = bs->lastgoal_decisionmaker; 444 bs->ordered = qtrue; 445 bs->ltgtype = bs->lastgoal_ltgtype; 446 memcpy(&bs->teamgoal, &bs->lastgoal_teamgoal, sizeof(bot_goal_t)); 447 bs->teammate = bs->lastgoal_teammate; 448 bs->teamgoal_time = FloatTime() + 300; 449 BotSetTeamStatus(bs); 450 // 451 if ( gametype == GT_CTF ) { 452 if ( bs->ltgtype == LTG_GETFLAG ) { 453 bot_goal_t *tb, *eb; 454 int tt, et; 455 456 tb = BotTeamFlag(bs); 457 eb = BotEnemyFlag(bs); 458 tt = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, tb->areanum, TFL_DEFAULT); 459 et = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, eb->areanum, TFL_DEFAULT); 460 // if the travel time towards the enemy base is larger than towards our base 461 if (et > tt) { 462 //get an alternative route goal towards the enemy base 463 BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs)); 464 } 465 } 466 } 467 return qtrue; 468 } 469 return qfalse; 470 } 471 472 /* 473 ================== 474 BotRefuseOrder 475 ================== 476 */ 477 void BotRefuseOrder(bot_state_t *bs) { 478 if (!bs->ordered) 479 return; 480 // if the bot was ordered to do something 481 if ( bs->order_time && bs->order_time > FloatTime() - 10 ) { 482 trap_EA_Action(bs->client, ACTION_NEGATIVE); 483 BotVoiceChat(bs, bs->decisionmaker, VOICECHAT_NO); 484 bs->order_time = 0; 485 } 486 } 487 488 /* 489 ================== 490 BotCTFSeekGoals 491 ================== 492 */ 493 void BotCTFSeekGoals(bot_state_t *bs) { 494 float rnd, l1, l2; 495 int flagstatus, c; 496 vec3_t dir; 497 aas_entityinfo_t entinfo; 498 499 //when carrying a flag in ctf the bot should rush to the base 500 if (BotCTFCarryingFlag(bs)) { 501 //if not already rushing to the base 502 if (bs->ltgtype != LTG_RUSHBASE) { 503 BotRefuseOrder(bs); 504 bs->ltgtype = LTG_RUSHBASE; 505 bs->teamgoal_time = FloatTime() + CTF_RUSHBASE_TIME; 506 bs->rushbaseaway_time = 0; 507 bs->decisionmaker = bs->client; 508 bs->ordered = qfalse; 509 // 510 switch(BotTeam(bs)) { 511 case TEAM_RED: VectorSubtract(bs->origin, ctf_blueflag.origin, dir); break; 512 case TEAM_BLUE: VectorSubtract(bs->origin, ctf_redflag.origin, dir); break; 513 default: VectorSet(dir, 999, 999, 999); break; 514 } 515 // if the bot picked up the flag very close to the enemy base 516 if ( VectorLength(dir) < 128 ) { 517 // get an alternative route goal through the enemy base 518 BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs)); 519 } else { 520 // don't use any alt route goal, just get the hell out of the base 521 bs->altroutegoal.areanum = 0; 522 } 523 BotSetUserInfo(bs, "teamtask", va("%d", TEAMTASK_OFFENSE)); 524 BotVoiceChat(bs, -1, VOICECHAT_IHAVEFLAG); 525 } 526 else if (bs->rushbaseaway_time > FloatTime()) { 527 if (BotTeam(bs) == TEAM_RED) flagstatus = bs->redflagstatus; 528 else flagstatus = bs->blueflagstatus; 529 //if the flag is back 530 if (flagstatus == 0) { 531 bs->rushbaseaway_time = 0; 532 } 533 } 534 return; 535 } 536 // if the bot decided to follow someone 537 if ( bs->ltgtype == LTG_TEAMACCOMPANY && !bs->ordered ) { 538 // if the team mate being accompanied no longer carries the flag 539 BotEntityInfo(bs->teammate, &entinfo); 540 if (!EntityCarriesFlag(&entinfo)) { 541 bs->ltgtype = 0; 542 } 543 } 544 // 545 if (BotTeam(bs) == TEAM_RED) flagstatus = bs->redflagstatus * 2 + bs->blueflagstatus; 546 else flagstatus = bs->blueflagstatus * 2 + bs->redflagstatus; 547 //if our team has the enemy flag and our flag is at the base 548 if (flagstatus == 1) { 549 // 550 if (bs->owndecision_time < FloatTime()) { 551 //if Not defending the base already 552 if (!(bs->ltgtype == LTG_DEFENDKEYAREA && 553 (bs->teamgoal.number == ctf_redflag.number || 554 bs->teamgoal.number == ctf_blueflag.number))) { 555 //if there is a visible team mate flag carrier 556 c = BotTeamFlagCarrierVisible(bs); 557 if (c >= 0 && 558 // and not already following the team mate flag carrier 559 (bs->ltgtype != LTG_TEAMACCOMPANY || bs->teammate != c)) { 560 // 561 BotRefuseOrder(bs); 562 //follow the flag carrier 563 bs->decisionmaker = bs->client; 564 bs->ordered = qfalse; 565 //the team mate 566 bs->teammate = c; 567 //last time the team mate was visible 568 bs->teammatevisible_time = FloatTime(); 569 //no message 570 bs->teammessage_time = 0; 571 //no arrive message 572 bs->arrive_time = 1; 573 // 574 BotVoiceChat(bs, bs->teammate, VOICECHAT_ONFOLLOW); 575 //get the team goal time 576 bs->teamgoal_time = FloatTime() + TEAM_ACCOMPANY_TIME; 577 bs->ltgtype = LTG_TEAMACCOMPANY; 578 bs->formation_dist = 3.5 * 32; //3.5 meter 579 BotSetTeamStatus(bs); 580 bs->owndecision_time = FloatTime() + 5; 581 } 582 } 583 } 584 return; 585 } 586 //if the enemy has our flag 587 else if (flagstatus == 2) { 588 // 589 if (bs->owndecision_time < FloatTime()) { 590 //if enemy flag carrier is visible 591 c = BotEnemyFlagCarrierVisible(bs); 592 if (c >= 0) { 593 //FIXME: fight enemy flag carrier 594 } 595 //if not already doing something important 596 if (bs->ltgtype != LTG_GETFLAG && 597 bs->ltgtype != LTG_RETURNFLAG && 598 bs->ltgtype != LTG_TEAMHELP && 599 bs->ltgtype != LTG_TEAMACCOMPANY && 600 bs->ltgtype != LTG_CAMPORDER && 601 bs->ltgtype != LTG_PATROL && 602 bs->ltgtype != LTG_GETITEM) { 603 604 BotRefuseOrder(bs); 605 bs->decisionmaker = bs->client; 606 bs->ordered = qfalse; 607 // 608 if (random() < 0.5) { 609 //go for the enemy flag 610 bs->ltgtype = LTG_GETFLAG; 611 } 612 else { 613 bs->ltgtype = LTG_RETURNFLAG; 614 } 615 //no team message 616 bs->teammessage_time = 0; 617 //set the time the bot will stop getting the flag 618 bs->teamgoal_time = FloatTime() + CTF_GETFLAG_TIME; 619 //get an alternative route goal towards the enemy base 620 BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs)); 621 // 622 BotSetTeamStatus(bs); 623 bs->owndecision_time = FloatTime() + 5; 624 } 625 } 626 return; 627 } 628 //if both flags Not at their bases 629 else if (flagstatus == 3) { 630 // 631 if (bs->owndecision_time < FloatTime()) { 632 // if not trying to return the flag and not following the team flag carrier 633 if ( bs->ltgtype != LTG_RETURNFLAG && bs->ltgtype != LTG_TEAMACCOMPANY ) { 634 // 635 c = BotTeamFlagCarrierVisible(bs); 636 // if there is a visible team mate flag carrier 637 if (c >= 0) { 638 BotRefuseOrder(bs); 639 //follow the flag carrier 640 bs->decisionmaker = bs->client; 641 bs->ordered = qfalse; 642 //the team mate 643 bs->teammate = c; 644 //last time the team mate was visible 645 bs->teammatevisible_time = FloatTime(); 646 //no message 647 bs->teammessage_time = 0; 648 //no arrive message 649 bs->arrive_time = 1; 650 // 651 BotVoiceChat(bs, bs->teammate, VOICECHAT_ONFOLLOW); 652 //get the team goal time 653 bs->teamgoal_time = FloatTime() + TEAM_ACCOMPANY_TIME; 654 bs->ltgtype = LTG_TEAMACCOMPANY; 655 bs->formation_dist = 3.5 * 32; //3.5 meter 656 // 657 BotSetTeamStatus(bs); 658 bs->owndecision_time = FloatTime() + 5; 659 } 660 else { 661 BotRefuseOrder(bs); 662 bs->decisionmaker = bs->client; 663 bs->ordered = qfalse; 664 //get the enemy flag 665 bs->teammessage_time = FloatTime() + 2 * random(); 666 //get the flag 667 bs->ltgtype = LTG_RETURNFLAG; 668 //set the time the bot will stop getting the flag 669 bs->teamgoal_time = FloatTime() + CTF_RETURNFLAG_TIME; 670 //get an alternative route goal towards the enemy base 671 BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs)); 672 // 673 BotSetTeamStatus(bs); 674 bs->owndecision_time = FloatTime() + 5; 675 } 676 } 677 } 678 return; 679 } 680 // don't just do something wait for the bot team leader to give orders 681 if (BotTeamLeader(bs)) { 682 return; 683 } 684 // if the bot is ordered to do something 685 if ( bs->lastgoal_ltgtype ) { 686 bs->teamgoal_time += 60; 687 } 688 // if the bot decided to do something on it's own and has a last ordered goal 689 if ( !bs->ordered && bs->lastgoal_ltgtype ) { 690 bs->ltgtype = 0; 691 } 692 //if already a CTF or team goal 693 if (bs->ltgtype == LTG_TEAMHELP || 694 bs->ltgtype == LTG_TEAMACCOMPANY || 695 bs->ltgtype == LTG_DEFENDKEYAREA || 696 bs->ltgtype == LTG_GETFLAG || 697 bs->ltgtype == LTG_RUSHBASE || 698 bs->ltgtype == LTG_RETURNFLAG || 699 bs->ltgtype == LTG_CAMPORDER || 700 bs->ltgtype == LTG_PATROL || 701 bs->ltgtype == LTG_GETITEM || 702 bs->ltgtype == LTG_MAKELOVE_UNDER || 703 bs->ltgtype == LTG_MAKELOVE_ONTOP) { 704 return; 705 } 706 // 707 if (BotSetLastOrderedTask(bs)) 708 return; 709 // 710 if (bs->owndecision_time > FloatTime()) 711 return;; 712 //if the bot is roaming 713 if (bs->ctfroam_time > FloatTime()) 714 return; 715 //if the bot has anough aggression to decide what to do 716 if (BotAggression(bs) < 50) 717 return; 718 //set the time to send a message to the team mates 719 bs->teammessage_time = FloatTime() + 2 * random(); 720 // 721 if (bs->teamtaskpreference & (TEAMTP_ATTACKER|TEAMTP_DEFENDER)) { 722 if (bs->teamtaskpreference & TEAMTP_ATTACKER) { 723 l1 = 0.7f; 724 } 725 else { 726 l1 = 0.2f; 727 } 728 l2 = 0.9f; 729 } 730 else { 731 l1 = 0.4f; 732 l2 = 0.7f; 733 } 734 //get the flag or defend the base 735 rnd = random(); 736 if (rnd < l1 && ctf_redflag.areanum && ctf_blueflag.areanum) { 737 bs->decisionmaker = bs->client; 738 bs->ordered = qfalse; 739 bs->ltgtype = LTG_GETFLAG; 740 //set the time the bot will stop getting the flag 741 bs->teamgoal_time = FloatTime() + CTF_GETFLAG_TIME; 742 //get an alternative route goal towards the enemy base 743 BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs)); 744 BotSetTeamStatus(bs); 745 } 746 else if (rnd < l2 && ctf_redflag.areanum && ctf_blueflag.areanum) { 747 bs->decisionmaker = bs->client; 748 bs->ordered = qfalse; 749 // 750 if (BotTeam(bs) == TEAM_RED) memcpy(&bs->teamgoal, &ctf_redflag, sizeof(bot_goal_t)); 751 else memcpy(&bs->teamgoal, &ctf_blueflag, sizeof(bot_goal_t)); 752 //set the ltg type 753 bs->ltgtype = LTG_DEFENDKEYAREA; 754 //set the time the bot stops defending the base 755 bs->teamgoal_time = FloatTime() + TEAM_DEFENDKEYAREA_TIME; 756 bs->defendaway_time = 0; 757 BotSetTeamStatus(bs); 758 } 759 else { 760 bs->ltgtype = 0; 761 //set the time the bot will stop roaming 762 bs->ctfroam_time = FloatTime() + CTF_ROAM_TIME; 763 BotSetTeamStatus(bs); 764 } 765 bs->owndecision_time = FloatTime() + 5; 766 #ifdef DEBUG 767 BotPrintTeamGoal(bs); 768 #endif //DEBUG 769 } 770 771 /* 772 ================== 773 BotCTFRetreatGoals 774 ================== 775 */ 776 void BotCTFRetreatGoals(bot_state_t *bs) { 777 //when carrying a flag in ctf the bot should rush to the base 778 if (BotCTFCarryingFlag(bs)) { 779 //if not already rushing to the base 780 if (bs->ltgtype != LTG_RUSHBASE) { 781 BotRefuseOrder(bs); 782 bs->ltgtype = LTG_RUSHBASE; 783 bs->teamgoal_time = FloatTime() + CTF_RUSHBASE_TIME; 784 bs->rushbaseaway_time = 0; 785 bs->decisionmaker = bs->client; 786 bs->ordered = qfalse; 787 BotSetTeamStatus(bs); 788 } 789 } 790 } 791 792 #ifdef MISSIONPACK 793 /* 794 ================== 795 Bot1FCTFSeekGoals 796 ================== 797 */ 798 void Bot1FCTFSeekGoals(bot_state_t *bs) { 799 aas_entityinfo_t entinfo; 800 float rnd, l1, l2; 801 int c; 802 803 //when carrying a flag in ctf the bot should rush to the base 804 if (Bot1FCTFCarryingFlag(bs)) { 805 //if not already rushing to the base 806 if (bs->ltgtype != LTG_RUSHBASE) { 807 BotRefuseOrder(bs); 808 bs->ltgtype = LTG_RUSHBASE; 809 bs->teamgoal_time = FloatTime() + CTF_RUSHBASE_TIME; 810 bs->rushbaseaway_time = 0; 811 bs->decisionmaker = bs->client; 812 bs->ordered = qfalse; 813 //get an alternative route goal towards the enemy base 814 BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs)); 815 // 816 BotSetTeamStatus(bs); 817 BotVoiceChat(bs, -1, VOICECHAT_IHAVEFLAG); 818 } 819 return; 820 } 821 // if the bot decided to follow someone 822 if ( bs->ltgtype == LTG_TEAMACCOMPANY && !bs->ordered ) { 823 // if the team mate being accompanied no longer carries the flag 824 BotEntityInfo(bs->teammate, &entinfo); 825 if (!EntityCarriesFlag(&entinfo)) { 826 bs->ltgtype = 0; 827 } 828 } 829 //our team has the flag 830 if (bs->neutralflagstatus == 1) { 831 if (bs->owndecision_time < FloatTime()) { 832 // if not already following someone 833 if (bs->ltgtype != LTG_TEAMACCOMPANY) { 834 //if there is a visible team mate flag carrier 835 c = BotTeamFlagCarrierVisible(bs); 836 if (c >= 0) { 837 BotRefuseOrder(bs); 838 //follow the flag carrier 839 bs->decisionmaker = bs->client; 840 bs->ordered = qfalse; 841 //the team mate 842 bs->teammate = c; 843 //last time the team mate was visible 844 bs->teammatevisible_time = FloatTime(); 845 //no message 846 bs->teammessage_time = 0; 847 //no arrive message 848 bs->arrive_time = 1; 849 // 850 BotVoiceChat(bs, bs->teammate, VOICECHAT_ONFOLLOW); 851 //get the team goal time 852 bs->teamgoal_time = FloatTime() + TEAM_ACCOMPANY_TIME; 853 bs->ltgtype = LTG_TEAMACCOMPANY; 854 bs->formation_dist = 3.5 * 32; //3.5 meter 855 BotSetTeamStatus(bs); 856 bs->owndecision_time = FloatTime() + 5; 857 return; 858 } 859 } 860 //if already a CTF or team goal 861 if (bs->ltgtype == LTG_TEAMHELP || 862 bs->ltgtype == LTG_TEAMACCOMPANY || 863 bs->ltgtype == LTG_DEFENDKEYAREA || 864 bs->ltgtype == LTG_GETFLAG || 865 bs->ltgtype == LTG_RUSHBASE || 866 bs->ltgtype == LTG_CAMPORDER || 867 bs->ltgtype == LTG_PATROL || 868 bs->ltgtype == LTG_ATTACKENEMYBASE || 869 bs->ltgtype == LTG_GETITEM || 870 bs->ltgtype == LTG_MAKELOVE_UNDER || 871 bs->ltgtype == LTG_MAKELOVE_ONTOP) { 872 return; 873 } 874 //if not already attacking the enemy base 875 if (bs->ltgtype != LTG_ATTACKENEMYBASE) { 876 BotRefuseOrder(bs); 877 bs->decisionmaker = bs->client; 878 bs->ordered = qfalse; 879 // 880 if (BotTeam(bs) == TEAM_RED) memcpy(&bs->teamgoal, &ctf_blueflag, sizeof(bot_goal_t)); 881 else memcpy(&bs->teamgoal, &ctf_redflag, sizeof(bot_goal_t)); 882 //set the ltg type 883 bs->ltgtype = LTG_ATTACKENEMYBASE; 884 //set the time the bot will stop getting the flag 885 bs->teamgoal_time = FloatTime() + TEAM_ATTACKENEMYBASE_TIME; 886 BotSetTeamStatus(bs); 887 bs->owndecision_time = FloatTime() + 5; 888 } 889 } 890 return; 891 } 892 //enemy team has the flag 893 else if (bs->neutralflagstatus == 2) { 894 if (bs->owndecision_time < FloatTime()) { 895 c = BotEnemyFlagCarrierVisible(bs); 896 if (c >= 0) { 897 //FIXME: attack enemy flag carrier 898 } 899 //if already a CTF or team goal 900 if (bs->ltgtype == LTG_TEAMHELP || 901 bs->ltgtype == LTG_TEAMACCOMPANY || 902 bs->ltgtype == LTG_CAMPORDER || 903 bs->ltgtype == LTG_PATROL || 904 bs->ltgtype == LTG_GETITEM) { 905 return; 906 } 907 // if not already defending the base 908 if (bs->ltgtype != LTG_DEFENDKEYAREA) { 909 BotRefuseOrder(bs); 910 bs->decisionmaker = bs->client; 911 bs->ordered = qfalse; 912 // 913 if (BotTeam(bs) == TEAM_RED) memcpy(&bs->teamgoal, &ctf_redflag, sizeof(bot_goal_t)); 914 else memcpy(&bs->teamgoal, &ctf_blueflag, sizeof(bot_goal_t)); 915 //set the ltg type 916 bs->ltgtype = LTG_DEFENDKEYAREA; 917 //set the time the bot stops defending the base 918 bs->teamgoal_time = FloatTime() + TEAM_DEFENDKEYAREA_TIME; 919 bs->defendaway_time = 0; 920 BotSetTeamStatus(bs); 921 bs->owndecision_time = FloatTime() + 5; 922 } 923 } 924 return; 925 } 926 // don't just do something wait for the bot team leader to give orders 927 if (BotTeamLeader(bs)) { 928 return; 929 } 930 // if the bot is ordered to do something 931 if ( bs->lastgoal_ltgtype ) { 932 bs->teamgoal_time += 60; 933 } 934 // if the bot decided to do something on it's own and has a last ordered goal 935 if ( !bs->ordered && bs->lastgoal_ltgtype ) { 936 bs->ltgtype = 0; 937 } 938 //if already a CTF or team goal 939 if (bs->ltgtype == LTG_TEAMHELP || 940 bs->ltgtype == LTG_TEAMACCOMPANY || 941 bs->ltgtype == LTG_DEFENDKEYAREA || 942 bs->ltgtype == LTG_GETFLAG || 943 bs->ltgtype == LTG_RUSHBASE || 944 bs->ltgtype == LTG_RETURNFLAG || 945 bs->ltgtype == LTG_CAMPORDER || 946 bs->ltgtype == LTG_PATROL || 947 bs->ltgtype == LTG_ATTACKENEMYBASE || 948 bs->ltgtype == LTG_GETITEM || 949 bs->ltgtype == LTG_MAKELOVE_UNDER || 950 bs->ltgtype == LTG_MAKELOVE_ONTOP) { 951 return; 952 } 953 // 954 if (BotSetLastOrderedTask(bs)) 955 return; 956 // 957 if (bs->owndecision_time > FloatTime()) 958 return;; 959 //if the bot is roaming 960 if (bs->ctfroam_time > FloatTime()) 961 return; 962 //if the bot has anough aggression to decide what to do 963 if (BotAggression(bs) < 50) 964 return; 965 //set the time to send a message to the team mates 966 bs->teammessage_time = FloatTime() + 2 * random(); 967 // 968 if (bs->teamtaskpreference & (TEAMTP_ATTACKER|TEAMTP_DEFENDER)) { 969 if (bs->teamtaskpreference & TEAMTP_ATTACKER) { 970 l1 = 0.7f; 971 } 972 else { 973 l1 = 0.2f; 974 } 975 l2 = 0.9f; 976 } 977 else { 978 l1 = 0.4f; 979 l2 = 0.7f; 980 } 981 //get the flag or defend the base 982 rnd = random(); 983 if (rnd < l1 && ctf_neutralflag.areanum) { 984 bs->decisionmaker = bs->client; 985 bs->ordered = qfalse; 986 bs->ltgtype = LTG_GETFLAG; 987 //set the time the bot will stop getting the flag 988 bs->teamgoal_time = FloatTime() + CTF_GETFLAG_TIME; 989 BotSetTeamStatus(bs); 990 } 991 else if (rnd < l2 && ctf_redflag.areanum && ctf_blueflag.areanum) { 992 bs->decisionmaker = bs->client; 993 bs->ordered = qfalse; 994 // 995 if (BotTeam(bs) == TEAM_RED) memcpy(&bs->teamgoal, &ctf_redflag, sizeof(bot_goal_t)); 996 else memcpy(&bs->teamgoal, &ctf_blueflag, sizeof(bot_goal_t)); 997 //set the ltg type 998 bs->ltgtype = LTG_DEFENDKEYAREA; 999 //set the time the bot stops defending the base 1000 bs->teamgoal_time = FloatTime() + TEAM_DEFENDKEYAREA_TIME; 1001 bs->defendaway_time = 0; 1002 BotSetTeamStatus(bs); 1003 } 1004 else { 1005 bs->ltgtype = 0; 1006 //set the time the bot will stop roaming 1007 bs->ctfroam_time = FloatTime() + CTF_ROAM_TIME; 1008 BotSetTeamStatus(bs); 1009 } 1010 bs->owndecision_time = FloatTime() + 5; 1011 #ifdef DEBUG 1012 BotPrintTeamGoal(bs); 1013 #endif //DEBUG 1014 } 1015 1016 /* 1017 ================== 1018 Bot1FCTFRetreatGoals 1019 ================== 1020 */ 1021 void Bot1FCTFRetreatGoals(bot_state_t *bs) { 1022 //when carrying a flag in ctf the bot should rush to the enemy base 1023 if (Bot1FCTFCarryingFlag(bs)) { 1024 //if not already rushing to the base 1025 if (bs->ltgtype != LTG_RUSHBASE) { 1026 BotRefuseOrder(bs); 1027 bs->ltgtype = LTG_RUSHBASE; 1028 bs->teamgoal_time = FloatTime() + CTF_RUSHBASE_TIME; 1029 bs->rushbaseaway_time = 0; 1030 bs->decisionmaker = bs->client; 1031 bs->ordered = qfalse; 1032 //get an alternative route goal towards the enemy base 1033 BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs)); 1034 BotSetTeamStatus(bs); 1035 } 1036 } 1037 } 1038 1039 /* 1040 ================== 1041 BotObeliskSeekGoals 1042 ================== 1043 */ 1044 void BotObeliskSeekGoals(bot_state_t *bs) { 1045 float rnd, l1, l2; 1046 1047 // don't just do something wait for the bot team leader to give orders 1048 if (BotTeamLeader(bs)) { 1049 return; 1050 } 1051 // if the bot is ordered to do something 1052 if ( bs->lastgoal_ltgtype ) { 1053 bs->teamgoal_time += 60; 1054 } 1055 //if already a team goal 1056 if (bs->ltgtype == LTG_TEAMHELP || 1057 bs->ltgtype == LTG_TEAMACCOMPANY || 1058 bs->ltgtype == LTG_DEFENDKEYAREA || 1059 bs->ltgtype == LTG_GETFLAG || 1060 bs->ltgtype == LTG_RUSHBASE || 1061 bs->ltgtype == LTG_RETURNFLAG || 1062 bs->ltgtype == LTG_CAMPORDER || 1063 bs->ltgtype == LTG_PATROL || 1064 bs->ltgtype == LTG_ATTACKENEMYBASE || 1065 bs->ltgtype == LTG_GETITEM || 1066 bs->ltgtype == LTG_MAKELOVE_UNDER || 1067 bs->ltgtype == LTG_MAKELOVE_ONTOP) { 1068 return; 1069 } 1070 // 1071 if (BotSetLastOrderedTask(bs)) 1072 return; 1073 //if the bot is roaming 1074 if (bs->ctfroam_time > FloatTime()) 1075 return; 1076 //if the bot has anough aggression to decide what to do 1077 if (BotAggression(bs) < 50) 1078 return; 1079 //set the time to send a message to the team mates 1080 bs->teammessage_time = FloatTime() + 2 * random(); 1081 // 1082 if (bs->teamtaskpreference & (TEAMTP_ATTACKER|TEAMTP_DEFENDER)) { 1083 if (bs->teamtaskpreference & TEAMTP_ATTACKER) { 1084 l1 = 0.7f; 1085 } 1086 else { 1087 l1 = 0.2f; 1088 } 1089 l2 = 0.9f; 1090 } 1091 else { 1092 l1 = 0.4f; 1093 l2 = 0.7f; 1094 } 1095 //get the flag or defend the base 1096 rnd = random(); 1097 if (rnd < l1 && redobelisk.areanum && blueobelisk.areanum) { 1098 bs->decisionmaker = bs->client; 1099 bs->ordered = qfalse; 1100 // 1101 if (BotTeam(bs) == TEAM_RED) memcpy(&bs->teamgoal, &blueobelisk, sizeof(bot_goal_t)); 1102 else memcpy(&bs->teamgoal, &redobelisk, sizeof(bot_goal_t)); 1103 //set the ltg type 1104 bs->ltgtype = LTG_ATTACKENEMYBASE; 1105 //set the time the bot will stop attacking the enemy base 1106 bs->teamgoal_time = FloatTime() + TEAM_ATTACKENEMYBASE_TIME; 1107 //get an alternate route goal towards the enemy base 1108 BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs)); 1109 BotSetTeamStatus(bs); 1110 } 1111 else if (rnd < l2 && redobelisk.areanum && blueobelisk.areanum) { 1112 bs->decisionmaker = bs->client; 1113 bs->ordered = qfalse; 1114 // 1115 if (BotTeam(bs) == TEAM_RED) memcpy(&bs->teamgoal, &redobelisk, sizeof(bot_goal_t)); 1116 else memcpy(&bs->teamgoal, &blueobelisk, sizeof(bot_goal_t)); 1117 //set the ltg type 1118 bs->ltgtype = LTG_DEFENDKEYAREA; 1119 //set the time the bot stops defending the base 1120 bs->teamgoal_time = FloatTime() + TEAM_DEFENDKEYAREA_TIME; 1121 bs->defendaway_time = 0; 1122 BotSetTeamStatus(bs); 1123 } 1124 else { 1125 bs->ltgtype = 0; 1126 //set the time the bot will stop roaming 1127 bs->ctfroam_time = FloatTime() + CTF_ROAM_TIME; 1128 BotSetTeamStatus(bs); 1129 } 1130 } 1131 1132 /* 1133 ================== 1134 BotGoHarvest 1135 ================== 1136 */ 1137 void BotGoHarvest(bot_state_t *bs) { 1138 // 1139 if (BotTeam(bs) == TEAM_RED) memcpy(&bs->teamgoal, &blueobelisk, sizeof(bot_goal_t)); 1140 else memcpy(&bs->teamgoal, &redobelisk, sizeof(bot_goal_t)); 1141 //set the ltg type 1142 bs->ltgtype = LTG_HARVEST; 1143 //set the time the bot will stop harvesting 1144 bs->teamgoal_time = FloatTime() + TEAM_HARVEST_TIME; 1145 bs->harvestaway_time = 0; 1146 BotSetTeamStatus(bs); 1147 } 1148 1149 /* 1150 ================== 1151 BotObeliskRetreatGoals 1152 ================== 1153 */ 1154 void BotObeliskRetreatGoals(bot_state_t *bs) { 1155 //nothing special 1156 } 1157 1158 /* 1159 ================== 1160 BotHarvesterSeekGoals 1161 ================== 1162 */ 1163 void BotHarvesterSeekGoals(bot_state_t *bs) { 1164 aas_entityinfo_t entinfo; 1165 float rnd, l1, l2; 1166 int c; 1167 1168 //when carrying cubes in harvester the bot should rush to the base 1169 if (BotHarvesterCarryingCubes(bs)) { 1170 //if not already rushing to the base 1171 if (bs->ltgtype != LTG_RUSHBASE) { 1172 BotRefuseOrder(bs); 1173 bs->ltgtype = LTG_RUSHBASE; 1174 bs->teamgoal_time = FloatTime() + CTF_RUSHBASE_TIME; 1175 bs->rushbaseaway_time = 0; 1176 bs->decisionmaker = bs->client; 1177 bs->ordered = qfalse; 1178 //get an alternative route goal towards the enemy base 1179 BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs)); 1180 // 1181 BotSetTeamStatus(bs); 1182 } 1183 return; 1184 } 1185 // don't just do something wait for the bot team leader to give orders 1186 if (BotTeamLeader(bs)) { 1187 return; 1188 } 1189 // if the bot decided to follow someone 1190 if ( bs->ltgtype == LTG_TEAMACCOMPANY && !bs->ordered ) { 1191 // if the team mate being accompanied no longer carries the flag 1192 BotEntityInfo(bs->teammate, &entinfo); 1193 if (!EntityCarriesCubes(&entinfo)) { 1194 bs->ltgtype = 0; 1195 } 1196 } 1197 // if the bot is ordered to do something 1198 if ( bs->lastgoal_ltgtype ) { 1199 bs->teamgoal_time += 60; 1200 } 1201 //if not yet doing something 1202 if (bs->ltgtype == LTG_TEAMHELP || 1203 bs->ltgtype == LTG_TEAMACCOMPANY || 1204 bs->ltgtype == LTG_DEFENDKEYAREA || 1205 bs->ltgtype == LTG_GETFLAG || 1206 bs->ltgtype == LTG_CAMPORDER || 1207 bs->ltgtype == LTG_PATROL || 1208 bs->ltgtype == LTG_ATTACKENEMYBASE || 1209 bs->ltgtype == LTG_HARVEST || 1210 bs->ltgtype == LTG_GETITEM || 1211 bs->ltgtype == LTG_MAKELOVE_UNDER || 1212 bs->ltgtype == LTG_MAKELOVE_ONTOP) { 1213 return; 1214 } 1215 // 1216 if (BotSetLastOrderedTask(bs)) 1217 return; 1218 //if the bot is roaming 1219 if (bs->ctfroam_time > FloatTime()) 1220 return; 1221 //if the bot has anough aggression to decide what to do 1222 if (BotAggression(bs) < 50) 1223 return; 1224 //set the time to send a message to the team mates 1225 bs->teammessage_time = FloatTime() + 2 * random(); 1226 // 1227 c = BotEnemyCubeCarrierVisible(bs); 1228 if (c >= 0) { 1229 //FIXME: attack enemy cube carrier 1230 } 1231 if (bs->ltgtype != LTG_TEAMACCOMPANY) { 1232 //if there is a visible team mate carrying cubes 1233 c = BotTeamCubeCarrierVisible(bs); 1234 if (c >= 0) { 1235 //follow the team mate carrying cubes 1236 bs->decisionmaker = bs->client; 1237 bs->ordered = qfalse; 1238 //the team mate 1239 bs->teammate = c; 1240 //last time the team mate was visible 1241 bs->teammatevisible_time = FloatTime(); 1242 //no message 1243 bs->teammessage_time = 0; 1244 //no arrive message 1245 bs->arrive_time = 1; 1246 // 1247 BotVoiceChat(bs, bs->teammate, VOICECHAT_ONFOLLOW); 1248 //get the team goal time 1249 bs->teamgoal_time = FloatTime() + TEAM_ACCOMPANY_TIME; 1250 bs->ltgtype = LTG_TEAMACCOMPANY; 1251 bs->formation_dist = 3.5 * 32; //3.5 meter 1252 BotSetTeamStatus(bs); 1253 return; 1254 } 1255 } 1256 // 1257 if (bs->teamtaskpreference & (TEAMTP_ATTACKER|TEAMTP_DEFENDER)) { 1258 if (bs->teamtaskpreference & TEAMTP_ATTACKER) { 1259 l1 = 0.7f; 1260 } 1261 else { 1262 l1 = 0.2f; 1263 } 1264 l2 = 0.9f; 1265 } 1266 else { 1267 l1 = 0.4f; 1268 l2 = 0.7f; 1269 } 1270 // 1271 rnd = random(); 1272 if (rnd < l1 && redobelisk.areanum && blueobelisk.areanum) { 1273 bs->decisionmaker = bs->client; 1274 bs->ordered = qfalse; 1275 BotGoHarvest(bs); 1276 } 1277 else if (rnd < l2 && redobelisk.areanum && blueobelisk.areanum) { 1278 bs->decisionmaker = bs->client; 1279 bs->ordered = qfalse; 1280 // 1281 if (BotTeam(bs) == TEAM_RED) memcpy(&bs->teamgoal, &redobelisk, sizeof(bot_goal_t)); 1282 else memcpy(&bs->teamgoal, &blueobelisk, sizeof(bot_goal_t)); 1283 //set the ltg type 1284 bs->ltgtype = LTG_DEFENDKEYAREA; 1285 //set the time the bot stops defending the base 1286 bs->teamgoal_time = FloatTime() + TEAM_DEFENDKEYAREA_TIME; 1287 bs->defendaway_time = 0; 1288 BotSetTeamStatus(bs); 1289 } 1290 else { 1291 bs->ltgtype = 0; 1292 //set the time the bot will stop roaming 1293 bs->ctfroam_time = FloatTime() + CTF_ROAM_TIME; 1294 BotSetTeamStatus(bs); 1295 } 1296 } 1297 1298 /* 1299 ================== 1300 BotHarvesterRetreatGoals 1301 ================== 1302 */ 1303 void BotHarvesterRetreatGoals(bot_state_t *bs) { 1304 //when carrying cubes in harvester the bot should rush to the base 1305 if (BotHarvesterCarryingCubes(bs)) { 1306 //if not already rushing to the base 1307 if (bs->ltgtype != LTG_RUSHBASE) { 1308 BotRefuseOrder(bs); 1309 bs->ltgtype = LTG_RUSHBASE; 1310 bs->teamgoal_time = FloatTime() + CTF_RUSHBASE_TIME; 1311 bs->rushbaseaway_time = 0; 1312 bs->decisionmaker = bs->client; 1313 bs->ordered = qfalse; 1314 BotSetTeamStatus(bs); 1315 } 1316 return; 1317 } 1318 } 1319 #endif 1320 1321 /* 1322 ================== 1323 BotTeamGoals 1324 ================== 1325 */ 1326 void BotTeamGoals(bot_state_t *bs, int retreat) { 1327 1328 if ( retreat ) { 1329 if (gametype == GT_CTF) { 1330 BotCTFRetreatGoals(bs); 1331 } 1332 #ifdef MISSIONPACK 1333 else if (gametype == GT_1FCTF) { 1334 Bot1FCTFRetreatGoals(bs); 1335 } 1336 else if (gametype == GT_OBELISK) { 1337 BotObeliskRetreatGoals(bs); 1338 } 1339 else if (gametype == GT_HARVESTER) { 1340 BotHarvesterRetreatGoals(bs); 1341 } 1342 #endif 1343 } 1344 else { 1345 if (gametype == GT_CTF) { 1346 //decide what to do in CTF mode 1347 BotCTFSeekGoals(bs); 1348 } 1349 #ifdef MISSIONPACK 1350 else if (gametype == GT_1FCTF) { 1351 Bot1FCTFSeekGoals(bs); 1352 } 1353 else if (gametype == GT_OBELISK) { 1354 BotObeliskSeekGoals(bs); 1355 } 1356 else if (gametype == GT_HARVESTER) { 1357 BotHarvesterSeekGoals(bs); 1358 } 1359 #endif 1360 } 1361 // reset the order time which is used to see if 1362 // we decided to refuse an order 1363 bs->order_time = 0; 1364 } 1365 1366 /* 1367 ================== 1368 BotPointAreaNum 1369 ================== 1370 */ 1371 int BotPointAreaNum(vec3_t origin) { 1372 int areanum, numareas, areas[10]; 1373 vec3_t end; 1374 1375 areanum = trap_AAS_PointAreaNum(origin); 1376 if (areanum) return areanum; 1377 VectorCopy(origin, end); 1378 end[2] += 10; 1379 numareas = trap_AAS_TraceAreas(origin, end, areas, NULL, 10); 1380 if (numareas > 0) return areas[0]; 1381 return 0; 1382 } 1383 1384 /* 1385 ================== 1386 ClientName 1387 ================== 1388 */ 1389 char *ClientName(int client, char *name, int size) { 1390 char buf[MAX_INFO_STRING]; 1391 1392 if (client < 0 || client >= MAX_CLIENTS) { 1393 BotAI_Print(PRT_ERROR, "ClientName: client out of range\n"); 1394 return "[client out of range]"; 1395 } 1396 trap_GetConfigstring(CS_PLAYERS+client, buf, sizeof(buf)); 1397 strncpy(name, Info_ValueForKey(buf, "n"), size-1); 1398 name[size-1] = '\0'; 1399 Q_CleanStr( name ); 1400 return name; 1401 } 1402 1403 /* 1404 ================== 1405 ClientSkin 1406 ================== 1407 */ 1408 char *ClientSkin(int client, char *skin, int size) { 1409 char buf[MAX_INFO_STRING]; 1410 1411 if (client < 0 || client >= MAX_CLIENTS) { 1412 BotAI_Print(PRT_ERROR, "ClientSkin: client out of range\n"); 1413 return "[client out of range]"; 1414 } 1415 trap_GetConfigstring(CS_PLAYERS+client, buf, sizeof(buf)); 1416 strncpy(skin, Info_ValueForKey(buf, "model"), size-1); 1417 skin[size-1] = '\0'; 1418 return skin; 1419 } 1420 1421 /* 1422 ================== 1423 ClientFromName 1424 ================== 1425 */ 1426 int ClientFromName(char *name) { 1427 int i; 1428 char buf[MAX_INFO_STRING]; 1429 static int maxclients; 1430 1431 if (!maxclients) 1432 maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); 1433 for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { 1434 trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); 1435 Q_CleanStr( buf ); 1436 if (!Q_stricmp(Info_ValueForKey(buf, "n"), name)) return i; 1437 } 1438 return -1; 1439 } 1440 1441 /* 1442 ================== 1443 ClientOnSameTeamFromName 1444 ================== 1445 */ 1446 int ClientOnSameTeamFromName(bot_state_t *bs, char *name) { 1447 int i; 1448 char buf[MAX_INFO_STRING]; 1449 static int maxclients; 1450 1451 if (!maxclients) 1452 maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); 1453 for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { 1454 if (!BotSameTeam(bs, i)) 1455 continue; 1456 trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); 1457 Q_CleanStr( buf ); 1458 if (!Q_stricmp(Info_ValueForKey(buf, "n"), name)) return i; 1459 } 1460 return -1; 1461 } 1462 1463 /* 1464 ================== 1465 stristr 1466 ================== 1467 */ 1468 char *stristr(char *str, char *charset) { 1469 int i; 1470 1471 while(*str) { 1472 for (i = 0; charset[i] && str[i]; i++) { 1473 if (toupper(charset[i]) != toupper(str[i])) break; 1474 } 1475 if (!charset[i]) return str; 1476 str++; 1477 } 1478 return NULL; 1479 } 1480 1481 /* 1482 ================== 1483 EasyClientName 1484 ================== 1485 */ 1486 char *EasyClientName(int client, char *buf, int size) { 1487 int i; 1488 char *str1, *str2, *ptr, c; 1489 char name[128]; 1490 1491 strcpy(name, ClientName(client, name, sizeof(name))); 1492 for (i = 0; name[i]; i++) name[i] &= 127; 1493 //remove all spaces 1494 for (ptr = strstr(name, " "); ptr; ptr = strstr(name, " ")) { 1495 memmove(ptr, ptr+1, strlen(ptr+1)+1); 1496 } 1497 //check for [x] and ]x[ clan names 1498 str1 = strstr(name, "["); 1499 str2 = strstr(name, "]"); 1500 if (str1 && str2) { 1501 if (str2 > str1) memmove(str1, str2+1, strlen(str2+1)+1); 1502 else memmove(str2, str1+1, strlen(str1+1)+1); 1503 } 1504 //remove Mr prefix 1505 if ((name[0] == 'm' || name[0] == 'M') && 1506 (name[1] == 'r' || name[1] == 'R')) { 1507 memmove(name, name+2, strlen(name+2)+1); 1508 } 1509 //only allow lower case alphabet characters 1510 ptr = name; 1511 while(*ptr) { 1512 c = *ptr; 1513 if ((c >= 'a' && c <= 'z') || 1514 (c >= '0' && c <= '9') || c == '_') { 1515 ptr++; 1516 } 1517 else if (c >= 'A' && c <= 'Z') { 1518 *ptr += 'a' - 'A'; 1519 ptr++; 1520 } 1521 else { 1522 memmove(ptr, ptr+1, strlen(ptr + 1)+1); 1523 } 1524 } 1525 strncpy(buf, name, size-1); 1526 buf[size-1] = '\0'; 1527 return buf; 1528 } 1529 1530 /* 1531 ================== 1532 BotSynonymContext 1533 ================== 1534 */ 1535 int BotSynonymContext(bot_state_t *bs) { 1536 int context; 1537 1538 context = CONTEXT_NORMAL|CONTEXT_NEARBYITEM|CONTEXT_NAMES; 1539 // 1540 if (gametype == GT_CTF 1541 #ifdef MISSIONPACK 1542 || gametype == GT_1FCTF 1543 #endif 1544 ) { 1545 if (BotTeam(bs) == TEAM_RED) context |= CONTEXT_CTFREDTEAM; 1546 else context |= CONTEXT_CTFBLUETEAM; 1547 } 1548 #ifdef MISSIONPACK 1549 else if (gametype == GT_OBELISK) { 1550 if (BotTeam(bs) == TEAM_RED) context |= CONTEXT_OBELISKREDTEAM; 1551 else context |= CONTEXT_OBELISKBLUETEAM; 1552 } 1553 else if (gametype == GT_HARVESTER) { 1554 if (BotTeam(bs) == TEAM_RED) context |= CONTEXT_HARVESTERREDTEAM; 1555 else context |= CONTEXT_HARVESTERBLUETEAM; 1556 } 1557 #endif 1558 return context; 1559 } 1560 1561 /* 1562 ================== 1563 BotChooseWeapon 1564 ================== 1565 */ 1566 void BotChooseWeapon(bot_state_t *bs) { 1567 int newweaponnum; 1568 1569 if (bs->cur_ps.weaponstate == WEAPON_RAISING || 1570 bs->cur_ps.weaponstate == WEAPON_DROPPING) { 1571 trap_EA_SelectWeapon(bs->client, bs->weaponnum); 1572 } 1573 else { 1574 newweaponnum = trap_BotChooseBestFightWeapon(bs->ws, bs->inventory); 1575 if (bs->weaponnum != newweaponnum) bs->weaponchange_time = FloatTime(); 1576 bs->weaponnum = newweaponnum; 1577 //BotAI_Print(PRT_MESSAGE, "bs->weaponnum = %d\n", bs->weaponnum); 1578 trap_EA_SelectWeapon(bs->client, bs->weaponnum); 1579 } 1580 } 1581 1582 /* 1583 ================== 1584 BotSetupForMovement 1585 ================== 1586 */ 1587 void BotSetupForMovement(bot_state_t *bs) { 1588 bot_initmove_t initmove; 1589 1590 memset(&initmove, 0, sizeof(bot_initmove_t)); 1591 VectorCopy(bs->cur_ps.origin, initmove.origin); 1592 VectorCopy(bs->cur_ps.velocity, initmove.velocity); 1593 VectorClear(initmove.viewoffset); 1594 initmove.viewoffset[2] += bs->cur_ps.viewheight; 1595 initmove.entitynum = bs->entitynum; 1596 initmove.client = bs->client; 1597 initmove.thinktime = bs->thinktime; 1598 //set the onground flag 1599 if (bs->cur_ps.groundEntityNum != ENTITYNUM_NONE) initmove.or_moveflags |= MFL_ONGROUND; 1600 //set the teleported flag 1601 if ((bs->cur_ps.pm_flags & PMF_TIME_KNOCKBACK) && (bs->cur_ps.pm_time > 0)) { 1602 initmove.or_moveflags |= MFL_TELEPORTED; 1603 } 1604 //set the waterjump flag 1605 if ((bs->cur_ps.pm_flags & PMF_TIME_WATERJUMP) && (bs->cur_ps.pm_time > 0)) { 1606 initmove.or_moveflags |= MFL_WATERJUMP; 1607 } 1608 //set presence type 1609 if (bs->cur_ps.pm_flags & PMF_DUCKED) initmove.presencetype = PRESENCE_CROUCH; 1610 else initmove.presencetype = PRESENCE_NORMAL; 1611 // 1612 if (bs->walker > 0.5) initmove.or_moveflags |= MFL_WALK; 1613 // 1614 VectorCopy(bs->viewangles, initmove.viewangles); 1615 // 1616 trap_BotInitMoveState(bs->ms, &initmove); 1617 } 1618 1619 /* 1620 ================== 1621 BotCheckItemPickup 1622 ================== 1623 */ 1624 void BotCheckItemPickup(bot_state_t *bs, int *oldinventory) { 1625 #ifdef MISSIONPACK 1626 int offence, leader; 1627 1628 if (gametype <= GT_TEAM) 1629 return; 1630 1631 offence = -1; 1632 // go into offence if picked up the kamikaze or invulnerability 1633 if (!oldinventory[INVENTORY_KAMIKAZE] && bs->inventory[INVENTORY_KAMIKAZE] >= 1) { 1634 offence = qtrue; 1635 } 1636 if (!oldinventory[INVENTORY_INVULNERABILITY] && bs->inventory[INVENTORY_INVULNERABILITY] >= 1) { 1637 offence = qtrue; 1638 } 1639 // if not already wearing the kamikaze or invulnerability 1640 if (!bs->inventory[INVENTORY_KAMIKAZE] && !bs->inventory[INVENTORY_INVULNERABILITY]) { 1641 if (!oldinventory[INVENTORY_SCOUT] && bs->inventory[INVENTORY_SCOUT] >= 1) { 1642 offence = qtrue; 1643 } 1644 if (!oldinventory[INVENTORY_GUARD] && bs->inventory[INVENTORY_GUARD] >= 1) { 1645 offence = qtrue; 1646 } 1647 if (!oldinventory[INVENTORY_DOUBLER] && bs->inventory[INVENTORY_DOUBLER] >= 1) { 1648 offence = qfalse; 1649 } 1650 if (!oldinventory[INVENTORY_AMMOREGEN] && bs->inventory[INVENTORY_AMMOREGEN] >= 1) { 1651 offence = qfalse; 1652 } 1653 } 1654 1655 if (offence >= 0) { 1656 leader = ClientFromName(bs->teamleader); 1657 if (offence) { 1658 if (!(bs->teamtaskpreference & TEAMTP_ATTACKER)) { 1659 // if we have a bot team leader 1660 if (BotTeamLeader(bs)) { 1661 // tell the leader we want to be on offence 1662 BotVoiceChat(bs, leader, VOICECHAT_WANTONOFFENSE); 1663 //BotAI_BotInitialChat(bs, "wantoffence", NULL); 1664 //trap_BotEnterChat(bs->cs, leader, CHAT_TELL); 1665 } 1666 else if (g_spSkill.integer <= 3) { 1667 if ( bs->ltgtype != LTG_GETFLAG && 1668 bs->ltgtype != LTG_ATTACKENEMYBASE && 1669 bs->ltgtype != LTG_HARVEST ) { 1670 // 1671 if ((gametype != GT_CTF || (bs->redflagstatus == 0 && bs->blueflagstatus == 0)) && 1672 (gametype != GT_1FCTF || bs->neutralflagstatus == 0) ) { 1673 // tell the leader we want to be on offence 1674 BotVoiceChat(bs, leader, VOICECHAT_WANTONOFFENSE); 1675 //BotAI_BotInitialChat(bs, "wantoffence", NULL); 1676 //trap_BotEnterChat(bs->cs, leader, CHAT_TELL); 1677 } 1678 } 1679 bs->teamtaskpreference |= TEAMTP_ATTACKER; 1680 } 1681 } 1682 bs->teamtaskpreference &= ~TEAMTP_DEFENDER; 1683 } 1684 else { 1685 if (!(bs->teamtaskpreference & TEAMTP_DEFENDER)) { 1686 // if we have a bot team leader 1687 if (BotTeamLeader(bs)) { 1688 // tell the leader we want to be on defense 1689 BotVoiceChat(bs, -1, VOICECHAT_WANTONDEFENSE); 1690 //BotAI_BotInitialChat(bs, "wantdefence", NULL); 1691 //trap_BotEnterChat(bs->cs, leader, CHAT_TELL); 1692 } 1693 else if (g_spSkill.integer <= 3) { 1694 if ( bs->ltgtype != LTG_DEFENDKEYAREA ) { 1695 // 1696 if ((gametype != GT_CTF || (bs->redflagstatus == 0 && bs->blueflagstatus == 0)) && 1697 (gametype != GT_1FCTF || bs->neutralflagstatus == 0) ) { 1698 // tell the leader we want to be on defense 1699 BotVoiceChat(bs, -1, VOICECHAT_WANTONDEFENSE); 1700 //BotAI_BotInitialChat(bs, "wantdefence", NULL); 1701 //trap_BotEnterChat(bs->cs, leader, CHAT_TELL); 1702 } 1703 } 1704 } 1705 bs->teamtaskpreference |= TEAMTP_DEFENDER; 1706 } 1707 bs->teamtaskpreference &= ~TEAMTP_ATTACKER; 1708 } 1709 } 1710 #endif 1711 } 1712 1713 /* 1714 ================== 1715 BotUpdateInventory 1716 ================== 1717 */ 1718 void BotUpdateInventory(bot_state_t *bs) { 1719 int oldinventory[MAX_ITEMS]; 1720 1721 memcpy(oldinventory, bs->inventory, sizeof(oldinventory)); 1722 //armor 1723 bs->inventory[INVENTORY_ARMOR] = bs->cur_ps.stats[STAT_ARMOR]; 1724 //weapons 1725 bs->inventory[INVENTORY_GAUNTLET] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_GAUNTLET)) != 0; 1726 bs->inventory[INVENTORY_SHOTGUN] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_SHOTGUN)) != 0; 1727 bs->inventory[INVENTORY_MACHINEGUN] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_MACHINEGUN)) != 0; 1728 bs->inventory[INVENTORY_GRENADELAUNCHER] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_GRENADE_LAUNCHER)) != 0; 1729 bs->inventory[INVENTORY_ROCKETLAUNCHER] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_ROCKET_LAUNCHER)) != 0; 1730 bs->inventory[INVENTORY_LIGHTNING] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_LIGHTNING)) != 0; 1731 bs->inventory[INVENTORY_RAILGUN] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_RAILGUN)) != 0; 1732 bs->inventory[INVENTORY_PLASMAGUN] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_PLASMAGUN)) != 0; 1733 bs->inventory[INVENTORY_BFG10K] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_BFG)) != 0; 1734 bs->inventory[INVENTORY_GRAPPLINGHOOK] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_GRAPPLING_HOOK)) != 0; 1735 #ifdef MISSIONPACK 1736 bs->inventory[INVENTORY_NAILGUN] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_NAILGUN)) != 0;; 1737 bs->inventory[INVENTORY_PROXLAUNCHER] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_PROX_LAUNCHER)) != 0;; 1738 bs->inventory[INVENTORY_CHAINGUN] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_CHAINGUN)) != 0;; 1739 #endif 1740 //ammo 1741 bs->inventory[INVENTORY_SHELLS] = bs->cur_ps.ammo[WP_SHOTGUN]; 1742 bs->inventory[INVENTORY_BULLETS] = bs->cur_ps.ammo[WP_MACHINEGUN]; 1743 bs->inventory[INVENTORY_GRENADES] = bs->cur_ps.ammo[WP_GRENADE_LAUNCHER]; 1744 bs->inventory[INVENTORY_CELLS] = bs->cur_ps.ammo[WP_PLASMAGUN]; 1745 bs->inventory[INVENTORY_LIGHTNINGAMMO] = bs->cur_ps.ammo[WP_LIGHTNING]; 1746 bs->inventory[INVENTORY_ROCKETS] = bs->cur_ps.ammo[WP_ROCKET_LAUNCHER]; 1747 bs->inventory[INVENTORY_SLUGS] = bs->cur_ps.ammo[WP_RAILGUN]; 1748 bs->inventory[INVENTORY_BFGAMMO] = bs->cur_ps.ammo[WP_BFG]; 1749 #ifdef MISSIONPACK 1750 bs->inventory[INVENTORY_NAILS] = bs->cur_ps.ammo[WP_NAILGUN]; 1751 bs->inventory[INVENTORY_MINES] = bs->cur_ps.ammo[WP_PROX_LAUNCHER]; 1752 bs->inventory[INVENTORY_BELT] = bs->cur_ps.ammo[WP_CHAINGUN]; 1753 #endif 1754 //powerups 1755 bs->inventory[INVENTORY_HEALTH] = bs->cur_ps.stats[STAT_HEALTH]; 1756 bs->inventory[INVENTORY_TELEPORTER] = bs->cur_ps.stats[STAT_HOLDABLE_ITEM] == MODELINDEX_TELEPORTER; 1757 bs->inventory[INVENTORY_MEDKIT] = bs->cur_ps.stats[STAT_HOLDABLE_ITEM] == MODELINDEX_MEDKIT; 1758 #ifdef MISSIONPACK 1759 bs->inventory[INVENTORY_KAMIKAZE] = bs->cur_ps.stats[STAT_HOLDABLE_ITEM] == MODELINDEX_KAMIKAZE; 1760 bs->inventory[INVENTORY_PORTAL] = bs->cur_ps.stats[STAT_HOLDABLE_ITEM] == MODELINDEX_PORTAL; 1761 bs->inventory[INVENTORY_INVULNERABILITY] = bs->cur_ps.stats[STAT_HOLDABLE_ITEM] == MODELINDEX_INVULNERABILITY; 1762 #endif 1763 bs->inventory[INVENTORY_QUAD] = bs->cur_ps.powerups[PW_QUAD] != 0; 1764 bs->inventory[INVENTORY_ENVIRONMENTSUIT] = bs->cur_ps.powerups[PW_BATTLESUIT] != 0; 1765 bs->inventory[INVENTORY_HASTE] = bs->cur_ps.powerups[PW_HASTE] != 0; 1766 bs->inventory[INVENTORY_INVISIBILITY] = bs->cur_ps.powerups[PW_INVIS] != 0; 1767 bs->inventory[INVENTORY_REGEN] = bs->cur_ps.powerups[PW_REGEN] != 0; 1768 bs->inventory[INVENTORY_FLIGHT] = bs->cur_ps.powerups[PW_FLIGHT] != 0; 1769 #ifdef MISSIONPACK 1770 bs->inventory[INVENTORY_SCOUT] = bs->cur_ps.stats[STAT_PERSISTANT_POWERUP] == MODELINDEX_SCOUT; 1771 bs->inventory[INVENTORY_GUARD] = bs->cur_ps.stats[STAT_PERSISTANT_POWERUP] == MODELINDEX_GUARD; 1772 bs->inventory[INVENTORY_DOUBLER] = bs->cur_ps.stats[STAT_PERSISTANT_POWERUP] == MODELINDEX_DOUBLER; 1773 bs->inventory[INVENTORY_AMMOREGEN] = bs->cur_ps.stats[STAT_PERSISTANT_POWERUP] == MODELINDEX_AMMOREGEN; 1774 #endif 1775 bs->inventory[INVENTORY_REDFLAG] = bs->cur_ps.powerups[PW_REDFLAG] != 0; 1776 bs->inventory[INVENTORY_BLUEFLAG] = bs->cur_ps.powerups[PW_BLUEFLAG] != 0; 1777 #ifdef MISSIONPACK 1778 bs->inventory[INVENTORY_NEUTRALFLAG] = bs->cur_ps.powerups[PW_NEUTRALFLAG] != 0; 1779 if (BotTeam(bs) == TEAM_RED) { 1780 bs->inventory[INVENTORY_REDCUBE] = bs->cur_ps.generic1; 1781 bs->inventory[INVENTORY_BLUECUBE] = 0; 1782 } 1783 else { 1784 bs->inventory[INVENTORY_REDCUBE] = 0; 1785 bs->inventory[INVENTORY_BLUECUBE] = bs->cur_ps.generic1; 1786 } 1787 #endif 1788 BotCheckItemPickup(bs, oldinventory); 1789 } 1790 1791 /* 1792 ================== 1793 BotUpdateBattleInventory 1794 ================== 1795 */ 1796 void BotUpdateBattleInventory(bot_state_t *bs, int enemy) { 1797 vec3_t dir; 1798 aas_entityinfo_t entinfo; 1799 1800 BotEntityInfo(enemy, &entinfo); 1801 VectorSubtract(entinfo.origin, bs->origin, dir); 1802 bs->inventory[ENEMY_HEIGHT] = (int) dir[2]; 1803 dir[2] = 0; 1804 bs->inventory[ENEMY_HORIZONTAL_DIST] = (int) VectorLength(dir); 1805 //FIXME: add num visible enemies and num visible team mates to the inventory 1806 } 1807 1808 #ifdef MISSIONPACK 1809 /* 1810 ================== 1811 BotUseKamikaze 1812 ================== 1813 */ 1814 #define KAMIKAZE_DIST 1024 1815 1816 void BotUseKamikaze(bot_state_t *bs) { 1817 int c, teammates, enemies; 1818 aas_entityinfo_t entinfo; 1819 vec3_t dir, target; 1820 bot_goal_t *goal; 1821 bsp_trace_t trace; 1822 1823 //if the bot has no kamikaze 1824 if (bs->inventory[INVENTORY_KAMIKAZE] <= 0) 1825 return; 1826 if (bs->kamikaze_time > FloatTime()) 1827 return; 1828 bs->kamikaze_time = FloatTime() + 0.2; 1829 if (gametype == GT_CTF) { 1830 //never use kamikaze if the team flag carrier is visible 1831 if (BotCTFCarryingFlag(bs)) 1832 return; 1833 c = BotTeamFlagCarrierVisible(bs); 1834 if (c >= 0) { 1835 BotEntityInfo(c, &entinfo); 1836 VectorSubtract(entinfo.origin, bs->origin, dir); 1837 if (VectorLengthSquared(dir) < Square(KAMIKAZE_DIST)) 1838 return; 1839 } 1840 c = BotEnemyFlagCarrierVisible(bs); 1841 if (c >= 0) { 1842 BotEntityInfo(c, &entinfo); 1843 VectorSubtract(entinfo.origin, bs->origin, dir); 1844 if (VectorLengthSquared(dir) < Square(KAMIKAZE_DIST)) { 1845 trap_EA_Use(bs->client); 1846 return; 1847 } 1848 } 1849 } 1850 else if (gametype == GT_1FCTF) { 1851 //never use kamikaze if the team flag carrier is visible 1852 if (Bot1FCTFCarryingFlag(bs)) 1853 return; 1854 c = BotTeamFlagCarrierVisible(bs); 1855 if (c >= 0) { 1856 BotEntityInfo(c, &entinfo); 1857 VectorSubtract(entinfo.origin, bs->origin, dir); 1858 if (VectorLengthSquared(dir) < Square(KAMIKAZE_DIST)) 1859 return; 1860 } 1861 c = BotEnemyFlagCarrierVisible(bs); 1862 if (c >= 0) { 1863 BotEntityInfo(c, &entinfo); 1864 VectorSubtract(entinfo.origin, bs->origin, dir); 1865 if (VectorLengthSquared(dir) < Square(KAMIKAZE_DIST)) { 1866 trap_EA_Use(bs->client); 1867 return; 1868 } 1869 } 1870 } 1871 else if (gametype == GT_OBELISK) { 1872 switch(BotTeam(bs)) { 1873 case TEAM_RED: goal = &blueobelisk; break; 1874 default: goal = &redobelisk; break; 1875 } 1876 //if the obelisk is visible 1877 VectorCopy(goal->origin, target); 1878 target[2] += 1; 1879 VectorSubtract(bs->origin, target, dir); 1880 if (VectorLengthSquared(dir) < Square(KAMIKAZE_DIST * 0.9)) { 1881 BotAI_Trace(&trace, bs->eye, NULL, NULL, target, bs->client, CONTENTS_SOLID); 1882 if (trace.fraction >= 1 || trace.ent == goal->entitynum) { 1883 trap_EA_Use(bs->client); 1884 return; 1885 } 1886 } 1887 } 1888 else if (gametype == GT_HARVESTER) { 1889 // 1890 if (BotHarvesterCarryingCubes(bs)) 1891 return; 1892 //never use kamikaze if a team mate carrying cubes is visible 1893 c = BotTeamCubeCarrierVisible(bs); 1894 if (c >= 0) { 1895 BotEntityInfo(c, &entinfo); 1896 VectorSubtract(entinfo.origin, bs->origin, dir); 1897 if (VectorLengthSquared(dir) < Square(KAMIKAZE_DIST)) 1898 return; 1899 } 1900 c = BotEnemyCubeCarrierVisible(bs); 1901 if (c >= 0) { 1902 BotEntityInfo(c, &entinfo); 1903 VectorSubtract(entinfo.origin, bs->origin, dir); 1904 if (VectorLengthSquared(dir) < Square(KAMIKAZE_DIST)) { 1905 trap_EA_Use(bs->client); 1906 return; 1907 } 1908 } 1909 } 1910 // 1911 BotVisibleTeamMatesAndEnemies(bs, &teammates, &enemies, KAMIKAZE_DIST); 1912 // 1913 if (enemies > 2 && enemies > teammates+1) { 1914 trap_EA_Use(bs->client); 1915 return; 1916 } 1917 } 1918 1919 /* 1920 ================== 1921 BotUseInvulnerability 1922 ================== 1923 */ 1924 void BotUseInvulnerability(bot_state_t *bs) { 1925 int c; 1926 vec3_t dir, target; 1927 bot_goal_t *goal; 1928 bsp_trace_t trace; 1929 1930 //if the bot has no invulnerability 1931 if (bs->inventory[INVENTORY_INVULNERABILITY] <= 0) 1932 return; 1933 if (bs->invulnerability_time > FloatTime()) 1934 return; 1935 bs->invulnerability_time = FloatTime() + 0.2; 1936 if (gametype == GT_CTF) { 1937 //never use kamikaze if the team flag carrier is visible 1938 if (BotCTFCarryingFlag(bs)) 1939 return; 1940 c = BotEnemyFlagCarrierVisible(bs); 1941 if (c >= 0) 1942 return; 1943 //if near enemy flag and the flag is visible 1944 switch(BotTeam(bs)) { 1945 case TEAM_RED: goal = &ctf_blueflag; break; 1946 default: goal = &ctf_redflag; break; 1947 } 1948 //if the obelisk is visible 1949 VectorCopy(goal->origin, target); 1950 target[2] += 1; 1951 VectorSubtract(bs->origin, target, dir); 1952 if (VectorLengthSquared(dir) < Square(200)) { 1953 BotAI_Trace(&trace, bs->eye, NULL, NULL, target, bs->client, CONTENTS_SOLID); 1954 if (trace.fraction >= 1 || trace.ent == goal->entitynum) { 1955 trap_EA_Use(bs->client); 1956 return; 1957 } 1958 } 1959 } 1960 else if (gametype == GT_1FCTF) { 1961 //never use kamikaze if the team flag carrier is visible 1962 if (Bot1FCTFCarryingFlag(bs)) 1963 return; 1964 c = BotEnemyFlagCarrierVisible(bs); 1965 if (c >= 0) 1966 return; 1967 //if near enemy flag and the flag is visible 1968 switch(BotTeam(bs)) { 1969 case TEAM_RED: goal = &ctf_blueflag; break; 1970 default: goal = &ctf_redflag; break; 1971 } 1972 //if the obelisk is visible 1973 VectorCopy(goal->origin, target); 1974 target[2] += 1; 1975 VectorSubtract(bs->origin, target, dir); 1976 if (VectorLengthSquared(dir) < Square(200)) { 1977 BotAI_Trace(&trace, bs->eye, NULL, NULL, target, bs->client, CONTENTS_SOLID); 1978 if (trace.fraction >= 1 || trace.ent == goal->entitynum) { 1979 trap_EA_Use(bs->client); 1980 return; 1981 } 1982 } 1983 } 1984 else if (gametype == GT_OBELISK) { 1985 switch(BotTeam(bs)) { 1986 case TEAM_RED: goal = &blueobelisk; break; 1987 default: goal = &redobelisk; break; 1988 } 1989 //if the obelisk is visible 1990 VectorCopy(goal->origin, target); 1991 target[2] += 1; 1992 VectorSubtract(bs->origin, target, dir); 1993 if (VectorLengthSquared(dir) < Square(300)) { 1994 BotAI_Trace(&trace, bs->eye, NULL, NULL, target, bs->client, CONTENTS_SOLID); 1995 if (trace.fraction >= 1 || trace.ent == goal->entitynum) { 1996 trap_EA_Use(bs->client); 1997 return; 1998 } 1999 } 2000 } 2001 else if (gametype == GT_HARVESTER) { 2002 // 2003 if (BotHarvesterCarryingCubes(bs)) 2004 return; 2005 c = BotEnemyCubeCarrierVisible(bs); 2006 if (c >= 0) 2007 return; 2008 //if near enemy base and enemy base is visible 2009 switch(BotTeam(bs)) { 2010 case TEAM_RED: goal = &blueobelisk; break; 2011 default: goal = &redobelisk; break; 2012 } 2013 //if the obelisk is visible 2014 VectorCopy(goal->origin, target); 2015 target[2] += 1; 2016 VectorSubtract(bs->origin, target, dir); 2017 if (VectorLengthSquared(dir) < Square(200)) { 2018 BotAI_Trace(&trace, bs->eye, NULL, NULL, target, bs->client, CONTENTS_SOLID); 2019 if (trace.fraction >= 1 || trace.ent == goal->entitynum) { 2020 trap_EA_Use(bs->client); 2021 return; 2022 } 2023 } 2024 } 2025 } 2026 #endif 2027 2028 /* 2029 ================== 2030 BotBattleUseItems 2031 ================== 2032 */ 2033 void BotBattleUseItems(bot_state_t *bs) { 2034 if (bs->inventory[INVENTORY_HEALTH] < 40) { 2035 if (bs->inventory[INVENTORY_TELEPORTER] > 0) { 2036 if (!BotCTFCarryingFlag(bs) 2037 #ifdef MISSIONPACK 2038 && !Bot1FCTFCarryingFlag(bs) 2039 && !BotHarvesterCarryingCubes(bs) 2040 #endif 2041 ) { 2042 trap_EA_Use(bs->client); 2043 } 2044 } 2045 } 2046 if (bs->inventory[INVENTORY_HEALTH] < 60) { 2047 if (bs->inventory[INVENTORY_MEDKIT] > 0) { 2048 trap_EA_Use(bs->client); 2049 } 2050 } 2051 #ifdef MISSIONPACK 2052 BotUseKamikaze(bs); 2053 BotUseInvulnerability(bs); 2054 #endif 2055 } 2056 2057 /* 2058 ================== 2059 BotSetTeleportTime 2060 ================== 2061 */ 2062 void BotSetTeleportTime(bot_state_t *bs) { 2063 if ((bs->cur_ps.eFlags ^ bs->last_eFlags) & EF_TELEPORT_BIT) { 2064 bs->teleport_time = FloatTime(); 2065 } 2066 bs->last_eFlags = bs->cur_ps.eFlags; 2067 } 2068 2069 /* 2070 ================== 2071 BotIsDead 2072 ================== 2073 */ 2074 qboolean BotIsDead(bot_state_t *bs) { 2075 return (bs->cur_ps.pm_type == PM_DEAD); 2076 } 2077 2078 /* 2079 ================== 2080 BotIsObserver 2081 ================== 2082 */ 2083 qboolean BotIsObserver(bot_state_t *bs) { 2084 char buf[MAX_INFO_STRING]; 2085 if (bs->cur_ps.pm_type == PM_SPECTATOR) return qtrue; 2086 trap_GetConfigstring(CS_PLAYERS+bs->client, buf, sizeof(buf)); 2087 if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) return qtrue; 2088 return qfalse; 2089 } 2090 2091 /* 2092 ================== 2093 BotIntermission 2094 ================== 2095 */ 2096 qboolean BotIntermission(bot_state_t *bs) { 2097 //NOTE: we shouldn't be looking at the game code... 2098 if (level.intermissiontime) return qtrue; 2099 return (bs->cur_ps.pm_type == PM_FREEZE || bs->cur_ps.pm_type == PM_INTERMISSION); 2100 } 2101 2102 /* 2103 ================== 2104 BotInLavaOrSlime 2105 ================== 2106 */ 2107 qboolean BotInLavaOrSlime(bot_state_t *bs) { 2108 vec3_t feet; 2109 2110 VectorCopy(bs->origin, feet); 2111 feet[2] -= 23; 2112 return (trap_AAS_PointContents(feet) & (CONTENTS_LAVA|CONTENTS_SLIME)); 2113 } 2114 2115 /* 2116 ================== 2117 BotCreateWayPoint 2118 ================== 2119 */ 2120 bot_waypoint_t *BotCreateWayPoint(char *name, vec3_t origin, int areanum) { 2121 bot_waypoint_t *wp; 2122 vec3_t waypointmins = {-8, -8, -8}, waypointmaxs = {8, 8, 8}; 2123 2124 wp = botai_freewaypoints; 2125 if ( !wp ) { 2126 BotAI_Print( PRT_WARNING, "BotCreateWayPoint: Out of waypoints\n" ); 2127 return NULL; 2128 } 2129 botai_freewaypoints = botai_freewaypoints->next; 2130 2131 Q_strncpyz( wp->name, name, sizeof(wp->name) ); 2132 VectorCopy(origin, wp->goal.origin); 2133 VectorCopy(waypointmins, wp->goal.mins); 2134 VectorCopy(waypointmaxs, wp->goal.maxs); 2135 wp->goal.areanum = areanum; 2136 wp->next = NULL; 2137 wp->prev = NULL; 2138 return wp; 2139 } 2140 2141 /* 2142 ================== 2143 BotFindWayPoint 2144 ================== 2145 */ 2146 bot_waypoint_t *BotFindWayPoint(bot_waypoint_t *waypoints, char *name) { 2147 bot_waypoint_t *wp; 2148 2149 for (wp = waypoints; wp; wp = wp->next) { 2150 if (!Q_stricmp(wp->name, name)) return wp; 2151 } 2152 return NULL; 2153 } 2154 2155 /* 2156 ================== 2157 BotFreeWaypoints 2158 ================== 2159 */ 2160 void BotFreeWaypoints(bot_waypoint_t *wp) { 2161 bot_waypoint_t *nextwp; 2162 2163 for (; wp; wp = nextwp) { 2164 nextwp = wp->next; 2165 wp->next = botai_freewaypoints; 2166 botai_freewaypoints = wp; 2167 } 2168 } 2169 2170 /* 2171 ================== 2172 BotInitWaypoints 2173 ================== 2174 */ 2175 void BotInitWaypoints(void) { 2176 int i; 2177 2178 botai_freewaypoints = NULL; 2179 for (i = 0; i < MAX_WAYPOINTS; i++) { 2180 botai_waypoints[i].next = botai_freewaypoints; 2181 botai_freewaypoints = &botai_waypoints[i]; 2182 } 2183 } 2184 2185 /* 2186 ================== 2187 TeamPlayIsOn 2188 ================== 2189 */ 2190 int TeamPlayIsOn(void) { 2191 return ( gametype >= GT_TEAM ); 2192 } 2193 2194 /* 2195 ================== 2196 BotAggression 2197 ================== 2198 */ 2199 float BotAggression(bot_state_t *bs) { 2200 //if the bot has quad 2201 if (bs->inventory[INVENTORY_QUAD]) { 2202 //if the bot is not holding the gauntlet or the enemy is really nearby 2203 if (bs->weaponnum != WP_GAUNTLET || 2204 bs->inventory[ENEMY_HORIZONTAL_DIST] < 80) { 2205 return 70; 2206 } 2207 } 2208 //if the enemy is located way higher than the bot 2209 if (bs->inventory[ENEMY_HEIGHT] > 200) return 0; 2210 //if the bot is very low on health 2211 if (bs->inventory[INVENTORY_HEALTH] < 60) return 0; 2212 //if the bot is low on health 2213 if (bs->inventory[INVENTORY_HEALTH] < 80) { 2214 //if the bot has insufficient armor 2215 if (bs->inventory[INVENTORY_ARMOR] < 40) return 0; 2216 } 2217 //if the bot can use the bfg 2218 if (bs->inventory[INVENTORY_BFG10K] > 0 && 2219 bs->inventory[INVENTORY_BFGAMMO] > 7) return 100; 2220 //if the bot can use the railgun 2221 if (bs->inventory[INVENTORY_RAILGUN] > 0 && 2222 bs->inventory[INVENTORY_SLUGS] > 5) return 95; 2223 //if the bot can use the lightning gun 2224 if (bs->inventory[INVENTORY_LIGHTNING] > 0 && 2225 bs->inventory[INVENTORY_LIGHTNINGAMMO] > 50) return 90; 2226 //if the bot can use the rocketlauncher 2227 if (bs->inventory[INVENTORY_ROCKETLAUNCHER] > 0 && 2228 bs->inventory[INVENTORY_ROCKETS] > 5) return 90; 2229 //if the bot can use the plasmagun 2230 if (bs->inventory[INVENTORY_PLASMAGUN] > 0 && 2231 bs->inventory[INVENTORY_CELLS] > 40) return 85; 2232 //if the bot can use the grenade launcher 2233 if (bs->inventory[INVENTORY_GRENADELAUNCHER] > 0 && 2234 bs->inventory[INVENTORY_GRENADES] > 10) return 80; 2235 //if the bot can use the shotgun 2236 if (bs->inventory[INVENTORY_SHOTGUN] > 0 && 2237 bs->inventory[INVENTORY_SHELLS] > 10) return 50; 2238 //otherwise the bot is not feeling too good 2239 return 0; 2240 } 2241 2242 /* 2243 ================== 2244 BotFeelingBad 2245 ================== 2246 */ 2247 float BotFeelingBad(bot_state_t *bs) { 2248 if (bs->weaponnum == WP_GAUNTLET) { 2249 return 100; 2250 } 2251 if (bs->inventory[INVENTORY_HEALTH] < 40) { 2252 return 100; 2253 } 2254 if (bs->weaponnum == WP_MACHINEGUN) { 2255 return 90; 2256 } 2257 if (bs->inventory[INVENTORY_HEALTH] < 60) { 2258 return 80; 2259 } 2260 return 0; 2261 } 2262 2263 /* 2264 ================== 2265 BotWantsToRetreat 2266 ================== 2267 */ 2268 int BotWantsToRetreat(bot_state_t *bs) { 2269 aas_entityinfo_t entinfo; 2270 2271 if (gametype == GT_CTF) { 2272 //always retreat when carrying a CTF flag 2273 if (BotCTFCarryingFlag(bs)) 2274 return qtrue; 2275 } 2276 #ifdef MISSIONPACK 2277 else if (gametype == GT_1FCTF) { 2278 //if carrying the flag then always retreat 2279 if (Bot1FCTFCarryingFlag(bs)) 2280 return qtrue; 2281 } 2282 else if (gametype == GT_OBELISK) { 2283 //the bots should be dedicated to attacking the enemy obelisk 2284 if (bs->ltgtype == LTG_ATTACKENEMYBASE) { 2285 if (bs->enemy != redobelisk.entitynum || 2286 bs->enemy != blueobelisk.entitynum) { 2287 return qtrue; 2288 } 2289 } 2290 if (BotFeelingBad(bs) > 50) { 2291 return qtrue; 2292 } 2293 return qfalse; 2294 } 2295 else if (gametype == GT_HARVESTER) { 2296 //if carrying cubes then always retreat 2297 if (BotHarvesterCarryingCubes(bs)) return qtrue; 2298 } 2299 #endif 2300 // 2301 if (bs->enemy >= 0) { 2302 //if the enemy is carrying a flag 2303 BotEntityInfo(bs->enemy, &entinfo); 2304 if (EntityCarriesFlag(&entinfo)) 2305 return qfalse; 2306 } 2307 //if the bot is getting the flag 2308 if (bs->ltgtype == LTG_GETFLAG) 2309 return qtrue; 2310 // 2311 if (BotAggression(bs) < 50) 2312 return qtrue; 2313 return qfalse; 2314 } 2315 2316 /* 2317 ================== 2318 BotWantsToChase 2319 ================== 2320 */ 2321 int BotWantsToChase(bot_state_t *bs) { 2322 aas_entityinfo_t entinfo; 2323 2324 if (gametype == GT_CTF) { 2325 //never chase when carrying a CTF flag 2326 if (BotCTFCarryingFlag(bs)) 2327 return qfalse; 2328 //always chase if the enemy is carrying a flag 2329 BotEntityInfo(bs->enemy, &entinfo); 2330 if (EntityCarriesFlag(&entinfo)) 2331 return qtrue; 2332 } 2333 #ifdef MISSIONPACK 2334 else if (gametype == GT_1FCTF) { 2335 //never chase if carrying the flag 2336 if (Bot1FCTFCarryingFlag(bs)) 2337 return qfalse; 2338 //always chase if the enemy is carrying a flag 2339 BotEntityInfo(bs->enemy, &entinfo); 2340 if (EntityCarriesFlag(&entinfo)) 2341 return qtrue; 2342 } 2343 else if (gametype == GT_OBELISK) { 2344 //the bots should be dedicated to attacking the enemy obelisk 2345 if (bs->ltgtype == LTG_ATTACKENEMYBASE) { 2346 if (bs->enemy != redobelisk.entitynum || 2347 bs->enemy != blueobelisk.entitynum) { 2348 return qfalse; 2349 } 2350 } 2351 } 2352 else if (gametype == GT_HARVESTER) { 2353 //never chase if carrying cubes 2354 if (BotHarvesterCarryingCubes(bs)) 2355 return qfalse; 2356 } 2357 #endif 2358 //if the bot is getting the flag 2359 if (bs->ltgtype == LTG_GETFLAG) 2360 return qfalse; 2361 // 2362 if (BotAggression(bs) > 50) 2363 return qtrue; 2364 return qfalse; 2365 } 2366 2367 /* 2368 ================== 2369 BotWantsToHelp 2370 ================== 2371 */ 2372 int BotWantsToHelp(bot_state_t *bs) { 2373 return qtrue; 2374 } 2375 2376 /* 2377 ================== 2378 BotCanAndWantsToRocketJump 2379 ================== 2380 */ 2381 int BotCanAndWantsToRocketJump(bot_state_t *bs) { 2382 float rocketjumper; 2383 2384 //if rocket jumping is disabled 2385 if (!bot_rocketjump.integer) return qfalse; 2386 //if no rocket launcher 2387 if (bs->inventory[INVENTORY_ROCKETLAUNCHER] <= 0) return qfalse; 2388 //if low on rockets 2389 if (bs->inventory[INVENTORY_ROCKETS] < 3) return qfalse; 2390 //never rocket jump with the Quad 2391 if (bs->inventory[INVENTORY_QUAD]) return qfalse; 2392 //if low on health 2393 if (bs->inventory[INVENTORY_HEALTH] < 60) return qfalse; 2394 //if not full health 2395 if (bs->inventory[INVENTORY_HEALTH] < 90) { 2396 //if the bot has insufficient armor 2397 if (bs->inventory[INVENTORY_ARMOR] < 40) return qfalse; 2398 } 2399 rocketjumper = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_WEAPONJUMPING, 0, 1); 2400 if (rocketjumper < 0.5) return qfalse; 2401 return qtrue; 2402 } 2403 2404 /* 2405 ================== 2406 BotHasPersistantPowerupAndWeapon 2407 ================== 2408 */ 2409 int BotHasPersistantPowerupAndWeapon(bot_state_t *bs) { 2410 #ifdef MISSIONPACK 2411 // if the bot does not have a persistant powerup 2412 if (!bs->inventory[INVENTORY_SCOUT] && 2413 !bs->inventory[INVENTORY_GUARD] && 2414 !bs->inventory[INVENTORY_DOUBLER] && 2415 !bs->inventory[INVENTORY_AMMOREGEN] ) { 2416 return qfalse; 2417 } 2418 #endif 2419 //if the bot is very low on health 2420 if (bs->inventory[INVENTORY_HEALTH] < 60) return qfalse; 2421 //if the bot is low on health 2422 if (bs->inventory[INVENTORY_HEALTH] < 80) { 2423 //if the bot has insufficient armor 2424 if (bs->inventory[INVENTORY_ARMOR] < 40) return qfalse; 2425 } 2426 //if the bot can use the bfg 2427 if (bs->inventory[INVENTORY_BFG10K] > 0 && 2428 bs->inventory[INVENTORY_BFGAMMO] > 7) return qtrue; 2429 //if the bot can use the railgun 2430 if (bs->inventory[INVENTORY_RAILGUN] > 0 && 2431 bs->inventory[INVENTORY_SLUGS] > 5) return qtrue; 2432 //if the bot can use the lightning gun 2433 if (bs->inventory[INVENTORY_LIGHTNING] > 0 && 2434 bs->inventory[INVENTORY_LIGHTNINGAMMO] > 50) return qtrue; 2435 //if the bot can use the rocketlauncher 2436 if (bs->inventory[INVENTORY_ROCKETLAUNCHER] > 0 && 2437 bs->inventory[INVENTORY_ROCKETS] > 5) return qtrue; 2438 // 2439 if (bs->inventory[INVENTORY_NAILGUN] > 0 && 2440 bs->inventory[INVENTORY_NAILS] > 5) return qtrue; 2441 // 2442 if (bs->inventory[INVENTORY_PROXLAUNCHER] > 0 && 2443 bs->inventory[INVENTORY_MINES] > 5) return qtrue; 2444 // 2445 if (bs->inventory[INVENTORY_CHAINGUN] > 0 && 2446 bs->inventory[INVENTORY_BELT] > 40) return qtrue; 2447 //if the bot can use the plasmagun 2448 if (bs->inventory[INVENTORY_PLASMAGUN] > 0 && 2449 bs->inventory[INVENTORY_CELLS] > 20) return qtrue; 2450 return qfalse; 2451 } 2452 2453 /* 2454 ================== 2455 BotGoCamp 2456 ================== 2457 */ 2458 void BotGoCamp(bot_state_t *bs, bot_goal_t *goal) { 2459 float camper; 2460 2461 bs->decisionmaker = bs->client; 2462 //set message time to zero so bot will NOT show any message 2463 bs->teammessage_time = 0; 2464 //set the ltg type 2465 bs->ltgtype = LTG_CAMP; 2466 //set the team goal 2467 memcpy(&bs->teamgoal, goal, sizeof(bot_goal_t)); 2468 //get the team goal time 2469 camper = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CAMPER, 0, 1); 2470 if (camper > 0.99) bs->teamgoal_time = FloatTime() + 99999; 2471 else bs->teamgoal_time = FloatTime() + 120 + 180 * camper + random() * 15; 2472 //set the last time the bot started camping 2473 bs->camp_time = FloatTime(); 2474 //the teammate that requested the camping 2475 bs->teammate = 0; 2476 //do NOT type arrive message 2477 bs->arrive_time = 1; 2478 } 2479 2480 /* 2481 ================== 2482 BotWantsToCamp 2483 ================== 2484 */ 2485 int BotWantsToCamp(bot_state_t *bs) { 2486 float camper; 2487 int cs, traveltime, besttraveltime; 2488 bot_goal_t goal, bestgoal; 2489 2490 camper = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CAMPER, 0, 1); 2491 if (camper < 0.1) return qfalse; 2492 //if the bot has a team goal 2493 if (bs->ltgtype == LTG_TEAMHELP || 2494 bs->ltgtype == LTG_TEAMACCOMPANY || 2495 bs->ltgtype == LTG_DEFENDKEYAREA || 2496 bs->ltgtype == LTG_GETFLAG || 2497 bs->ltgtype == LTG_RUSHBASE || 2498 bs->ltgtype == LTG_CAMP || 2499 bs->ltgtype == LTG_CAMPORDER || 2500 bs->ltgtype == LTG_PATROL) { 2501 return qfalse; 2502 } 2503 //if camped recently 2504 if (bs->camp_time > FloatTime() - 60 + 300 * (1-camper)) return qfalse; 2505 // 2506 if (random() > camper) { 2507 bs->camp_time = FloatTime(); 2508 return qfalse; 2509 } 2510 //if the bot isn't healthy anough 2511 if (BotAggression(bs) < 50) return qfalse; 2512 //the bot should have at least have the rocket launcher, the railgun or the bfg10k with some ammo 2513 if ((bs->inventory[INVENTORY_ROCKETLAUNCHER] <= 0 || bs->inventory[INVENTORY_ROCKETS < 10]) && 2514 (bs->inventory[INVENTORY_RAILGUN] <= 0 || bs->inventory[INVENTORY_SLUGS] < 10) && 2515 (bs->inventory[INVENTORY_BFG10K] <= 0 || bs->inventory[INVENTORY_BFGAMMO] < 10)) { 2516 return qfalse; 2517 } 2518 //find the closest camp spot 2519 besttraveltime = 99999; 2520 for (cs = trap_BotGetNextCampSpotGoal(0, &goal); cs; cs = trap_BotGetNextCampSpotGoal(cs, &goal)) { 2521 traveltime = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, goal.areanum, TFL_DEFAULT); 2522 if (traveltime && traveltime < besttraveltime) { 2523 besttraveltime = traveltime; 2524 memcpy(&bestgoal, &goal, sizeof(bot_goal_t)); 2525 } 2526 } 2527 if (besttraveltime > 150) return qfalse; 2528 //ok found a camp spot, go camp there 2529 BotGoCamp(bs, &bestgoal); 2530 bs->ordered = qfalse; 2531 // 2532 return qtrue; 2533 } 2534 2535 /* 2536 ================== 2537 BotDontAvoid 2538 ================== 2539 */ 2540 void BotDontAvoid(bot_state_t *bs, char *itemname) { 2541 bot_goal_t goal; 2542 int num; 2543 2544 num = trap_BotGetLevelItemGoal(-1, itemname, &goal); 2545 while(num >= 0) { 2546 trap_BotRemoveFromAvoidGoals(bs->gs, goal.number); 2547 num = trap_BotGetLevelItemGoal(num, itemname, &goal); 2548 } 2549 } 2550 2551 /* 2552 ================== 2553 BotGoForPowerups 2554 ================== 2555 */ 2556 void BotGoForPowerups(bot_state_t *bs) { 2557 2558 //don't avoid any of the powerups anymore 2559 BotDontAvoid(bs, "Quad Damage"); 2560 BotDontAvoid(bs, "Regeneration"); 2561 BotDontAvoid(bs, "Battle Suit"); 2562 BotDontAvoid(bs, "Speed"); 2563 BotDontAvoid(bs, "Invisibility"); 2564 //BotDontAvoid(bs, "Flight"); 2565 //reset the long term goal time so the bot will go for the powerup 2566 //NOTE: the long term goal type doesn't change 2567 bs->ltg_time = 0; 2568 } 2569 2570 /* 2571 ================== 2572 BotRoamGoal 2573 ================== 2574 */ 2575 void BotRoamGoal(bot_state_t *bs, vec3_t goal) { 2576 int pc, i; 2577 float len, rnd; 2578 vec3_t dir, bestorg, belowbestorg; 2579 bsp_trace_t trace; 2580 2581 for (i = 0; i < 10; i++) { 2582 //start at the bot origin 2583 VectorCopy(bs->origin, bestorg); 2584 rnd = random(); 2585 if (rnd > 0.25) { 2586 //add a random value to the x-coordinate 2587 if (random() < 0.5) bestorg[0] -= 800 * random() + 100; 2588 else bestorg[0] += 800 * random() + 100; 2589 } 2590 if (rnd < 0.75) { 2591 //add a random value to the y-coordinate 2592 if (random() < 0.5) bestorg[1] -= 800 * random() + 100; 2593 else bestorg[1] += 800 * random() + 100; 2594 } 2595 //add a random value to the z-coordinate (NOTE: 48 = maxjump?) 2596 bestorg[2] += 2 * 48 * crandom(); 2597 //trace a line from the origin to the roam target 2598 BotAI_Trace(&trace, bs->origin, NULL, NULL, bestorg, bs->entitynum, MASK_SOLID); 2599 //direction and length towards the roam target 2600 VectorSubtract(trace.endpos, bs->origin, dir); 2601 len = VectorNormalize(dir); 2602 //if the roam target is far away anough 2603 if (len > 200) { 2604 //the roam target is in the given direction before walls 2605 VectorScale(dir, len * trace.fraction - 40, dir); 2606 VectorAdd(bs->origin, dir, bestorg); 2607 //get the coordinates of the floor below the roam target 2608 belowbestorg[0] = bestorg[0]; 2609 belowbestorg[1] = bestorg[1]; 2610 belowbestorg[2] = bestorg[2] - 800; 2611 BotAI_Trace(&trace, bestorg, NULL, NULL, belowbestorg, bs->entitynum, MASK_SOLID); 2612 // 2613 if (!trace.startsolid) { 2614 trace.endpos[2]++; 2615 pc = trap_PointContents(trace.endpos, bs->entitynum); 2616 if (!(pc & (CONTENTS_LAVA | CONTENTS_SLIME))) { 2617 VectorCopy(bestorg, goal); 2618 return; 2619 } 2620 } 2621 } 2622 } 2623 VectorCopy(bestorg, goal); 2624 } 2625 2626 /* 2627 ================== 2628 BotAttackMove 2629 ================== 2630 */ 2631 bot_moveresult_t BotAttackMove(bot_state_t *bs, int tfl) { 2632 int movetype, i, attackentity; 2633 float attack_skill, jumper, croucher, dist, strafechange_time; 2634 float attack_dist, attack_range; 2635 vec3_t forward, backward, sideward, hordir, up = {0, 0, 1}; 2636 aas_entityinfo_t entinfo; 2637 bot_moveresult_t moveresult; 2638 bot_goal_t goal; 2639 2640 attackentity = bs->enemy; 2641 // 2642 if (bs->attackchase_time > FloatTime()) { 2643 //create the chase goal 2644 goal.entitynum = attackentity; 2645 goal.areanum = bs->lastenemyareanum; 2646 VectorCopy(bs->lastenemyorigin, goal.origin); 2647 VectorSet(goal.mins, -8, -8, -8); 2648 VectorSet(goal.maxs, 8, 8, 8); 2649 //initialize the movement state 2650 BotSetupForMovement(bs); 2651 //move towards the goal 2652 trap_BotMoveToGoal(&moveresult, bs->ms, &goal, tfl); 2653 return moveresult; 2654 } 2655 // 2656 memset(&moveresult, 0, sizeof(bot_moveresult_t)); 2657 // 2658 attack_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_ATTACK_SKILL, 0, 1); 2659 jumper = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_JUMPER, 0, 1); 2660 croucher = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CROUCHER, 0, 1); 2661 //if the bot is really stupid 2662 if (attack_skill < 0.2) return moveresult; 2663 //initialize the movement state 2664 BotSetupForMovement(bs); 2665 //get the enemy entity info 2666 BotEntityInfo(attackentity, &entinfo); 2667 //direction towards the enemy 2668 VectorSubtract(entinfo.origin, bs->origin, forward); 2669 //the distance towards the enemy 2670 dist = VectorNormalize(forward); 2671 VectorNegate(forward, backward); 2672 //walk, crouch or jump 2673 movetype = MOVE_WALK; 2674 // 2675 if (bs->attackcrouch_time < FloatTime() - 1) { 2676 if (random() < jumper) { 2677 movetype = MOVE_JUMP; 2678 } 2679 //wait at least one second before crouching again 2680 else if (bs->attackcrouch_time < FloatTime() - 1 && random() < croucher) { 2681 bs->attackcrouch_time = FloatTime() + croucher * 5; 2682 } 2683 } 2684 if (bs->attackcrouch_time > FloatTime()) movetype = MOVE_CROUCH; 2685 //if the bot should jump 2686 if (movetype == MOVE_JUMP) { 2687 //if jumped last frame 2688 if (bs->attackjump_time > FloatTime()) { 2689 movetype = MOVE_WALK; 2690 } 2691 else { 2692 bs->attackjump_time = FloatTime() + 1; 2693 } 2694 } 2695 if (bs->cur_ps.weapon == WP_GAUNTLET) { 2696 attack_dist = 0; 2697 attack_range = 0; 2698 } 2699 else { 2700 attack_dist = IDEAL_ATTACKDIST; 2701 attack_range = 40; 2702 } 2703 //if the bot is stupid 2704 if (attack_skill <= 0.4) { 2705 //just walk to or away from the enemy 2706 if (dist > attack_dist + attack_range) { 2707 if (trap_BotMoveInDirection(bs->ms, forward, 400, movetype)) return moveresult; 2708 } 2709 if (dist < attack_dist - attack_range) { 2710 if (trap_BotMoveInDirection(bs->ms, backward, 400, movetype)) return moveresult; 2711 } 2712 return moveresult; 2713 } 2714 //increase the strafe time 2715 bs->attackstrafe_time += bs->thinktime; 2716 //get the strafe change time 2717 strafechange_time = 0.4 + (1 - attack_skill) * 0.2; 2718 if (attack_skill > 0.7) strafechange_time += crandom() * 0.2; 2719 //if the strafe direction should be changed 2720 if (bs->attackstrafe_time > strafechange_time) { 2721 //some magic number :) 2722 if (random() > 0.935) { 2723 //flip the strafe direction 2724 bs->flags ^= BFL_STRAFERIGHT; 2725 bs->attackstrafe_time = 0; 2726 } 2727 } 2728 // 2729 for (i = 0; i < 2; i++) { 2730 hordir[0] = forward[0]; 2731 hordir[1] = forward[1]; 2732 hordir[2] = 0; 2733 VectorNormalize(hordir); 2734 //get the sideward vector 2735 CrossProduct(hordir, up, sideward); 2736 //reverse the vector depending on the strafe direction 2737 if (bs->flags & BFL_STRAFERIGHT) VectorNegate(sideward, sideward); 2738 //randomly go back a little 2739 if (random() > 0.9) { 2740 VectorAdd(sideward, backward, sideward); 2741 } 2742 else { 2743 //walk forward or backward to get at the ideal attack distance 2744 if (dist > attack_dist + attack_range) { 2745 VectorAdd(sideward, forward, sideward); 2746 } 2747 else if (dist < attack_dist - attack_range) { 2748 VectorAdd(sideward, backward, sideward); 2749 } 2750 } 2751 //perform the movement 2752 if (trap_BotMoveInDirection(bs->ms, sideward, 400, movetype)) 2753 return moveresult; 2754 //movement failed, flip the strafe direction 2755 bs->flags ^= BFL_STRAFERIGHT; 2756 bs->attackstrafe_time = 0; 2757 } 2758 //bot couldn't do any usefull movement 2759 // bs->attackchase_time = AAS_Time() + 6; 2760 return moveresult; 2761 } 2762 2763 /* 2764 ================== 2765 BotSameTeam 2766 ================== 2767 */ 2768 int BotSameTeam(bot_state_t *bs, int entnum) { 2769 char info1[1024], info2[1024]; 2770 2771 if (bs->client < 0 || bs->client >= MAX_CLIENTS) { 2772 //BotAI_Print(PRT_ERROR, "BotSameTeam: client out of range\n"); 2773 return qfalse; 2774 } 2775 if (entnum < 0 || entnum >= MAX_CLIENTS) { 2776 //BotAI_Print(PRT_ERROR, "BotSameTeam: client out of range\n"); 2777 return qfalse; 2778 } 2779 if ( gametype >= GT_TEAM ) { 2780 trap_GetConfigstring(CS_PLAYERS+bs->client, info1, sizeof(info1)); 2781 trap_GetConfigstring(CS_PLAYERS+entnum, info2, sizeof(info2)); 2782 // 2783 if (atoi(Info_ValueForKey(info1, "t")) == atoi(Info_ValueForKey(info2, "t"))) return qtrue; 2784 } 2785 return qfalse; 2786 } 2787 2788 /* 2789 ================== 2790 InFieldOfVision 2791 ================== 2792 */ 2793 qboolean InFieldOfVision(vec3_t viewangles, float fov, vec3_t angles) 2794 { 2795 int i; 2796 float diff, angle; 2797 2798 for (i = 0; i < 2; i++) { 2799 angle = AngleMod(viewangles[i]); 2800 angles[i] = AngleMod(angles[i]); 2801 diff = angles[i] - angle; 2802 if (angles[i] > angle) { 2803 if (diff > 180.0) diff -= 360.0; 2804 } 2805 else { 2806 if (diff < -180.0) diff += 360.0; 2807 } 2808 if (diff > 0) { 2809 if (diff > fov * 0.5) return qfalse; 2810 } 2811 else { 2812 if (diff < -fov * 0.5) return qfalse; 2813 } 2814 } 2815 return qtrue; 2816 } 2817 2818 /* 2819 ================== 2820 BotEntityVisible 2821 2822 returns visibility in the range [0, 1] taking fog and water surfaces into account 2823 ================== 2824 */ 2825 float BotEntityVisible(int viewer, vec3_t eye, vec3_t viewangles, float fov, int ent) { 2826 int i, contents_mask, passent, hitent, infog, inwater, otherinfog, pc; 2827 float squaredfogdist, waterfactor, vis, bestvis; 2828 bsp_trace_t trace; 2829 aas_entityinfo_t entinfo; 2830 vec3_t dir, entangles, start, end, middle; 2831 2832 //calculate middle of bounding box 2833 BotEntityInfo(ent, &entinfo); 2834 VectorAdd(entinfo.mins, entinfo.maxs, middle); 2835 VectorScale(middle, 0.5, middle); 2836 VectorAdd(entinfo.origin, middle, middle); 2837 //check if entity is within field of vision 2838 VectorSubtract(middle, eye, dir); 2839 vectoangles(dir, entangles); 2840 if (!InFieldOfVision(viewangles, fov, entangles)) return 0; 2841 // 2842 pc = trap_AAS_PointContents(eye); 2843 infog = (pc & CONTENTS_FOG); 2844 inwater = (pc & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER)); 2845 // 2846 bestvis = 0; 2847 for (i = 0; i < 3; i++) { 2848 //if the point is not in potential visible sight 2849 //if (!AAS_inPVS(eye, middle)) continue; 2850 // 2851 contents_mask = CONTENTS_SOLID|CONTENTS_PLAYERCLIP; 2852 passent = viewer; 2853 hitent = ent; 2854 VectorCopy(eye, start); 2855 VectorCopy(middle, end); 2856 //if the entity is in water, lava or slime 2857 if (trap_AAS_PointContents(middle) & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER)) { 2858 contents_mask |= (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER); 2859 } 2860 //if eye is in water, lava or slime 2861 if (inwater) { 2862 if (!(contents_mask & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER))) { 2863 passent = ent; 2864 hitent = viewer; 2865 VectorCopy(middle, start); 2866 VectorCopy(eye, end); 2867 } 2868 contents_mask ^= (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER); 2869 } 2870 //trace from start to end 2871 BotAI_Trace(&trace, start, NULL, NULL, end, passent, contents_mask); 2872 //if water was hit 2873 waterfactor = 1.0; 2874 if (trace.contents & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER)) { 2875 //if the water surface is translucent 2876 if (1) { 2877 //trace through the water 2878 contents_mask &= ~(CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER); 2879 BotAI_Trace(&trace, trace.endpos, NULL, NULL, end, passent, contents_mask); 2880 waterfactor = 0.5; 2881 } 2882 } 2883 //if a full trace or the hitent was hit 2884 if (trace.fraction >= 1 || trace.ent == hitent) { 2885 //check for fog, assuming there's only one fog brush where 2886 //either the viewer or the entity is in or both are in 2887 otherinfog = (trap_AAS_PointContents(middle) & CONTENTS_FOG); 2888 if (infog && otherinfog) { 2889 VectorSubtract(trace.endpos, eye, dir); 2890 squaredfogdist = VectorLengthSquared(dir); 2891 } 2892 else if (infog) { 2893 VectorCopy(trace.endpos, start); 2894 BotAI_Trace(&trace, start, NULL, NULL, eye, viewer, CONTENTS_FOG); 2895 VectorSubtract(eye, trace.endpos, dir); 2896 squaredfogdist = VectorLengthSquared(dir); 2897 } 2898 else if (otherinfog) { 2899 VectorCopy(trace.endpos, end); 2900 BotAI_Trace(&trace, eye, NULL, NULL, end, viewer, CONTENTS_FOG); 2901 VectorSubtract(end, trace.endpos, dir); 2902 squaredfogdist = VectorLengthSquared(dir); 2903 } 2904 else { 2905 //if the entity and the viewer are not in fog assume there's no fog in between 2906 squaredfogdist = 0; 2907 } 2908 //decrease visibility with the view distance through fog 2909 vis = 1 / ((squaredfogdist * 0.001) < 1 ? 1 : (squaredfogdist * 0.001)); 2910 //if entering water visibility is reduced 2911 vis *= waterfactor; 2912 // 2913 if (vis > bestvis) bestvis = vis; 2914 //if pretty much no fog 2915 if (bestvis >= 0.95) return bestvis; 2916 } 2917 //check bottom and top of bounding box as well 2918 if (i == 0) middle[2] += entinfo.mins[2]; 2919 else if (i == 1) middle[2] += entinfo.maxs[2] - entinfo.mins[2]; 2920 } 2921 return bestvis; 2922 } 2923 2924 /* 2925 ================== 2926 BotFindEnemy 2927 ================== 2928 */ 2929 int BotFindEnemy(bot_state_t *bs, int curenemy) { 2930 int i, healthdecrease; 2931 float f, alertness, easyfragger, vis; 2932 float squaredist, cursquaredist; 2933 aas_entityinfo_t entinfo, curenemyinfo; 2934 vec3_t dir, angles; 2935 2936 alertness = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_ALERTNESS, 0, 1); 2937 easyfragger = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_EASY_FRAGGER, 0, 1); 2938 //check if the health decreased 2939 healthdecrease = bs->lasthealth > bs->inventory[INVENTORY_HEALTH]; 2940 //remember the current health value 2941 bs->lasthealth = bs->inventory[INVENTORY_HEALTH]; 2942 // 2943 if (curenemy >= 0) { 2944 BotEntityInfo(curenemy, &curenemyinfo); 2945 if (EntityCarriesFlag(&curenemyinfo)) return qfalse; 2946 VectorSubtract(curenemyinfo.origin, bs->origin, dir); 2947 cursquaredist = VectorLengthSquared(dir); 2948 } 2949 else { 2950 cursquaredist = 0; 2951 } 2952 #ifdef MISSIONPACK 2953 if (gametype == GT_OBELISK) { 2954 vec3_t target; 2955 bot_goal_t *goal; 2956 bsp_trace_t trace; 2957 2958 if (BotTeam(bs) == TEAM_RED) 2959 goal = &blueobelisk; 2960 else 2961 goal = &redobelisk; 2962 //if the obelisk is visible 2963 VectorCopy(goal->origin, target); 2964 target[2] += 1; 2965 BotAI_Trace(&trace, bs->eye, NULL, NULL, target, bs->client, CONTENTS_SOLID); 2966 if (trace.fraction >= 1 || trace.ent == goal->entitynum) { 2967 if (goal->entitynum == bs->enemy) { 2968 return qfalse; 2969 } 2970 bs->enemy = goal->entitynum; 2971 bs->enemysight_time = FloatTime(); 2972 bs->enemysuicide = qfalse; 2973 bs->enemydeath_time = 0; 2974 bs->enemyvisible_time = FloatTime(); 2975 return qtrue; 2976 } 2977 } 2978 #endif 2979 // 2980 for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { 2981 2982 if (i == bs->client) continue; 2983 //if it's the current enemy 2984 if (i == curenemy) continue; 2985 // 2986 BotEntityInfo(i, &entinfo); 2987 // 2988 if (!entinfo.valid) continue; 2989 //if the enemy isn't dead and the enemy isn't the bot self 2990 if (EntityIsDead(&entinfo) || entinfo.number == bs->entitynum) continue; 2991 //if the enemy is invisible and not shooting 2992 if (EntityIsInvisible(&entinfo) && !EntityIsShooting(&entinfo)) { 2993 continue; 2994 } 2995 //if not an easy fragger don't shoot at chatting players 2996 if (easyfragger < 0.5 && EntityIsChatting(&entinfo)) continue; 2997 // 2998 if (lastteleport_time > FloatTime() - 3) { 2999 VectorSubtract(entinfo.origin, lastteleport_origin, dir); 3000 if (VectorLengthSquared(dir) < Square(70)) continue; 3001 } 3002 //calculate the distance towards the enemy 3003 VectorSubtract(entinfo.origin, bs->origin, dir); 3004 squaredist = VectorLengthSquared(dir); 3005 //if this entity is not carrying a flag 3006 if (!EntityCarriesFlag(&entinfo)) 3007 { 3008 //if this enemy is further away than the current one 3009 if (curenemy >= 0 && squaredist > cursquaredist) continue; 3010 } //end if 3011 //if the bot has no 3012 if (squaredist > Square(900.0 + alertness * 4000.0)) continue; 3013 //if on the same team 3014 if (BotSameTeam(bs, i)) continue; 3015 //if the bot's health decreased or the enemy is shooting 3016 if (curenemy < 0 && (healthdecrease || EntityIsShooting(&entinfo))) 3017 f = 360; 3018 else 3019 f = 90 + 90 - (90 - (squaredist > Square(810) ? Square(810) : squaredist) / (810 * 9)); 3020 //check if the enemy is visible 3021 vis = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, f, i); 3022 if (vis <= 0) continue; 3023 //if the enemy is quite far away, not shooting and the bot is not damaged 3024 if (curenemy < 0 && squaredist > Square(100) && !healthdecrease && !EntityIsShooting(&entinfo)) 3025 { 3026 //check if we can avoid this enemy 3027 VectorSubtract(bs->origin, entinfo.origin, dir); 3028 vectoangles(dir, angles); 3029 //if the bot isn't in the fov of the enemy 3030 if (!InFieldOfVision(entinfo.angles, 90, angles)) { 3031 //update some stuff for this enemy 3032 BotUpdateBattleInventory(bs, i); 3033 //if the bot doesn't really want to fight 3034 if (BotWantsToRetreat(bs)) continue; 3035 } 3036 } 3037 //found an enemy 3038 bs->enemy = entinfo.number; 3039 if (curenemy >= 0) bs->enemysight_time = FloatTime() - 2; 3040 else bs->enemysight_time = FloatTime(); 3041 bs->enemysuicide = qfalse; 3042 bs->enemydeath_time = 0; 3043 bs->enemyvisible_time = FloatTime(); 3044 return qtrue; 3045 } 3046 return qfalse; 3047 } 3048 3049 /* 3050 ================== 3051 BotTeamFlagCarrierVisible 3052 ================== 3053 */ 3054 int BotTeamFlagCarrierVisible(bot_state_t *bs) { 3055 int i; 3056 float vis; 3057 aas_entityinfo_t entinfo; 3058 3059 for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { 3060 if (i == bs->client) 3061 continue; 3062 // 3063 BotEntityInfo(i, &entinfo); 3064 //if this player is active 3065 if (!entinfo.valid) 3066 continue; 3067 //if this player is carrying a flag 3068 if (!EntityCarriesFlag(&entinfo)) 3069 continue; 3070 //if the flag carrier is not on the same team 3071 if (!BotSameTeam(bs, i)) 3072 continue; 3073 //if the flag carrier is not visible 3074 vis = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, i); 3075 if (vis <= 0) 3076 continue; 3077 // 3078 return i; 3079 } 3080 return -1; 3081 } 3082 3083 /* 3084 ================== 3085 BotTeamFlagCarrier 3086 ================== 3087 */ 3088 int BotTeamFlagCarrier(bot_state_t *bs) { 3089 int i; 3090 aas_entityinfo_t entinfo; 3091 3092 for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { 3093 if (i == bs->client) 3094 continue; 3095 // 3096 BotEntityInfo(i, &entinfo); 3097 //if this player is active 3098 if (!entinfo.valid) 3099 continue; 3100 //if this player is carrying a flag 3101 if (!EntityCarriesFlag(&entinfo)) 3102 continue; 3103 //if the flag carrier is not on the same team 3104 if (!BotSameTeam(bs, i)) 3105 continue; 3106 // 3107 return i; 3108 } 3109 return -1; 3110 } 3111 3112 /* 3113 ================== 3114 BotEnemyFlagCarrierVisible 3115 ================== 3116 */ 3117 int BotEnemyFlagCarrierVisible(bot_state_t *bs) { 3118 int i; 3119 float vis; 3120 aas_entityinfo_t entinfo; 3121 3122 for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { 3123 if (i == bs->client) 3124 continue; 3125 // 3126 BotEntityInfo(i, &entinfo); 3127 //if this player is active 3128 if (!entinfo.valid) 3129 continue; 3130 //if this player is carrying a flag 3131 if (!EntityCarriesFlag(&entinfo)) 3132 continue; 3133 //if the flag carrier is on the same team 3134 if (BotSameTeam(bs, i)) 3135 continue; 3136 //if the flag carrier is not visible 3137 vis = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, i); 3138 if (vis <= 0) 3139 continue; 3140 // 3141 return i; 3142 } 3143 return -1; 3144 } 3145 3146 /* 3147 ================== 3148 BotVisibleTeamMatesAndEnemies 3149 ================== 3150 */ 3151 void BotVisibleTeamMatesAndEnemies(bot_state_t *bs, int *teammates, int *enemies, float range) { 3152 int i; 3153 float vis; 3154 aas_entityinfo_t entinfo; 3155 vec3_t dir; 3156 3157 if (teammates) 3158 *teammates = 0; 3159 if (enemies) 3160 *enemies = 0; 3161 for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { 3162 if (i == bs->client) 3163 continue; 3164 // 3165 BotEntityInfo(i, &entinfo); 3166 //if this player is active 3167 if (!entinfo.valid) 3168 continue; 3169 //if this player is carrying a flag 3170 if (!EntityCarriesFlag(&entinfo)) 3171 continue; 3172 //if not within range 3173 VectorSubtract(entinfo.origin, bs->origin, dir); 3174 if (VectorLengthSquared(dir) > Square(range)) 3175 continue; 3176 //if the flag carrier is not visible 3177 vis = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, i); 3178 if (vis <= 0) 3179 continue; 3180 //if the flag carrier is on the same team 3181 if (BotSameTeam(bs, i)) { 3182 if (teammates) 3183 (*teammates)++; 3184 } 3185 else { 3186 if (enemies) 3187 (*enemies)++; 3188 } 3189 } 3190 } 3191 3192 #ifdef MISSIONPACK 3193 /* 3194 ================== 3195 BotTeamCubeCarrierVisible 3196 ================== 3197 */ 3198 int BotTeamCubeCarrierVisible(bot_state_t *bs) { 3199 int i; 3200 float vis; 3201 aas_entityinfo_t entinfo; 3202 3203 for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { 3204 if (i == bs->client) continue; 3205 // 3206 BotEntityInfo(i, &entinfo); 3207 //if this player is active 3208 if (!entinfo.valid) continue; 3209 //if this player is carrying a flag 3210 if (!EntityCarriesCubes(&entinfo)) continue; 3211 //if the flag carrier is not on the same team 3212 if (!BotSameTeam(bs, i)) continue; 3213 //if the flag carrier is not visible 3214 vis = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, i); 3215 if (vis <= 0) continue; 3216 // 3217 return i; 3218 } 3219 return -1; 3220 } 3221 3222 /* 3223 ================== 3224 BotEnemyCubeCarrierVisible 3225 ================== 3226 */ 3227 int BotEnemyCubeCarrierVisible(bot_state_t *bs) { 3228 int i; 3229 float vis; 3230 aas_entityinfo_t entinfo; 3231 3232 for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { 3233 if (i == bs->client) 3234 continue; 3235 // 3236 BotEntityInfo(i, &entinfo); 3237 //if this player is active 3238 if (!entinfo.valid) 3239 continue; 3240 //if this player is carrying a flag 3241 if (!EntityCarriesCubes(&entinfo)) continue; 3242 //if the flag carrier is on the same team 3243 if (BotSameTeam(bs, i)) 3244 continue; 3245 //if the flag carrier is not visible 3246 vis = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, i); 3247 if (vis <= 0) 3248 continue; 3249 // 3250 return i; 3251 } 3252 return -1; 3253 } 3254 #endif 3255 3256 /* 3257 ================== 3258 BotAimAtEnemy 3259 ================== 3260 */ 3261 void BotAimAtEnemy(bot_state_t *bs) { 3262 int i, enemyvisible; 3263 float dist, f, aim_skill, aim_accuracy, speed, reactiontime; 3264 vec3_t dir, bestorigin, end, start, groundtarget, cmdmove, enemyvelocity; 3265 vec3_t mins = {-4,-4,-4}, maxs = {4, 4, 4}; 3266 weaponinfo_t wi; 3267 aas_entityinfo_t entinfo; 3268 bot_goal_t goal; 3269 bsp_trace_t trace; 3270 vec3_t target; 3271 3272 //if the bot has no enemy 3273 if (bs->enemy < 0) { 3274 return; 3275 } 3276 //get the enemy entity information 3277 BotEntityInfo(bs->enemy, &entinfo); 3278 //if this is not a player (should be an obelisk) 3279 if (bs->enemy >= MAX_CLIENTS) { 3280 //if the obelisk is visible 3281 VectorCopy(entinfo.origin, target); 3282 #ifdef MISSIONPACK 3283 // if attacking an obelisk 3284 if ( bs->enemy == redobelisk.entitynum || 3285 bs->enemy == blueobelisk.entitynum ) { 3286 target[2] += 32; 3287 } 3288 #endif 3289 //aim at the obelisk 3290 VectorSubtract(target, bs->eye, dir); 3291 vectoangles(dir, bs->ideal_viewangles); 3292 //set the aim target before trying to attack 3293 VectorCopy(target, bs->aimtarget); 3294 return; 3295 } 3296 // 3297 //BotAI_Print(PRT_MESSAGE, "client %d: aiming at client %d\n", bs->entitynum, bs->enemy); 3298 // 3299 aim_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_SKILL, 0, 1); 3300 aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY, 0, 1); 3301 // 3302 if (aim_skill > 0.95) { 3303 //don't aim too early 3304 reactiontime = 0.5 * trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_REACTIONTIME, 0, 1); 3305 if (bs->enemysight_time > FloatTime() - reactiontime) return; 3306 if (bs->teleport_time > FloatTime() - reactiontime) return; 3307 } 3308 3309 //get the weapon information 3310 trap_BotGetWeaponInfo(bs->ws, bs->weaponnum, &wi); 3311 //get the weapon specific aim accuracy and or aim skill 3312 if (wi.number == WP_MACHINEGUN) { 3313 aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_MACHINEGUN, 0, 1); 3314 } 3315 else if (wi.number == WP_SHOTGUN) { 3316 aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_SHOTGUN, 0, 1); 3317 } 3318 else if (wi.number == WP_GRENADE_LAUNCHER) { 3319 aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_GRENADELAUNCHER, 0, 1); 3320 aim_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_SKILL_GRENADELAUNCHER, 0, 1); 3321 } 3322 else if (wi.number == WP_ROCKET_LAUNCHER) { 3323 aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_ROCKETLAUNCHER, 0, 1); 3324 aim_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_SKILL_ROCKETLAUNCHER, 0, 1); 3325 } 3326 else if (wi.number == WP_LIGHTNING) { 3327 aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_LIGHTNING, 0, 1); 3328 } 3329 else if (wi.number == WP_RAILGUN) { 3330 aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_RAILGUN, 0, 1); 3331 } 3332 else if (wi.number == WP_PLASMAGUN) { 3333 aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_PLASMAGUN, 0, 1); 3334 aim_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_SKILL_PLASMAGUN, 0, 1); 3335 } 3336 else if (wi.number == WP_BFG) { 3337 aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_BFG10K, 0, 1); 3338 aim_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_SKILL_BFG10K, 0, 1); 3339 } 3340 // 3341 if (aim_accuracy <= 0) aim_accuracy = 0.0001f; 3342 //get the enemy entity information 3343 BotEntityInfo(bs->enemy, &entinfo); 3344 //if the enemy is invisible then shoot crappy most of the time 3345 if (EntityIsInvisible(&entinfo)) { 3346 if (random() > 0.1) aim_accuracy *= 0.4f; 3347 } 3348 // 3349 VectorSubtract(entinfo.origin, entinfo.lastvisorigin, enemyvelocity); 3350 VectorScale(enemyvelocity, 1 / entinfo.update_time, enemyvelocity); 3351 //enemy origin and velocity is remembered every 0.5 seconds 3352 if (bs->enemyposition_time < FloatTime()) { 3353 // 3354 bs->enemyposition_time = FloatTime() + 0.5; 3355 VectorCopy(enemyvelocity, bs->enemyvelocity); 3356 VectorCopy(entinfo.origin, bs->enemyorigin); 3357 } 3358 //if not extremely skilled 3359 if (aim_skill < 0.9) { 3360 VectorSubtract(entinfo.origin, bs->enemyorigin, dir); 3361 //if the enemy moved a bit 3362 if (VectorLengthSquared(dir) > Square(48)) { 3363 //if the enemy changed direction 3364 if (DotProduct(bs->enemyvelocity, enemyvelocity) < 0) { 3365 //aim accuracy should be worse now 3366 aim_accuracy *= 0.7f; 3367 } 3368 } 3369 } 3370 //check visibility of enemy 3371 enemyvisible = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy); 3372 //if the enemy is visible 3373 if (enemyvisible) { 3374 // 3375 VectorCopy(entinfo.origin, bestorigin); 3376 bestorigin[2] += 8; 3377 //get the start point shooting from 3378 //NOTE: the x and y projectile start offsets are ignored 3379 VectorCopy(bs->origin, start); 3380 start[2] += bs->cur_ps.viewheight; 3381 start[2] += wi.offset[2]; 3382 // 3383 BotAI_Trace(&trace, start, mins, maxs, bestorigin, bs->entitynum, MASK_SHOT); 3384 //if the enemy is NOT hit 3385 if (trace.fraction <= 1 && trace.ent != entinfo.number) { 3386 bestorigin[2] += 16; 3387 } 3388 //if it is not an instant hit weapon the bot might want to predict the enemy 3389 if (wi.speed) { 3390 // 3391 VectorSubtract(bestorigin, bs->origin, dir); 3392 dist = VectorLength(dir); 3393 VectorSubtract(entinfo.origin, bs->enemyorigin, dir); 3394 //if the enemy is NOT pretty far away and strafing just small steps left and right 3395 if (!(dist > 100 && VectorLengthSquared(dir) < Square(32))) { 3396 //if skilled anough do exact prediction 3397 if (aim_skill > 0.8 && 3398 //if the weapon is ready to fire 3399 bs->cur_ps.weaponstate == WEAPON_READY) { 3400 aas_clientmove_t move; 3401 vec3_t origin; 3402 3403 VectorSubtract(entinfo.origin, bs->origin, dir); 3404 //distance towards the enemy 3405 dist = VectorLength(dir); 3406 //direction the enemy is moving in 3407 VectorSubtract(entinfo.origin, entinfo.lastvisorigin, dir); 3408 // 3409 VectorScale(dir, 1 / entinfo.update_time, dir); 3410 // 3411 VectorCopy(entinfo.origin, origin); 3412 origin[2] += 1; 3413 // 3414 VectorClear(cmdmove); 3415 //AAS_ClearShownDebugLines(); 3416 trap_AAS_PredictClientMovement(&move, bs->enemy, origin, 3417 PRESENCE_CROUCH, qfalse, 3418 dir, cmdmove, 0, 3419 dist * 10 / wi.speed, 0.1f, 0, 0, qfalse); 3420 VectorCopy(move.endpos, bestorigin); 3421 //BotAI_Print(PRT_MESSAGE, "%1.1f predicted speed = %f, frames = %f\n", FloatTime(), VectorLength(dir), dist * 10 / wi.speed); 3422 } 3423 //if not that skilled do linear prediction 3424 else if (aim_skill > 0.4) { 3425 VectorSubtract(entinfo.origin, bs->origin, dir); 3426 //distance towards the enemy 3427 dist = VectorLength(dir); 3428 //direction the enemy is moving in 3429 VectorSubtract(entinfo.origin, entinfo.lastvisorigin, dir); 3430 dir[2] = 0; 3431 // 3432 speed = VectorNormalize(dir) / entinfo.update_time; 3433 //botimport.Print(PRT_MESSAGE, "speed = %f, wi->speed = %f\n", speed, wi->speed); 3434 //best spot to aim at 3435 VectorMA(entinfo.origin, (dist / wi.speed) * speed, dir, bestorigin); 3436 } 3437 } 3438 } 3439 //if the projectile does radial damage 3440 if (aim_skill > 0.6 && wi.proj.damagetype & DAMAGETYPE_RADIAL) { 3441 //if the enemy isn't standing significantly higher than the bot 3442 if (entinfo.origin[2] < bs->origin[2] + 16) { 3443 //try to aim at the ground in front of the enemy 3444 VectorCopy(entinfo.origin, end); 3445 end[2] -= 64; 3446 BotAI_Trace(&trace, entinfo.origin, NULL, NULL, end, entinfo.number, MASK_SHOT); 3447 // 3448 VectorCopy(bestorigin, groundtarget); 3449 if (trace.startsolid) groundtarget[2] = entinfo.origin[2] - 16; 3450 else groundtarget[2] = trace.endpos[2] - 8; 3451 //trace a line from projectile start to ground target 3452 BotAI_Trace(&trace, start, NULL, NULL, groundtarget, bs->entitynum, MASK_SHOT); 3453 //if hitpoint is not vertically too far from the ground target 3454 if (fabs(trace.endpos[2] - groundtarget[2]) < 50) { 3455 VectorSubtract(trace.endpos, groundtarget, dir); 3456 //if the hitpoint is near anough the ground target 3457 if (VectorLengthSquared(dir) < Square(60)) { 3458 VectorSubtract(trace.endpos, start, dir); 3459 //if the hitpoint is far anough from the bot 3460 if (VectorLengthSquared(dir) > Square(100)) { 3461 //check if the bot is visible from the ground target 3462 trace.endpos[2] += 1; 3463 BotAI_Trace(&trace, trace.endpos, NULL, NULL, entinfo.origin, entinfo.number, MASK_SHOT); 3464 if (trace.fraction >= 1) { 3465 //botimport.Print(PRT_MESSAGE, "%1.1f aiming at ground\n", AAS_Time()); 3466 VectorCopy(groundtarget, bestorigin); 3467 } 3468 } 3469 } 3470 } 3471 } 3472 } 3473 bestorigin[0] += 20 * crandom() * (1 - aim_accuracy); 3474 bestorigin[1] += 20 * crandom() * (1 - aim_accuracy); 3475 bestorigin[2] += 10 * crandom() * (1 - aim_accuracy); 3476 } 3477 else { 3478 // 3479 VectorCopy(bs->lastenemyorigin, bestorigin); 3480 bestorigin[2] += 8; 3481 //if the bot is skilled anough 3482 if (aim_skill > 0.5) { 3483 //do prediction shots around corners 3484 if (wi.number == WP_BFG || 3485 wi.number == WP_ROCKET_LAUNCHER || 3486 wi.number == WP_GRENADE_LAUNCHER) { 3487 //create the chase goal 3488 goal.entitynum = bs->client; 3489 goal.areanum = bs->areanum; 3490 VectorCopy(bs->eye, goal.origin); 3491 VectorSet(goal.mins, -8, -8, -8); 3492 VectorSet(goal.maxs, 8, 8, 8); 3493 // 3494 if (trap_BotPredictVisiblePosition(bs->lastenemyorigin, bs->lastenemyareanum, &goal, TFL_DEFAULT, target)) { 3495 VectorSubtract(target, bs->eye, dir); 3496 if (VectorLengthSquared(dir) > Square(80)) { 3497 VectorCopy(target, bestorigin); 3498 bestorigin[2] -= 20; 3499 } 3500 } 3501 aim_accuracy = 1; 3502 } 3503 } 3504 } 3505 // 3506 if (enemyvisible) { 3507 BotAI_Trace(&trace, bs->eye, NULL, NULL, bestorigin, bs->entitynum, MASK_SHOT); 3508 VectorCopy(trace.endpos, bs->aimtarget); 3509 } 3510 else { 3511 VectorCopy(bestorigin, bs->aimtarget); 3512 } 3513 //get aim direction 3514 VectorSubtract(bestorigin, bs->eye, dir); 3515 // 3516 if (wi.number == WP_MACHINEGUN || 3517 wi.number == WP_SHOTGUN || 3518 wi.number == WP_LIGHTNING || 3519 wi.number == WP_RAILGUN) { 3520 //distance towards the enemy 3521 dist = VectorLength(dir); 3522 if (dist > 150) dist = 150; 3523 f = 0.6 + dist / 150 * 0.4; 3524 aim_accuracy *= f; 3525 } 3526 //add some random stuff to the aim direction depending on the aim accuracy 3527 if (aim_accuracy < 0.8) { 3528 VectorNormalize(dir); 3529 for (i = 0; i < 3; i++) dir[i] += 0.3 * crandom() * (1 - aim_accuracy); 3530 } 3531 //set the ideal view angles 3532 vectoangles(dir, bs->ideal_viewangles); 3533 //take the weapon spread into account for lower skilled bots 3534 bs->ideal_viewangles[PITCH] += 6 * wi.vspread * crandom() * (1 - aim_accuracy); 3535 bs->ideal_viewangles[PITCH] = AngleMod(bs->ideal_viewangles[PITCH]); 3536 bs->ideal_viewangles[YAW] += 6 * wi.hspread * crandom() * (1 - aim_accuracy); 3537 bs->ideal_viewangles[YAW] = AngleMod(bs->ideal_viewangles[YAW]); 3538 //if the bots should be really challenging 3539 if (bot_challenge.integer) { 3540 //if the bot is really accurate and has the enemy in view for some time 3541 if (aim_accuracy > 0.9 && bs->enemysight_time < FloatTime() - 1) { 3542 //set the view angles directly 3543 if (bs->ideal_viewangles[PITCH] > 180) bs->ideal_viewangles[PITCH] -= 360; 3544 VectorCopy(bs->ideal_viewangles, bs->viewangles); 3545 trap_EA_View(bs->client, bs->viewangles); 3546 } 3547 } 3548 } 3549 3550 /* 3551 ================== 3552 BotCheckAttack 3553 ================== 3554 */ 3555 void BotCheckAttack(bot_state_t *bs) { 3556 float points, reactiontime, fov, firethrottle; 3557 int attackentity; 3558 bsp_trace_t bsptrace; 3559 //float selfpreservation; 3560 vec3_t forward, right, start, end, dir, angles; 3561 weaponinfo_t wi; 3562 bsp_trace_t trace; 3563 aas_entityinfo_t entinfo; 3564 vec3_t mins = {-8, -8, -8}, maxs = {8, 8, 8}; 3565 3566 attackentity = bs->enemy; 3567 // 3568 BotEntityInfo(attackentity, &entinfo); 3569 // if not attacking a player 3570 if (attackentity >= MAX_CLIENTS) { 3571 #ifdef MISSIONPACK 3572 // if attacking an obelisk 3573 if ( entinfo.number == redobelisk.entitynum || 3574 entinfo.number == blueobelisk.entitynum ) { 3575 // if obelisk is respawning return 3576 if ( g_entities[entinfo.number].activator && 3577 g_entities[entinfo.number].activator->s.frame == 2 ) { 3578 return; 3579 } 3580 } 3581 #endif 3582 } 3583 // 3584 reactiontime = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_REACTIONTIME, 0, 1); 3585 if (bs->enemysight_time > FloatTime() - reactiontime) return; 3586 if (bs->teleport_time > FloatTime() - reactiontime) return; 3587 //if changing weapons 3588 if (bs->weaponchange_time > FloatTime() - 0.1) return; 3589 //check fire throttle characteristic 3590 if (bs->firethrottlewait_time > FloatTime()) return; 3591 firethrottle = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_FIRETHROTTLE, 0, 1); 3592 if (bs->firethrottleshoot_time < FloatTime()) { 3593 if (random() > firethrottle) { 3594 bs->firethrottlewait_time = FloatTime() + firethrottle; 3595 bs->firethrottleshoot_time = 0; 3596 } 3597 else { 3598 bs->firethrottleshoot_time = FloatTime() + 1 - firethrottle; 3599 bs->firethrottlewait_time = 0; 3600 } 3601 } 3602 // 3603 // 3604 VectorSubtract(bs->aimtarget, bs->eye, dir); 3605 // 3606 if (bs->weaponnum == WP_GAUNTLET) { 3607 if (VectorLengthSquared(dir) > Square(60)) { 3608 return; 3609 } 3610 } 3611 if (VectorLengthSquared(dir) < Square(100)) 3612 fov = 120; 3613 else 3614 fov = 50; 3615 // 3616 vectoangles(dir, angles); 3617 if (!InFieldOfVision(bs->viewangles, fov, angles)) 3618 return; 3619 BotAI_Trace(&bsptrace, bs->eye, NULL, NULL, bs->aimtarget, bs->client, CONTENTS_SOLID|CONTENTS_PLAYERCLIP); 3620 if (bsptrace.fraction < 1 && bsptrace.ent != attackentity) 3621 return; 3622 3623 //get the weapon info 3624 trap_BotGetWeaponInfo(bs->ws, bs->weaponnum, &wi); 3625 //get the start point shooting from 3626 VectorCopy(bs->origin, start); 3627 start[2] += bs->cur_ps.viewheight; 3628 AngleVectors(bs->viewangles, forward, right, NULL); 3629 start[0] += forward[0] * wi.offset[0] + right[0] * wi.offset[1]; 3630 start[1] += forward[1] * wi.offset[0] + right[1] * wi.offset[1]; 3631 start[2] += forward[2] * wi.offset[0] + right[2] * wi.offset[1] + wi.offset[2]; 3632 //end point aiming at 3633 VectorMA(start, 1000, forward, end); 3634 //a little back to make sure not inside a very close enemy 3635 VectorMA(start, -12, forward, start); 3636 BotAI_Trace(&trace, start, mins, maxs, end, bs->entitynum, MASK_SHOT); 3637 //if the entity is a client 3638 if (trace.ent > 0 && trace.ent <= MAX_CLIENTS) { 3639 if (trace.ent != attackentity) { 3640 //if a teammate is hit 3641 if (BotSameTeam(bs, trace.ent)) 3642 return; 3643 } 3644 } 3645 //if won't hit the enemy or not attacking a player (obelisk) 3646 if (trace.ent != attackentity || attackentity >= MAX_CLIENTS) { 3647 //if the projectile does radial damage 3648 if (wi.proj.damagetype & DAMAGETYPE_RADIAL) { 3649 if (trace.fraction * 1000 < wi.proj.radius) { 3650 points = (wi.proj.damage - 0.5 * trace.fraction * 1000) * 0.5; 3651 if (points > 0) { 3652 return; 3653 } 3654 } 3655 //FIXME: check if a teammate gets radial damage 3656 } 3657 } 3658 //if fire has to be release to activate weapon 3659 if (wi.flags & WFL_FIRERELEASED) { 3660 if (bs->flags & BFL_ATTACKED) { 3661 trap_EA_Attack(bs->client); 3662 } 3663 } 3664 else { 3665 trap_EA_Attack(bs->client); 3666 } 3667 bs->flags ^= BFL_ATTACKED; 3668 } 3669 3670 /* 3671 ================== 3672 BotMapScripts 3673 ================== 3674 */ 3675 void BotMapScripts(bot_state_t *bs) { 3676 char info[1024]; 3677 char mapname[128]; 3678 int i, shootbutton; 3679 float aim_accuracy; 3680 aas_entityinfo_t entinfo; 3681 vec3_t dir; 3682 3683 trap_GetServerinfo(info, sizeof(info)); 3684 3685 strncpy(mapname, Info_ValueForKey( info, "mapname" ), sizeof(mapname)-1); 3686 mapname[sizeof(mapname)-1] = '\0'; 3687 3688 if (!Q_stricmp(mapname, "q3tourney6")) { 3689 vec3_t mins = {700, 204, 672}, maxs = {964, 468, 680}; 3690 vec3_t buttonorg = {304, 352, 920}; 3691 //NOTE: NEVER use the func_bobbing in q3tourney6 3692 bs->tfl &= ~TFL_FUNCBOB; 3693 //if the bot is below the bounding box 3694 if (bs->origin[0] > mins[0] && bs->origin[0] < maxs[0]) { 3695 if (bs->origin[1] > mins[1] && bs->origin[1] < maxs[1]) { 3696 if (bs->origin[2] < mins[2]) { 3697 return; 3698 } 3699 } 3700 } 3701 shootbutton = qfalse; 3702 //if an enemy is below this bounding box then shoot the button 3703 for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { 3704 3705 if (i == bs->client) continue; 3706 // 3707 BotEntityInfo(i, &entinfo); 3708 // 3709 if (!entinfo.valid) continue; 3710 //if the enemy isn't dead and the enemy isn't the bot self 3711 if (EntityIsDead(&entinfo) || entinfo.number == bs->entitynum) continue; 3712 // 3713 if (entinfo.origin[0] > mins[0] && entinfo.origin[0] < maxs[0]) { 3714 if (entinfo.origin[1] > mins[1] && entinfo.origin[1] < maxs[1]) { 3715 if (entinfo.origin[2] < mins[2]) { 3716 //if there's a team mate below the crusher 3717 if (BotSameTeam(bs, i)) { 3718 shootbutton = qfalse; 3719 break; 3720 } 3721 else { 3722 shootbutton = qtrue; 3723 } 3724 } 3725 } 3726 } 3727 } 3728 if (shootbutton) { 3729 bs->flags |= BFL_IDEALVIEWSET; 3730 VectorSubtract(buttonorg, bs->eye, dir); 3731 vectoangles(dir, bs->ideal_viewangles); 3732 aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY, 0, 1); 3733 bs->ideal_viewangles[PITCH] += 8 * crandom() * (1 - aim_accuracy); 3734 bs->ideal_viewangles[PITCH] = AngleMod(bs->ideal_viewangles[PITCH]); 3735 bs->ideal_viewangles[YAW] += 8 * crandom() * (1 - aim_accuracy); 3736 bs->ideal_viewangles[YAW] = AngleMod(bs->ideal_viewangles[YAW]); 3737 // 3738 if (InFieldOfVision(bs->viewangles, 20, bs->ideal_viewangles)) { 3739 trap_EA_Attack(bs->client); 3740 } 3741 } 3742 } 3743 else if (!Q_stricmp(mapname, "mpq3tourney6")) { 3744 //NOTE: NEVER use the func_bobbing in mpq3tourney6 3745 bs->tfl &= ~TFL_FUNCBOB; 3746 } 3747 } 3748 3749 /* 3750 ================== 3751 BotSetMovedir 3752 ================== 3753 */ 3754 // bk001205 - made these static 3755 static vec3_t VEC_UP = {0, -1, 0}; 3756 static vec3_t MOVEDIR_UP = {0, 0, 1}; 3757 static vec3_t VEC_DOWN = {0, -2, 0}; 3758 static vec3_t MOVEDIR_DOWN = {0, 0, -1}; 3759 3760 void BotSetMovedir(vec3_t angles, vec3_t movedir) { 3761 if (VectorCompare(angles, VEC_UP)) { 3762 VectorCopy(MOVEDIR_UP, movedir); 3763 } 3764 else if (VectorCompare(angles, VEC_DOWN)) { 3765 VectorCopy(MOVEDIR_DOWN, movedir); 3766 } 3767 else { 3768 AngleVectors(angles, movedir, NULL, NULL); 3769 } 3770 } 3771 3772 /* 3773 ================== 3774 BotModelMinsMaxs 3775 3776 this is ugly 3777 ================== 3778 */ 3779 int BotModelMinsMaxs(int modelindex, int eType, int contents, vec3_t mins, vec3_t maxs) { 3780 gentity_t *ent; 3781 int i; 3782 3783 ent = &g_entities[0]; 3784 for (i = 0; i < level.num_entities; i++, ent++) { 3785 if ( !ent->inuse ) { 3786 continue; 3787 } 3788 if ( eType && ent->s.eType != eType) { 3789 continue; 3790 } 3791 if ( contents && ent->r.contents != contents) { 3792 continue; 3793 } 3794 if (ent->s.modelindex == modelindex) { 3795 if (mins) 3796 VectorAdd(ent->r.currentOrigin, ent->r.mins, mins); 3797 if (maxs) 3798 VectorAdd(ent->r.currentOrigin, ent->r.maxs, maxs); 3799 return i; 3800 } 3801 } 3802 if (mins) 3803 VectorClear(mins); 3804 if (maxs) 3805 VectorClear(maxs); 3806 return 0; 3807 } 3808 3809 /* 3810 ================== 3811 BotFuncButtonGoal 3812 ================== 3813 */ 3814 int BotFuncButtonActivateGoal(bot_state_t *bs, int bspent, bot_activategoal_t *activategoal) { 3815 int i, areas[10], numareas, modelindex, entitynum; 3816 char model[128]; 3817 float lip, dist, health, angle; 3818 vec3_t size, start, end, mins, maxs, angles, points[10]; 3819 vec3_t movedir, origin, goalorigin, bboxmins, bboxmaxs; 3820 vec3_t extramins = {1, 1, 1}, extramaxs = {-1, -1, -1}; 3821 bsp_trace_t bsptrace; 3822 3823 activategoal->shoot = qfalse; 3824 VectorClear(activategoal->target); 3825 //create a bot goal towards the button 3826 trap_AAS_ValueForBSPEpairKey(bspent, "model", model, sizeof(model)); 3827 if (!*model) 3828 return qfalse; 3829 modelindex = atoi(model+1); 3830 if (!modelindex) 3831 return qfalse; 3832 VectorClear(angles); 3833 entitynum = BotModelMinsMaxs(modelindex, ET_MOVER, 0, mins, maxs); 3834 //get the lip of the button 3835 trap_AAS_FloatForBSPEpairKey(bspent, "lip", &lip); 3836 if (!lip) lip = 4; 3837 //get the move direction from the angle 3838 trap_AAS_FloatForBSPEpairKey(bspent, "angle", &angle); 3839 VectorSet(angles, 0, angle, 0); 3840 BotSetMovedir(angles, movedir); 3841 //button size 3842 VectorSubtract(maxs, mins, size); 3843 //button origin 3844 VectorAdd(mins, maxs, origin); 3845 VectorScale(origin, 0.5, origin); 3846 //touch distance of the button 3847 dist = fabs(movedir[0]) * size[0] + fabs(movedir[1]) * size[1] + fabs(movedir[2]) * size[2]; 3848 dist *= 0.5; 3849 // 3850 trap_AAS_FloatForBSPEpairKey(bspent, "health", &health); 3851 //if the button is shootable 3852 if (health) { 3853 //calculate the shoot target 3854 VectorMA(origin, -dist, movedir, goalorigin); 3855 // 3856 VectorCopy(goalorigin, activategoal->target); 3857 activategoal->shoot = qtrue; 3858 // 3859 BotAI_Trace(&bsptrace, bs->eye, NULL, NULL, goalorigin, bs->entitynum, MASK_SHOT); 3860 // if the button is visible from the current position 3861 if (bsptrace.fraction >= 1.0 || bsptrace.ent == entitynum) { 3862 // 3863 activategoal->goal.entitynum = entitynum; //NOTE: this is the entity number of the shootable button 3864 activategoal->goal.number = 0; 3865 activategoal->goal.flags = 0; 3866 VectorCopy(bs->origin, activategoal->goal.origin); 3867 activategoal->goal.areanum = bs->areanum; 3868 VectorSet(activategoal->goal.mins, -8, -8, -8); 3869 VectorSet(activategoal->goal.maxs, 8, 8, 8); 3870 // 3871 return qtrue; 3872 } 3873 else { 3874 //create a goal from where the button is visible and shoot at the button from there 3875 //add bounding box size to the dist 3876 trap_AAS_PresenceTypeBoundingBox(PRESENCE_CROUCH, bboxmins, bboxmaxs); 3877 for (i = 0; i < 3; i++) { 3878 if (movedir[i] < 0) dist += fabs(movedir[i]) * fabs(bboxmaxs[i]); 3879 else dist += fabs(movedir[i]) * fabs(bboxmins[i]); 3880 } 3881 //calculate the goal origin 3882 VectorMA(origin, -dist, movedir, goalorigin); 3883 // 3884 VectorCopy(goalorigin, start); 3885 start[2] += 24; 3886 VectorCopy(start, end); 3887 end[2] -= 512; 3888 numareas = trap_AAS_TraceAreas(start, end, areas, points, 10); 3889 // 3890 for (i = numareas-1; i >= 0; i--) { 3891 if (trap_AAS_AreaReachability(areas[i])) { 3892 break; 3893 } 3894 } 3895 if (i < 0) { 3896 // FIXME: trace forward and maybe in other directions to find a valid area 3897 } 3898 if (i >= 0) { 3899 // 3900 VectorCopy(points[i], activategoal->goal.origin); 3901 activategoal->goal.areanum = areas[i]; 3902 VectorSet(activategoal->goal.mins, 8, 8, 8); 3903 VectorSet(activategoal->goal.maxs, -8, -8, -8); 3904 // 3905 for (i = 0; i < 3; i++) 3906 { 3907 if (movedir[i] < 0) activategoal->goal.maxs[i] += fabs(movedir[i]) * fabs(extramaxs[i]); 3908 else activategoal->goal.mins[i] += fabs(movedir[i]) * fabs(extramins[i]); 3909 } //end for 3910 // 3911 activategoal->goal.entitynum = entitynum; 3912 activategoal->goal.number = 0; 3913 activategoal->goal.flags = 0; 3914 return qtrue; 3915 } 3916 } 3917 return qfalse; 3918 } 3919 else { 3920 //add bounding box size to the dist 3921 trap_AAS_PresenceTypeBoundingBox(PRESENCE_CROUCH, bboxmins, bboxmaxs); 3922 for (i = 0; i < 3; i++) { 3923 if (movedir[i] < 0) dist += fabs(movedir[i]) * fabs(bboxmaxs[i]); 3924 else dist += fabs(movedir[i]) * fabs(bboxmins[i]); 3925 } 3926 //calculate the goal origin 3927 VectorMA(origin, -dist, movedir, goalorigin); 3928 // 3929 VectorCopy(goalorigin, start); 3930 start[2] += 24; 3931 VectorCopy(start, end); 3932 end[2] -= 100; 3933 numareas = trap_AAS_TraceAreas(start, end, areas, NULL, 10); 3934 // 3935 for (i = 0; i < numareas; i++) { 3936 if (trap_AAS_AreaReachability(areas[i])) { 3937 break; 3938 } 3939 } 3940 if (i < numareas) { 3941 // 3942 VectorCopy(origin, activategoal->goal.origin); 3943 activategoal->goal.areanum = areas[i]; 3944 VectorSubtract(mins, origin, activategoal->goal.mins); 3945 VectorSubtract(maxs, origin, activategoal->goal.maxs); 3946 // 3947 for (i = 0; i < 3; i++) 3948 { 3949 if (movedir[i] < 0) activategoal->goal.maxs[i] += fabs(movedir[i]) * fabs(extramaxs[i]); 3950 else activategoal->goal.mins[i] += fabs(movedir[i]) * fabs(extramins[i]); 3951 } //end for 3952 // 3953 activategoal->goal.entitynum = entitynum; 3954 activategoal->goal.number = 0; 3955 activategoal->goal.flags = 0; 3956 return qtrue; 3957 } 3958 } 3959 return qfalse; 3960 } 3961 3962 /* 3963 ================== 3964 BotFuncDoorGoal 3965 ================== 3966 */ 3967 int BotFuncDoorActivateGoal(bot_state_t *bs, int bspent, bot_activategoal_t *activategoal) { 3968 int modelindex, entitynum; 3969 char model[MAX_INFO_STRING]; 3970 vec3_t mins, maxs, origin, angles; 3971 3972 //shoot at the shootable door 3973 trap_AAS_ValueForBSPEpairKey(bspent, "model", model, sizeof(model)); 3974 if (!*model) 3975 return qfalse; 3976 modelindex = atoi(model+1); 3977 if (!modelindex) 3978 return qfalse; 3979 VectorClear(angles); 3980 entitynum = BotModelMinsMaxs(modelindex, ET_MOVER, 0, mins, maxs); 3981 //door origin 3982 VectorAdd(mins, maxs, origin); 3983 VectorScale(origin, 0.5, origin); 3984 VectorCopy(origin, activategoal->target); 3985 activategoal->shoot = qtrue; 3986 // 3987 activategoal->goal.entitynum = entitynum; //NOTE: this is the entity number of the shootable door 3988 activategoal->goal.number = 0; 3989 activategoal->goal.flags = 0; 3990 VectorCopy(bs->origin, activategoal->goal.origin); 3991 activategoal->goal.areanum = bs->areanum; 3992 VectorSet(activategoal->goal.mins, -8, -8, -8); 3993 VectorSet(activategoal->goal.maxs, 8, 8, 8); 3994 return qtrue; 3995 } 3996 3997 /* 3998 ================== 3999 BotTriggerMultipleGoal 4000 ================== 4001 */ 4002 int BotTriggerMultipleActivateGoal(bot_state_t *bs, int bspent, bot_activategoal_t *activategoal) { 4003 int i, areas[10], numareas, modelindex, entitynum; 4004 char model[128]; 4005 vec3_t start, end, mins, maxs, angles; 4006 vec3_t origin, goalorigin; 4007 4008 activategoal->shoot = qfalse; 4009 VectorClear(activategoal->target); 4010 //create a bot goal towards the trigger 4011 trap_AAS_ValueForBSPEpairKey(bspent, "model", model, sizeof(model)); 4012 if (!*model) 4013 return qfalse; 4014 modelindex = atoi(model+1); 4015 if (!modelindex) 4016 return qfalse; 4017 VectorClear(angles); 4018 entitynum = BotModelMinsMaxs(modelindex, 0, CONTENTS_TRIGGER, mins, maxs); 4019 //trigger origin 4020 VectorAdd(mins, maxs, origin); 4021 VectorScale(origin, 0.5, origin); 4022 VectorCopy(origin, goalorigin); 4023 // 4024 VectorCopy(goalorigin, start); 4025 start[2] += 24; 4026 VectorCopy(start, end); 4027 end[2] -= 100; 4028 numareas = trap_AAS_TraceAreas(start, end, areas, NULL, 10); 4029 // 4030 for (i = 0; i < numareas; i++) { 4031 if (trap_AAS_AreaReachability(areas[i])) { 4032 break; 4033 } 4034 } 4035 if (i < numareas) { 4036 VectorCopy(origin, activategoal->goal.origin); 4037 activategoal->goal.areanum = areas[i]; 4038 VectorSubtract(mins, origin, activategoal->goal.mins); 4039 VectorSubtract(maxs, origin, activategoal->goal.maxs); 4040 // 4041 activategoal->goal.entitynum = entitynum; 4042 activategoal->goal.number = 0; 4043 activategoal->goal.flags = 0; 4044 return qtrue; 4045 } 4046 return qfalse; 4047 } 4048 4049 /* 4050 ================== 4051 BotPopFromActivateGoalStack 4052 ================== 4053 */ 4054 int BotPopFromActivateGoalStack(bot_state_t *bs) { 4055 if (!bs->activatestack) 4056 return qfalse; 4057 BotEnableActivateGoalAreas(bs->activatestack, qtrue); 4058 bs->activatestack->inuse = qfalse; 4059 bs->activatestack->justused_time = FloatTime(); 4060 bs->activatestack = bs->activatestack->next; 4061 return qtrue; 4062 } 4063 4064 /* 4065 ================== 4066 BotPushOntoActivateGoalStack 4067 ================== 4068 */ 4069 int BotPushOntoActivateGoalStack(bot_state_t *bs, bot_activategoal_t *activategoal) { 4070 int i, best; 4071 float besttime; 4072 4073 best = -1; 4074 besttime = FloatTime() + 9999; 4075 // 4076 for (i = 0; i < MAX_ACTIVATESTACK; i++) { 4077 if (!bs->activategoalheap[i].inuse) { 4078 if (bs->activategoalheap[i].justused_time < besttime) { 4079 besttime = bs->activategoalheap[i].justused_time; 4080 best = i; 4081 } 4082 } 4083 } 4084 if (best != -1) { 4085 memcpy(&bs->activategoalheap[best], activategoal, sizeof(bot_activategoal_t)); 4086 bs->activategoalheap[best].inuse = qtrue; 4087 bs->activategoalheap[best].next = bs->activatestack; 4088 bs->activatestack = &bs->activategoalheap[best]; 4089 return qtrue; 4090 } 4091 return qfalse; 4092 } 4093 4094 /* 4095 ================== 4096 BotClearActivateGoalStack 4097 ================== 4098 */ 4099 void BotClearActivateGoalStack(bot_state_t *bs) { 4100 while(bs->activatestack) 4101 BotPopFromActivateGoalStack(bs); 4102 } 4103 4104 /* 4105 ================== 4106 BotEnableActivateGoalAreas 4107 ================== 4108 */ 4109 void BotEnableActivateGoalAreas(bot_activategoal_t *activategoal, int enable) { 4110 int i; 4111 4112 if (activategoal->areasdisabled == !enable) 4113 return; 4114 for (i = 0; i < activategoal->numareas; i++) 4115 trap_AAS_EnableRoutingArea( activategoal->areas[i], enable ); 4116 activategoal->areasdisabled = !enable; 4117 } 4118 4119 /* 4120 ================== 4121 BotIsGoingToActivateEntity 4122 ================== 4123 */ 4124 int BotIsGoingToActivateEntity(bot_state_t *bs, int entitynum) { 4125 bot_activategoal_t *a; 4126 int i; 4127 4128 for (a = bs->activatestack; a; a = a->next) { 4129 if (a->time < FloatTime()) 4130 continue; 4131 if (a->goal.entitynum == entitynum) 4132 return qtrue; 4133 } 4134 for (i = 0; i < MAX_ACTIVATESTACK; i++) { 4135 if (bs->activategoalheap[i].inuse) 4136 continue; 4137 // 4138 if (bs->activategoalheap[i].goal.entitynum == entitynum) { 4139 // if the bot went for this goal less than 2 seconds ago 4140 if (bs->activategoalheap[i].justused_time > FloatTime() - 2) 4141 return qtrue; 4142 } 4143 } 4144 return qfalse; 4145 } 4146 4147 /* 4148 ================== 4149 BotGetActivateGoal 4150 4151 returns the number of the bsp entity to activate 4152 goal->entitynum will be set to the game entity to activate 4153 ================== 4154 */ 4155 //#define OBSTACLEDEBUG 4156 4157 int BotGetActivateGoal(bot_state_t *bs, int entitynum, bot_activategoal_t *activategoal) { 4158 int i, ent, cur_entities[10], spawnflags, modelindex, areas[MAX_ACTIVATEAREAS*2], numareas, t; 4159 char model[MAX_INFO_STRING], tmpmodel[128]; 4160 char target[128], classname[128]; 4161 float health; 4162 char targetname[10][128]; 4163 aas_entityinfo_t entinfo; 4164 aas_areainfo_t areainfo; 4165 vec3_t origin, angles, absmins, absmaxs; 4166 4167 memset(activategoal, 0, sizeof(bot_activategoal_t)); 4168 BotEntityInfo(entitynum, &entinfo); 4169 Com_sprintf(model, sizeof( model ), "*%d", entinfo.modelindex); 4170 for (ent = trap_AAS_NextBSPEntity(0); ent; ent = trap_AAS_NextBSPEntity(ent)) { 4171 if (!trap_AAS_ValueForBSPEpairKey(ent, "model", tmpmodel, sizeof(tmpmodel))) continue; 4172 if (!strcmp(model, tmpmodel)) break; 4173 } 4174 if (!ent) { 4175 BotAI_Print(PRT_ERROR, "BotGetActivateGoal: no entity found with model %s\n", model); 4176 return 0; 4177 } 4178 trap_AAS_ValueForBSPEpairKey(ent, "classname", classname, sizeof(classname)); 4179 if (!classname) { 4180 BotAI_Print(PRT_ERROR, "BotGetActivateGoal: entity with model %s has no classname\n", model); 4181 return 0; 4182 } 4183 //if it is a door 4184 if (!strcmp(classname, "func_door")) { 4185 if (trap_AAS_FloatForBSPEpairKey(ent, "health", &health)) { 4186 //if the door has health then the door must be shot to open 4187 if (health) { 4188 BotFuncDoorActivateGoal(bs, ent, activategoal); 4189 return ent; 4190 } 4191 } 4192 // 4193 trap_AAS_IntForBSPEpairKey(ent, "spawnflags", &spawnflags); 4194 // if the door starts open then just wait for the door to return 4195 if ( spawnflags & 1 ) 4196 return 0; 4197 //get the door origin 4198 if (!trap_AAS_VectorForBSPEpairKey(ent, "origin", origin)) { 4199 VectorClear(origin); 4200 } 4201 //if the door is open or opening already 4202 if (!VectorCompare(origin, entinfo.origin)) 4203 return 0; 4204 // store all the areas the door is in 4205 trap_AAS_ValueForBSPEpairKey(ent, "model", model, sizeof(model)); 4206 if (*model) { 4207 modelindex = atoi(model+1); 4208 if (modelindex) { 4209 VectorClear(angles); 4210 BotModelMinsMaxs(modelindex, ET_MOVER, 0, absmins, absmaxs); 4211 // 4212 numareas = trap_AAS_BBoxAreas(absmins, absmaxs, areas, MAX_ACTIVATEAREAS*2); 4213 // store the areas with reachabilities first 4214 for (i = 0; i < numareas; i++) { 4215 if (activategoal->numareas >= MAX_ACTIVATEAREAS) 4216 break; 4217 if ( !trap_AAS_AreaReachability(areas[i]) ) { 4218 continue; 4219 } 4220 trap_AAS_AreaInfo(areas[i], &areainfo); 4221 if (areainfo.contents & AREACONTENTS_MOVER) { 4222 activategoal->areas[activategoal->numareas++] = areas[i]; 4223 } 4224 } 4225 // store any remaining areas 4226 for (i = 0; i < numareas; i++) { 4227 if (activategoal->numareas >= MAX_ACTIVATEAREAS) 4228 break; 4229 if ( trap_AAS_AreaReachability(areas[i]) ) { 4230 continue; 4231 } 4232 trap_AAS_AreaInfo(areas[i], &areainfo); 4233 if (areainfo.contents & AREACONTENTS_MOVER) { 4234 activategoal->areas[activategoal->numareas++] = areas[i]; 4235 } 4236 } 4237 } 4238 } 4239 } 4240 // if the bot is blocked by or standing on top of a button 4241 if (!strcmp(classname, "func_button")) { 4242 return 0; 4243 } 4244 // get the targetname so we can find an entity with a matching target 4245 if (!trap_AAS_ValueForBSPEpairKey(ent, "targetname", targetname[0], sizeof(targetname[0]))) { 4246 if (bot_developer.integer) { 4247 BotAI_Print(PRT_ERROR, "BotGetActivateGoal: entity with model \"%s\" has no targetname\n", model); 4248 } 4249 return 0; 4250 } 4251 // allow tree-like activation 4252 cur_entities[0] = trap_AAS_NextBSPEntity(0); 4253 for (i = 0; i >= 0 && i < 10;) { 4254 for (ent = cur_entities[i]; ent; ent = trap_AAS_NextBSPEntity(ent)) { 4255 if (!trap_AAS_ValueForBSPEpairKey(ent, "target", target, sizeof(target))) continue; 4256 if (!strcmp(targetname[i], target)) { 4257 cur_entities[i] = trap_AAS_NextBSPEntity(ent); 4258 break; 4259 } 4260 } 4261 if (!ent) { 4262 if (bot_developer.integer) { 4263 BotAI_Print(PRT_ERROR, "BotGetActivateGoal: no entity with target \"%s\"\n", targetname[i]); 4264 } 4265 i--; 4266 continue; 4267 } 4268 if (!trap_AAS_ValueForBSPEpairKey(ent, "classname", classname, sizeof(classname))) { 4269 if (bot_developer.integer) { 4270 BotAI_Print(PRT_ERROR, "BotGetActivateGoal: entity with target \"%s\" has no classname\n", targetname[i]); 4271 } 4272 continue; 4273 } 4274 // BSP button model 4275 if (!strcmp(classname, "func_button")) { 4276 // 4277 if (!BotFuncButtonActivateGoal(bs, ent, activategoal)) 4278 continue; 4279 // if the bot tries to activate this button already 4280 if ( bs->activatestack && bs->activatestack->inuse && 4281 bs->activatestack->goal.entitynum == activategoal->goal.entitynum && 4282 bs->activatestack->time > FloatTime() && 4283 bs->activatestack->start_time < FloatTime() - 2) 4284 continue; 4285 // if the bot is in a reachability area 4286 if ( trap_AAS_AreaReachability(bs->areanum) ) { 4287 // disable all areas the blocking entity is in 4288 BotEnableActivateGoalAreas( activategoal, qfalse ); 4289 // 4290 t = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, activategoal->goal.areanum, bs->tfl); 4291 // if the button is not reachable 4292 if (!t) { 4293 continue; 4294 } 4295 activategoal->time = FloatTime() + t * 0.01 + 5; 4296 } 4297 return ent; 4298 } 4299 // invisible trigger multiple box 4300 else if (!strcmp(classname, "trigger_multiple")) { 4301 // 4302 if (!BotTriggerMultipleActivateGoal(bs, ent, activategoal)) 4303 continue; 4304 // if the bot tries to activate this trigger already 4305 if ( bs->activatestack && bs->activatestack->inuse && 4306 bs->activatestack->goal.entitynum == activategoal->goal.entitynum && 4307 bs->activatestack->time > FloatTime() && 4308 bs->activatestack->start_time < FloatTime() - 2) 4309 continue; 4310 // if the bot is in a reachability area 4311 if ( trap_AAS_AreaReachability(bs->areanum) ) { 4312 // disable all areas the blocking entity is in 4313 BotEnableActivateGoalAreas( activategoal, qfalse ); 4314 // 4315 t = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, activategoal->goal.areanum, bs->tfl); 4316 // if the trigger is not reachable 4317 if (!t) { 4318 continue; 4319 } 4320 activategoal->time = FloatTime() + t * 0.01 + 5; 4321 } 4322 return ent; 4323 } 4324 else if (!strcmp(classname, "func_timer")) { 4325 // just skip the func_timer 4326 continue; 4327 } 4328 // the actual button or trigger might be linked through a target_relay or target_delay 4329 else if (!strcmp(classname, "target_relay") || !strcmp(classname, "target_delay")) { 4330 if (trap_AAS_ValueForBSPEpairKey(ent, "targetname", targetname[i+1], sizeof(targetname[0]))) { 4331 i++; 4332 cur_entities[i] = trap_AAS_NextBSPEntity(0); 4333 } 4334 } 4335 } 4336 #ifdef OBSTACLEDEBUG 4337 BotAI_Print(PRT_ERROR, "BotGetActivateGoal: no valid activator for entity with target \"%s\"\n", targetname[0]); 4338 #endif 4339 return 0; 4340 } 4341 4342 /* 4343 ================== 4344 BotGoForActivateGoal 4345 ================== 4346 */ 4347 int BotGoForActivateGoal(bot_state_t *bs, bot_activategoal_t *activategoal) { 4348 aas_entityinfo_t activateinfo; 4349 4350 activategoal->inuse = qtrue; 4351 if (!activategoal->time) 4352 activategoal->time = FloatTime() + 10; 4353 activategoal->start_time = FloatTime(); 4354 BotEntityInfo(activategoal->goal.entitynum, &activateinfo); 4355 VectorCopy(activateinfo.origin, activategoal->origin); 4356 // 4357 if (BotPushOntoActivateGoalStack(bs, activategoal)) { 4358 // enter the activate entity AI node 4359 AIEnter_Seek_ActivateEntity(bs, "BotGoForActivateGoal"); 4360 return qtrue; 4361 } 4362 else { 4363 // enable any routing areas that were disabled 4364 BotEnableActivateGoalAreas(activategoal, qtrue); 4365 return qfalse; 4366 } 4367 } 4368 4369 /* 4370 ================== 4371 BotPrintActivateGoalInfo 4372 ================== 4373 */ 4374 void BotPrintActivateGoalInfo(bot_state_t *bs, bot_activategoal_t *activategoal, int bspent) { 4375 char netname[MAX_NETNAME]; 4376 char classname[128]; 4377 char buf[128]; 4378 4379 ClientName(bs->client, netname, sizeof(netname)); 4380 trap_AAS_ValueForBSPEpairKey(bspent, "classname", classname, sizeof(classname)); 4381 if (activategoal->shoot) { 4382 Com_sprintf(buf, sizeof(buf), "%s: I have to shoot at a %s from %1.1f %1.1f %1.1f in area %d\n", 4383 netname, classname, 4384 activategoal->goal.origin[0], 4385 activategoal->goal.origin[1], 4386 activategoal->goal.origin[2], 4387 activategoal->goal.areanum); 4388 } 4389 else { 4390 Com_sprintf(buf, sizeof(buf), "%s: I have to activate a %s at %1.1f %1.1f %1.1f in area %d\n", 4391 netname, classname, 4392 activategoal->goal.origin[0], 4393 activategoal->goal.origin[1], 4394 activategoal->goal.origin[2], 4395 activategoal->goal.areanum); 4396 } 4397 trap_EA_Say(bs->client, buf); 4398 } 4399 4400 /* 4401 ================== 4402 BotRandomMove 4403 ================== 4404 */ 4405 void BotRandomMove(bot_state_t *bs, bot_moveresult_t *moveresult) { 4406 vec3_t dir, angles; 4407 4408 angles[0] = 0; 4409 angles[1] = random() * 360; 4410 angles[2] = 0; 4411 AngleVectors(angles, dir, NULL, NULL); 4412 4413 trap_BotMoveInDirection(bs->ms, dir, 400, MOVE_WALK); 4414 4415 moveresult->failure = qfalse; 4416 VectorCopy(dir, moveresult->movedir); 4417 } 4418 4419 /* 4420 ================== 4421 BotAIBlocked 4422 4423 Very basic handling of bots being blocked by other entities. 4424 Check what kind of entity is blocking the bot and try to activate 4425 it. If that's not an option then try to walk around or over the entity. 4426 Before the bot ends in this part of the AI it should predict which doors to 4427 open, which buttons to activate etc. 4428 ================== 4429 */ 4430 void BotAIBlocked(bot_state_t *bs, bot_moveresult_t *moveresult, int activate) { 4431 int movetype, bspent; 4432 vec3_t hordir, start, end, mins, maxs, sideward, angles, up = {0, 0, 1}; 4433 aas_entityinfo_t entinfo; 4434 bot_activategoal_t activategoal; 4435 4436 // if the bot is not blocked by anything 4437 if (!moveresult->blocked) { 4438 bs->notblocked_time = FloatTime(); 4439 return; 4440 } 4441 // if stuck in a solid area 4442 if ( moveresult->type == RESULTTYPE_INSOLIDAREA ) { 4443 // move in a random direction in the hope to get out 4444 BotRandomMove(bs, moveresult); 4445 // 4446 return; 4447 } 4448 // get info for the entity that is blocking the bot 4449 BotEntityInfo(moveresult->blockentity, &entinfo); 4450 #ifdef OBSTACLEDEBUG 4451 ClientName(bs->client, netname, sizeof(netname)); 4452 BotAI_Print(PRT_MESSAGE, "%s: I'm blocked by model %d\n", netname, entinfo.modelindex); 4453 #endif // OBSTACLEDEBUG 4454 // if blocked by a bsp model and the bot wants to activate it 4455 if (activate && entinfo.modelindex > 0 && entinfo.modelindex <= max_bspmodelindex) { 4456 // find the bsp entity which should be activated in order to get the blocking entity out of the way 4457 bspent = BotGetActivateGoal(bs, entinfo.number, &activategoal); 4458 if (bspent) { 4459 // 4460 if (bs->activatestack && !bs->activatestack->inuse) 4461 bs->activatestack = NULL; 4462 // if not already trying to activate this entity 4463 if (!BotIsGoingToActivateEntity(bs, activategoal.goal.entitynum)) { 4464 // 4465 BotGoForActivateGoal(bs, &activategoal); 4466 } 4467 // if ontop of an obstacle or 4468 // if the bot is not in a reachability area it'll still 4469 // need some dynamic obstacle avoidance, otherwise return 4470 if (!(moveresult->flags & MOVERESULT_ONTOPOFOBSTACLE) && 4471 trap_AAS_AreaReachability(bs->areanum)) 4472 return; 4473 } 4474 else { 4475 // enable any routing areas that were disabled 4476 BotEnableActivateGoalAreas(&activategoal, qtrue); 4477 } 4478 } 4479 // just some basic dynamic obstacle avoidance code 4480 hordir[0] = moveresult->movedir[0]; 4481 hordir[1] = moveresult->movedir[1]; 4482 hordir[2] = 0; 4483 // if no direction just take a random direction 4484 if (VectorNormalize(hordir) < 0.1) { 4485 VectorSet(angles, 0, 360 * random(), 0); 4486 AngleVectors(angles, hordir, NULL, NULL); 4487 } 4488 // 4489 //if (moveresult->flags & MOVERESULT_ONTOPOFOBSTACLE) movetype = MOVE_JUMP; 4490 //else 4491 movetype = MOVE_WALK; 4492 // if there's an obstacle at the bot's feet and head then 4493 // the bot might be able to crouch through 4494 VectorCopy(bs->origin, start); 4495 start[2] += 18; 4496 VectorMA(start, 5, hordir, end); 4497 VectorSet(mins, -16, -16, -24); 4498 VectorSet(maxs, 16, 16, 4); 4499 // 4500 //bsptrace = AAS_Trace(start, mins, maxs, end, bs->entitynum, MASK_PLAYERSOLID); 4501 //if (bsptrace.fraction >= 1) movetype = MOVE_CROUCH; 4502 // get the sideward vector 4503 CrossProduct(hordir, up, sideward); 4504 // 4505 if (bs->flags & BFL_AVOIDRIGHT) VectorNegate(sideward, sideward); 4506 // try to crouch straight forward? 4507 if (movetype != MOVE_CROUCH || !trap_BotMoveInDirection(bs->ms, hordir, 400, movetype)) { 4508 // perform the movement 4509 if (!trap_BotMoveInDirection(bs->ms, sideward, 400, movetype)) { 4510 // flip the avoid direction flag 4511 bs->flags ^= BFL_AVOIDRIGHT; 4512 // flip the direction 4513 // VectorNegate(sideward, sideward); 4514 VectorMA(sideward, -1, hordir, sideward); 4515 // move in the other direction 4516 trap_BotMoveInDirection(bs->ms, sideward, 400, movetype); 4517 } 4518 } 4519 // 4520 if (bs->notblocked_time < FloatTime() - 0.4) { 4521 // just reset goals and hope the bot will go into another direction? 4522 // is this still needed?? 4523 if (bs->ainode == AINode_Seek_NBG) bs->nbg_time = 0; 4524 else if (bs->ainode == AINode_Seek_LTG) bs->ltg_time = 0; 4525 } 4526 } 4527 4528 /* 4529 ================== 4530 BotAIPredictObstacles 4531 4532 Predict the route towards the goal and check if the bot 4533 will be blocked by certain obstacles. When the bot has obstacles 4534 on it's path the bot should figure out if they can be removed 4535 by activating certain entities. 4536 ================== 4537 */ 4538 int BotAIPredictObstacles(bot_state_t *bs, bot_goal_t *goal) { 4539 int modelnum, entitynum, bspent; 4540 bot_activategoal_t activategoal; 4541 aas_predictroute_t route; 4542 4543 if (!bot_predictobstacles.integer) 4544 return qfalse; 4545 4546 // always predict when the goal change or at regular intervals 4547 if (bs->predictobstacles_goalareanum == goal->areanum && 4548 bs->predictobstacles_time > FloatTime() - 6) { 4549 return qfalse; 4550 } 4551 bs->predictobstacles_goalareanum = goal->areanum; 4552 bs->predictobstacles_time = FloatTime(); 4553 4554 // predict at most 100 areas or 10 seconds ahead 4555 trap_AAS_PredictRoute(&route, bs->areanum, bs->origin, 4556 goal->areanum, bs->tfl, 100, 1000, 4557 RSE_USETRAVELTYPE|RSE_ENTERCONTENTS, 4558 AREACONTENTS_MOVER, TFL_BRIDGE, 0); 4559 // if bot has to travel through an area with a mover 4560 if (route.stopevent & RSE_ENTERCONTENTS) { 4561 // if the bot will run into a mover 4562 if (route.endcontents & AREACONTENTS_MOVER) { 4563 //NOTE: this only works with bspc 2.1 or higher 4564 modelnum = (route.endcontents & AREACONTENTS_MODELNUM) >> AREACONTENTS_MODELNUMSHIFT; 4565 if (modelnum) { 4566 // 4567 entitynum = BotModelMinsMaxs(modelnum, ET_MOVER, 0, NULL, NULL); 4568 if (entitynum) { 4569 //NOTE: BotGetActivateGoal already checks if the door is open or not 4570 bspent = BotGetActivateGoal(bs, entitynum, &activategoal); 4571 if (bspent) { 4572 // 4573 if (bs->activatestack && !bs->activatestack->inuse) 4574 bs->activatestack = NULL; 4575 // if not already trying to activate this entity 4576 if (!BotIsGoingToActivateEntity(bs, activategoal.goal.entitynum)) { 4577 // 4578 //BotAI_Print(PRT_MESSAGE, "blocked by mover model %d, entity %d ?\n", modelnum, entitynum); 4579 // 4580 BotGoForActivateGoal(bs, &activategoal); 4581 return qtrue; 4582 } 4583 else { 4584 // enable any routing areas that were disabled 4585 BotEnableActivateGoalAreas(&activategoal, qtrue); 4586 } 4587 } 4588 } 4589 } 4590 } 4591 } 4592 else if (route.stopevent & RSE_USETRAVELTYPE) { 4593 if (route.endtravelflags & TFL_BRIDGE) { 4594 //FIXME: check if the bridge is available to travel over 4595 } 4596 } 4597 return qfalse; 4598 } 4599 4600 /* 4601 ================== 4602 BotCheckConsoleMessages 4603 ================== 4604 */ 4605 void BotCheckConsoleMessages(bot_state_t *bs) { 4606 char botname[MAX_NETNAME], message[MAX_MESSAGE_SIZE], netname[MAX_NETNAME], *ptr; 4607 float chat_reply; 4608 int context, handle; 4609 bot_consolemessage_t m; 4610 bot_match_t match; 4611 4612 //the name of this bot 4613 ClientName(bs->client, botname, sizeof(botname)); 4614 // 4615 while((handle = trap_BotNextConsoleMessage(bs->cs, &m)) != 0) { 4616 //if the chat state is flooded with messages the bot will read them quickly 4617 if (trap_BotNumConsoleMessages(bs->cs) < 10) { 4618 //if it is a chat message the bot needs some time to read it 4619 if (m.type == CMS_CHAT && m.time > FloatTime() - (1 + random())) break; 4620 } 4621 // 4622 ptr = m.message; 4623 //if it is a chat message then don't unify white spaces and don't 4624 //replace synonyms in the netname 4625 if (m.type == CMS_CHAT) { 4626 // 4627 if (trap_BotFindMatch(m.message, &match, MTCONTEXT_REPLYCHAT)) { 4628 ptr = m.message + match.variables[MESSAGE].offset; 4629 } 4630 } 4631 //unify the white spaces in the message 4632 trap_UnifyWhiteSpaces(ptr); 4633 //replace synonyms in the right context 4634 context = BotSynonymContext(bs); 4635 trap_BotReplaceSynonyms(ptr, context); 4636 //if there's no match 4637 if (!BotMatchMessage(bs, m.message)) { 4638 //if it is a chat message 4639 if (m.type == CMS_CHAT && !bot_nochat.integer) { 4640 // 4641 if (!trap_BotFindMatch(m.message, &match, MTCONTEXT_REPLYCHAT)) { 4642 trap_BotRemoveConsoleMessage(bs->cs, handle); 4643 continue; 4644 } 4645 //don't use eliza chats with team messages 4646 if (match.subtype & ST_TEAM) { 4647 trap_BotRemoveConsoleMessage(bs->cs, handle); 4648 continue; 4649 } 4650 // 4651 trap_BotMatchVariable(&match, NETNAME, netname, sizeof(netname)); 4652 trap_BotMatchVariable(&match, MESSAGE, message, sizeof(message)); 4653 //if this is a message from the bot self 4654 if (bs->client == ClientFromName(netname)) { 4655 trap_BotRemoveConsoleMessage(bs->cs, handle); 4656 continue; 4657 } 4658 //unify the message 4659 trap_UnifyWhiteSpaces(message); 4660 // 4661 trap_Cvar_Update(&bot_testrchat); 4662 if (bot_testrchat.integer) { 4663 // 4664 trap_BotLibVarSet("bot_testrchat", "1"); 4665 //if bot replies with a chat message 4666 if (trap_BotReplyChat(bs->cs, message, context, CONTEXT_REPLY, 4667 NULL, NULL, 4668 NULL, NULL, 4669 NULL, NULL, 4670 botname, netname)) { 4671 BotAI_Print(PRT_MESSAGE, "------------------------\n"); 4672 } 4673 else { 4674 BotAI_Print(PRT_MESSAGE, "**** no valid reply ****\n"); 4675 } 4676 } 4677 //if at a valid chat position and not chatting already and not in teamplay 4678 else if (bs->ainode != AINode_Stand && BotValidChatPosition(bs) && !TeamPlayIsOn()) { 4679 chat_reply = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_REPLY, 0, 1); 4680 if (random() < 1.5 / (NumBots()+1) && random() < chat_reply) { 4681 //if bot replies with a chat message 4682 if (trap_BotReplyChat(bs->cs, message, context, CONTEXT_REPLY, 4683 NULL, NULL, 4684 NULL, NULL, 4685 NULL, NULL, 4686 botname, netname)) { 4687 //remove the console message 4688 trap_BotRemoveConsoleMessage(bs->cs, handle); 4689 bs->stand_time = FloatTime() + BotChatTime(bs); 4690 AIEnter_Stand(bs, "BotCheckConsoleMessages: reply chat"); 4691 //EA_Say(bs->client, bs->cs.chatmessage); 4692 break; 4693 } 4694 } 4695 } 4696 } 4697 } 4698 //remove the console message 4699 trap_BotRemoveConsoleMessage(bs->cs, handle); 4700 } 4701 } 4702 4703 /* 4704 ================== 4705 BotCheckEvents 4706 ================== 4707 */ 4708 void BotCheckForGrenades(bot_state_t *bs, entityState_t *state) { 4709 // if this is not a grenade 4710 if (state->eType != ET_MISSILE || state->weapon != WP_GRENADE_LAUNCHER) 4711 return; 4712 // try to avoid the grenade 4713 trap_BotAddAvoidSpot(bs->ms, state->pos.trBase, 160, AVOID_ALWAYS); 4714 } 4715 4716 #ifdef MISSIONPACK 4717 /* 4718 ================== 4719 BotCheckForProxMines 4720 ================== 4721 */ 4722 void BotCheckForProxMines(bot_state_t *bs, entityState_t *state) { 4723 // if this is not a prox mine 4724 if (state->eType != ET_MISSILE || state->weapon != WP_PROX_LAUNCHER) 4725 return; 4726 // if this prox mine is from someone on our own team 4727 if (state->generic1 == BotTeam(bs)) 4728 return; 4729 // if the bot doesn't have a weapon to deactivate the mine 4730 if (!(bs->inventory[INVENTORY_PLASMAGUN] > 0 && bs->inventory[INVENTORY_CELLS] > 0) && 4731 !(bs->inventory[INVENTORY_ROCKETLAUNCHER] > 0 && bs->inventory[INVENTORY_ROCKETS] > 0) && 4732 !(bs->inventory[INVENTORY_BFG10K] > 0 && bs->inventory[INVENTORY_BFGAMMO] > 0) ) { 4733 return; 4734 } 4735 // try to avoid the prox mine 4736 trap_BotAddAvoidSpot(bs->ms, state->pos.trBase, 160, AVOID_ALWAYS); 4737 // 4738 if (bs->numproxmines >= MAX_PROXMINES) 4739 return; 4740 bs->proxmines[bs->numproxmines] = state->number; 4741 bs->numproxmines++; 4742 } 4743 4744 /* 4745 ================== 4746 BotCheckForKamikazeBody 4747 ================== 4748 */ 4749 void BotCheckForKamikazeBody(bot_state_t *bs, entityState_t *state) { 4750 // if this entity is not wearing the kamikaze 4751 if (!(state->eFlags & EF_KAMIKAZE)) 4752 return; 4753 // if this entity isn't dead 4754 if (!(state->eFlags & EF_DEAD)) 4755 return; 4756 //remember this kamikaze body 4757 bs->kamikazebody = state->number; 4758 } 4759 #endif 4760 4761 /* 4762 ================== 4763 BotCheckEvents 4764 ================== 4765 */ 4766 void BotCheckEvents(bot_state_t *bs, entityState_t *state) { 4767 int event; 4768 char buf[128]; 4769 #ifdef MISSIONPACK 4770 aas_entityinfo_t entinfo; 4771 #endif 4772 4773 //NOTE: this sucks, we're accessing the gentity_t directly 4774 //but there's no other fast way to do it right now 4775 if (bs->entityeventTime[state->number] == g_entities[state->number].eventTime) { 4776 return; 4777 } 4778 bs->entityeventTime[state->number] = g_entities[state->number].eventTime; 4779 //if it's an event only entity 4780 if (state->eType > ET_EVENTS) { 4781 event = (state->eType - ET_EVENTS) & ~EV_EVENT_BITS; 4782 } 4783 else { 4784 event = state->event & ~EV_EVENT_BITS; 4785 } 4786 // 4787 switch(event) { 4788 //client obituary event 4789 case EV_OBITUARY: 4790 { 4791 int target, attacker, mod; 4792 4793 target = state->otherEntityNum; 4794 attacker = state->otherEntityNum2; 4795 mod = state->eventParm; 4796 // 4797 if (target == bs->client) { 4798 bs->botdeathtype = mod; 4799 bs->lastkilledby = attacker; 4800 // 4801 if (target == attacker || 4802 target == ENTITYNUM_NONE || 4803 target == ENTITYNUM_WORLD) bs->botsuicide = qtrue; 4804 else bs->botsuicide = qfalse; 4805 // 4806 bs->num_deaths++; 4807 } 4808 //else if this client was killed by the bot 4809 else if (attacker == bs->client) { 4810 bs->enemydeathtype = mod; 4811 bs->lastkilledplayer = target; 4812 bs->killedenemy_time = FloatTime(); 4813 // 4814 bs->num_kills++; 4815 } 4816 else if (attacker == bs->enemy && target == attacker) { 4817 bs->enemysuicide = qtrue; 4818 } 4819 // 4820 #ifdef MISSIONPACK 4821 if (gametype == GT_1FCTF) { 4822 // 4823 BotEntityInfo(target, &entinfo); 4824 if ( entinfo.powerups & ( 1 << PW_NEUTRALFLAG ) ) { 4825 if (!BotSameTeam(bs, target)) { 4826 bs->neutralflagstatus = 3; //enemy dropped the flag 4827 bs->flagstatuschanged = qtrue; 4828 } 4829 } 4830 } 4831 #endif 4832 break; 4833 } 4834 case EV_GLOBAL_SOUND: 4835 { 4836 if (state->eventParm < 0 || state->eventParm > MAX_SOUNDS) { 4837 BotAI_Print(PRT_ERROR, "EV_GLOBAL_SOUND: eventParm (%d) out of range\n", state->eventParm); 4838 break; 4839 } 4840 trap_GetConfigstring(CS_SOUNDS + state->eventParm, buf, sizeof(buf)); 4841 /* 4842 if (!strcmp(buf, "sound/teamplay/flagret_red.wav")) { 4843 //red flag is returned 4844 bs->redflagstatus = 0; 4845 bs->flagstatuschanged = qtrue; 4846 } 4847 else if (!strcmp(buf, "sound/teamplay/flagret_blu.wav")) { 4848 //blue flag is returned 4849 bs->blueflagstatus = 0; 4850 bs->flagstatuschanged = qtrue; 4851 } 4852 else*/ 4853 #ifdef MISSIONPACK 4854 if (!strcmp(buf, "sound/items/kamikazerespawn.wav" )) { 4855 //the kamikaze respawned so dont avoid it 4856 BotDontAvoid(bs, "Kamikaze"); 4857 } 4858 else 4859 #endif 4860 if (!strcmp(buf, "sound/items/poweruprespawn.wav")) { 4861 //powerup respawned... go get it 4862 BotGoForPowerups(bs); 4863 } 4864 break; 4865 } 4866 case EV_GLOBAL_TEAM_SOUND: 4867 { 4868 if (gametype == GT_CTF) { 4869 switch(state->eventParm) { 4870 case GTS_RED_CAPTURE: 4871 bs->blueflagstatus = 0; 4872 bs->redflagstatus = 0; 4873 bs->flagstatuschanged = qtrue; 4874 break; //see BotMatch_CTF 4875 case GTS_BLUE_CAPTURE: 4876 bs->blueflagstatus = 0; 4877 bs->redflagstatus = 0; 4878 bs->flagstatuschanged = qtrue; 4879 break; //see BotMatch_CTF 4880 case GTS_RED_RETURN: 4881 //blue flag is returned 4882 bs->blueflagstatus = 0; 4883 bs->flagstatuschanged = qtrue; 4884 break; 4885 case GTS_BLUE_RETURN: 4886 //red flag is returned 4887 bs->redflagstatus = 0; 4888 bs->flagstatuschanged = qtrue; 4889 break; 4890 case GTS_RED_TAKEN: 4891 //blue flag is taken 4892 bs->blueflagstatus = 1; 4893 bs->flagstatuschanged = qtrue; 4894 break; //see BotMatch_CTF 4895 case GTS_BLUE_TAKEN: 4896 //red flag is taken 4897 bs->redflagstatus = 1; 4898 bs->flagstatuschanged = qtrue; 4899 break; //see BotMatch_CTF 4900 } 4901 } 4902 #ifdef MISSIONPACK 4903 else if (gametype == GT_1FCTF) { 4904 switch(state->eventParm) { 4905 case GTS_RED_CAPTURE: 4906 bs->neutralflagstatus = 0; 4907 bs->flagstatuschanged = qtrue; 4908 break; 4909 case GTS_BLUE_CAPTURE: 4910 bs->neutralflagstatus = 0; 4911 bs->flagstatuschanged = qtrue; 4912 break; 4913 case GTS_RED_RETURN: 4914 //flag has returned 4915 bs->neutralflagstatus = 0; 4916 bs->flagstatuschanged = qtrue; 4917 break; 4918 case GTS_BLUE_RETURN: 4919 //flag has returned 4920 bs->neutralflagstatus = 0; 4921 bs->flagstatuschanged = qtrue; 4922 break; 4923 case GTS_RED_TAKEN: 4924 bs->neutralflagstatus = BotTeam(bs) == TEAM_RED ? 2 : 1; //FIXME: check Team_TakeFlagSound in g_team.c 4925 bs->flagstatuschanged = qtrue; 4926 break; 4927 case GTS_BLUE_TAKEN: 4928 bs->neutralflagstatus = BotTeam(bs) == TEAM_BLUE ? 2 : 1; //FIXME: check Team_TakeFlagSound in g_team.c 4929 bs->flagstatuschanged = qtrue; 4930 break; 4931 } 4932 } 4933 #endif 4934 break; 4935 } 4936 case EV_PLAYER_TELEPORT_IN: 4937 { 4938 VectorCopy(state->origin, lastteleport_origin); 4939 lastteleport_time = FloatTime(); 4940 break; 4941 } 4942 case EV_GENERAL_SOUND: 4943 { 4944 //if this sound is played on the bot 4945 if (state->number == bs->client) { 4946 if (state->eventParm < 0 || state->eventParm > MAX_SOUNDS) { 4947 BotAI_Print(PRT_ERROR, "EV_GENERAL_SOUND: eventParm (%d) out of range\n", state->eventParm); 4948 break; 4949 } 4950 //check out the sound 4951 trap_GetConfigstring(CS_SOUNDS + state->eventParm, buf, sizeof(buf)); 4952 //if falling into a death pit 4953 if (!strcmp(buf, "*falling1.wav")) { 4954 //if the bot has a personal teleporter 4955 if (bs->inventory[INVENTORY_TELEPORTER] > 0) { 4956 //use the holdable item 4957 trap_EA_Use(bs->client); 4958 } 4959 } 4960 } 4961 break; 4962 } 4963 case EV_FOOTSTEP: 4964 case EV_FOOTSTEP_METAL: 4965 case EV_FOOTSPLASH: 4966 case EV_FOOTWADE: 4967 case EV_SWIM: 4968 case EV_FALL_SHORT: 4969 case EV_FALL_MEDIUM: 4970 case EV_FALL_FAR: 4971 case EV_STEP_4: 4972 case EV_STEP_8: 4973 case EV_STEP_12: 4974 case EV_STEP_16: 4975 case EV_JUMP_PAD: 4976 case EV_JUMP: 4977 case EV_TAUNT: 4978 case EV_WATER_TOUCH: 4979 case EV_WATER_LEAVE: 4980 case EV_WATER_UNDER: 4981 case EV_WATER_CLEAR: 4982 case EV_ITEM_PICKUP: 4983 case EV_GLOBAL_ITEM_PICKUP: 4984 case EV_NOAMMO: 4985 case EV_CHANGE_WEAPON: 4986 case EV_FIRE_WEAPON: 4987 //FIXME: either add to sound queue or mark player as someone making noise 4988 break; 4989 case EV_USE_ITEM0: 4990 case EV_USE_ITEM1: 4991 case EV_USE_ITEM2: 4992 case EV_USE_ITEM3: 4993 case EV_USE_ITEM4: 4994 case EV_USE_ITEM5: 4995 case EV_USE_ITEM6: 4996 case EV_USE_ITEM7: 4997 case EV_USE_ITEM8: 4998 case EV_USE_ITEM9: 4999 case EV_USE_ITEM10: 5000 case EV_USE_ITEM11: 5001 case EV_USE_ITEM12: 5002 case EV_USE_ITEM13: 5003 case EV_USE_ITEM14: 5004 break; 5005 } 5006 } 5007 5008 /* 5009 ================== 5010 BotCheckSnapshot 5011 ================== 5012 */ 5013 void BotCheckSnapshot(bot_state_t *bs) { 5014 int ent; 5015 entityState_t state; 5016 5017 //remove all avoid spots 5018 trap_BotAddAvoidSpot(bs->ms, vec3_origin, 0, AVOID_CLEAR); 5019 //reset kamikaze body 5020 bs->kamikazebody = 0; 5021 //reset number of proxmines 5022 bs->numproxmines = 0; 5023 // 5024 ent = 0; 5025 while( ( ent = BotAI_GetSnapshotEntity( bs->client, ent, &state ) ) != -1 ) { 5026 //check the entity state for events 5027 BotCheckEvents(bs, &state); 5028 //check for grenades the bot should avoid 5029 BotCheckForGrenades(bs, &state); 5030 // 5031 #ifdef MISSIONPACK 5032 //check for proximity mines which the bot should deactivate 5033 BotCheckForProxMines(bs, &state); 5034 //check for dead bodies with the kamikaze effect which should be gibbed 5035 BotCheckForKamikazeBody(bs, &state); 5036 #endif 5037 } 5038 //check the player state for events 5039 BotAI_GetEntityState(bs->client, &state); 5040 //copy the player state events to the entity state 5041 state.event = bs->cur_ps.externalEvent; 5042 state.eventParm = bs->cur_ps.externalEventParm; 5043 // 5044 BotCheckEvents(bs, &state); 5045 } 5046 5047 /* 5048 ================== 5049 BotCheckAir 5050 ================== 5051 */ 5052 void BotCheckAir(bot_state_t *bs) { 5053 if (bs->inventory[INVENTORY_ENVIRONMENTSUIT] <= 0) { 5054 if (trap_AAS_PointContents(bs->eye) & (CONTENTS_WATER|CONTENTS_SLIME|CONTENTS_LAVA)) { 5055 return; 5056 } 5057 } 5058 bs->lastair_time = FloatTime(); 5059 } 5060 5061 /* 5062 ================== 5063 BotAlternateRoute 5064 ================== 5065 */ 5066 bot_goal_t *BotAlternateRoute(bot_state_t *bs, bot_goal_t *goal) { 5067 int t; 5068 5069 // if the bot has an alternative route goal 5070 if (bs->altroutegoal.areanum) { 5071 // 5072 if (bs->reachedaltroutegoal_time) 5073 return goal; 5074 // travel time towards alternative route goal 5075 t = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, bs->altroutegoal.areanum, bs->tfl); 5076 if (t && t < 20) { 5077 //BotAI_Print(PRT_MESSAGE, "reached alternate route goal\n"); 5078 bs->reachedaltroutegoal_time = FloatTime(); 5079 } 5080 memcpy(goal, &bs->altroutegoal, sizeof(bot_goal_t)); 5081 return &bs->altroutegoal; 5082 } 5083 return goal; 5084 } 5085 5086 /* 5087 ================== 5088 BotGetAlternateRouteGoal 5089 ================== 5090 */ 5091 int BotGetAlternateRouteGoal(bot_state_t *bs, int base) { 5092 aas_altroutegoal_t *altroutegoals; 5093 bot_goal_t *goal; 5094 int numaltroutegoals, rnd; 5095 5096 if (base == TEAM_RED) { 5097 altroutegoals = red_altroutegoals; 5098 numaltroutegoals = red_numaltroutegoals; 5099 } 5100 else { 5101 altroutegoals = blue_altroutegoals; 5102 numaltroutegoals = blue_numaltroutegoals; 5103 } 5104 if (!numaltroutegoals) 5105 return qfalse; 5106 rnd = (float) random() * numaltroutegoals; 5107 if (rnd >= numaltroutegoals) 5108 rnd = numaltroutegoals-1; 5109 goal = &bs->altroutegoal; 5110 goal->areanum = altroutegoals[rnd].areanum; 5111 VectorCopy(altroutegoals[rnd].origin, goal->origin); 5112 VectorSet(goal->mins, -8, -8, -8); 5113 VectorSet(goal->maxs, 8, 8, 8); 5114 goal->entitynum = 0; 5115 goal->iteminfo = 0; 5116 goal->number = 0; 5117 goal->flags = 0; 5118 // 5119 bs->reachedaltroutegoal_time = 0; 5120 return qtrue; 5121 } 5122 5123 /* 5124 ================== 5125 BotSetupAlternateRouteGoals 5126 ================== 5127 */ 5128 void BotSetupAlternativeRouteGoals(void) { 5129 5130 if (altroutegoals_setup) 5131 return; 5132 #ifdef MISSIONPACK 5133 if (gametype == GT_CTF) { 5134 if (trap_BotGetLevelItemGoal(-1, "Neutral Flag", &ctf_neutralflag) < 0) 5135 BotAI_Print(PRT_WARNING, "no alt routes without Neutral Flag\n"); 5136 if (ctf_neutralflag.areanum) { 5137 // 5138 red_numaltroutegoals = trap_AAS_AlternativeRouteGoals( 5139 ctf_neutralflag.origin, ctf_neutralflag.areanum, 5140 ctf_redflag.origin, ctf_redflag.areanum, TFL_DEFAULT, 5141 red_altroutegoals, MAX_ALTROUTEGOALS, 5142 ALTROUTEGOAL_CLUSTERPORTALS| 5143 ALTROUTEGOAL_VIEWPORTALS); 5144 blue_numaltroutegoals = trap_AAS_AlternativeRouteGoals( 5145 ctf_neutralflag.origin, ctf_neutralflag.areanum, 5146 ctf_blueflag.origin, ctf_blueflag.areanum, TFL_DEFAULT, 5147 blue_altroutegoals, MAX_ALTROUTEGOALS, 5148 ALTROUTEGOAL_CLUSTERPORTALS| 5149 ALTROUTEGOAL_VIEWPORTALS); 5150 } 5151 } 5152 else if (gametype == GT_1FCTF) { 5153 // 5154 red_numaltroutegoals = trap_AAS_AlternativeRouteGoals( 5155 ctf_neutralflag.origin, ctf_neutralflag.areanum, 5156 ctf_redflag.origin, ctf_redflag.areanum, TFL_DEFAULT, 5157 red_altroutegoals, MAX_ALTROUTEGOALS, 5158 ALTROUTEGOAL_CLUSTERPORTALS| 5159 ALTROUTEGOAL_VIEWPORTALS); 5160 blue_numaltroutegoals = trap_AAS_AlternativeRouteGoals( 5161 ctf_neutralflag.origin, ctf_neutralflag.areanum, 5162 ctf_blueflag.origin, ctf_blueflag.areanum, TFL_DEFAULT, 5163 blue_altroutegoals, MAX_ALTROUTEGOALS, 5164 ALTROUTEGOAL_CLUSTERPORTALS| 5165 ALTROUTEGOAL_VIEWPORTALS); 5166 } 5167 else if (gametype == GT_OBELISK) { 5168 if (trap_BotGetLevelItemGoal(-1, "Neutral Obelisk", &neutralobelisk) < 0) 5169 BotAI_Print(PRT_WARNING, "Harvester without neutral obelisk\n"); 5170 // 5171 red_numaltroutegoals = trap_AAS_AlternativeRouteGoals( 5172 neutralobelisk.origin, neutralobelisk.areanum, 5173 redobelisk.origin, redobelisk.areanum, TFL_DEFAULT, 5174 red_altroutegoals, MAX_ALTROUTEGOALS, 5175 ALTROUTEGOAL_CLUSTERPORTALS| 5176 ALTROUTEGOAL_VIEWPORTALS); 5177 blue_numaltroutegoals = trap_AAS_AlternativeRouteGoals( 5178 neutralobelisk.origin, neutralobelisk.areanum, 5179 blueobelisk.origin, blueobelisk.areanum, TFL_DEFAULT, 5180 blue_altroutegoals, MAX_ALTROUTEGOALS, 5181 ALTROUTEGOAL_CLUSTERPORTALS| 5182 ALTROUTEGOAL_VIEWPORTALS); 5183 } 5184 else if (gametype == GT_HARVESTER) { 5185 // 5186 red_numaltroutegoals = trap_AAS_AlternativeRouteGoals( 5187 neutralobelisk.origin, neutralobelisk.areanum, 5188 redobelisk.origin, redobelisk.areanum, TFL_DEFAULT, 5189 red_altroutegoals, MAX_ALTROUTEGOALS, 5190 ALTROUTEGOAL_CLUSTERPORTALS| 5191 ALTROUTEGOAL_VIEWPORTALS); 5192 blue_numaltroutegoals = trap_AAS_AlternativeRouteGoals( 5193 neutralobelisk.origin, neutralobelisk.areanum, 5194 blueobelisk.origin, blueobelisk.areanum, TFL_DEFAULT, 5195 blue_altroutegoals, MAX_ALTROUTEGOALS, 5196 ALTROUTEGOAL_CLUSTERPORTALS| 5197 ALTROUTEGOAL_VIEWPORTALS); 5198 } 5199 #endif 5200 altroutegoals_setup = qtrue; 5201 } 5202 5203 /* 5204 ================== 5205 BotDeathmatchAI 5206 ================== 5207 */ 5208 void BotDeathmatchAI(bot_state_t *bs, float thinktime) { 5209 char gender[144], name[144], buf[144]; 5210 char userinfo[MAX_INFO_STRING]; 5211 int i; 5212 5213 //if the bot has just been setup 5214 if (bs->setupcount > 0) { 5215 bs->setupcount--; 5216 if (bs->setupcount > 0) return; 5217 //get the gender characteristic 5218 trap_Characteristic_String(bs->character, CHARACTERISTIC_GENDER, gender, sizeof(gender)); 5219 //set the bot gender 5220 trap_GetUserinfo(bs->client, userinfo, sizeof(userinfo)); 5221 Info_SetValueForKey(userinfo, "sex", gender); 5222 trap_SetUserinfo(bs->client, userinfo); 5223 //set the team 5224 if ( !bs->map_restart && g_gametype.integer != GT_TOURNAMENT ) { 5225 Com_sprintf(buf, sizeof(buf), "team %s", bs->settings.team); 5226 trap_EA_Command(bs->client, buf); 5227 } 5228 //set the chat gender 5229 if (gender[0] == 'm') trap_BotSetChatGender(bs->cs, CHAT_GENDERMALE); 5230 else if (gender[0] == 'f') trap_BotSetChatGender(bs->cs, CHAT_GENDERFEMALE); 5231 else trap_BotSetChatGender(bs->cs, CHAT_GENDERLESS); 5232 //set the chat name 5233 ClientName(bs->client, name, sizeof(name)); 5234 trap_BotSetChatName(bs->cs, name, bs->client); 5235 // 5236 bs->lastframe_health = bs->inventory[INVENTORY_HEALTH]; 5237 bs->lasthitcount = bs->cur_ps.persistant[PERS_HITS]; 5238 // 5239 bs->setupcount = 0; 5240 // 5241 BotSetupAlternativeRouteGoals(); 5242 } 5243 //no ideal view set 5244 bs->flags &= ~BFL_IDEALVIEWSET; 5245 // 5246 if (!BotIntermission(bs)) { 5247 //set the teleport time 5248 BotSetTeleportTime(bs); 5249 //update some inventory values 5250 BotUpdateInventory(bs); 5251 //check out the snapshot 5252 BotCheckSnapshot(bs); 5253 //check for air 5254 BotCheckAir(bs); 5255 } 5256 //check the console messages 5257 BotCheckConsoleMessages(bs); 5258 //if not in the intermission and not in observer mode 5259 if (!BotIntermission(bs) && !BotIsObserver(bs)) { 5260 //do team AI 5261 BotTeamAI(bs); 5262 } 5263 //if the bot has no ai node 5264 if (!bs->ainode) { 5265 AIEnter_Seek_LTG(bs, "BotDeathmatchAI: no ai node"); 5266 } 5267 //if the bot entered the game less than 8 seconds ago 5268 if (!bs->entergamechat && bs->entergame_time > FloatTime() - 8) { 5269 if (BotChat_EnterGame(bs)) { 5270 bs->stand_time = FloatTime() + BotChatTime(bs); 5271 AIEnter_Stand(bs, "BotDeathmatchAI: chat enter game"); 5272 } 5273 bs->entergamechat = qtrue; 5274 } 5275 //reset the node switches from the previous frame 5276 BotResetNodeSwitches(); 5277 //execute AI nodes 5278 for (i = 0; i < MAX_NODESWITCHES; i++) { 5279 if (bs->ainode(bs)) break; 5280 } 5281 //if the bot removed itself :) 5282 if (!bs->inuse) return; 5283 //if the bot executed too many AI nodes 5284 if (i >= MAX_NODESWITCHES) { 5285 trap_BotDumpGoalStack(bs->gs); 5286 trap_BotDumpAvoidGoals(bs->gs); 5287 BotDumpNodeSwitches(bs); 5288 ClientName(bs->client, name, sizeof(name)); 5289 BotAI_Print(PRT_ERROR, "%s at %1.1f switched more than %d AI nodes\n", name, FloatTime(), MAX_NODESWITCHES); 5290 } 5291 // 5292 bs->lastframe_health = bs->inventory[INVENTORY_HEALTH]; 5293 bs->lasthitcount = bs->cur_ps.persistant[PERS_HITS]; 5294 } 5295 5296 /* 5297 ================== 5298 BotSetEntityNumForGoalWithModel 5299 ================== 5300 */ 5301 void BotSetEntityNumForGoalWithModel(bot_goal_t *goal, int eType, char *modelname) { 5302 gentity_t *ent; 5303 int i, modelindex; 5304 vec3_t dir; 5305 5306 modelindex = G_ModelIndex( modelname ); 5307 ent = &g_entities[0]; 5308 for (i = 0; i < level.num_entities; i++, ent++) { 5309 if ( !ent->inuse ) { 5310 continue; 5311 } 5312 if ( eType && ent->s.eType != eType) { 5313 continue; 5314 } 5315 if (ent->s.modelindex != modelindex) { 5316 continue; 5317 } 5318 VectorSubtract(goal->origin, ent->s.origin, dir); 5319 if (VectorLengthSquared(dir) < Square(10)) { 5320 goal->entitynum = i; 5321 return; 5322 } 5323 } 5324 } 5325 5326 /* 5327 ================== 5328 BotSetEntityNumForGoal 5329 ================== 5330 */ 5331 void BotSetEntityNumForGoal(bot_goal_t *goal, char *classname) { 5332 gentity_t *ent; 5333 int i; 5334 vec3_t dir; 5335 5336 ent = &g_entities[0]; 5337 for (i = 0; i < level.num_entities; i++, ent++) { 5338 if ( !ent->inuse ) { 5339 continue; 5340 } 5341 if ( !Q_stricmp(ent->classname, classname) ) { 5342 continue; 5343 } 5344 VectorSubtract(goal->origin, ent->s.origin, dir); 5345 if (VectorLengthSquared(dir) < Square(10)) { 5346 goal->entitynum = i; 5347 return; 5348 } 5349 } 5350 } 5351 5352 /* 5353 ================== 5354 BotGoalForBSPEntity 5355 ================== 5356 */ 5357 int BotGoalForBSPEntity( char *classname, bot_goal_t *goal ) { 5358 char value[MAX_INFO_STRING]; 5359 vec3_t origin, start, end; 5360 int ent, numareas, areas[10]; 5361 5362 memset(goal, 0, sizeof(bot_goal_t)); 5363 for (ent = trap_AAS_NextBSPEntity(0); ent; ent = trap_AAS_NextBSPEntity(ent)) { 5364 if (!trap_AAS_ValueForBSPEpairKey(ent, "classname", value, sizeof(value))) 5365 continue; 5366 if (!strcmp(value, classname)) { 5367 if (!trap_AAS_VectorForBSPEpairKey(ent, "origin", origin)) 5368 return qfalse; 5369 VectorCopy(origin, goal->origin); 5370 VectorCopy(origin, start); 5371 start[2] -= 32; 5372 VectorCopy(origin, end); 5373 end[2] += 32; 5374 numareas = trap_AAS_TraceAreas(start, end, areas, NULL, 10); 5375 if (!numareas) 5376 return qfalse; 5377 goal->areanum = areas[0]; 5378 return qtrue; 5379 } 5380 } 5381 return qfalse; 5382 } 5383 5384 /* 5385 ================== 5386 BotSetupDeathmatchAI 5387 ================== 5388 */ 5389 void BotSetupDeathmatchAI(void) { 5390 int ent, modelnum; 5391 char model[128]; 5392 5393 gametype = trap_Cvar_VariableIntegerValue("g_gametype"); 5394 maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); 5395 5396 trap_Cvar_Register(&bot_rocketjump, "bot_rocketjump", "1", 0); 5397 trap_Cvar_Register(&bot_grapple, "bot_grapple", "0", 0); 5398 trap_Cvar_Register(&bot_fastchat, "bot_fastchat", "0", 0); 5399 trap_Cvar_Register(&bot_nochat, "bot_nochat", "0", 0); 5400 trap_Cvar_Register(&bot_testrchat, "bot_testrchat", "0", 0); 5401 trap_Cvar_Register(&bot_challenge, "bot_challenge", "0", 0); 5402 trap_Cvar_Register(&bot_predictobstacles, "bot_predictobstacles", "1", 0); 5403 trap_Cvar_Register(&g_spSkill, "g_spSkill", "2", 0); 5404 // 5405 if (gametype == GT_CTF) { 5406 if (trap_BotGetLevelItemGoal(-1, "Red Flag", &ctf_redflag) < 0) 5407 BotAI_Print(PRT_WARNING, "CTF without Red Flag\n"); 5408 if (trap_BotGetLevelItemGoal(-1, "Blue Flag", &ctf_blueflag) < 0) 5409 BotAI_Print(PRT_WARNING, "CTF without Blue Flag\n"); 5410 } 5411 #ifdef MISSIONPACK 5412 else if (gametype == GT_1FCTF) { 5413 if (trap_BotGetLevelItemGoal(-1, "Neutral Flag", &ctf_neutralflag) < 0) 5414 BotAI_Print(PRT_WARNING, "One Flag CTF without Neutral Flag\n"); 5415 if (trap_BotGetLevelItemGoal(-1, "Red Flag", &ctf_redflag) < 0) 5416 BotAI_Print(PRT_WARNING, "CTF without Red Flag\n"); 5417 if (trap_BotGetLevelItemGoal(-1, "Blue Flag", &ctf_blueflag) < 0) 5418 BotAI_Print(PRT_WARNING, "CTF without Blue Flag\n"); 5419 } 5420 else if (gametype == GT_OBELISK) { 5421 if (trap_BotGetLevelItemGoal(-1, "Red Obelisk", &redobelisk) < 0) 5422 BotAI_Print(PRT_WARNING, "Obelisk without red obelisk\n"); 5423 BotSetEntityNumForGoal(&redobelisk, "team_redobelisk"); 5424 if (trap_BotGetLevelItemGoal(-1, "Blue Obelisk", &blueobelisk) < 0) 5425 BotAI_Print(PRT_WARNING, "Obelisk without blue obelisk\n"); 5426 BotSetEntityNumForGoal(&blueobelisk, "team_blueobelisk"); 5427 } 5428 else if (gametype == GT_HARVESTER) { 5429 if (trap_BotGetLevelItemGoal(-1, "Red Obelisk", &redobelisk) < 0) 5430 BotAI_Print(PRT_WARNING, "Harvester without red obelisk\n"); 5431 BotSetEntityNumForGoal(&redobelisk, "team_redobelisk"); 5432 if (trap_BotGetLevelItemGoal(-1, "Blue Obelisk", &blueobelisk) < 0) 5433 BotAI_Print(PRT_WARNING, "Harvester without blue obelisk\n"); 5434 BotSetEntityNumForGoal(&blueobelisk, "team_blueobelisk"); 5435 if (trap_BotGetLevelItemGoal(-1, "Neutral Obelisk", &neutralobelisk) < 0) 5436 BotAI_Print(PRT_WARNING, "Harvester without neutral obelisk\n"); 5437 BotSetEntityNumForGoal(&neutralobelisk, "team_neutralobelisk"); 5438 } 5439 #endif 5440 5441 max_bspmodelindex = 0; 5442 for (ent = trap_AAS_NextBSPEntity(0); ent; ent = trap_AAS_NextBSPEntity(ent)) { 5443 if (!trap_AAS_ValueForBSPEpairKey(ent, "model", model, sizeof(model))) continue; 5444 if (model[0] == '*') { 5445 modelnum = atoi(model+1); 5446 if (modelnum > max_bspmodelindex) 5447 max_bspmodelindex = modelnum; 5448 } 5449 } 5450 //initialize the waypoint heap 5451 BotInitWaypoints(); 5452 } 5453 5454 /* 5455 ================== 5456 BotShutdownDeathmatchAI 5457 ================== 5458 */ 5459 void BotShutdownDeathmatchAI(void) { 5460 altroutegoals_setup = qfalse; 5461 }