1//************************************************************************** 2//** 3//** ## ## ## ## ## #### #### ### ### 4//** ## ## ## ## ## ## ## ## ## ## #### #### 5//** ## ## ## ## ## ## ## ## ## ## ## ## ## ## 6//** ## ## ######## ## ## ## ## ## ## ## ### ## 7//** ### ## ## ### ## ## ## ## ## ## 8//** # ## ## # #### #### ## ## 9//** 10//** $Id: BotPlayer.vc 4111 2009-11-14 23:46:45Z dj_jl $ 11//** 12//** Copyright (C) 1999-2006 Jānis Legzdiņš 13//** 14//** This program is free software; you can redistribute it and/or 15//** modify it under the terms of the GNU General Public License 16//** as published by the Free Software Foundation; either version 2 17//** of the License, or (at your option) any later version. 18//** 19//** This program is distributed in the hope that it will be useful, 20//** but WITHOUT ANY WARRANTY; without even the implied warranty of 21//** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22//** GNU General Public License for more details. 23//** 24//************************************************************************** 25// 26// This section contains the main bot AI. The 27// main bot AI loop, B_Think, is called every tic. 28// Also included are various bot decision-making 29// procedures, such as B_CheckStuff and B_SetEnemy. 30// 31//************************************************************************** 32 33// Main bot class 34class BotPlayer : GameObject; 35 36const float FORWARDWALK = 200.0; 37const float FORWARDRUN = 400.0; 38const float SIDEWALK = 192.0; 39const float SIDERUN = 320.0; 40 41/*const float MAX_TRAVERSE_DIST (1024*FRACUNIT) //10 meters, used within b_func.c*/ 42const float AVOID_DIST = 512.0; //Try avoid incoming missiles once they reached this close 43const float SAFE_SELF_MISDIST = 128.0; //Distance from self to target where it's safe to pull a rocket. 44/*const float FRIEND_DIST (128*FRACUNIT) //To friend. 45const float DARK_DIST (256*FRACUNIT) //Distance that bot can see enemies in the dark from. 46const float WHATS_DARK 64 //light value thats classed as dark. 47const float MAX_MONSTER_TARGET_DIST (1024*FRACUNIT) //Too high can slow down the performance, see P_mobj.c*/ 48const float ENEMY_SCAN_FOV = 120.0; 49/*const float MAXMOVEHEIGHT (32*FRACUNIT) //MAXSTEPMOVE but with jumping counted in. 50const float GETINCOMBAT (512*FRACUNIT) //Max distance to item. if it's due to be icked up in a combat situation.*/ 51const float SHOOTFOV = 60.0; 52/*const float MAXROAM (5*TICRATE) //When this time is elapsed the bot will roam after something else.*/ 53 54/* 55 56 Class definitions for botinfo, chatinfo, and 57 other various bot information thingamabobers. 58*/ 59 60enum 61{ 62 bsk_verypoor, 63 bsk_poor, 64 bsk_low, 65 bsk_medium, 66 bsk_high, 67 bsk_excellent, 68 bsk_supreme 69}; 70 71struct botinfo_t 72{ 73 string Name; // Bot's name*/ 74 int accuracy; // Accuracy with "instant" weapons (this includes "leading") 75 int intelect; // Accuracy with "missile" weapons (rocket launcher, etc.) 76 int evade; // Ability to dodge incoming missiles 77 int anticip; // Ability to anticipate "instant" shots 78 int reaction; // Overall reaction time (lower is "better") 79/* int pisschance; // Chance the bot will get pissed when his threshold is reached 80 int threshold; // How much it takes to frustrate/piss off the bot 81 int dangerlevel; // When health is below this, we need some health 82 int wpfav; // Favorite weapon 83 int chatinfo; // Bot's chat strings 84 int chattime; // How long it takes us to type a line 85 int chatty; // How talkative the bot is 86 char *colour; // Colour (in form of a string) 87 char *gender; // Male/female/it :) 88 char *skin; // Skin 89 int railcolour; // Railgun trail colour 90 bool revealed; // Hidden bots must be revealed*/ 91 string userinfo; 92}; 93 94/*struct chatline_t 95{ 96 char *string; 97 chattype_t bot; 98 chatline_t *line; 99}; 100 101struct chatinfo_t 102{ 103 chatline_t intro[5]; 104 chatline_t inter[5]; 105 chatline_t rare[5]; 106 chatline_t frag[15]; 107 chatline_t died[10]; 108 chatline_t roam[10]; 109 chatline_t pissed[5]; 110 chatline_t frustrated[5]; 111 chatline_t special[20]; 112} chatinfo_t;*/ 113 114// The things the bot knows about their enemy 115//struct enemyinfo_t 116//{ 117// int health; 118// weapontype_t weap; 119//}; 120 121// 122// Bot states 123// 124enum 125{ 126 // Deciding what to do 127 BST_NOTHING, 128 // Bot is "fetching" something (skull, whatever) 129 BST_OFFENSE, 130 // Bot is defending something 131 BST_DEFENSE, 132 // Bot is heading to scoring place 133 BST_RETURN 134}; 135 136Player player; // Points to reference player 137Actor botmo; 138 139// Destinations 140Actor item; // Item (roam towards weapon, etc) 141bool bItemIsWeapon; 142bool bItemIsPowerup; 143Actor goal; // Teamgame goal spot 144Actor node; // Node we're heading towards 145Actor prev; // Previous node we were at 146TVec posdest; // Position of our destination (doesn't have to be an actor) 147bool posdest_valid; 148Actor enemy; // The dead meat. 149TVec lastpos; // Last place we saw our enemy 150bool lastpos_valid; 151Actor missile; // A threathing missile that got to be avoided. 152 153Actor ally; // Ally to tag along with 154float t_strafe; 155float t_react; 156float t_fire; // Tics left until our gun will actually fire again 157float t_anticip; 158 159float forwardmove; // For building ucmd 160float sidemove; 161 162// Misc booleans 163bool bAllRound; 164bool bNewItemIsWeapon; 165bool bNewItemIsPowerup; 166 167botinfo_t info; // Aiming, name, perfection, yadda yadda 168 169int angerlevel; 170 171float angle; // The wanted angle that the bot tries to get every tic. 172 173//========================================================================== 174// 175// BotTick 176// 177// Main bot function 178// 179//========================================================================== 180 181void BotTick(float deltaTime) 182{ 183 botmo = Actor(player.MO); 184 185 player.ForwardMove = 0.0; 186 player.SideMove = 0.0; 187 player.FlyMove = 0.0; 188 player.Buttons = 0; 189 190 // We're dead, so hit space to respawn 191 if (!player.Health) 192 { 193 player.Buttons |= BT_USE; 194 return; 195 } 196 197 // Weed out any bad destinations/enemies 198 CheckStuff(); 199 200 // Destination setting... 201 SetEnemy(); 202 Scan(); 203 204 // Turning towards destination... 205 BotAim(); 206 Turn(); 207 208 // Moving towards destination 209 Attack(); 210 Move(deltaTime); 211 212 Pitch(); 213 214 // Only walk if we're on skill 0 215 if (!MainGameInfo(player.Level.Game).botskill) 216 { 217 if (forwardmove == FORWARDRUN) 218 forwardmove = FORWARDWALK; 219 if (forwardmove == -FORWARDRUN) 220 forwardmove = -FORWARDWALK; 221 if (sidemove == SIDERUN) 222 sidemove = SIDEWALK; 223 if (sidemove == -SIDERUN) 224 sidemove = -SIDEWALK; 225 } 226 227 player.SideMove = sidemove; 228 player.ForwardMove = forwardmove; 229 230 t_react -= deltaTime; 231 if (t_react <= 0.0) 232 { 233 t_react = 0.0; 234 } 235 t_anticip -= deltaTime; 236 if (t_anticip <= 0.0) 237 { 238 t_anticip = 0.0; 239 } 240} 241 242//========================================================================== 243// 244// AngleTo 245// 246//========================================================================== 247 248float AngleTo(TVec dest) 249{ 250 TVec dir; 251 TAVec ang; 252 253 dir = dest - botmo.Origin; 254 VectorAngles(&dir, &ang); 255 return ang.yaw; 256} 257 258//========================================================================== 259// 260// CheckItem 261// 262// Determines if we should bother picking up an item or not 263// 264//========================================================================== 265 266bool CheckItem(Actor item) 267{ 268 bNewItemIsWeapon = false; 269 bNewItemIsPowerup = false; 270//????? 271 if (!item) 272 return false; 273 if (!item.bSpecial) 274 return false; 275 276 Weapon Wpn = Weapon(item); 277 if (Wpn) 278 { 279 bNewItemIsWeapon = true; 280 281 // If we don't have the weapon, pick it up 282 if (!botmo.FindInventory(class<Inventory>(Wpn.Class))) 283 return true; 284 285 // If we have no more room for the ammo it gives 286 if (botmo.FindInventory(Wpn.AmmoType1).Amount == 287 botmo.FindInventory(Wpn.AmmoType1).MaxAmount) 288 return false; 289 290 // Can't pick it up because we have it and it's not a dropped weapon 291 if (!item.bDropped) 292 return false; 293 } 294 295 if (Ammo(item)) 296 { 297 // If we have no more room for the ammo it gives 298 Inventory AmmoItem = botmo.FindInventory(Ammo(item).GetParentAmmo()); 299 if (AmmoItem && AmmoItem.Amount >= AmmoItem.MaxAmount) 300 return false; 301 } 302 303 if (Health(item) && (botmo.Health >= Player::MAXHEALTH)) 304 return false; 305 BasicArmor Armor = BasicArmor(botmo.FindInventory(BasicArmor)); 306 if (BasicArmorPickup(item) && (Armor.Amount >= BasicArmorPickup(item).SaveAmount)) 307 return false; 308 309 // Guess we're okay 310 return true; 311} 312 313//========================================================================== 314// 315// SetEnemy 316// 317//========================================================================== 318 319void SetEnemy() 320{ 321 if (enemy && enemy.Health > 0 && player.MO && 322 player.MO.CanSee(enemy)) 323 { 324 return; 325 } 326 327 bAllRound = !!enemy; 328 enemy = FindEnemy(); 329 330 if (!enemy) 331 return; 332 333 // Double check the validity of the enemy 334 if (!enemy.bShootable) 335 enemy = none; 336} 337 338//========================================================================== 339// 340// CheckTo 341// 342// Checks if an location is reachable 343// 344//========================================================================== 345 346bool CheckTo(TVec pos) 347{ 348 float dist; 349 float an; 350 351 dist = Length(botmo.Origin - pos); 352 an = GetAngle(); 353 354 if (!CheckPath(an, dist)) 355 return false; 356 357 return true; 358} 359 360//========================================================================== 361// 362// CheckStuff 363// 364// Make sure that our destinations/enemies and everything are valid. 365// 366//========================================================================== 367 368void CheckStuff() 369{ 370 if (item) 371 { 372 if (!item.bSpecial || item.IsDestroyed() || // somebody picked 373 !CheckTo(item.Origin)) // Can't reach 374 item = none; 375 } 376 377 if (missile) 378 { 379 if (!missile.bMissile || missile.IsDestroyed()) 380 missile = none; 381 } 382 383 if (node) 384 { 385 if (!CheckTo(node.Origin) || 386 (botmo.DistTo2(node) < botmo.Radius)) 387 { 388 prev = node; 389 node = none; 390 } 391 } 392 393 if (posdest_valid) 394 { 395 TVec dir; 396 dir = posdest - botmo.Origin; 397 dir.z = 0.0; 398 if ((Length(dir) < botmo.Radius) || !CheckTo(posdest)) 399 { 400 posdest_valid = false; 401 } 402 } 403 404 if (lastpos_valid) 405 { 406 TVec dir; 407 dir = lastpos - botmo.Origin; 408 dir.z = 0.0; 409 if ((Length(dir) < botmo.Radius) || !CheckTo(lastpos)) 410 { 411 lastpos_valid = false; 412 } 413 } 414 415 if (enemy) 416 { 417 if (enemy.Health <= 0 || !enemy.bShootable) 418 enemy = none; 419 } 420} 421 422//========================================================================== 423// 424// Scan 425// 426// Scan all mobj's visible to the bot for incoming missiles, enemies, and 427// various items to pick up. 428// 429//========================================================================== 430 431void Scan() 432{ 433 Actor actor; 434 435 foreach botmo.AllThinkers(Actor, actor) 436 { 437 if (!actor.bSpecial && !actor.bMissile) 438 { 439 // Not interested in this one 440 continue; 441 } 442 if (Check_LOS(actor, 90.0)) 443 { 444 // Look for special items 445 if (!item && actor.bSpecial) 446 { 447 if (CheckItem(actor)) 448 { 449 item = actor; 450 bItemIsWeapon = bNewItemIsWeapon; 451 bItemIsPowerup = bNewItemIsPowerup; 452 } 453 } 454 else if (!missile && actor.bMissile && 455 (botmo.DistTo(actor) < AVOID_DIST)) 456 { 457 missile = actor; 458 } 459 } 460 } 461} 462 463//========================================================================== 464// 465// SkillLower 466// 467//========================================================================== 468 469int SkillLower(int skill, int num) 470{ 471 if (num <= 0) 472 return skill; 473 474 skill -= num; 475 if (skill < 0) 476 skill = 0; 477 478 return skill; 479} 480 481//========================================================================== 482// 483// SetAngle 484// 485//========================================================================== 486 487void SetAngle(float an) 488{ 489 angle = AngleMod360(an); 490} 491 492//========================================================================== 493// 494// GetAngle 495// 496//========================================================================== 497 498float GetAngle() 499{ 500 return angle; 501} 502 503//========================================================================== 504// 505// IsDangerous 506// 507// Checks if a sector is dangerous. 508// 509//========================================================================== 510 511bool IsDangerous(sector_t *sec) 512{ 513 switch (sec->special & ~SECSPEC_SECRET_MASK) 514 { 515 case 71: // Damage_Sludge 516 case 82: // Damage_LavaWimpy 517 case 83: // Damage_LavaHefty 518 case 84: // Scroll_EastLavaDamage 519 return true; 520 } 521 return false; 522} 523 524//========================================================================== 525// 526// CheckPath 527// 528// Checks for obstructions at a certain angle and distance. Returns true if 529// the path is clear, and false is the path is blocked. 530// 531//========================================================================== 532 533bool CheckPath(float ang, float dist) 534{ 535 float x1, y1, x2, y2; 536 intercept_t* in; 537 float bottracerange; 538 TVec bottracedir; 539 540 bottracerange = dist; 541 bottracedir.x = cos(ang); 542 bottracedir.y = sin(ang); 543 bottracedir.z = 0.0; 544 x1 = botmo.Origin.x; 545 y1 = botmo.Origin.y; 546 x2 = x1 + dist * bottracedir.x; 547 y2 = y1 + dist * bottracedir.y; 548 549 EntityEx(player.MO).UseLines(Player::USERANGE, Player::USETHINGRANGE, '*usefail'); 550 551 foreach botmo.PathTraverse(in, x1, y1, x2, y2, PT_ADDLINES) 552 { 553 Actor th; 554 line_t *ld; 555 TVec hit_point; 556 557 if (in->bIsALine) 558 { 559 sector_t *back; 560 sector_t *front; 561 opening_t *open; 562 float diffheight; 563 564 ld = in->line; // This linedef 565 hit_point = botmo.Origin + (bottracerange * in->frac) * bottracedir; 566 567 // Line is impassible 568 if (!(ld->flags & ML_TWOSIDED) || (ld->flags & (ML_BLOCKING | ML_BLOCKPLAYERS | ML_BLOCKEVERYTHING))) 569 return false; 570 571 // Line isn't two sided 572 if (!ld->backsector) 573 return false; 574 575 if (!PointOnPlaneSide(botmo.Origin, ld)) 576 { 577 back = ld->backsector; 578 front = ld->frontsector; 579 } 580 else 581 { 582 back = ld->frontsector; 583 front = ld->backsector; 584 } 585 586 // Sector is dangerous 587 if (IsDangerous(back)) 588 return false; 589 590 // crosses a two sided line 591 open = LineOpenings(ld, hit_point); 592 open = FindOpening(open, hit_point.z, hit_point.z + botmo.Height); 593 // No valid openings 594 if (!open) 595 { 596 return false; 597 } 598 599 diffheight = GetPlanePointZ(&back->floor, hit_point) - 600 GetPlanePointZ(&front->floor, hit_point); 601 602 // No cliff jumping unless we're going after something 603 if (-diffheight > 32.0 && !enemy) 604 { 605 return false; 606 } 607 608 if (diffheight > 0.0) 609 { 610 if (diffheight > 48.0) 611 { 612/* if (front->SSpecial == ThrustThingZ || front->springpadzone) 613 { 614 return true; 615 } 616 else*/ 617 return false; 618 } 619 else if (diffheight <= 48.0 && diffheight >= 24.0) 620 { 621 player.Buttons |= BT_JUMP; 622 } 623 } 624 } 625 else 626 { 627 th = Actor(in->Thing); 628 629 if (th == botmo) 630 continue; 631 632 if (th.bSolid) 633 return false; 634 635/* fixed_t diffheight = (th->z + th->Height) - shootthing->z; 636 637 if (diffheight > 0) 638 { 639 if (diffheight <= 48*FRACUNIT && diffheight >= 24*FRACUNIT) 640 { 641 bot->player.cmd.ucmd.buttons |= BT_JUMP; 642 return true; 643 } 644 else if (diffheight <= 24*FRACUNIT) 645 return true; 646 else 647 return false; 648 649 } 650*/ 651 } 652 } 653 return true; 654} 655 656//========================================================================== 657// 658// Check_LOS 659// 660// Doesnt check LOS, checks visibility with a set view angle. 661// B_Checksight checks LOS (straight line) 662// 663// Check if mo1 has free line to mo2 and if mo2 is within mo1 viewangle 664// (vangle) given with normal degrees. If these conditions are true, the 665// function returns true. GOOD TO KNOW is that the players view angle in 666// doom is 90 degrees infront. 667// 668//========================================================================== 669 670bool Check_LOS(Actor to, float vangle) 671{ 672 if (!botmo.CanSee(to)) 673 return false; // out of sight 674 if (vangle == 360.0) 675 return true; 676 if (vangle == 0.0) 677 return false; //Looker seems to be blind. 678 679 return fabs(AngleMod180(AngleTo(to.Origin) - botmo.Angles.yaw)) <= 680 vangle / 2.0; 681} 682 683//========================================================================== 684// 685// BotAim 686// 687//========================================================================== 688 689void BotAim() 690{ 691 float dist; 692 bool right; 693 float an; 694 695 if (!enemy) 696 return; 697 698 if (t_react) 699 return; 700 701 // Distance to enemy. 702 dist = botmo.DistTo2(enemy); 703 704 right = !!(P_Random() & 1); 705 an = AngleTo(enemy.Origin); 706 707 // [BC] Cajun prediction... maybe use this somewhere 708 /* 709 { 710 //Here goes the prediction. 711 dist = P_AproxDistance(MO->x - enemy->x, MO->y - enemy->y); 712 fixed_t m = (dist/FRACUNIT) / mobjinfo[MT_PLASMA].speed; 713 bot->SetAngle(R_PointToAngle2(MO->x, 714 MO->y, 715 enemy->x + FixedMul (enemy->momx, (m*2*FRACUNIT)), 716 enemy->y + FixedMul (enemy->momy, (m*2*FRACUNIT)))); 717 } 718 */ 719 720 // Fix me: Implement botskill, accuracy, and intelligence 721 if (player.ReadyWeapon.bBotProjectile) 722 { 723 // Splash weapons 724// bot->SetAngle(R_PointToAngle2(MO->x, MO->y, enemy->x, enemy->y)); 725 726 // Projectile weapons 727 switch (info.intelect) 728 { 729 case bsk_verypoor: 730 case bsk_poor: 731 case bsk_low: 732 // Aim right at the enemy 733 SetAngle(an); 734 break; 735 case bsk_medium: 736 case bsk_high: 737 case bsk_excellent: 738 case bsk_supreme: 739 if (right) 740 SetAngle(an + Random() * 20.0); 741 else 742 SetAngle(an - Random() * 20.0); 743 break; 744 745 default: 746 Error("Unknown bot skill level: %d", info.accuracy); 747 return; 748 } 749 } 750 else 751 { 752 // Instant weapons 753 switch (info.accuracy) 754 { 755 case bsk_verypoor: 756 if (right) 757 SetAngle(an + Random() * 60.0); 758 else 759 SetAngle(an - Random() * 60.0); 760 break; 761 case bsk_poor: 762 if (right) 763 SetAngle(an + Random() * 45.0); 764 else 765 SetAngle(an - Random() * 45.0); 766 break; 767 case bsk_low: 768 if (right) 769 SetAngle(an + Random() * 30.0); 770 else 771 SetAngle(an - Random() * 30.0); 772 break; 773 case bsk_medium: 774 if (right) 775 SetAngle(an + Random() * 15.0); 776 else 777 SetAngle(an - Random() * 15.0); 778 break; 779 case bsk_high: 780 SetAngle(an); 781 break; 782 case bsk_excellent: 783 SetAngle(AngleTo(enemy.Origin + enemy.Velocity * 0.1)); 784 break; 785 case bsk_supreme://FIXME 786 SetAngle(AngleTo(enemy.Origin + enemy.Velocity * 0.1)); 787 break; 788 default: 789 Error("Unknown bot skill level: %d", info.accuracy); 790 return; 791 } 792 } 793} 794 795//========================================================================== 796// 797// FindEnemy 798// 799//========================================================================== 800 801Actor FindEnemy() 802{ 803 float closest_dist, temp; 804 Actor target; 805 float vangle; 806 int i; 807 808 //Note: It's hard to ambush a bot who is not alone 809 if (bAllRound || ally) 810 vangle = 360.0; 811 else 812 vangle = ENEMY_SCAN_FOV; 813 bAllRound = false; 814 815 target = none; 816 closest_dist = 99999.0; 817 818 // Search for player enemies 819 for (i = 0; i < MAXPLAYERS; i++) 820 { 821 if (player.Level.Game.Players[i] && 822 player.Level.Game.Players[i].MO.Health > 0 && botmo != player.Level.Game.Players[i].MO) 823 { 824 //Here's a strange one, when bot is standing still, the CanSee within Check_LOS almost always returns false. tought it should be the same checksight as below but.. (below works) something must be fuckin wierd screded up. 825 if (Check_LOS(Actor(player.Level.Game.Players[i].MO), vangle)) 826 { 827 if (botmo.CanSee(player.Level.Game.Players[i].MO)) 828 { 829 temp = player.Level.Game.Players[i].MO.DistTo(botmo); 830 if (temp < closest_dist) 831 { 832 closest_dist = temp; 833 target = Actor(player.Level.Game.Players[i].MO); 834 } 835 } 836 } 837 } 838 } 839 840 return target; 841} 842 843//========================================================================== 844// 845// Roam 846// 847// Handle non-attack/dodging movement 848// 849//========================================================================== 850 851void Roam() 852{ 853 TVec dest; 854 855 if (lastpos_valid) 856 { 857 TVec dir; 858 859 dir = lastpos - botmo.Origin; 860 dir.z = 0.0; 861 if (Length(dir) <= 32.0) 862 { 863 lastpos_valid = false; 864 } 865 } 866 867 // Order of item response precedence: 868 if (goal) 869 { 870 dest = goal.Origin; 871 } 872 else if (lastpos_valid) 873 { 874 dest = lastpos; 875 } 876 else if (item) 877 { 878 dest = item.Origin; 879 } 880 else if (node) 881 { 882 dest = node.Origin; 883 } 884 else if (posdest_valid) 885 { 886 dest = posdest; 887 } 888 else 889 { 890 // No target, so just run around until we find something 891 int r = P_Random(); 892 float an = GetAngle(); 893 float dist; 894 895 for (dist = 256.0; dist >= 64.0; dist -= 64.0) 896 { 897 if (CheckPath(an, dist)) 898 { 899 posdest_valid = true; 900 posdest.x = botmo.Origin.x + dist * cos(an); 901 posdest.y = botmo.Origin.y + dist * sin(an); 902 posdest.z = botmo.Origin.z; 903 SetAngle(an); 904 break; 905 } 906 907 if (CheckPath(an + 45.0, dist)) 908 { 909 posdest_valid = true; 910 posdest.x = botmo.Origin.x + dist * cos(an + 45.0); 911 posdest.y = botmo.Origin.y + dist * sin(an + 45.0); 912 posdest.z = botmo.Origin.z; 913 SetAngle(an + 45.0); 914 break; 915 } 916 917 // Left is no good, try right 918 if (CheckPath(an - 45.0, dist)) 919 { 920 posdest_valid = true; 921 posdest.x = botmo.Origin.x + dist * cos(an - 45.0); 922 posdest.y = botmo.Origin.y + dist * sin(an - 45.0); 923 posdest.z = botmo.Origin.z; 924 SetAngle(an - 45.0); 925 break; 926 } 927 } 928 if (posdest_valid) 929 { 930 dest = posdest; 931 } 932 else 933 { 934 SetAngle(GetAngle() + 45.0 / 3.0); 935 forwardmove = -FORWARDWALK; 936 return; 937 } 938 } 939 940 forwardmove = FORWARDRUN; 941 SetAngle(AngleTo(dest)); 942} 943 944//========================================================================== 945// 946// Move 947// 948// Main bot movement function. Dodging/attacking movement is also handled 949// here 950// 951//========================================================================== 952 953void Move(float deltaTime) 954{ 955 float dist; 956 957 // Worry about missiles above all else 958 if (missile) 959 { 960 if (t_strafe) 961 { 962 t_strafe -= deltaTime; 963 if (!t_strafe) 964 { 965 // Don't change direction while dodging missiles (that could be bad) 966 //bot->sidemove = -bot->sidemove; 967 t_strafe = 2.0; 968 } 969 } 970 971 // Look at the missle and sidestep it 972 SetAngle(AngleTo(missile.Origin)); 973 forwardmove = -FORWARDRUN; 974 return; 975 } 976 977 // Anticipate a shot: time to dodge! 978 if (enemy && t_anticip <= 0.25) 979 { 980 if (!sidemove) 981 sidemove = SIDERUN; 982 983 switch (info.anticip) 984 { 985 case bsk_verypoor: 986 // Deer caught in the headlights 987 sidemove = 0.0; 988 return; 989 990 case bsk_poor: 991 // Always walk right 992 sidemove = SIDEWALK; 993 break; 994 995 case bsk_low: 996 // Always run right 997 sidemove = SIDERUN; 998 break; 999 1000 case bsk_medium: 1001 // Just switch directions every couple seconds 1002 if (t_strafe) 1003 { 1004 t_strafe -= deltaTime; 1005 if (t_strafe <= 0.0) 1006 { 1007 sidemove = -sidemove; 1008 t_strafe = 2.0; 1009 } 1010 } 1011 break; 1012 1013 case bsk_high: 1014 // Switch directions when we think our opponent will fire 1015 if (!t_anticip) 1016 sidemove = -sidemove; 1017 break; 1018 1019 case bsk_excellent: 1020 // Move in a somewhat random direction when we think our opponent will fire 1021 if (!t_anticip) 1022 { 1023 sidemove = Random() < 0.5 ? SIDERUN : -SIDERUN; 1024 1025 if (Random() < 0.5) 1026 { 1027 forwardmove = -FORWARDWALK; 1028 } 1029 } 1030 break; 1031 1032 case bsk_supreme: 1033 // What a squirmy little fucker! 1034 if (!t_anticip) 1035 { 1036 switch (P_Random() & 3) 1037 { 1038 case 0: 1039 sidemove = SIDERUN; 1040 break; 1041 case 1: 1042 sidemove = SIDEWALK; 1043 break; 1044 case 2: 1045 sidemove = -SIDERUN; 1046 break; 1047 case 3: 1048 sidemove = -SIDEWALK; 1049 break; 1050 } 1051 1052 switch (P_Random() & 3) 1053 { 1054 case 0: 1055 forwardmove = FORWARDRUN; 1056 break; 1057 case 1: 1058 forwardmove = FORWARDWALK; 1059 break; 1060 case 2: 1061 forwardmove = -FORWARDRUN; 1062 break; 1063 case 3: 1064 forwardmove = -FORWARDWALK; 1065 break; 1066 } 1067 } 1068 break; 1069 1070 default: 1071 Error("Unknown bot skill level: %d", info.anticip); 1072 return; 1073 } 1074 } 1075 1076 // Now handle attack movement 1077 if (enemy) 1078 { 1079 bool noforward; 1080 bool noside; 1081 float an; 1082 1083 noforward = false; 1084 noside = false; 1085 if (!sidemove ) 1086 sidemove = SIDERUN; 1087 1088 if (t_strafe) 1089 { 1090 t_strafe -= deltaTime; 1091 if (t_strafe <= 0.0) 1092 { 1093 sidemove = -sidemove; 1094 t_strafe = 2.0; 1095 } 1096 } 1097 1098 dist = botmo.DistTo(enemy); 1099 1100 // Remember where we saw him last in case he gets away 1101 lastpos = enemy.Origin; 1102 lastpos_valid = true; 1103 1104 // Check if we'd rather pick up something than fight 1105 if (item) 1106 { 1107 if (bItemIsPowerup || 1108 (bItemIsWeapon && player.ReadyWeapon.bWimpyWeapon)) 1109 { 1110 Roam(); 1111 return; 1112 } 1113 } 1114 1115 an = botmo.Angles.yaw; 1116 if (sidemove < 0.0) 1117 an -= 90.0; 1118 else 1119 an += 90.0; 1120 1121 if (!CheckPath(an, 48.0)) // We're blocked, so go the other way! 1122 sidemove = -sidemove; 1123 1124 if (CheckTo(enemy.Origin) && dist > player.ReadyWeapon.BotCombatDist) 1125 forwardmove = FORWARDRUN; 1126 else 1127 forwardmove = -FORWARDRUN; 1128 1129 return; 1130 } 1131 1132 if (t_strafe) 1133 { 1134 t_strafe -= deltaTime; 1135 if (t_strafe <= 0.0) 1136 { 1137 sidemove = -sidemove; 1138 t_strafe = 2.0; 1139 } 1140 } 1141 1142 // Roam after an item 1143 Roam(); 1144} 1145 1146//========================================================================== 1147// 1148// Attack 1149// 1150//========================================================================== 1151 1152void Attack() 1153{ 1154 // Still reacting to something or we don't have an enemy to fight 1155 if (t_react || !enemy) 1156 return; 1157 1158 // No point in firing if we won't hit them 1159 if (!Check_LOS(enemy, SHOOTFOV)) 1160 return; 1161 1162 player.Buttons |= BT_ATTACK; 1163} 1164 1165//========================================================================== 1166// 1167// Turn 1168// 1169// [BC] Ahh, the new and improved turning... 1170// 1171//========================================================================== 1172 1173void Turn() 1174{ 1175 float distance; 1176 1177 distance = GetAngle() - botmo.Angles.yaw; 1178 1179 if (!enemy) 1180 { 1181 player.ViewAngles.yaw = GetAngle(); 1182 return; 1183 } 1184 1185 // [BC] Don't act crazy while trying to aim 1186 switch (info.accuracy) 1187 { 1188 case bsk_verypoor: 1189 case bsk_poor: 1190 case bsk_low: 1191 if (distance > 7.5) 1192 distance = 7.5; 1193 if (distance < -7.5) 1194 distance = -7.5; 1195 break; 1196 case bsk_medium: 1197 if (distance > 15.0) 1198 distance = 15.0; 1199 if (distance < -15.0) 1200 distance = -15.0; 1201 break; 1202 case bsk_high: 1203 if (distance > 22.5) 1204 distance = 22.5; 1205 if (distance < -22.5) 1206 distance = -22.5; 1207 break; 1208 case bsk_excellent: 1209 if (distance > 30.0) 1210 distance = 30.0; 1211 if (distance < -30.0) 1212 distance = -30.0; 1213 break; 1214 case bsk_supreme: 1215 if (distance > 37.5) 1216 distance = 37.5; 1217 if (distance < -37.5) 1218 distance = -37.5; 1219 break; 1220 } 1221 player.ViewAngles.yaw = AngleMod360(botmo.Angles.yaw + distance); 1222} 1223 1224//========================================================================== 1225// 1226// Pitch 1227// 1228//========================================================================== 1229 1230void Pitch() 1231{ 1232 if (enemy) 1233 { 1234 TVec dir; 1235 TAVec ang; 1236 1237 dir = enemy.Origin - botmo.Origin; 1238 VectorAngles(&dir, &ang); 1239 botmo.Angles.pitch = ang.pitch; 1240 } 1241 else 1242 { 1243 botmo.Angles.pitch = 0.0; 1244 } 1245} 1246 1247//========================================================================== 1248// 1249// Killed 1250// 1251//========================================================================== 1252 1253void Killed(EntityEx victim) 1254{ 1255 // [BC] Let some anger out 1256 angerlevel -= 5; 1257 enemy = none; 1258 1259 // [BC] Don't need to worry about following him anymore 1260 lastpos_valid = false; 1261} 1262 1263//========================================================================== 1264// 1265// Died 1266// 1267//========================================================================== 1268 1269void Died(EntityEx killer) 1270{ 1271} 1272 1273//========================================================================== 1274// 1275// OnBeginPlay 1276// 1277//========================================================================== 1278 1279void OnBeginPlay() 1280{ 1281 int botskill = 2; 1282 int bottype = 0; 1283 botinfo_t *binfo; 1284 int i; 1285 1286 if (strcmp(player.PlayerName, "")) 1287 { 1288 for (i = 0; i < MainGameInfo::NUMTOTALBOTS; i++) 1289 { 1290 if (!stricmp(MainGameInfo(player.Level.Game).botinfo[i].Name, player.PlayerName)) 1291 { 1292 bottype = i; 1293 break; 1294 } 1295 } 1296 // We've already handled the "what if there's no match" exception 1297 } 1298 else 1299 { 1300 // If the user doesn't input a name, don't 1301 // spawn one of the "special" bots, only one of the 1302 // normal ones. 1303 bottype = P_Random() % MainGameInfo::NUMBOTTYPES; 1304 } 1305 1306 binfo = &MainGameInfo(player.Level.Game).botinfo[bottype]; 1307 1308 t_strafe = 1.0; 1309 1310 if (botskill > 4) 1311 botskill = 4; 1312 if (botskill < 0) 1313 botskill = 0; 1314 1315 // Implement skill settings 1316 info.accuracy = SkillLower(binfo->accuracy, 4 - botskill); 1317 info.intelect = SkillLower(binfo->intelect, 4 - botskill); 1318 info.evade = SkillLower(binfo->evade, 4 - botskill); 1319 info.anticip = SkillLower(binfo->anticip, 4 - botskill); 1320 info.reaction = SkillLower(binfo->reaction, 4 - botskill); 1321 player.UserInfo = binfo->userinfo; 1322} 1323 1324//========================================================================== 1325// 1326// OnSpawn 1327// 1328//========================================================================== 1329 1330void OnSpawn() 1331{ 1332} 1333 1334defaultproperties 1335{ 1336} 1337