bastd.actor.spazbot
Bot versions of Spaz.
1# Released under the MIT License. See LICENSE for details. 2# 3"""Bot versions of Spaz.""" 4# pylint: disable=too-many-lines 5 6from __future__ import annotations 7 8import random 9import weakref 10from typing import TYPE_CHECKING 11 12import ba 13from bastd.actor.spaz import Spaz 14 15if TYPE_CHECKING: 16 from typing import Any, Sequence, Callable 17 from bastd.actor.flag import Flag 18 19LITE_BOT_COLOR = (1.2, 0.9, 0.2) 20LITE_BOT_HIGHLIGHT = (1.0, 0.5, 0.6) 21DEFAULT_BOT_COLOR = (0.6, 0.6, 0.6) 22DEFAULT_BOT_HIGHLIGHT = (0.1, 0.3, 0.1) 23PRO_BOT_COLOR = (1.0, 0.2, 0.1) 24PRO_BOT_HIGHLIGHT = (0.6, 0.1, 0.05) 25 26 27class SpazBotPunchedMessage: 28 """A message saying a ba.SpazBot got punched. 29 30 Category: **Message Classes** 31 """ 32 33 spazbot: SpazBot 34 """The ba.SpazBot that got punched.""" 35 36 damage: int 37 """How much damage was done to the SpazBot.""" 38 39 def __init__(self, spazbot: SpazBot, damage: int): 40 """Instantiate a message with the given values.""" 41 self.spazbot = spazbot 42 self.damage = damage 43 44 45class SpazBotDiedMessage: 46 """A message saying a ba.SpazBot has died. 47 48 Category: **Message Classes** 49 """ 50 51 spazbot: SpazBot 52 """The SpazBot that was killed.""" 53 54 killerplayer: ba.Player | None 55 """The ba.Player that killed it (or None).""" 56 57 how: ba.DeathType 58 """The particular type of death.""" 59 60 def __init__(self, spazbot: SpazBot, killerplayer: ba.Player | None, 61 how: ba.DeathType): 62 """Instantiate with given values.""" 63 self.spazbot = spazbot 64 self.killerplayer = killerplayer 65 self.how = how 66 67 68class SpazBot(Spaz): 69 """A really dumb AI version of ba.Spaz. 70 71 Category: **Bot Classes** 72 73 Add these to a ba.BotSet to use them. 74 75 Note: currently the AI has no real ability to 76 navigate obstacles and so should only be used 77 on wide-open maps. 78 79 When a SpazBot is killed, it delivers a ba.SpazBotDiedMessage 80 to the current activity. 81 82 When a SpazBot is punched, it delivers a ba.SpazBotPunchedMessage 83 to the current activity. 84 """ 85 86 character = 'Spaz' 87 punchiness = 0.5 88 throwiness = 0.7 89 static = False 90 bouncy = False 91 run = False 92 charge_dist_min = 0.0 # When we can start a new charge. 93 charge_dist_max = 2.0 # When we can start a new charge. 94 run_dist_min = 0.0 # How close we can be to continue running. 95 charge_speed_min = 0.4 96 charge_speed_max = 1.0 97 throw_dist_min = 5.0 98 throw_dist_max = 9.0 99 throw_rate = 1.0 100 default_bomb_type = 'normal' 101 default_bomb_count = 3 102 start_cursed = False 103 color = DEFAULT_BOT_COLOR 104 highlight = DEFAULT_BOT_HIGHLIGHT 105 106 def __init__(self) -> None: 107 """Instantiate a spaz-bot.""" 108 super().__init__(color=self.color, 109 highlight=self.highlight, 110 character=self.character, 111 source_player=None, 112 start_invincible=False, 113 can_accept_powerups=False) 114 115 # If you need to add custom behavior to a bot, set this to a callable 116 # which takes one arg (the bot) and returns False if the bot's normal 117 # update should be run and True if not. 118 self.update_callback: Callable[[SpazBot], Any] | None = None 119 activity = self.activity 120 assert isinstance(activity, ba.GameActivity) 121 self._map = weakref.ref(activity.map) 122 self.last_player_attacked_by: ba.Player | None = None 123 self.last_attacked_time = 0.0 124 self.last_attacked_type: tuple[str, str] | None = None 125 self.target_point_default: ba.Vec3 | None = None 126 self.held_count = 0 127 self.last_player_held_by: ba.Player | None = None 128 self.target_flag: Flag | None = None 129 self._charge_speed = 0.5 * (self.charge_speed_min + 130 self.charge_speed_max) 131 self._lead_amount = 0.5 132 self._mode = 'wait' 133 self._charge_closing_in = False 134 self._last_charge_dist = 0.0 135 self._running = False 136 self._last_jump_time = 0.0 137 138 self._throw_release_time: float | None = None 139 self._have_dropped_throw_bomb: bool | None = None 140 self._player_pts: list[tuple[ba.Vec3, ba.Vec3]] | None = None 141 142 # These cooldowns didn't exist when these bots were calibrated, 143 # so take them out of the equation. 144 self._jump_cooldown = 0 145 self._pickup_cooldown = 0 146 self._fly_cooldown = 0 147 self._bomb_cooldown = 0 148 149 if self.start_cursed: 150 self.curse() 151 152 @property 153 def map(self) -> ba.Map: 154 """The map this bot was created on.""" 155 mval = self._map() 156 assert mval is not None 157 return mval 158 159 def _get_target_player_pt(self) -> tuple[ba.Vec3 | None, ba.Vec3 | None]: 160 """Returns the position and velocity of our target. 161 162 Both values will be None in the case of no target. 163 """ 164 assert self.node 165 botpt = ba.Vec3(self.node.position) 166 closest_dist: float | None = None 167 closest_vel: ba.Vec3 | None = None 168 closest: ba.Vec3 | None = None 169 assert self._player_pts is not None 170 for plpt, plvel in self._player_pts: 171 dist = (plpt - botpt).length() 172 173 # Ignore player-points that are significantly below the bot 174 # (keeps bots from following players off cliffs). 175 if (closest_dist is None 176 or dist < closest_dist) and (plpt[1] > botpt[1] - 5.0): 177 closest_dist = dist 178 closest_vel = plvel 179 closest = plpt 180 if closest_dist is not None: 181 assert closest_vel is not None 182 assert closest is not None 183 return (ba.Vec3(closest[0], closest[1], closest[2]), 184 ba.Vec3(closest_vel[0], closest_vel[1], closest_vel[2])) 185 return None, None 186 187 def set_player_points(self, pts: list[tuple[ba.Vec3, ba.Vec3]]) -> None: 188 """Provide the spaz-bot with the locations of its enemies.""" 189 self._player_pts = pts 190 191 def update_ai(self) -> None: 192 """Should be called periodically to update the spaz' AI.""" 193 # pylint: disable=too-many-branches 194 # pylint: disable=too-many-statements 195 # pylint: disable=too-many-locals 196 if self.update_callback is not None: 197 if self.update_callback(self): 198 # Bot has been handled. 199 return 200 201 if not self.node: 202 return 203 204 pos = self.node.position 205 our_pos = ba.Vec3(pos[0], 0, pos[2]) 206 can_attack = True 207 208 target_pt_raw: ba.Vec3 | None 209 target_vel: ba.Vec3 | None 210 211 # If we're a flag-bearer, we're pretty simple-minded - just walk 212 # towards the flag and try to pick it up. 213 if self.target_flag: 214 if self.node.hold_node: 215 holding_flag = (self.node.hold_node.getnodetype() == 'flag') 216 else: 217 holding_flag = False 218 219 # If we're holding the flag, just walk left. 220 if holding_flag: 221 # Just walk left. 222 self.node.move_left_right = -1.0 223 self.node.move_up_down = 0.0 224 225 # Otherwise try to go pick it up. 226 elif self.target_flag.node: 227 target_pt_raw = ba.Vec3(*self.target_flag.node.position) 228 diff = (target_pt_raw - our_pos) 229 diff = ba.Vec3(diff[0], 0, diff[2]) # Don't care about y. 230 dist = diff.length() 231 to_target = diff.normalized() 232 233 # If we're holding some non-flag item, drop it. 234 if self.node.hold_node: 235 self.node.pickup_pressed = True 236 self.node.pickup_pressed = False 237 return 238 239 # If we're a runner, run only when not super-near the flag. 240 if self.run and dist > 3.0: 241 self._running = True 242 self.node.run = 1.0 243 else: 244 self._running = False 245 self.node.run = 0.0 246 247 self.node.move_left_right = to_target.x 248 self.node.move_up_down = -to_target.z 249 if dist < 1.25: 250 self.node.pickup_pressed = True 251 self.node.pickup_pressed = False 252 return 253 254 # Not a flag-bearer. If we're holding anything but a bomb, drop it. 255 if self.node.hold_node: 256 holding_bomb = (self.node.hold_node.getnodetype() 257 in ['bomb', 'prop']) 258 if not holding_bomb: 259 self.node.pickup_pressed = True 260 self.node.pickup_pressed = False 261 return 262 263 target_pt_raw, target_vel = self._get_target_player_pt() 264 265 if target_pt_raw is None: 266 # Use default target if we've got one. 267 if self.target_point_default is not None: 268 target_pt_raw = self.target_point_default 269 target_vel = ba.Vec3(0, 0, 0) 270 can_attack = False 271 272 # With no target, we stop moving and drop whatever we're holding. 273 else: 274 self.node.move_left_right = 0 275 self.node.move_up_down = 0 276 if self.node.hold_node: 277 self.node.pickup_pressed = True 278 self.node.pickup_pressed = False 279 return 280 281 # We don't want height to come into play. 282 target_pt_raw[1] = 0.0 283 assert target_vel is not None 284 target_vel[1] = 0.0 285 286 dist_raw = (target_pt_raw - our_pos).length() 287 288 # Use a point out in front of them as real target. 289 # (more out in front the farther from us they are) 290 target_pt = (target_pt_raw + 291 target_vel * dist_raw * 0.3 * self._lead_amount) 292 293 diff = (target_pt - our_pos) 294 dist = diff.length() 295 to_target = diff.normalized() 296 297 if self._mode == 'throw': 298 # We can only throw if alive and well. 299 if not self._dead and not self.node.knockout: 300 301 assert self._throw_release_time is not None 302 time_till_throw = self._throw_release_time - ba.time() 303 304 if not self.node.hold_node: 305 # If we haven't thrown yet, whip out the bomb. 306 if not self._have_dropped_throw_bomb: 307 self.drop_bomb() 308 self._have_dropped_throw_bomb = True 309 310 # Otherwise our lack of held node means we successfully 311 # released our bomb; lets retreat now. 312 else: 313 self._mode = 'flee' 314 315 # Oh crap, we're holding a bomb; better throw it. 316 elif time_till_throw <= 0.0: 317 # Jump and throw. 318 def _safe_pickup(node: ba.Node) -> None: 319 if node and self.node: 320 self.node.pickup_pressed = True 321 self.node.pickup_pressed = False 322 323 if dist > 5.0: 324 self.node.jump_pressed = True 325 self.node.jump_pressed = False 326 327 # Throws: 328 ba.timer(0.1, ba.Call(_safe_pickup, self.node)) 329 else: 330 # Throws: 331 ba.timer(0.1, ba.Call(_safe_pickup, self.node)) 332 333 if self.static: 334 if time_till_throw < 0.3: 335 speed = 1.0 336 elif time_till_throw < 0.7 and dist > 3.0: 337 speed = -1.0 # Whiplash for long throws. 338 else: 339 speed = 0.02 340 else: 341 if time_till_throw < 0.7: 342 # Right before throw charge full speed towards target. 343 speed = 1.0 344 else: 345 # Earlier we can hold or move backward for a whiplash. 346 speed = 0.0125 347 self.node.move_left_right = to_target.x * speed 348 self.node.move_up_down = to_target.z * -1.0 * speed 349 350 elif self._mode == 'charge': 351 if random.random() < 0.3: 352 self._charge_speed = random.uniform(self.charge_speed_min, 353 self.charge_speed_max) 354 355 # If we're a runner we run during charges *except when near 356 # an edge (otherwise we tend to fly off easily). 357 if self.run and dist_raw > self.run_dist_min: 358 self._lead_amount = 0.3 359 self._running = True 360 self.node.run = 1.0 361 else: 362 self._lead_amount = 0.01 363 self._running = False 364 self.node.run = 0.0 365 366 self.node.move_left_right = to_target.x * self._charge_speed 367 self.node.move_up_down = to_target.z * -1.0 * self._charge_speed 368 369 elif self._mode == 'wait': 370 # Every now and then, aim towards our target. 371 # Other than that, just stand there. 372 if ba.time(timeformat=ba.TimeFormat.MILLISECONDS) % 1234 < 100: 373 self.node.move_left_right = to_target.x * (400.0 / 33000) 374 self.node.move_up_down = to_target.z * (-400.0 / 33000) 375 else: 376 self.node.move_left_right = 0 377 self.node.move_up_down = 0 378 379 elif self._mode == 'flee': 380 # Even if we're a runner, only run till we get away from our 381 # target (if we keep running we tend to run off edges). 382 if self.run and dist < 3.0: 383 self._running = True 384 self.node.run = 1.0 385 else: 386 self._running = False 387 self.node.run = 0.0 388 self.node.move_left_right = to_target.x * -1.0 389 self.node.move_up_down = to_target.z 390 391 # We might wanna switch states unless we're doing a throw 392 # (in which case that's our sole concern). 393 if self._mode != 'throw': 394 395 # If we're currently charging, keep track of how far we are 396 # from our target. When this value increases it means our charge 397 # is over (ran by them or something). 398 if self._mode == 'charge': 399 if (self._charge_closing_in 400 and self._last_charge_dist < dist < 3.0): 401 self._charge_closing_in = False 402 self._last_charge_dist = dist 403 404 # If we have a clean shot, throw! 405 if (self.throw_dist_min <= dist < self.throw_dist_max 406 and random.random() < self.throwiness and can_attack): 407 self._mode = 'throw' 408 self._lead_amount = ((0.4 + random.random() * 0.6) 409 if dist_raw > 4.0 else 410 (0.1 + random.random() * 0.4)) 411 self._have_dropped_throw_bomb = False 412 self._throw_release_time = (ba.time() + 413 (1.0 / self.throw_rate) * 414 (0.8 + 1.3 * random.random())) 415 416 # If we're static, always charge (which for us means barely move). 417 elif self.static: 418 self._mode = 'wait' 419 420 # If we're too close to charge (and aren't in the middle of an 421 # existing charge) run away. 422 elif dist < self.charge_dist_min and not self._charge_closing_in: 423 # ..unless we're near an edge, in which case we've got no 424 # choice but to charge. 425 if self.map.is_point_near_edge(our_pos, self._running): 426 if self._mode != 'charge': 427 self._mode = 'charge' 428 self._lead_amount = 0.2 429 self._charge_closing_in = True 430 self._last_charge_dist = dist 431 else: 432 self._mode = 'flee' 433 434 # We're within charging distance, backed against an edge, 435 # or farther than our max throw distance.. chaaarge! 436 elif (dist < self.charge_dist_max or dist > self.throw_dist_max 437 or self.map.is_point_near_edge(our_pos, self._running)): 438 if self._mode != 'charge': 439 self._mode = 'charge' 440 self._lead_amount = 0.01 441 self._charge_closing_in = True 442 self._last_charge_dist = dist 443 444 # We're too close to throw but too far to charge - either run 445 # away or just chill if we're near an edge. 446 elif dist < self.throw_dist_min: 447 # Charge if either we're within charge range or 448 # cant retreat to throw. 449 self._mode = 'flee' 450 451 # Do some awesome jumps if we're running. 452 # FIXME: pylint: disable=too-many-boolean-expressions 453 if ((self._running and 1.2 < dist < 2.2 454 and ba.time() - self._last_jump_time > 1.0) 455 or (self.bouncy and ba.time() - self._last_jump_time > 0.4 456 and random.random() < 0.5)): 457 self._last_jump_time = ba.time() 458 self.node.jump_pressed = True 459 self.node.jump_pressed = False 460 461 # Throw punches when real close. 462 if dist < (1.6 if self._running else 1.2) and can_attack: 463 if random.random() < self.punchiness: 464 self.on_punch_press() 465 self.on_punch_release() 466 467 def on_punched(self, damage: int) -> None: 468 """ 469 Method override; sends ba.SpazBotPunchedMessage 470 to the current activity. 471 """ 472 ba.getactivity().handlemessage(SpazBotPunchedMessage(self, damage)) 473 474 def on_expire(self) -> None: 475 super().on_expire() 476 477 # We're being torn down; release our callback(s) so there's 478 # no chance of them keeping activities or other things alive. 479 self.update_callback = None 480 481 def handlemessage(self, msg: Any) -> Any: 482 # pylint: disable=too-many-branches 483 assert not self.expired 484 485 # Keep track of if we're being held and by who most recently. 486 if isinstance(msg, ba.PickedUpMessage): 487 super().handlemessage(msg) # Augment standard behavior. 488 self.held_count += 1 489 picked_up_by = msg.node.source_player 490 if picked_up_by: 491 self.last_player_held_by = picked_up_by 492 493 elif isinstance(msg, ba.DroppedMessage): 494 super().handlemessage(msg) # Augment standard behavior. 495 self.held_count -= 1 496 if self.held_count < 0: 497 print('ERROR: spaz held_count < 0') 498 499 # Let's count someone dropping us as an attack. 500 try: 501 if msg.node: 502 picked_up_by = msg.node.source_player 503 else: 504 picked_up_by = None 505 except Exception: 506 ba.print_exception('Error on SpazBot DroppedMessage.') 507 picked_up_by = None 508 509 if picked_up_by: 510 self.last_player_attacked_by = picked_up_by 511 self.last_attacked_time = ba.time() 512 self.last_attacked_type = ('picked_up', 'default') 513 514 elif isinstance(msg, ba.DieMessage): 515 516 # Report normal deaths for scoring purposes. 517 if not self._dead and not msg.immediate: 518 519 killerplayer: ba.Player | None 520 521 # If this guy was being held at the time of death, the 522 # holder is the killer. 523 if self.held_count > 0 and self.last_player_held_by: 524 killerplayer = self.last_player_held_by 525 else: 526 # If they were attacked by someone in the last few 527 # seconds that person's the killer. 528 # Otherwise it was a suicide. 529 if (self.last_player_attacked_by 530 and ba.time() - self.last_attacked_time < 4.0): 531 killerplayer = self.last_player_attacked_by 532 else: 533 killerplayer = None 534 activity = self._activity() 535 536 # (convert dead player refs to None) 537 if not killerplayer: 538 killerplayer = None 539 if activity is not None: 540 activity.handlemessage( 541 SpazBotDiedMessage(self, killerplayer, msg.how)) 542 super().handlemessage(msg) # Augment standard behavior. 543 544 # Keep track of the player who last hit us for point rewarding. 545 elif isinstance(msg, ba.HitMessage): 546 source_player = msg.get_source_player(ba.Player) 547 if source_player: 548 self.last_player_attacked_by = source_player 549 self.last_attacked_time = ba.time() 550 self.last_attacked_type = (msg.hit_type, msg.hit_subtype) 551 super().handlemessage(msg) 552 else: 553 super().handlemessage(msg) 554 555 556class BomberBot(SpazBot): 557 """A bot that throws regular bombs and occasionally punches. 558 559 category: Bot Classes 560 """ 561 character = 'Spaz' 562 punchiness = 0.3 563 564 565class BomberBotLite(BomberBot): 566 """A less aggressive yellow version of ba.BomberBot. 567 568 category: Bot Classes 569 """ 570 color = LITE_BOT_COLOR 571 highlight = LITE_BOT_HIGHLIGHT 572 punchiness = 0.2 573 throw_rate = 0.7 574 throwiness = 0.1 575 charge_speed_min = 0.6 576 charge_speed_max = 0.6 577 578 579class BomberBotStaticLite(BomberBotLite): 580 """A less aggressive generally immobile weak version of ba.BomberBot. 581 582 category: Bot Classes 583 """ 584 static = True 585 throw_dist_min = 0.0 586 587 588class BomberBotStatic(BomberBot): 589 """A version of ba.BomberBot who generally stays in one place. 590 591 category: Bot Classes 592 """ 593 static = True 594 throw_dist_min = 0.0 595 596 597class BomberBotPro(BomberBot): 598 """A more powerful version of ba.BomberBot. 599 600 category: Bot Classes 601 """ 602 points_mult = 2 603 color = PRO_BOT_COLOR 604 highlight = PRO_BOT_HIGHLIGHT 605 default_bomb_count = 3 606 default_boxing_gloves = True 607 punchiness = 0.7 608 throw_rate = 1.3 609 run = True 610 run_dist_min = 6.0 611 612 613class BomberBotProShielded(BomberBotPro): 614 """A more powerful version of ba.BomberBot who starts with shields. 615 616 category: Bot Classes 617 """ 618 points_mult = 3 619 default_shields = True 620 621 622class BomberBotProStatic(BomberBotPro): 623 """A more powerful ba.BomberBot who generally stays in one place. 624 625 category: Bot Classes 626 """ 627 static = True 628 throw_dist_min = 0.0 629 630 631class BomberBotProStaticShielded(BomberBotProShielded): 632 """A powerful ba.BomberBot with shields who is generally immobile. 633 634 category: Bot Classes 635 """ 636 static = True 637 throw_dist_min = 0.0 638 639 640class BrawlerBot(SpazBot): 641 """A bot who walks and punches things. 642 643 category: Bot Classes 644 """ 645 character = 'Kronk' 646 punchiness = 0.9 647 charge_dist_max = 9999.0 648 charge_speed_min = 1.0 649 charge_speed_max = 1.0 650 throw_dist_min = 9999 651 throw_dist_max = 9999 652 653 654class BrawlerBotLite(BrawlerBot): 655 """A weaker version of ba.BrawlerBot. 656 657 category: Bot Classes 658 """ 659 color = LITE_BOT_COLOR 660 highlight = LITE_BOT_HIGHLIGHT 661 punchiness = 0.3 662 charge_speed_min = 0.6 663 charge_speed_max = 0.6 664 665 666class BrawlerBotPro(BrawlerBot): 667 """A stronger version of ba.BrawlerBot. 668 669 category: Bot Classes 670 """ 671 color = PRO_BOT_COLOR 672 highlight = PRO_BOT_HIGHLIGHT 673 run = True 674 run_dist_min = 4.0 675 default_boxing_gloves = True 676 punchiness = 0.95 677 points_mult = 2 678 679 680class BrawlerBotProShielded(BrawlerBotPro): 681 """A stronger version of ba.BrawlerBot who starts with shields. 682 683 category: Bot Classes 684 """ 685 default_shields = True 686 points_mult = 3 687 688 689class ChargerBot(SpazBot): 690 """A speedy melee attack bot. 691 692 category: Bot Classes 693 """ 694 695 character = 'Snake Shadow' 696 punchiness = 1.0 697 run = True 698 charge_dist_min = 10.0 699 charge_dist_max = 9999.0 700 charge_speed_min = 1.0 701 charge_speed_max = 1.0 702 throw_dist_min = 9999 703 throw_dist_max = 9999 704 points_mult = 2 705 706 707class BouncyBot(SpazBot): 708 """A speedy attacking melee bot that jumps constantly. 709 710 category: Bot Classes 711 """ 712 713 color = (1, 1, 1) 714 highlight = (1.0, 0.5, 0.5) 715 character = 'Easter Bunny' 716 punchiness = 1.0 717 run = True 718 bouncy = True 719 default_boxing_gloves = True 720 charge_dist_min = 10.0 721 charge_dist_max = 9999.0 722 charge_speed_min = 1.0 723 charge_speed_max = 1.0 724 throw_dist_min = 9999 725 throw_dist_max = 9999 726 points_mult = 2 727 728 729class ChargerBotPro(ChargerBot): 730 """A stronger ba.ChargerBot. 731 732 category: Bot Classes 733 """ 734 color = PRO_BOT_COLOR 735 highlight = PRO_BOT_HIGHLIGHT 736 default_shields = True 737 default_boxing_gloves = True 738 points_mult = 3 739 740 741class ChargerBotProShielded(ChargerBotPro): 742 """A stronger ba.ChargerBot who starts with shields. 743 744 category: Bot Classes 745 """ 746 default_shields = True 747 points_mult = 4 748 749 750class TriggerBot(SpazBot): 751 """A slow moving bot with trigger bombs. 752 753 category: Bot Classes 754 """ 755 character = 'Zoe' 756 punchiness = 0.75 757 throwiness = 0.7 758 charge_dist_max = 1.0 759 charge_speed_min = 0.3 760 charge_speed_max = 0.5 761 throw_dist_min = 3.5 762 throw_dist_max = 5.5 763 default_bomb_type = 'impact' 764 points_mult = 2 765 766 767class TriggerBotStatic(TriggerBot): 768 """A ba.TriggerBot who generally stays in one place. 769 770 category: Bot Classes 771 """ 772 static = True 773 throw_dist_min = 0.0 774 775 776class TriggerBotPro(TriggerBot): 777 """A stronger version of ba.TriggerBot. 778 779 category: Bot Classes 780 """ 781 color = PRO_BOT_COLOR 782 highlight = PRO_BOT_HIGHLIGHT 783 default_bomb_count = 3 784 default_boxing_gloves = True 785 charge_speed_min = 1.0 786 charge_speed_max = 1.0 787 punchiness = 0.9 788 throw_rate = 1.3 789 run = True 790 run_dist_min = 6.0 791 points_mult = 3 792 793 794class TriggerBotProShielded(TriggerBotPro): 795 """A stronger version of ba.TriggerBot who starts with shields. 796 797 category: Bot Classes 798 """ 799 default_shields = True 800 points_mult = 4 801 802 803class StickyBot(SpazBot): 804 """A crazy bot who runs and throws sticky bombs. 805 806 category: Bot Classes 807 """ 808 character = 'Mel' 809 punchiness = 0.9 810 throwiness = 1.0 811 run = True 812 charge_dist_min = 4.0 813 charge_dist_max = 10.0 814 charge_speed_min = 1.0 815 charge_speed_max = 1.0 816 throw_dist_min = 0.0 817 throw_dist_max = 4.0 818 throw_rate = 2.0 819 default_bomb_type = 'sticky' 820 default_bomb_count = 3 821 points_mult = 3 822 823 824class StickyBotStatic(StickyBot): 825 """A crazy bot who throws sticky-bombs but generally stays in one place. 826 827 category: Bot Classes 828 """ 829 static = True 830 831 832class ExplodeyBot(SpazBot): 833 """A bot who runs and explodes in 5 seconds. 834 835 category: Bot Classes 836 """ 837 character = 'Jack Morgan' 838 run = True 839 charge_dist_min = 0.0 840 charge_dist_max = 9999 841 charge_speed_min = 1.0 842 charge_speed_max = 1.0 843 throw_dist_min = 9999 844 throw_dist_max = 9999 845 start_cursed = True 846 points_mult = 4 847 848 849class ExplodeyBotNoTimeLimit(ExplodeyBot): 850 """A bot who runs but does not explode on his own. 851 852 category: Bot Classes 853 """ 854 curse_time = None 855 856 857class ExplodeyBotShielded(ExplodeyBot): 858 """A ba.ExplodeyBot who starts with shields. 859 860 category: Bot Classes 861 """ 862 default_shields = True 863 points_mult = 5 864 865 866class SpazBotSet: 867 """A container/controller for one or more ba.SpazBots. 868 869 category: Bot Classes 870 """ 871 872 def __init__(self) -> None: 873 """Create a bot-set.""" 874 875 # We spread our bots out over a few lists so we can update 876 # them in a staggered fashion. 877 self._bot_list_count = 5 878 self._bot_add_list = 0 879 self._bot_update_list = 0 880 self._bot_lists: list[list[SpazBot]] = [ 881 [] for _ in range(self._bot_list_count) 882 ] 883 self._spawn_sound = ba.getsound('spawn') 884 self._spawning_count = 0 885 self._bot_update_timer: ba.Timer | None = None 886 self.start_moving() 887 888 def __del__(self) -> None: 889 self.clear() 890 891 def spawn_bot( 892 self, 893 bot_type: type[SpazBot], 894 pos: Sequence[float], 895 spawn_time: float = 3.0, 896 on_spawn_call: Callable[[SpazBot], Any] | None = None) -> None: 897 """Spawn a bot from this set.""" 898 from bastd.actor import spawner 899 spawner.Spawner(pt=pos, 900 spawn_time=spawn_time, 901 send_spawn_message=False, 902 spawn_callback=ba.Call(self._spawn_bot, bot_type, pos, 903 on_spawn_call)) 904 self._spawning_count += 1 905 906 def _spawn_bot(self, bot_type: type[SpazBot], pos: Sequence[float], 907 on_spawn_call: Callable[[SpazBot], Any] | None) -> None: 908 spaz = bot_type() 909 ba.playsound(self._spawn_sound, position=pos) 910 assert spaz.node 911 spaz.node.handlemessage('flash') 912 spaz.node.is_area_of_interest = False 913 spaz.handlemessage(ba.StandMessage(pos, random.uniform(0, 360))) 914 self.add_bot(spaz) 915 self._spawning_count -= 1 916 if on_spawn_call is not None: 917 on_spawn_call(spaz) 918 919 def have_living_bots(self) -> bool: 920 """Return whether any bots in the set are alive or spawning.""" 921 return (self._spawning_count > 0 922 or any(any(b.is_alive() for b in l) for l in self._bot_lists)) 923 924 def get_living_bots(self) -> list[SpazBot]: 925 """Get the living bots in the set.""" 926 bots: list[SpazBot] = [] 927 for botlist in self._bot_lists: 928 for bot in botlist: 929 if bot.is_alive(): 930 bots.append(bot) 931 return bots 932 933 def _update(self) -> None: 934 935 # Update one of our bot lists each time through. 936 # First off, remove no-longer-existing bots from the list. 937 try: 938 bot_list = self._bot_lists[self._bot_update_list] = ([ 939 b for b in self._bot_lists[self._bot_update_list] if b 940 ]) 941 except Exception: 942 bot_list = [] 943 ba.print_exception('Error updating bot list: ' + 944 str(self._bot_lists[self._bot_update_list])) 945 self._bot_update_list = (self._bot_update_list + 946 1) % self._bot_list_count 947 948 # Update our list of player points for the bots to use. 949 player_pts = [] 950 for player in ba.getactivity().players: 951 assert isinstance(player, ba.Player) 952 try: 953 # TODO: could use abstracted player.position here so we 954 # don't have to assume their actor type, but we have no 955 # abstracted velocity as of yet. 956 if player.is_alive(): 957 assert isinstance(player.actor, Spaz) 958 assert player.actor.node 959 player_pts.append((ba.Vec3(player.actor.node.position), 960 ba.Vec3(player.actor.node.velocity))) 961 except Exception: 962 ba.print_exception('Error on bot-set _update.') 963 964 for bot in bot_list: 965 bot.set_player_points(player_pts) 966 bot.update_ai() 967 968 def clear(self) -> None: 969 """Immediately clear out any bots in the set.""" 970 971 # Don't do this if the activity is shutting down or dead. 972 activity = ba.getactivity(doraise=False) 973 if activity is None or activity.expired: 974 return 975 976 for i, bot_list in enumerate(self._bot_lists): 977 for bot in bot_list: 978 bot.handlemessage(ba.DieMessage(immediate=True)) 979 self._bot_lists[i] = [] 980 981 def start_moving(self) -> None: 982 """Start processing bot AI updates so they start doing their thing.""" 983 self._bot_update_timer = ba.Timer(0.05, 984 ba.WeakCall(self._update), 985 repeat=True) 986 987 def stop_moving(self) -> None: 988 """Tell all bots to stop moving and stops updating their AI. 989 990 Useful when players have won and you want the 991 enemy bots to just stand and look bewildered. 992 """ 993 self._bot_update_timer = None 994 for botlist in self._bot_lists: 995 for bot in botlist: 996 if bot.node: 997 bot.node.move_left_right = 0 998 bot.node.move_up_down = 0 999 1000 def celebrate(self, duration: float) -> None: 1001 """Tell all living bots in the set to celebrate momentarily. 1002 1003 Duration is given in seconds. 1004 """ 1005 msg = ba.CelebrateMessage(duration=duration) 1006 for botlist in self._bot_lists: 1007 for bot in botlist: 1008 if bot: 1009 bot.handlemessage(msg) 1010 1011 def final_celebrate(self) -> None: 1012 """Tell all bots in the set to stop what they were doing and celebrate. 1013 1014 Use this when the bots have won a game. 1015 """ 1016 self._bot_update_timer = None 1017 1018 # At this point stop doing anything but jumping and celebrating. 1019 for botlist in self._bot_lists: 1020 for bot in botlist: 1021 if bot: 1022 assert bot.node # (should exist if 'if bot' was True) 1023 bot.node.move_left_right = 0 1024 bot.node.move_up_down = 0 1025 ba.timer(0.5 * random.random(), 1026 ba.Call(bot.handlemessage, ba.CelebrateMessage())) 1027 jump_duration = random.randrange(400, 500) 1028 j = random.randrange(0, 200) 1029 for _i in range(10): 1030 bot.node.jump_pressed = True 1031 bot.node.jump_pressed = False 1032 j += jump_duration 1033 ba.timer(random.uniform(0.0, 1.0), 1034 ba.Call(bot.node.handlemessage, 'attack_sound')) 1035 ba.timer(random.uniform(1.0, 2.0), 1036 ba.Call(bot.node.handlemessage, 'attack_sound')) 1037 ba.timer(random.uniform(2.0, 3.0), 1038 ba.Call(bot.node.handlemessage, 'attack_sound')) 1039 1040 def add_bot(self, bot: SpazBot) -> None: 1041 """Add a ba.SpazBot instance to the set.""" 1042 self._bot_lists[self._bot_add_list].append(bot) 1043 self._bot_add_list = (self._bot_add_list + 1) % self._bot_list_count
28class SpazBotPunchedMessage: 29 """A message saying a ba.SpazBot got punched. 30 31 Category: **Message Classes** 32 """ 33 34 spazbot: SpazBot 35 """The ba.SpazBot that got punched.""" 36 37 damage: int 38 """How much damage was done to the SpazBot.""" 39 40 def __init__(self, spazbot: SpazBot, damage: int): 41 """Instantiate a message with the given values.""" 42 self.spazbot = spazbot 43 self.damage = damage
A message saying a ba.SpazBot got punched.
Category: Message Classes
40 def __init__(self, spazbot: SpazBot, damage: int): 41 """Instantiate a message with the given values.""" 42 self.spazbot = spazbot 43 self.damage = damage
Instantiate a message with the given values.
46class SpazBotDiedMessage: 47 """A message saying a ba.SpazBot has died. 48 49 Category: **Message Classes** 50 """ 51 52 spazbot: SpazBot 53 """The SpazBot that was killed.""" 54 55 killerplayer: ba.Player | None 56 """The ba.Player that killed it (or None).""" 57 58 how: ba.DeathType 59 """The particular type of death.""" 60 61 def __init__(self, spazbot: SpazBot, killerplayer: ba.Player | None, 62 how: ba.DeathType): 63 """Instantiate with given values.""" 64 self.spazbot = spazbot 65 self.killerplayer = killerplayer 66 self.how = how
A message saying a ba.SpazBot has died.
Category: Message Classes
61 def __init__(self, spazbot: SpazBot, killerplayer: ba.Player | None, 62 how: ba.DeathType): 63 """Instantiate with given values.""" 64 self.spazbot = spazbot 65 self.killerplayer = killerplayer 66 self.how = how
Instantiate with given values.
69class SpazBot(Spaz): 70 """A really dumb AI version of ba.Spaz. 71 72 Category: **Bot Classes** 73 74 Add these to a ba.BotSet to use them. 75 76 Note: currently the AI has no real ability to 77 navigate obstacles and so should only be used 78 on wide-open maps. 79 80 When a SpazBot is killed, it delivers a ba.SpazBotDiedMessage 81 to the current activity. 82 83 When a SpazBot is punched, it delivers a ba.SpazBotPunchedMessage 84 to the current activity. 85 """ 86 87 character = 'Spaz' 88 punchiness = 0.5 89 throwiness = 0.7 90 static = False 91 bouncy = False 92 run = False 93 charge_dist_min = 0.0 # When we can start a new charge. 94 charge_dist_max = 2.0 # When we can start a new charge. 95 run_dist_min = 0.0 # How close we can be to continue running. 96 charge_speed_min = 0.4 97 charge_speed_max = 1.0 98 throw_dist_min = 5.0 99 throw_dist_max = 9.0 100 throw_rate = 1.0 101 default_bomb_type = 'normal' 102 default_bomb_count = 3 103 start_cursed = False 104 color = DEFAULT_BOT_COLOR 105 highlight = DEFAULT_BOT_HIGHLIGHT 106 107 def __init__(self) -> None: 108 """Instantiate a spaz-bot.""" 109 super().__init__(color=self.color, 110 highlight=self.highlight, 111 character=self.character, 112 source_player=None, 113 start_invincible=False, 114 can_accept_powerups=False) 115 116 # If you need to add custom behavior to a bot, set this to a callable 117 # which takes one arg (the bot) and returns False if the bot's normal 118 # update should be run and True if not. 119 self.update_callback: Callable[[SpazBot], Any] | None = None 120 activity = self.activity 121 assert isinstance(activity, ba.GameActivity) 122 self._map = weakref.ref(activity.map) 123 self.last_player_attacked_by: ba.Player | None = None 124 self.last_attacked_time = 0.0 125 self.last_attacked_type: tuple[str, str] | None = None 126 self.target_point_default: ba.Vec3 | None = None 127 self.held_count = 0 128 self.last_player_held_by: ba.Player | None = None 129 self.target_flag: Flag | None = None 130 self._charge_speed = 0.5 * (self.charge_speed_min + 131 self.charge_speed_max) 132 self._lead_amount = 0.5 133 self._mode = 'wait' 134 self._charge_closing_in = False 135 self._last_charge_dist = 0.0 136 self._running = False 137 self._last_jump_time = 0.0 138 139 self._throw_release_time: float | None = None 140 self._have_dropped_throw_bomb: bool | None = None 141 self._player_pts: list[tuple[ba.Vec3, ba.Vec3]] | None = None 142 143 # These cooldowns didn't exist when these bots were calibrated, 144 # so take them out of the equation. 145 self._jump_cooldown = 0 146 self._pickup_cooldown = 0 147 self._fly_cooldown = 0 148 self._bomb_cooldown = 0 149 150 if self.start_cursed: 151 self.curse() 152 153 @property 154 def map(self) -> ba.Map: 155 """The map this bot was created on.""" 156 mval = self._map() 157 assert mval is not None 158 return mval 159 160 def _get_target_player_pt(self) -> tuple[ba.Vec3 | None, ba.Vec3 | None]: 161 """Returns the position and velocity of our target. 162 163 Both values will be None in the case of no target. 164 """ 165 assert self.node 166 botpt = ba.Vec3(self.node.position) 167 closest_dist: float | None = None 168 closest_vel: ba.Vec3 | None = None 169 closest: ba.Vec3 | None = None 170 assert self._player_pts is not None 171 for plpt, plvel in self._player_pts: 172 dist = (plpt - botpt).length() 173 174 # Ignore player-points that are significantly below the bot 175 # (keeps bots from following players off cliffs). 176 if (closest_dist is None 177 or dist < closest_dist) and (plpt[1] > botpt[1] - 5.0): 178 closest_dist = dist 179 closest_vel = plvel 180 closest = plpt 181 if closest_dist is not None: 182 assert closest_vel is not None 183 assert closest is not None 184 return (ba.Vec3(closest[0], closest[1], closest[2]), 185 ba.Vec3(closest_vel[0], closest_vel[1], closest_vel[2])) 186 return None, None 187 188 def set_player_points(self, pts: list[tuple[ba.Vec3, ba.Vec3]]) -> None: 189 """Provide the spaz-bot with the locations of its enemies.""" 190 self._player_pts = pts 191 192 def update_ai(self) -> None: 193 """Should be called periodically to update the spaz' AI.""" 194 # pylint: disable=too-many-branches 195 # pylint: disable=too-many-statements 196 # pylint: disable=too-many-locals 197 if self.update_callback is not None: 198 if self.update_callback(self): 199 # Bot has been handled. 200 return 201 202 if not self.node: 203 return 204 205 pos = self.node.position 206 our_pos = ba.Vec3(pos[0], 0, pos[2]) 207 can_attack = True 208 209 target_pt_raw: ba.Vec3 | None 210 target_vel: ba.Vec3 | None 211 212 # If we're a flag-bearer, we're pretty simple-minded - just walk 213 # towards the flag and try to pick it up. 214 if self.target_flag: 215 if self.node.hold_node: 216 holding_flag = (self.node.hold_node.getnodetype() == 'flag') 217 else: 218 holding_flag = False 219 220 # If we're holding the flag, just walk left. 221 if holding_flag: 222 # Just walk left. 223 self.node.move_left_right = -1.0 224 self.node.move_up_down = 0.0 225 226 # Otherwise try to go pick it up. 227 elif self.target_flag.node: 228 target_pt_raw = ba.Vec3(*self.target_flag.node.position) 229 diff = (target_pt_raw - our_pos) 230 diff = ba.Vec3(diff[0], 0, diff[2]) # Don't care about y. 231 dist = diff.length() 232 to_target = diff.normalized() 233 234 # If we're holding some non-flag item, drop it. 235 if self.node.hold_node: 236 self.node.pickup_pressed = True 237 self.node.pickup_pressed = False 238 return 239 240 # If we're a runner, run only when not super-near the flag. 241 if self.run and dist > 3.0: 242 self._running = True 243 self.node.run = 1.0 244 else: 245 self._running = False 246 self.node.run = 0.0 247 248 self.node.move_left_right = to_target.x 249 self.node.move_up_down = -to_target.z 250 if dist < 1.25: 251 self.node.pickup_pressed = True 252 self.node.pickup_pressed = False 253 return 254 255 # Not a flag-bearer. If we're holding anything but a bomb, drop it. 256 if self.node.hold_node: 257 holding_bomb = (self.node.hold_node.getnodetype() 258 in ['bomb', 'prop']) 259 if not holding_bomb: 260 self.node.pickup_pressed = True 261 self.node.pickup_pressed = False 262 return 263 264 target_pt_raw, target_vel = self._get_target_player_pt() 265 266 if target_pt_raw is None: 267 # Use default target if we've got one. 268 if self.target_point_default is not None: 269 target_pt_raw = self.target_point_default 270 target_vel = ba.Vec3(0, 0, 0) 271 can_attack = False 272 273 # With no target, we stop moving and drop whatever we're holding. 274 else: 275 self.node.move_left_right = 0 276 self.node.move_up_down = 0 277 if self.node.hold_node: 278 self.node.pickup_pressed = True 279 self.node.pickup_pressed = False 280 return 281 282 # We don't want height to come into play. 283 target_pt_raw[1] = 0.0 284 assert target_vel is not None 285 target_vel[1] = 0.0 286 287 dist_raw = (target_pt_raw - our_pos).length() 288 289 # Use a point out in front of them as real target. 290 # (more out in front the farther from us they are) 291 target_pt = (target_pt_raw + 292 target_vel * dist_raw * 0.3 * self._lead_amount) 293 294 diff = (target_pt - our_pos) 295 dist = diff.length() 296 to_target = diff.normalized() 297 298 if self._mode == 'throw': 299 # We can only throw if alive and well. 300 if not self._dead and not self.node.knockout: 301 302 assert self._throw_release_time is not None 303 time_till_throw = self._throw_release_time - ba.time() 304 305 if not self.node.hold_node: 306 # If we haven't thrown yet, whip out the bomb. 307 if not self._have_dropped_throw_bomb: 308 self.drop_bomb() 309 self._have_dropped_throw_bomb = True 310 311 # Otherwise our lack of held node means we successfully 312 # released our bomb; lets retreat now. 313 else: 314 self._mode = 'flee' 315 316 # Oh crap, we're holding a bomb; better throw it. 317 elif time_till_throw <= 0.0: 318 # Jump and throw. 319 def _safe_pickup(node: ba.Node) -> None: 320 if node and self.node: 321 self.node.pickup_pressed = True 322 self.node.pickup_pressed = False 323 324 if dist > 5.0: 325 self.node.jump_pressed = True 326 self.node.jump_pressed = False 327 328 # Throws: 329 ba.timer(0.1, ba.Call(_safe_pickup, self.node)) 330 else: 331 # Throws: 332 ba.timer(0.1, ba.Call(_safe_pickup, self.node)) 333 334 if self.static: 335 if time_till_throw < 0.3: 336 speed = 1.0 337 elif time_till_throw < 0.7 and dist > 3.0: 338 speed = -1.0 # Whiplash for long throws. 339 else: 340 speed = 0.02 341 else: 342 if time_till_throw < 0.7: 343 # Right before throw charge full speed towards target. 344 speed = 1.0 345 else: 346 # Earlier we can hold or move backward for a whiplash. 347 speed = 0.0125 348 self.node.move_left_right = to_target.x * speed 349 self.node.move_up_down = to_target.z * -1.0 * speed 350 351 elif self._mode == 'charge': 352 if random.random() < 0.3: 353 self._charge_speed = random.uniform(self.charge_speed_min, 354 self.charge_speed_max) 355 356 # If we're a runner we run during charges *except when near 357 # an edge (otherwise we tend to fly off easily). 358 if self.run and dist_raw > self.run_dist_min: 359 self._lead_amount = 0.3 360 self._running = True 361 self.node.run = 1.0 362 else: 363 self._lead_amount = 0.01 364 self._running = False 365 self.node.run = 0.0 366 367 self.node.move_left_right = to_target.x * self._charge_speed 368 self.node.move_up_down = to_target.z * -1.0 * self._charge_speed 369 370 elif self._mode == 'wait': 371 # Every now and then, aim towards our target. 372 # Other than that, just stand there. 373 if ba.time(timeformat=ba.TimeFormat.MILLISECONDS) % 1234 < 100: 374 self.node.move_left_right = to_target.x * (400.0 / 33000) 375 self.node.move_up_down = to_target.z * (-400.0 / 33000) 376 else: 377 self.node.move_left_right = 0 378 self.node.move_up_down = 0 379 380 elif self._mode == 'flee': 381 # Even if we're a runner, only run till we get away from our 382 # target (if we keep running we tend to run off edges). 383 if self.run and dist < 3.0: 384 self._running = True 385 self.node.run = 1.0 386 else: 387 self._running = False 388 self.node.run = 0.0 389 self.node.move_left_right = to_target.x * -1.0 390 self.node.move_up_down = to_target.z 391 392 # We might wanna switch states unless we're doing a throw 393 # (in which case that's our sole concern). 394 if self._mode != 'throw': 395 396 # If we're currently charging, keep track of how far we are 397 # from our target. When this value increases it means our charge 398 # is over (ran by them or something). 399 if self._mode == 'charge': 400 if (self._charge_closing_in 401 and self._last_charge_dist < dist < 3.0): 402 self._charge_closing_in = False 403 self._last_charge_dist = dist 404 405 # If we have a clean shot, throw! 406 if (self.throw_dist_min <= dist < self.throw_dist_max 407 and random.random() < self.throwiness and can_attack): 408 self._mode = 'throw' 409 self._lead_amount = ((0.4 + random.random() * 0.6) 410 if dist_raw > 4.0 else 411 (0.1 + random.random() * 0.4)) 412 self._have_dropped_throw_bomb = False 413 self._throw_release_time = (ba.time() + 414 (1.0 / self.throw_rate) * 415 (0.8 + 1.3 * random.random())) 416 417 # If we're static, always charge (which for us means barely move). 418 elif self.static: 419 self._mode = 'wait' 420 421 # If we're too close to charge (and aren't in the middle of an 422 # existing charge) run away. 423 elif dist < self.charge_dist_min and not self._charge_closing_in: 424 # ..unless we're near an edge, in which case we've got no 425 # choice but to charge. 426 if self.map.is_point_near_edge(our_pos, self._running): 427 if self._mode != 'charge': 428 self._mode = 'charge' 429 self._lead_amount = 0.2 430 self._charge_closing_in = True 431 self._last_charge_dist = dist 432 else: 433 self._mode = 'flee' 434 435 # We're within charging distance, backed against an edge, 436 # or farther than our max throw distance.. chaaarge! 437 elif (dist < self.charge_dist_max or dist > self.throw_dist_max 438 or self.map.is_point_near_edge(our_pos, self._running)): 439 if self._mode != 'charge': 440 self._mode = 'charge' 441 self._lead_amount = 0.01 442 self._charge_closing_in = True 443 self._last_charge_dist = dist 444 445 # We're too close to throw but too far to charge - either run 446 # away or just chill if we're near an edge. 447 elif dist < self.throw_dist_min: 448 # Charge if either we're within charge range or 449 # cant retreat to throw. 450 self._mode = 'flee' 451 452 # Do some awesome jumps if we're running. 453 # FIXME: pylint: disable=too-many-boolean-expressions 454 if ((self._running and 1.2 < dist < 2.2 455 and ba.time() - self._last_jump_time > 1.0) 456 or (self.bouncy and ba.time() - self._last_jump_time > 0.4 457 and random.random() < 0.5)): 458 self._last_jump_time = ba.time() 459 self.node.jump_pressed = True 460 self.node.jump_pressed = False 461 462 # Throw punches when real close. 463 if dist < (1.6 if self._running else 1.2) and can_attack: 464 if random.random() < self.punchiness: 465 self.on_punch_press() 466 self.on_punch_release() 467 468 def on_punched(self, damage: int) -> None: 469 """ 470 Method override; sends ba.SpazBotPunchedMessage 471 to the current activity. 472 """ 473 ba.getactivity().handlemessage(SpazBotPunchedMessage(self, damage)) 474 475 def on_expire(self) -> None: 476 super().on_expire() 477 478 # We're being torn down; release our callback(s) so there's 479 # no chance of them keeping activities or other things alive. 480 self.update_callback = None 481 482 def handlemessage(self, msg: Any) -> Any: 483 # pylint: disable=too-many-branches 484 assert not self.expired 485 486 # Keep track of if we're being held and by who most recently. 487 if isinstance(msg, ba.PickedUpMessage): 488 super().handlemessage(msg) # Augment standard behavior. 489 self.held_count += 1 490 picked_up_by = msg.node.source_player 491 if picked_up_by: 492 self.last_player_held_by = picked_up_by 493 494 elif isinstance(msg, ba.DroppedMessage): 495 super().handlemessage(msg) # Augment standard behavior. 496 self.held_count -= 1 497 if self.held_count < 0: 498 print('ERROR: spaz held_count < 0') 499 500 # Let's count someone dropping us as an attack. 501 try: 502 if msg.node: 503 picked_up_by = msg.node.source_player 504 else: 505 picked_up_by = None 506 except Exception: 507 ba.print_exception('Error on SpazBot DroppedMessage.') 508 picked_up_by = None 509 510 if picked_up_by: 511 self.last_player_attacked_by = picked_up_by 512 self.last_attacked_time = ba.time() 513 self.last_attacked_type = ('picked_up', 'default') 514 515 elif isinstance(msg, ba.DieMessage): 516 517 # Report normal deaths for scoring purposes. 518 if not self._dead and not msg.immediate: 519 520 killerplayer: ba.Player | None 521 522 # If this guy was being held at the time of death, the 523 # holder is the killer. 524 if self.held_count > 0 and self.last_player_held_by: 525 killerplayer = self.last_player_held_by 526 else: 527 # If they were attacked by someone in the last few 528 # seconds that person's the killer. 529 # Otherwise it was a suicide. 530 if (self.last_player_attacked_by 531 and ba.time() - self.last_attacked_time < 4.0): 532 killerplayer = self.last_player_attacked_by 533 else: 534 killerplayer = None 535 activity = self._activity() 536 537 # (convert dead player refs to None) 538 if not killerplayer: 539 killerplayer = None 540 if activity is not None: 541 activity.handlemessage( 542 SpazBotDiedMessage(self, killerplayer, msg.how)) 543 super().handlemessage(msg) # Augment standard behavior. 544 545 # Keep track of the player who last hit us for point rewarding. 546 elif isinstance(msg, ba.HitMessage): 547 source_player = msg.get_source_player(ba.Player) 548 if source_player: 549 self.last_player_attacked_by = source_player 550 self.last_attacked_time = ba.time() 551 self.last_attacked_type = (msg.hit_type, msg.hit_subtype) 552 super().handlemessage(msg) 553 else: 554 super().handlemessage(msg)
A really dumb AI version of ba.Spaz.
Category: Bot Classes
Add these to a ba.BotSet to use them.
Note: currently the AI has no real ability to navigate obstacles and so should only be used on wide-open maps.
When a SpazBot is killed, it delivers a ba.SpazBotDiedMessage to the current activity.
When a SpazBot is punched, it delivers a ba.SpazBotPunchedMessage to the current activity.
107 def __init__(self) -> None: 108 """Instantiate a spaz-bot.""" 109 super().__init__(color=self.color, 110 highlight=self.highlight, 111 character=self.character, 112 source_player=None, 113 start_invincible=False, 114 can_accept_powerups=False) 115 116 # If you need to add custom behavior to a bot, set this to a callable 117 # which takes one arg (the bot) and returns False if the bot's normal 118 # update should be run and True if not. 119 self.update_callback: Callable[[SpazBot], Any] | None = None 120 activity = self.activity 121 assert isinstance(activity, ba.GameActivity) 122 self._map = weakref.ref(activity.map) 123 self.last_player_attacked_by: ba.Player | None = None 124 self.last_attacked_time = 0.0 125 self.last_attacked_type: tuple[str, str] | None = None 126 self.target_point_default: ba.Vec3 | None = None 127 self.held_count = 0 128 self.last_player_held_by: ba.Player | None = None 129 self.target_flag: Flag | None = None 130 self._charge_speed = 0.5 * (self.charge_speed_min + 131 self.charge_speed_max) 132 self._lead_amount = 0.5 133 self._mode = 'wait' 134 self._charge_closing_in = False 135 self._last_charge_dist = 0.0 136 self._running = False 137 self._last_jump_time = 0.0 138 139 self._throw_release_time: float | None = None 140 self._have_dropped_throw_bomb: bool | None = None 141 self._player_pts: list[tuple[ba.Vec3, ba.Vec3]] | None = None 142 143 # These cooldowns didn't exist when these bots were calibrated, 144 # so take them out of the equation. 145 self._jump_cooldown = 0 146 self._pickup_cooldown = 0 147 self._fly_cooldown = 0 148 self._bomb_cooldown = 0 149 150 if self.start_cursed: 151 self.curse()
Instantiate a spaz-bot.
188 def set_player_points(self, pts: list[tuple[ba.Vec3, ba.Vec3]]) -> None: 189 """Provide the spaz-bot with the locations of its enemies.""" 190 self._player_pts = pts
Provide the spaz-bot with the locations of its enemies.
192 def update_ai(self) -> None: 193 """Should be called periodically to update the spaz' AI.""" 194 # pylint: disable=too-many-branches 195 # pylint: disable=too-many-statements 196 # pylint: disable=too-many-locals 197 if self.update_callback is not None: 198 if self.update_callback(self): 199 # Bot has been handled. 200 return 201 202 if not self.node: 203 return 204 205 pos = self.node.position 206 our_pos = ba.Vec3(pos[0], 0, pos[2]) 207 can_attack = True 208 209 target_pt_raw: ba.Vec3 | None 210 target_vel: ba.Vec3 | None 211 212 # If we're a flag-bearer, we're pretty simple-minded - just walk 213 # towards the flag and try to pick it up. 214 if self.target_flag: 215 if self.node.hold_node: 216 holding_flag = (self.node.hold_node.getnodetype() == 'flag') 217 else: 218 holding_flag = False 219 220 # If we're holding the flag, just walk left. 221 if holding_flag: 222 # Just walk left. 223 self.node.move_left_right = -1.0 224 self.node.move_up_down = 0.0 225 226 # Otherwise try to go pick it up. 227 elif self.target_flag.node: 228 target_pt_raw = ba.Vec3(*self.target_flag.node.position) 229 diff = (target_pt_raw - our_pos) 230 diff = ba.Vec3(diff[0], 0, diff[2]) # Don't care about y. 231 dist = diff.length() 232 to_target = diff.normalized() 233 234 # If we're holding some non-flag item, drop it. 235 if self.node.hold_node: 236 self.node.pickup_pressed = True 237 self.node.pickup_pressed = False 238 return 239 240 # If we're a runner, run only when not super-near the flag. 241 if self.run and dist > 3.0: 242 self._running = True 243 self.node.run = 1.0 244 else: 245 self._running = False 246 self.node.run = 0.0 247 248 self.node.move_left_right = to_target.x 249 self.node.move_up_down = -to_target.z 250 if dist < 1.25: 251 self.node.pickup_pressed = True 252 self.node.pickup_pressed = False 253 return 254 255 # Not a flag-bearer. If we're holding anything but a bomb, drop it. 256 if self.node.hold_node: 257 holding_bomb = (self.node.hold_node.getnodetype() 258 in ['bomb', 'prop']) 259 if not holding_bomb: 260 self.node.pickup_pressed = True 261 self.node.pickup_pressed = False 262 return 263 264 target_pt_raw, target_vel = self._get_target_player_pt() 265 266 if target_pt_raw is None: 267 # Use default target if we've got one. 268 if self.target_point_default is not None: 269 target_pt_raw = self.target_point_default 270 target_vel = ba.Vec3(0, 0, 0) 271 can_attack = False 272 273 # With no target, we stop moving and drop whatever we're holding. 274 else: 275 self.node.move_left_right = 0 276 self.node.move_up_down = 0 277 if self.node.hold_node: 278 self.node.pickup_pressed = True 279 self.node.pickup_pressed = False 280 return 281 282 # We don't want height to come into play. 283 target_pt_raw[1] = 0.0 284 assert target_vel is not None 285 target_vel[1] = 0.0 286 287 dist_raw = (target_pt_raw - our_pos).length() 288 289 # Use a point out in front of them as real target. 290 # (more out in front the farther from us they are) 291 target_pt = (target_pt_raw + 292 target_vel * dist_raw * 0.3 * self._lead_amount) 293 294 diff = (target_pt - our_pos) 295 dist = diff.length() 296 to_target = diff.normalized() 297 298 if self._mode == 'throw': 299 # We can only throw if alive and well. 300 if not self._dead and not self.node.knockout: 301 302 assert self._throw_release_time is not None 303 time_till_throw = self._throw_release_time - ba.time() 304 305 if not self.node.hold_node: 306 # If we haven't thrown yet, whip out the bomb. 307 if not self._have_dropped_throw_bomb: 308 self.drop_bomb() 309 self._have_dropped_throw_bomb = True 310 311 # Otherwise our lack of held node means we successfully 312 # released our bomb; lets retreat now. 313 else: 314 self._mode = 'flee' 315 316 # Oh crap, we're holding a bomb; better throw it. 317 elif time_till_throw <= 0.0: 318 # Jump and throw. 319 def _safe_pickup(node: ba.Node) -> None: 320 if node and self.node: 321 self.node.pickup_pressed = True 322 self.node.pickup_pressed = False 323 324 if dist > 5.0: 325 self.node.jump_pressed = True 326 self.node.jump_pressed = False 327 328 # Throws: 329 ba.timer(0.1, ba.Call(_safe_pickup, self.node)) 330 else: 331 # Throws: 332 ba.timer(0.1, ba.Call(_safe_pickup, self.node)) 333 334 if self.static: 335 if time_till_throw < 0.3: 336 speed = 1.0 337 elif time_till_throw < 0.7 and dist > 3.0: 338 speed = -1.0 # Whiplash for long throws. 339 else: 340 speed = 0.02 341 else: 342 if time_till_throw < 0.7: 343 # Right before throw charge full speed towards target. 344 speed = 1.0 345 else: 346 # Earlier we can hold or move backward for a whiplash. 347 speed = 0.0125 348 self.node.move_left_right = to_target.x * speed 349 self.node.move_up_down = to_target.z * -1.0 * speed 350 351 elif self._mode == 'charge': 352 if random.random() < 0.3: 353 self._charge_speed = random.uniform(self.charge_speed_min, 354 self.charge_speed_max) 355 356 # If we're a runner we run during charges *except when near 357 # an edge (otherwise we tend to fly off easily). 358 if self.run and dist_raw > self.run_dist_min: 359 self._lead_amount = 0.3 360 self._running = True 361 self.node.run = 1.0 362 else: 363 self._lead_amount = 0.01 364 self._running = False 365 self.node.run = 0.0 366 367 self.node.move_left_right = to_target.x * self._charge_speed 368 self.node.move_up_down = to_target.z * -1.0 * self._charge_speed 369 370 elif self._mode == 'wait': 371 # Every now and then, aim towards our target. 372 # Other than that, just stand there. 373 if ba.time(timeformat=ba.TimeFormat.MILLISECONDS) % 1234 < 100: 374 self.node.move_left_right = to_target.x * (400.0 / 33000) 375 self.node.move_up_down = to_target.z * (-400.0 / 33000) 376 else: 377 self.node.move_left_right = 0 378 self.node.move_up_down = 0 379 380 elif self._mode == 'flee': 381 # Even if we're a runner, only run till we get away from our 382 # target (if we keep running we tend to run off edges). 383 if self.run and dist < 3.0: 384 self._running = True 385 self.node.run = 1.0 386 else: 387 self._running = False 388 self.node.run = 0.0 389 self.node.move_left_right = to_target.x * -1.0 390 self.node.move_up_down = to_target.z 391 392 # We might wanna switch states unless we're doing a throw 393 # (in which case that's our sole concern). 394 if self._mode != 'throw': 395 396 # If we're currently charging, keep track of how far we are 397 # from our target. When this value increases it means our charge 398 # is over (ran by them or something). 399 if self._mode == 'charge': 400 if (self._charge_closing_in 401 and self._last_charge_dist < dist < 3.0): 402 self._charge_closing_in = False 403 self._last_charge_dist = dist 404 405 # If we have a clean shot, throw! 406 if (self.throw_dist_min <= dist < self.throw_dist_max 407 and random.random() < self.throwiness and can_attack): 408 self._mode = 'throw' 409 self._lead_amount = ((0.4 + random.random() * 0.6) 410 if dist_raw > 4.0 else 411 (0.1 + random.random() * 0.4)) 412 self._have_dropped_throw_bomb = False 413 self._throw_release_time = (ba.time() + 414 (1.0 / self.throw_rate) * 415 (0.8 + 1.3 * random.random())) 416 417 # If we're static, always charge (which for us means barely move). 418 elif self.static: 419 self._mode = 'wait' 420 421 # If we're too close to charge (and aren't in the middle of an 422 # existing charge) run away. 423 elif dist < self.charge_dist_min and not self._charge_closing_in: 424 # ..unless we're near an edge, in which case we've got no 425 # choice but to charge. 426 if self.map.is_point_near_edge(our_pos, self._running): 427 if self._mode != 'charge': 428 self._mode = 'charge' 429 self._lead_amount = 0.2 430 self._charge_closing_in = True 431 self._last_charge_dist = dist 432 else: 433 self._mode = 'flee' 434 435 # We're within charging distance, backed against an edge, 436 # or farther than our max throw distance.. chaaarge! 437 elif (dist < self.charge_dist_max or dist > self.throw_dist_max 438 or self.map.is_point_near_edge(our_pos, self._running)): 439 if self._mode != 'charge': 440 self._mode = 'charge' 441 self._lead_amount = 0.01 442 self._charge_closing_in = True 443 self._last_charge_dist = dist 444 445 # We're too close to throw but too far to charge - either run 446 # away or just chill if we're near an edge. 447 elif dist < self.throw_dist_min: 448 # Charge if either we're within charge range or 449 # cant retreat to throw. 450 self._mode = 'flee' 451 452 # Do some awesome jumps if we're running. 453 # FIXME: pylint: disable=too-many-boolean-expressions 454 if ((self._running and 1.2 < dist < 2.2 455 and ba.time() - self._last_jump_time > 1.0) 456 or (self.bouncy and ba.time() - self._last_jump_time > 0.4 457 and random.random() < 0.5)): 458 self._last_jump_time = ba.time() 459 self.node.jump_pressed = True 460 self.node.jump_pressed = False 461 462 # Throw punches when real close. 463 if dist < (1.6 if self._running else 1.2) and can_attack: 464 if random.random() < self.punchiness: 465 self.on_punch_press() 466 self.on_punch_release()
Should be called periodically to update the spaz' AI.
468 def on_punched(self, damage: int) -> None: 469 """ 470 Method override; sends ba.SpazBotPunchedMessage 471 to the current activity. 472 """ 473 ba.getactivity().handlemessage(SpazBotPunchedMessage(self, damage))
Method override; sends ba.SpazBotPunchedMessage to the current activity.
475 def on_expire(self) -> None: 476 super().on_expire() 477 478 # We're being torn down; release our callback(s) so there's 479 # no chance of them keeping activities or other things alive. 480 self.update_callback = None
Called for remaining ba.Actor
s when their ba.Activity shuts down.
Actors can use this opportunity to clear callbacks or other references which have the potential of keeping the ba.Activity alive inadvertently (Activities can not exit cleanly while any Python references to them remain.)
Once an actor is expired (see ba.Actor.is_expired()) it should no longer perform any game-affecting operations (creating, modifying, or deleting nodes, media, timers, etc.) Attempts to do so will likely result in errors.
482 def handlemessage(self, msg: Any) -> Any: 483 # pylint: disable=too-many-branches 484 assert not self.expired 485 486 # Keep track of if we're being held and by who most recently. 487 if isinstance(msg, ba.PickedUpMessage): 488 super().handlemessage(msg) # Augment standard behavior. 489 self.held_count += 1 490 picked_up_by = msg.node.source_player 491 if picked_up_by: 492 self.last_player_held_by = picked_up_by 493 494 elif isinstance(msg, ba.DroppedMessage): 495 super().handlemessage(msg) # Augment standard behavior. 496 self.held_count -= 1 497 if self.held_count < 0: 498 print('ERROR: spaz held_count < 0') 499 500 # Let's count someone dropping us as an attack. 501 try: 502 if msg.node: 503 picked_up_by = msg.node.source_player 504 else: 505 picked_up_by = None 506 except Exception: 507 ba.print_exception('Error on SpazBot DroppedMessage.') 508 picked_up_by = None 509 510 if picked_up_by: 511 self.last_player_attacked_by = picked_up_by 512 self.last_attacked_time = ba.time() 513 self.last_attacked_type = ('picked_up', 'default') 514 515 elif isinstance(msg, ba.DieMessage): 516 517 # Report normal deaths for scoring purposes. 518 if not self._dead and not msg.immediate: 519 520 killerplayer: ba.Player | None 521 522 # If this guy was being held at the time of death, the 523 # holder is the killer. 524 if self.held_count > 0 and self.last_player_held_by: 525 killerplayer = self.last_player_held_by 526 else: 527 # If they were attacked by someone in the last few 528 # seconds that person's the killer. 529 # Otherwise it was a suicide. 530 if (self.last_player_attacked_by 531 and ba.time() - self.last_attacked_time < 4.0): 532 killerplayer = self.last_player_attacked_by 533 else: 534 killerplayer = None 535 activity = self._activity() 536 537 # (convert dead player refs to None) 538 if not killerplayer: 539 killerplayer = None 540 if activity is not None: 541 activity.handlemessage( 542 SpazBotDiedMessage(self, killerplayer, msg.how)) 543 super().handlemessage(msg) # Augment standard behavior. 544 545 # Keep track of the player who last hit us for point rewarding. 546 elif isinstance(msg, ba.HitMessage): 547 source_player = msg.get_source_player(ba.Player) 548 if source_player: 549 self.last_player_attacked_by = source_player 550 self.last_attacked_time = ba.time() 551 self.last_attacked_type = (msg.hit_type, msg.hit_subtype) 552 super().handlemessage(msg) 553 else: 554 super().handlemessage(msg)
General message handling; can be passed any message object.
Inherited Members
- bastd.actor.spaz.Spaz
- node
- points_mult
- curse_time
- default_boxing_gloves
- default_shields
- exists
- add_dropped_bomb_callback
- is_alive
- set_score_text
- on_jump_press
- on_jump_release
- on_pickup_press
- on_pickup_release
- on_hold_position_press
- on_hold_position_release
- on_punch_press
- on_punch_release
- on_bomb_press
- on_bomb_release
- on_run
- on_fly_press
- on_fly_release
- on_move
- on_move_up_down
- on_move_left_right
- get_death_points
- curse
- equip_boxing_gloves
- equip_shields
- shield_decay
- drop_bomb
- set_land_mine_count
- curse_explode
- shatter
- set_bomb_count
- ba._actor.Actor
- autoretain
- expired
- activity
- getactivity
557class BomberBot(SpazBot): 558 """A bot that throws regular bombs and occasionally punches. 559 560 category: Bot Classes 561 """ 562 character = 'Spaz' 563 punchiness = 0.3
A bot that throws regular bombs and occasionally punches.
category: Bot Classes
Inherited Members
- SpazBot
- SpazBot
- throwiness
- static
- bouncy
- run
- charge_dist_min
- charge_dist_max
- run_dist_min
- charge_speed_min
- charge_speed_max
- throw_dist_min
- throw_dist_max
- throw_rate
- default_bomb_type
- default_bomb_count
- start_cursed
- color
- highlight
- map
- set_player_points
- update_ai
- on_punched
- on_expire
- handlemessage
- bastd.actor.spaz.Spaz
- node
- points_mult
- curse_time
- default_boxing_gloves
- default_shields
- exists
- add_dropped_bomb_callback
- is_alive
- set_score_text
- on_jump_press
- on_jump_release
- on_pickup_press
- on_pickup_release
- on_hold_position_press
- on_hold_position_release
- on_punch_press
- on_punch_release
- on_bomb_press
- on_bomb_release
- on_run
- on_fly_press
- on_fly_release
- on_move
- on_move_up_down
- on_move_left_right
- get_death_points
- curse
- equip_boxing_gloves
- equip_shields
- shield_decay
- drop_bomb
- set_land_mine_count
- curse_explode
- shatter
- set_bomb_count
- ba._actor.Actor
- autoretain
- expired
- activity
- getactivity
566class BomberBotLite(BomberBot): 567 """A less aggressive yellow version of ba.BomberBot. 568 569 category: Bot Classes 570 """ 571 color = LITE_BOT_COLOR 572 highlight = LITE_BOT_HIGHLIGHT 573 punchiness = 0.2 574 throw_rate = 0.7 575 throwiness = 0.1 576 charge_speed_min = 0.6 577 charge_speed_max = 0.6
A less aggressive yellow version of ba.BomberBot.
category: Bot Classes
Inherited Members
- SpazBot
- SpazBot
- static
- bouncy
- run
- charge_dist_min
- charge_dist_max
- run_dist_min
- throw_dist_min
- throw_dist_max
- default_bomb_type
- default_bomb_count
- start_cursed
- map
- set_player_points
- update_ai
- on_punched
- on_expire
- handlemessage
- bastd.actor.spaz.Spaz
- node
- points_mult
- curse_time
- default_boxing_gloves
- default_shields
- exists
- add_dropped_bomb_callback
- is_alive
- set_score_text
- on_jump_press
- on_jump_release
- on_pickup_press
- on_pickup_release
- on_hold_position_press
- on_hold_position_release
- on_punch_press
- on_punch_release
- on_bomb_press
- on_bomb_release
- on_run
- on_fly_press
- on_fly_release
- on_move
- on_move_up_down
- on_move_left_right
- get_death_points
- curse
- equip_boxing_gloves
- equip_shields
- shield_decay
- drop_bomb
- set_land_mine_count
- curse_explode
- shatter
- set_bomb_count
- ba._actor.Actor
- autoretain
- expired
- activity
- getactivity
580class BomberBotStaticLite(BomberBotLite): 581 """A less aggressive generally immobile weak version of ba.BomberBot. 582 583 category: Bot Classes 584 """ 585 static = True 586 throw_dist_min = 0.0
A less aggressive generally immobile weak version of ba.BomberBot.
category: Bot Classes
Inherited Members
- SpazBot
- SpazBot
- bouncy
- run
- charge_dist_min
- charge_dist_max
- run_dist_min
- throw_dist_max
- default_bomb_type
- default_bomb_count
- start_cursed
- map
- set_player_points
- update_ai
- on_punched
- on_expire
- handlemessage
- bastd.actor.spaz.Spaz
- node
- points_mult
- curse_time
- default_boxing_gloves
- default_shields
- exists
- add_dropped_bomb_callback
- is_alive
- set_score_text
- on_jump_press
- on_jump_release
- on_pickup_press
- on_pickup_release
- on_hold_position_press
- on_hold_position_release
- on_punch_press
- on_punch_release
- on_bomb_press
- on_bomb_release
- on_run
- on_fly_press
- on_fly_release
- on_move
- on_move_up_down
- on_move_left_right
- get_death_points
- curse
- equip_boxing_gloves
- equip_shields
- shield_decay
- drop_bomb
- set_land_mine_count
- curse_explode
- shatter
- set_bomb_count
- ba._actor.Actor
- autoretain
- expired
- activity
- getactivity
589class BomberBotStatic(BomberBot): 590 """A version of ba.BomberBot who generally stays in one place. 591 592 category: Bot Classes 593 """ 594 static = True 595 throw_dist_min = 0.0
A version of ba.BomberBot who generally stays in one place.
category: Bot Classes
Inherited Members
- SpazBot
- SpazBot
- throwiness
- bouncy
- run
- charge_dist_min
- charge_dist_max
- run_dist_min
- charge_speed_min
- charge_speed_max
- throw_dist_max
- throw_rate
- default_bomb_type
- default_bomb_count
- start_cursed
- color
- highlight
- map
- set_player_points
- update_ai
- on_punched
- on_expire
- handlemessage
- bastd.actor.spaz.Spaz
- node
- points_mult
- curse_time
- default_boxing_gloves
- default_shields
- exists
- add_dropped_bomb_callback
- is_alive
- set_score_text
- on_jump_press
- on_jump_release
- on_pickup_press
- on_pickup_release
- on_hold_position_press
- on_hold_position_release
- on_punch_press
- on_punch_release
- on_bomb_press
- on_bomb_release
- on_run
- on_fly_press
- on_fly_release
- on_move
- on_move_up_down
- on_move_left_right
- get_death_points
- curse
- equip_boxing_gloves
- equip_shields
- shield_decay
- drop_bomb
- set_land_mine_count
- curse_explode
- shatter
- set_bomb_count
- ba._actor.Actor
- autoretain
- expired
- activity
- getactivity
598class BomberBotPro(BomberBot): 599 """A more powerful version of ba.BomberBot. 600 601 category: Bot Classes 602 """ 603 points_mult = 2 604 color = PRO_BOT_COLOR 605 highlight = PRO_BOT_HIGHLIGHT 606 default_bomb_count = 3 607 default_boxing_gloves = True 608 punchiness = 0.7 609 throw_rate = 1.3 610 run = True 611 run_dist_min = 6.0
A more powerful version of ba.BomberBot.
category: Bot Classes
Inherited Members
- SpazBot
- SpazBot
- throwiness
- static
- bouncy
- charge_dist_min
- charge_dist_max
- charge_speed_min
- charge_speed_max
- throw_dist_min
- throw_dist_max
- default_bomb_type
- start_cursed
- map
- set_player_points
- update_ai
- on_punched
- on_expire
- handlemessage
- bastd.actor.spaz.Spaz
- node
- curse_time
- default_shields
- exists
- add_dropped_bomb_callback
- is_alive
- set_score_text
- on_jump_press
- on_jump_release
- on_pickup_press
- on_pickup_release
- on_hold_position_press
- on_hold_position_release
- on_punch_press
- on_punch_release
- on_bomb_press
- on_bomb_release
- on_run
- on_fly_press
- on_fly_release
- on_move
- on_move_up_down
- on_move_left_right
- get_death_points
- curse
- equip_boxing_gloves
- equip_shields
- shield_decay
- drop_bomb
- set_land_mine_count
- curse_explode
- shatter
- set_bomb_count
- ba._actor.Actor
- autoretain
- expired
- activity
- getactivity
614class BomberBotProShielded(BomberBotPro): 615 """A more powerful version of ba.BomberBot who starts with shields. 616 617 category: Bot Classes 618 """ 619 points_mult = 3 620 default_shields = True
A more powerful version of ba.BomberBot who starts with shields.
category: Bot Classes
Inherited Members
- SpazBot
- SpazBot
- throwiness
- static
- bouncy
- charge_dist_min
- charge_dist_max
- charge_speed_min
- charge_speed_max
- throw_dist_min
- throw_dist_max
- default_bomb_type
- start_cursed
- map
- set_player_points
- update_ai
- on_punched
- on_expire
- handlemessage
- BomberBotPro
- color
- highlight
- default_bomb_count
- default_boxing_gloves
- punchiness
- throw_rate
- run
- run_dist_min
- bastd.actor.spaz.Spaz
- node
- curse_time
- exists
- add_dropped_bomb_callback
- is_alive
- set_score_text
- on_jump_press
- on_jump_release
- on_pickup_press
- on_pickup_release
- on_hold_position_press
- on_hold_position_release
- on_punch_press
- on_punch_release
- on_bomb_press
- on_bomb_release
- on_run
- on_fly_press
- on_fly_release
- on_move
- on_move_up_down
- on_move_left_right
- get_death_points
- curse
- equip_boxing_gloves
- equip_shields
- shield_decay
- drop_bomb
- set_land_mine_count
- curse_explode
- shatter
- set_bomb_count
- ba._actor.Actor
- autoretain
- expired
- activity
- getactivity
623class BomberBotProStatic(BomberBotPro): 624 """A more powerful ba.BomberBot who generally stays in one place. 625 626 category: Bot Classes 627 """ 628 static = True 629 throw_dist_min = 0.0
A more powerful ba.BomberBot who generally stays in one place.
category: Bot Classes
Inherited Members
- SpazBot
- SpazBot
- throwiness
- bouncy
- charge_dist_min
- charge_dist_max
- charge_speed_min
- charge_speed_max
- throw_dist_max
- default_bomb_type
- start_cursed
- map
- set_player_points
- update_ai
- on_punched
- on_expire
- handlemessage
- BomberBotPro
- points_mult
- color
- highlight
- default_bomb_count
- default_boxing_gloves
- punchiness
- throw_rate
- run
- run_dist_min
- bastd.actor.spaz.Spaz
- node
- curse_time
- default_shields
- exists
- add_dropped_bomb_callback
- is_alive
- set_score_text
- on_jump_press
- on_jump_release
- on_pickup_press
- on_pickup_release
- on_hold_position_press
- on_hold_position_release
- on_punch_press
- on_punch_release
- on_bomb_press
- on_bomb_release
- on_run
- on_fly_press
- on_fly_release
- on_move
- on_move_up_down
- on_move_left_right
- get_death_points
- curse
- equip_boxing_gloves
- equip_shields
- shield_decay
- drop_bomb
- set_land_mine_count
- curse_explode
- shatter
- set_bomb_count
- ba._actor.Actor
- autoretain
- expired
- activity
- getactivity
632class BomberBotProStaticShielded(BomberBotProShielded): 633 """A powerful ba.BomberBot with shields who is generally immobile. 634 635 category: Bot Classes 636 """ 637 static = True 638 throw_dist_min = 0.0
A powerful ba.BomberBot with shields who is generally immobile.
category: Bot Classes
Inherited Members
- SpazBot
- SpazBot
- throwiness
- bouncy
- charge_dist_min
- charge_dist_max
- charge_speed_min
- charge_speed_max
- throw_dist_max
- default_bomb_type
- start_cursed
- map
- set_player_points
- update_ai
- on_punched
- on_expire
- handlemessage
- BomberBotPro
- color
- highlight
- default_bomb_count
- default_boxing_gloves
- punchiness
- throw_rate
- run
- run_dist_min
- bastd.actor.spaz.Spaz
- node
- curse_time
- exists
- add_dropped_bomb_callback
- is_alive
- set_score_text
- on_jump_press
- on_jump_release
- on_pickup_press
- on_pickup_release
- on_hold_position_press
- on_hold_position_release
- on_punch_press
- on_punch_release
- on_bomb_press
- on_bomb_release
- on_run
- on_fly_press
- on_fly_release
- on_move
- on_move_up_down
- on_move_left_right
- get_death_points
- curse
- equip_boxing_gloves
- equip_shields
- shield_decay
- drop_bomb
- set_land_mine_count
- curse_explode
- shatter
- set_bomb_count
- ba._actor.Actor
- autoretain
- expired
- activity
- getactivity
641class BrawlerBot(SpazBot): 642 """A bot who walks and punches things. 643 644 category: Bot Classes 645 """ 646 character = 'Kronk' 647 punchiness = 0.9 648 charge_dist_max = 9999.0 649 charge_speed_min = 1.0 650 charge_speed_max = 1.0 651 throw_dist_min = 9999 652 throw_dist_max = 9999
A bot who walks and punches things.
category: Bot Classes
Inherited Members
- SpazBot
- SpazBot
- throwiness
- static
- bouncy
- run
- charge_dist_min
- run_dist_min
- throw_rate
- default_bomb_type
- default_bomb_count
- start_cursed
- color
- highlight
- map
- set_player_points
- update_ai
- on_punched
- on_expire
- handlemessage
- bastd.actor.spaz.Spaz
- node
- points_mult
- curse_time
- default_boxing_gloves
- default_shields
- exists
- add_dropped_bomb_callback
- is_alive
- set_score_text
- on_jump_press
- on_jump_release
- on_pickup_press
- on_pickup_release
- on_hold_position_press
- on_hold_position_release
- on_punch_press
- on_punch_release
- on_bomb_press
- on_bomb_release
- on_run
- on_fly_press
- on_fly_release
- on_move
- on_move_up_down
- on_move_left_right
- get_death_points
- curse
- equip_boxing_gloves
- equip_shields
- shield_decay
- drop_bomb
- set_land_mine_count
- curse_explode
- shatter
- set_bomb_count
- ba._actor.Actor
- autoretain
- expired
- activity
- getactivity
655class BrawlerBotLite(BrawlerBot): 656 """A weaker version of ba.BrawlerBot. 657 658 category: Bot Classes 659 """ 660 color = LITE_BOT_COLOR 661 highlight = LITE_BOT_HIGHLIGHT 662 punchiness = 0.3 663 charge_speed_min = 0.6 664 charge_speed_max = 0.6
A weaker version of ba.BrawlerBot.
category: Bot Classes
Inherited Members
- SpazBot
- SpazBot
- throwiness
- static
- bouncy
- run
- charge_dist_min
- run_dist_min
- throw_rate
- default_bomb_type
- default_bomb_count
- start_cursed
- map
- set_player_points
- update_ai
- on_punched
- on_expire
- handlemessage
- bastd.actor.spaz.Spaz
- node
- points_mult
- curse_time
- default_boxing_gloves
- default_shields
- exists
- add_dropped_bomb_callback
- is_alive
- set_score_text
- on_jump_press
- on_jump_release
- on_pickup_press
- on_pickup_release
- on_hold_position_press
- on_hold_position_release
- on_punch_press
- on_punch_release
- on_bomb_press
- on_bomb_release
- on_run
- on_fly_press
- on_fly_release
- on_move
- on_move_up_down
- on_move_left_right
- get_death_points
- curse
- equip_boxing_gloves
- equip_shields
- shield_decay
- drop_bomb
- set_land_mine_count
- curse_explode
- shatter
- set_bomb_count
- ba._actor.Actor
- autoretain
- expired
- activity
- getactivity
667class BrawlerBotPro(BrawlerBot): 668 """A stronger version of ba.BrawlerBot. 669 670 category: Bot Classes 671 """ 672 color = PRO_BOT_COLOR 673 highlight = PRO_BOT_HIGHLIGHT 674 run = True 675 run_dist_min = 4.0 676 default_boxing_gloves = True 677 punchiness = 0.95 678 points_mult = 2
A stronger version of ba.BrawlerBot.
category: Bot Classes
Inherited Members
- SpazBot
- SpazBot
- throwiness
- static
- bouncy
- charge_dist_min
- throw_rate
- default_bomb_type
- default_bomb_count
- start_cursed
- map
- set_player_points
- update_ai
- on_punched
- on_expire
- handlemessage
- BrawlerBot
- character
- charge_dist_max
- charge_speed_min
- charge_speed_max
- throw_dist_min
- throw_dist_max
- bastd.actor.spaz.Spaz
- node
- curse_time
- default_shields
- exists
- add_dropped_bomb_callback
- is_alive
- set_score_text
- on_jump_press
- on_jump_release
- on_pickup_press
- on_pickup_release
- on_hold_position_press
- on_hold_position_release
- on_punch_press
- on_punch_release
- on_bomb_press
- on_bomb_release
- on_run
- on_fly_press
- on_fly_release
- on_move
- on_move_up_down
- on_move_left_right
- get_death_points
- curse
- equip_boxing_gloves
- equip_shields
- shield_decay
- drop_bomb
- set_land_mine_count
- curse_explode
- shatter
- set_bomb_count
- ba._actor.Actor
- autoretain
- expired
- activity
- getactivity
681class BrawlerBotProShielded(BrawlerBotPro): 682 """A stronger version of ba.BrawlerBot who starts with shields. 683 684 category: Bot Classes 685 """ 686 default_shields = True 687 points_mult = 3
A stronger version of ba.BrawlerBot who starts with shields.
category: Bot Classes
Inherited Members
- SpazBot
- SpazBot
- throwiness
- static
- bouncy
- charge_dist_min
- throw_rate
- default_bomb_type
- default_bomb_count
- start_cursed
- map
- set_player_points
- update_ai
- on_punched
- on_expire
- handlemessage
- BrawlerBot
- character
- charge_dist_max
- charge_speed_min
- charge_speed_max
- throw_dist_min
- throw_dist_max
- bastd.actor.spaz.Spaz
- node
- curse_time
- exists
- add_dropped_bomb_callback
- is_alive
- set_score_text
- on_jump_press
- on_jump_release
- on_pickup_press
- on_pickup_release
- on_hold_position_press
- on_hold_position_release
- on_punch_press
- on_punch_release
- on_bomb_press
- on_bomb_release
- on_run
- on_fly_press
- on_fly_release
- on_move
- on_move_up_down
- on_move_left_right
- get_death_points
- curse
- equip_boxing_gloves
- equip_shields
- shield_decay
- drop_bomb
- set_land_mine_count
- curse_explode
- shatter
- set_bomb_count
- ba._actor.Actor
- autoretain
- expired
- activity
- getactivity
690class ChargerBot(SpazBot): 691 """A speedy melee attack bot. 692 693 category: Bot Classes 694 """ 695 696 character = 'Snake Shadow' 697 punchiness = 1.0 698 run = True 699 charge_dist_min = 10.0 700 charge_dist_max = 9999.0 701 charge_speed_min = 1.0 702 charge_speed_max = 1.0 703 throw_dist_min = 9999 704 throw_dist_max = 9999 705 points_mult = 2
A speedy melee attack bot.
category: Bot Classes
Inherited Members
- SpazBot
- SpazBot
- throwiness
- static
- bouncy
- run_dist_min
- throw_rate
- default_bomb_type
- default_bomb_count
- start_cursed
- color
- highlight
- map
- set_player_points
- update_ai
- on_punched
- on_expire
- handlemessage
- bastd.actor.spaz.Spaz
- node
- curse_time
- default_boxing_gloves
- default_shields
- exists
- add_dropped_bomb_callback
- is_alive
- set_score_text
- on_jump_press
- on_jump_release
- on_pickup_press
- on_pickup_release
- on_hold_position_press
- on_hold_position_release
- on_punch_press
- on_punch_release
- on_bomb_press
- on_bomb_release
- on_run
- on_fly_press
- on_fly_release
- on_move
- on_move_up_down
- on_move_left_right
- get_death_points
- curse
- equip_boxing_gloves
- equip_shields
- shield_decay
- drop_bomb
- set_land_mine_count
- curse_explode
- shatter
- set_bomb_count
- ba._actor.Actor
- autoretain
- expired
- activity
- getactivity
708class BouncyBot(SpazBot): 709 """A speedy attacking melee bot that jumps constantly. 710 711 category: Bot Classes 712 """ 713 714 color = (1, 1, 1) 715 highlight = (1.0, 0.5, 0.5) 716 character = 'Easter Bunny' 717 punchiness = 1.0 718 run = True 719 bouncy = True 720 default_boxing_gloves = True 721 charge_dist_min = 10.0 722 charge_dist_max = 9999.0 723 charge_speed_min = 1.0 724 charge_speed_max = 1.0 725 throw_dist_min = 9999 726 throw_dist_max = 9999 727 points_mult = 2
A speedy attacking melee bot that jumps constantly.
category: Bot Classes
Inherited Members
- SpazBot
- SpazBot
- throwiness
- static
- run_dist_min
- throw_rate
- default_bomb_type
- default_bomb_count
- start_cursed
- map
- set_player_points
- update_ai
- on_punched
- on_expire
- handlemessage
- bastd.actor.spaz.Spaz
- node
- curse_time
- default_shields
- exists
- add_dropped_bomb_callback
- is_alive
- set_score_text
- on_jump_press
- on_jump_release
- on_pickup_press
- on_pickup_release
- on_hold_position_press
- on_hold_position_release
- on_punch_press
- on_punch_release
- on_bomb_press
- on_bomb_release
- on_run
- on_fly_press
- on_fly_release
- on_move
- on_move_up_down
- on_move_left_right
- get_death_points
- curse
- equip_boxing_gloves
- equip_shields
- shield_decay
- drop_bomb
- set_land_mine_count
- curse_explode
- shatter
- set_bomb_count
- ba._actor.Actor
- autoretain
- expired
- activity
- getactivity
730class ChargerBotPro(ChargerBot): 731 """A stronger ba.ChargerBot. 732 733 category: Bot Classes 734 """ 735 color = PRO_BOT_COLOR 736 highlight = PRO_BOT_HIGHLIGHT 737 default_shields = True 738 default_boxing_gloves = True 739 points_mult = 3
A stronger ba.ChargerBot.
category: Bot Classes
Inherited Members
- SpazBot
- SpazBot
- throwiness
- static
- bouncy
- run_dist_min
- throw_rate
- default_bomb_type
- default_bomb_count
- start_cursed
- map
- set_player_points
- update_ai
- on_punched
- on_expire
- handlemessage
- ChargerBot
- character
- punchiness
- run
- charge_dist_min
- charge_dist_max
- charge_speed_min
- charge_speed_max
- throw_dist_min
- throw_dist_max
- bastd.actor.spaz.Spaz
- node
- curse_time
- exists
- add_dropped_bomb_callback
- is_alive
- set_score_text
- on_jump_press
- on_jump_release
- on_pickup_press
- on_pickup_release
- on_hold_position_press
- on_hold_position_release
- on_punch_press
- on_punch_release
- on_bomb_press
- on_bomb_release
- on_run
- on_fly_press
- on_fly_release
- on_move
- on_move_up_down
- on_move_left_right
- get_death_points
- curse
- equip_boxing_gloves
- equip_shields
- shield_decay
- drop_bomb
- set_land_mine_count
- curse_explode
- shatter
- set_bomb_count
- ba._actor.Actor
- autoretain
- expired
- activity
- getactivity
742class ChargerBotProShielded(ChargerBotPro): 743 """A stronger ba.ChargerBot who starts with shields. 744 745 category: Bot Classes 746 """ 747 default_shields = True 748 points_mult = 4
A stronger ba.ChargerBot who starts with shields.
category: Bot Classes
Inherited Members
- SpazBot
- SpazBot
- throwiness
- static
- bouncy
- run_dist_min
- throw_rate
- default_bomb_type
- default_bomb_count
- start_cursed
- map
- set_player_points
- update_ai
- on_punched
- on_expire
- handlemessage
- ChargerBot
- character
- punchiness
- run
- charge_dist_min
- charge_dist_max
- charge_speed_min
- charge_speed_max
- throw_dist_min
- throw_dist_max
- bastd.actor.spaz.Spaz
- node
- curse_time
- exists
- add_dropped_bomb_callback
- is_alive
- set_score_text
- on_jump_press
- on_jump_release
- on_pickup_press
- on_pickup_release
- on_hold_position_press
- on_hold_position_release
- on_punch_press
- on_punch_release
- on_bomb_press
- on_bomb_release
- on_run
- on_fly_press
- on_fly_release
- on_move
- on_move_up_down
- on_move_left_right
- get_death_points
- curse
- equip_boxing_gloves
- equip_shields
- shield_decay
- drop_bomb
- set_land_mine_count
- curse_explode
- shatter
- set_bomb_count
- ba._actor.Actor
- autoretain
- expired
- activity
- getactivity
751class TriggerBot(SpazBot): 752 """A slow moving bot with trigger bombs. 753 754 category: Bot Classes 755 """ 756 character = 'Zoe' 757 punchiness = 0.75 758 throwiness = 0.7 759 charge_dist_max = 1.0 760 charge_speed_min = 0.3 761 charge_speed_max = 0.5 762 throw_dist_min = 3.5 763 throw_dist_max = 5.5 764 default_bomb_type = 'impact' 765 points_mult = 2
A slow moving bot with trigger bombs.
category: Bot Classes
Inherited Members
- SpazBot
- SpazBot
- static
- bouncy
- run
- charge_dist_min
- run_dist_min
- throw_rate
- default_bomb_count
- start_cursed
- color
- highlight
- map
- set_player_points
- update_ai
- on_punched
- on_expire
- handlemessage
- bastd.actor.spaz.Spaz
- node
- curse_time
- default_boxing_gloves
- default_shields
- exists
- add_dropped_bomb_callback
- is_alive
- set_score_text
- on_jump_press
- on_jump_release
- on_pickup_press
- on_pickup_release
- on_hold_position_press
- on_hold_position_release
- on_punch_press
- on_punch_release
- on_bomb_press
- on_bomb_release
- on_run
- on_fly_press
- on_fly_release
- on_move
- on_move_up_down
- on_move_left_right
- get_death_points
- curse
- equip_boxing_gloves
- equip_shields
- shield_decay
- drop_bomb
- set_land_mine_count
- curse_explode
- shatter
- set_bomb_count
- ba._actor.Actor
- autoretain
- expired
- activity
- getactivity
768class TriggerBotStatic(TriggerBot): 769 """A ba.TriggerBot who generally stays in one place. 770 771 category: Bot Classes 772 """ 773 static = True 774 throw_dist_min = 0.0
A ba.TriggerBot who generally stays in one place.
category: Bot Classes
Inherited Members
- SpazBot
- SpazBot
- bouncy
- run
- charge_dist_min
- run_dist_min
- throw_rate
- default_bomb_count
- start_cursed
- color
- highlight
- map
- set_player_points
- update_ai
- on_punched
- on_expire
- handlemessage
- TriggerBot
- character
- punchiness
- throwiness
- charge_dist_max
- charge_speed_min
- charge_speed_max
- throw_dist_max
- default_bomb_type
- points_mult
- bastd.actor.spaz.Spaz
- node
- curse_time
- default_boxing_gloves
- default_shields
- exists
- add_dropped_bomb_callback
- is_alive
- set_score_text
- on_jump_press
- on_jump_release
- on_pickup_press
- on_pickup_release
- on_hold_position_press
- on_hold_position_release
- on_punch_press
- on_punch_release
- on_bomb_press
- on_bomb_release
- on_run
- on_fly_press
- on_fly_release
- on_move
- on_move_up_down
- on_move_left_right
- get_death_points
- curse
- equip_boxing_gloves
- equip_shields
- shield_decay
- drop_bomb
- set_land_mine_count
- curse_explode
- shatter
- set_bomb_count
- ba._actor.Actor
- autoretain
- expired
- activity
- getactivity
777class TriggerBotPro(TriggerBot): 778 """A stronger version of ba.TriggerBot. 779 780 category: Bot Classes 781 """ 782 color = PRO_BOT_COLOR 783 highlight = PRO_BOT_HIGHLIGHT 784 default_bomb_count = 3 785 default_boxing_gloves = True 786 charge_speed_min = 1.0 787 charge_speed_max = 1.0 788 punchiness = 0.9 789 throw_rate = 1.3 790 run = True 791 run_dist_min = 6.0 792 points_mult = 3
A stronger version of ba.TriggerBot.
category: Bot Classes
Inherited Members
- SpazBot
- SpazBot
- static
- bouncy
- charge_dist_min
- start_cursed
- map
- set_player_points
- update_ai
- on_punched
- on_expire
- handlemessage
- bastd.actor.spaz.Spaz
- node
- curse_time
- default_shields
- exists
- add_dropped_bomb_callback
- is_alive
- set_score_text
- on_jump_press
- on_jump_release
- on_pickup_press
- on_pickup_release
- on_hold_position_press
- on_hold_position_release
- on_punch_press
- on_punch_release
- on_bomb_press
- on_bomb_release
- on_run
- on_fly_press
- on_fly_release
- on_move
- on_move_up_down
- on_move_left_right
- get_death_points
- curse
- equip_boxing_gloves
- equip_shields
- shield_decay
- drop_bomb
- set_land_mine_count
- curse_explode
- shatter
- set_bomb_count
- ba._actor.Actor
- autoretain
- expired
- activity
- getactivity
795class TriggerBotProShielded(TriggerBotPro): 796 """A stronger version of ba.TriggerBot who starts with shields. 797 798 category: Bot Classes 799 """ 800 default_shields = True 801 points_mult = 4
A stronger version of ba.TriggerBot who starts with shields.
category: Bot Classes
Inherited Members
- SpazBot
- SpazBot
- static
- bouncy
- charge_dist_min
- start_cursed
- map
- set_player_points
- update_ai
- on_punched
- on_expire
- handlemessage
- TriggerBotPro
- color
- highlight
- default_bomb_count
- default_boxing_gloves
- charge_speed_min
- charge_speed_max
- punchiness
- throw_rate
- run
- run_dist_min
- bastd.actor.spaz.Spaz
- node
- curse_time
- exists
- add_dropped_bomb_callback
- is_alive
- set_score_text
- on_jump_press
- on_jump_release
- on_pickup_press
- on_pickup_release
- on_hold_position_press
- on_hold_position_release
- on_punch_press
- on_punch_release
- on_bomb_press
- on_bomb_release
- on_run
- on_fly_press
- on_fly_release
- on_move
- on_move_up_down
- on_move_left_right
- get_death_points
- curse
- equip_boxing_gloves
- equip_shields
- shield_decay
- drop_bomb
- set_land_mine_count
- curse_explode
- shatter
- set_bomb_count
- ba._actor.Actor
- autoretain
- expired
- activity
- getactivity
804class StickyBot(SpazBot): 805 """A crazy bot who runs and throws sticky bombs. 806 807 category: Bot Classes 808 """ 809 character = 'Mel' 810 punchiness = 0.9 811 throwiness = 1.0 812 run = True 813 charge_dist_min = 4.0 814 charge_dist_max = 10.0 815 charge_speed_min = 1.0 816 charge_speed_max = 1.0 817 throw_dist_min = 0.0 818 throw_dist_max = 4.0 819 throw_rate = 2.0 820 default_bomb_type = 'sticky' 821 default_bomb_count = 3 822 points_mult = 3
A crazy bot who runs and throws sticky bombs.
category: Bot Classes
Inherited Members
- SpazBot
- SpazBot
- static
- bouncy
- run_dist_min
- start_cursed
- color
- highlight
- map
- set_player_points
- update_ai
- on_punched
- on_expire
- handlemessage
- bastd.actor.spaz.Spaz
- node
- curse_time
- default_boxing_gloves
- default_shields
- exists
- add_dropped_bomb_callback
- is_alive
- set_score_text
- on_jump_press
- on_jump_release
- on_pickup_press
- on_pickup_release
- on_hold_position_press
- on_hold_position_release
- on_punch_press
- on_punch_release
- on_bomb_press
- on_bomb_release
- on_run
- on_fly_press
- on_fly_release
- on_move
- on_move_up_down
- on_move_left_right
- get_death_points
- curse
- equip_boxing_gloves
- equip_shields
- shield_decay
- drop_bomb
- set_land_mine_count
- curse_explode
- shatter
- set_bomb_count
- ba._actor.Actor
- autoretain
- expired
- activity
- getactivity
825class StickyBotStatic(StickyBot): 826 """A crazy bot who throws sticky-bombs but generally stays in one place. 827 828 category: Bot Classes 829 """ 830 static = True
A crazy bot who throws sticky-bombs but generally stays in one place.
category: Bot Classes
Inherited Members
- SpazBot
- SpazBot
- bouncy
- run_dist_min
- start_cursed
- color
- highlight
- map
- set_player_points
- update_ai
- on_punched
- on_expire
- handlemessage
- StickyBot
- character
- punchiness
- throwiness
- run
- charge_dist_min
- charge_dist_max
- charge_speed_min
- charge_speed_max
- throw_dist_min
- throw_dist_max
- throw_rate
- default_bomb_type
- default_bomb_count
- points_mult
- bastd.actor.spaz.Spaz
- node
- curse_time
- default_boxing_gloves
- default_shields
- exists
- add_dropped_bomb_callback
- is_alive
- set_score_text
- on_jump_press
- on_jump_release
- on_pickup_press
- on_pickup_release
- on_hold_position_press
- on_hold_position_release
- on_punch_press
- on_punch_release
- on_bomb_press
- on_bomb_release
- on_run
- on_fly_press
- on_fly_release
- on_move
- on_move_up_down
- on_move_left_right
- get_death_points
- curse
- equip_boxing_gloves
- equip_shields
- shield_decay
- drop_bomb
- set_land_mine_count
- curse_explode
- shatter
- set_bomb_count
- ba._actor.Actor
- autoretain
- expired
- activity
- getactivity
833class ExplodeyBot(SpazBot): 834 """A bot who runs and explodes in 5 seconds. 835 836 category: Bot Classes 837 """ 838 character = 'Jack Morgan' 839 run = True 840 charge_dist_min = 0.0 841 charge_dist_max = 9999 842 charge_speed_min = 1.0 843 charge_speed_max = 1.0 844 throw_dist_min = 9999 845 throw_dist_max = 9999 846 start_cursed = True 847 points_mult = 4
A bot who runs and explodes in 5 seconds.
category: Bot Classes
Inherited Members
- SpazBot
- SpazBot
- punchiness
- throwiness
- static
- bouncy
- run_dist_min
- throw_rate
- default_bomb_type
- default_bomb_count
- color
- highlight
- map
- set_player_points
- update_ai
- on_punched
- on_expire
- handlemessage
- bastd.actor.spaz.Spaz
- node
- curse_time
- default_boxing_gloves
- default_shields
- exists
- add_dropped_bomb_callback
- is_alive
- set_score_text
- on_jump_press
- on_jump_release
- on_pickup_press
- on_pickup_release
- on_hold_position_press
- on_hold_position_release
- on_punch_press
- on_punch_release
- on_bomb_press
- on_bomb_release
- on_run
- on_fly_press
- on_fly_release
- on_move
- on_move_up_down
- on_move_left_right
- get_death_points
- curse
- equip_boxing_gloves
- equip_shields
- shield_decay
- drop_bomb
- set_land_mine_count
- curse_explode
- shatter
- set_bomb_count
- ba._actor.Actor
- autoretain
- expired
- activity
- getactivity
850class ExplodeyBotNoTimeLimit(ExplodeyBot): 851 """A bot who runs but does not explode on his own. 852 853 category: Bot Classes 854 """ 855 curse_time = None
A bot who runs but does not explode on his own.
category: Bot Classes
Inherited Members
- SpazBot
- SpazBot
- punchiness
- throwiness
- static
- bouncy
- run_dist_min
- throw_rate
- default_bomb_type
- default_bomb_count
- color
- highlight
- map
- set_player_points
- update_ai
- on_punched
- on_expire
- handlemessage
- ExplodeyBot
- character
- run
- charge_dist_min
- charge_dist_max
- charge_speed_min
- charge_speed_max
- throw_dist_min
- throw_dist_max
- start_cursed
- points_mult
- bastd.actor.spaz.Spaz
- node
- default_boxing_gloves
- default_shields
- exists
- add_dropped_bomb_callback
- is_alive
- set_score_text
- on_jump_press
- on_jump_release
- on_pickup_press
- on_pickup_release
- on_hold_position_press
- on_hold_position_release
- on_punch_press
- on_punch_release
- on_bomb_press
- on_bomb_release
- on_run
- on_fly_press
- on_fly_release
- on_move
- on_move_up_down
- on_move_left_right
- get_death_points
- curse
- equip_boxing_gloves
- equip_shields
- shield_decay
- drop_bomb
- set_land_mine_count
- curse_explode
- shatter
- set_bomb_count
- ba._actor.Actor
- autoretain
- expired
- activity
- getactivity
858class ExplodeyBotShielded(ExplodeyBot): 859 """A ba.ExplodeyBot who starts with shields. 860 861 category: Bot Classes 862 """ 863 default_shields = True 864 points_mult = 5
A ba.ExplodeyBot who starts with shields.
category: Bot Classes
Inherited Members
- SpazBot
- SpazBot
- punchiness
- throwiness
- static
- bouncy
- run_dist_min
- throw_rate
- default_bomb_type
- default_bomb_count
- color
- highlight
- map
- set_player_points
- update_ai
- on_punched
- on_expire
- handlemessage
- ExplodeyBot
- character
- run
- charge_dist_min
- charge_dist_max
- charge_speed_min
- charge_speed_max
- throw_dist_min
- throw_dist_max
- start_cursed
- bastd.actor.spaz.Spaz
- node
- curse_time
- default_boxing_gloves
- exists
- add_dropped_bomb_callback
- is_alive
- set_score_text
- on_jump_press
- on_jump_release
- on_pickup_press
- on_pickup_release
- on_hold_position_press
- on_hold_position_release
- on_punch_press
- on_punch_release
- on_bomb_press
- on_bomb_release
- on_run
- on_fly_press
- on_fly_release
- on_move
- on_move_up_down
- on_move_left_right
- get_death_points
- curse
- equip_boxing_gloves
- equip_shields
- shield_decay
- drop_bomb
- set_land_mine_count
- curse_explode
- shatter
- set_bomb_count
- ba._actor.Actor
- autoretain
- expired
- activity
- getactivity
867class SpazBotSet: 868 """A container/controller for one or more ba.SpazBots. 869 870 category: Bot Classes 871 """ 872 873 def __init__(self) -> None: 874 """Create a bot-set.""" 875 876 # We spread our bots out over a few lists so we can update 877 # them in a staggered fashion. 878 self._bot_list_count = 5 879 self._bot_add_list = 0 880 self._bot_update_list = 0 881 self._bot_lists: list[list[SpazBot]] = [ 882 [] for _ in range(self._bot_list_count) 883 ] 884 self._spawn_sound = ba.getsound('spawn') 885 self._spawning_count = 0 886 self._bot_update_timer: ba.Timer | None = None 887 self.start_moving() 888 889 def __del__(self) -> None: 890 self.clear() 891 892 def spawn_bot( 893 self, 894 bot_type: type[SpazBot], 895 pos: Sequence[float], 896 spawn_time: float = 3.0, 897 on_spawn_call: Callable[[SpazBot], Any] | None = None) -> None: 898 """Spawn a bot from this set.""" 899 from bastd.actor import spawner 900 spawner.Spawner(pt=pos, 901 spawn_time=spawn_time, 902 send_spawn_message=False, 903 spawn_callback=ba.Call(self._spawn_bot, bot_type, pos, 904 on_spawn_call)) 905 self._spawning_count += 1 906 907 def _spawn_bot(self, bot_type: type[SpazBot], pos: Sequence[float], 908 on_spawn_call: Callable[[SpazBot], Any] | None) -> None: 909 spaz = bot_type() 910 ba.playsound(self._spawn_sound, position=pos) 911 assert spaz.node 912 spaz.node.handlemessage('flash') 913 spaz.node.is_area_of_interest = False 914 spaz.handlemessage(ba.StandMessage(pos, random.uniform(0, 360))) 915 self.add_bot(spaz) 916 self._spawning_count -= 1 917 if on_spawn_call is not None: 918 on_spawn_call(spaz) 919 920 def have_living_bots(self) -> bool: 921 """Return whether any bots in the set are alive or spawning.""" 922 return (self._spawning_count > 0 923 or any(any(b.is_alive() for b in l) for l in self._bot_lists)) 924 925 def get_living_bots(self) -> list[SpazBot]: 926 """Get the living bots in the set.""" 927 bots: list[SpazBot] = [] 928 for botlist in self._bot_lists: 929 for bot in botlist: 930 if bot.is_alive(): 931 bots.append(bot) 932 return bots 933 934 def _update(self) -> None: 935 936 # Update one of our bot lists each time through. 937 # First off, remove no-longer-existing bots from the list. 938 try: 939 bot_list = self._bot_lists[self._bot_update_list] = ([ 940 b for b in self._bot_lists[self._bot_update_list] if b 941 ]) 942 except Exception: 943 bot_list = [] 944 ba.print_exception('Error updating bot list: ' + 945 str(self._bot_lists[self._bot_update_list])) 946 self._bot_update_list = (self._bot_update_list + 947 1) % self._bot_list_count 948 949 # Update our list of player points for the bots to use. 950 player_pts = [] 951 for player in ba.getactivity().players: 952 assert isinstance(player, ba.Player) 953 try: 954 # TODO: could use abstracted player.position here so we 955 # don't have to assume their actor type, but we have no 956 # abstracted velocity as of yet. 957 if player.is_alive(): 958 assert isinstance(player.actor, Spaz) 959 assert player.actor.node 960 player_pts.append((ba.Vec3(player.actor.node.position), 961 ba.Vec3(player.actor.node.velocity))) 962 except Exception: 963 ba.print_exception('Error on bot-set _update.') 964 965 for bot in bot_list: 966 bot.set_player_points(player_pts) 967 bot.update_ai() 968 969 def clear(self) -> None: 970 """Immediately clear out any bots in the set.""" 971 972 # Don't do this if the activity is shutting down or dead. 973 activity = ba.getactivity(doraise=False) 974 if activity is None or activity.expired: 975 return 976 977 for i, bot_list in enumerate(self._bot_lists): 978 for bot in bot_list: 979 bot.handlemessage(ba.DieMessage(immediate=True)) 980 self._bot_lists[i] = [] 981 982 def start_moving(self) -> None: 983 """Start processing bot AI updates so they start doing their thing.""" 984 self._bot_update_timer = ba.Timer(0.05, 985 ba.WeakCall(self._update), 986 repeat=True) 987 988 def stop_moving(self) -> None: 989 """Tell all bots to stop moving and stops updating their AI. 990 991 Useful when players have won and you want the 992 enemy bots to just stand and look bewildered. 993 """ 994 self._bot_update_timer = None 995 for botlist in self._bot_lists: 996 for bot in botlist: 997 if bot.node: 998 bot.node.move_left_right = 0 999 bot.node.move_up_down = 0 1000 1001 def celebrate(self, duration: float) -> None: 1002 """Tell all living bots in the set to celebrate momentarily. 1003 1004 Duration is given in seconds. 1005 """ 1006 msg = ba.CelebrateMessage(duration=duration) 1007 for botlist in self._bot_lists: 1008 for bot in botlist: 1009 if bot: 1010 bot.handlemessage(msg) 1011 1012 def final_celebrate(self) -> None: 1013 """Tell all bots in the set to stop what they were doing and celebrate. 1014 1015 Use this when the bots have won a game. 1016 """ 1017 self._bot_update_timer = None 1018 1019 # At this point stop doing anything but jumping and celebrating. 1020 for botlist in self._bot_lists: 1021 for bot in botlist: 1022 if bot: 1023 assert bot.node # (should exist if 'if bot' was True) 1024 bot.node.move_left_right = 0 1025 bot.node.move_up_down = 0 1026 ba.timer(0.5 * random.random(), 1027 ba.Call(bot.handlemessage, ba.CelebrateMessage())) 1028 jump_duration = random.randrange(400, 500) 1029 j = random.randrange(0, 200) 1030 for _i in range(10): 1031 bot.node.jump_pressed = True 1032 bot.node.jump_pressed = False 1033 j += jump_duration 1034 ba.timer(random.uniform(0.0, 1.0), 1035 ba.Call(bot.node.handlemessage, 'attack_sound')) 1036 ba.timer(random.uniform(1.0, 2.0), 1037 ba.Call(bot.node.handlemessage, 'attack_sound')) 1038 ba.timer(random.uniform(2.0, 3.0), 1039 ba.Call(bot.node.handlemessage, 'attack_sound')) 1040 1041 def add_bot(self, bot: SpazBot) -> None: 1042 """Add a ba.SpazBot instance to the set.""" 1043 self._bot_lists[self._bot_add_list].append(bot) 1044 self._bot_add_list = (self._bot_add_list + 1) % self._bot_list_count
A container/controller for one or more ba.SpazBots.
category: Bot Classes
873 def __init__(self) -> None: 874 """Create a bot-set.""" 875 876 # We spread our bots out over a few lists so we can update 877 # them in a staggered fashion. 878 self._bot_list_count = 5 879 self._bot_add_list = 0 880 self._bot_update_list = 0 881 self._bot_lists: list[list[SpazBot]] = [ 882 [] for _ in range(self._bot_list_count) 883 ] 884 self._spawn_sound = ba.getsound('spawn') 885 self._spawning_count = 0 886 self._bot_update_timer: ba.Timer | None = None 887 self.start_moving()
Create a bot-set.
892 def spawn_bot( 893 self, 894 bot_type: type[SpazBot], 895 pos: Sequence[float], 896 spawn_time: float = 3.0, 897 on_spawn_call: Callable[[SpazBot], Any] | None = None) -> None: 898 """Spawn a bot from this set.""" 899 from bastd.actor import spawner 900 spawner.Spawner(pt=pos, 901 spawn_time=spawn_time, 902 send_spawn_message=False, 903 spawn_callback=ba.Call(self._spawn_bot, bot_type, pos, 904 on_spawn_call)) 905 self._spawning_count += 1
Spawn a bot from this set.
920 def have_living_bots(self) -> bool: 921 """Return whether any bots in the set are alive or spawning.""" 922 return (self._spawning_count > 0 923 or any(any(b.is_alive() for b in l) for l in self._bot_lists))
Return whether any bots in the set are alive or spawning.
925 def get_living_bots(self) -> list[SpazBot]: 926 """Get the living bots in the set.""" 927 bots: list[SpazBot] = [] 928 for botlist in self._bot_lists: 929 for bot in botlist: 930 if bot.is_alive(): 931 bots.append(bot) 932 return bots
Get the living bots in the set.
969 def clear(self) -> None: 970 """Immediately clear out any bots in the set.""" 971 972 # Don't do this if the activity is shutting down or dead. 973 activity = ba.getactivity(doraise=False) 974 if activity is None or activity.expired: 975 return 976 977 for i, bot_list in enumerate(self._bot_lists): 978 for bot in bot_list: 979 bot.handlemessage(ba.DieMessage(immediate=True)) 980 self._bot_lists[i] = []
Immediately clear out any bots in the set.
982 def start_moving(self) -> None: 983 """Start processing bot AI updates so they start doing their thing.""" 984 self._bot_update_timer = ba.Timer(0.05, 985 ba.WeakCall(self._update), 986 repeat=True)
Start processing bot AI updates so they start doing their thing.
988 def stop_moving(self) -> None: 989 """Tell all bots to stop moving and stops updating their AI. 990 991 Useful when players have won and you want the 992 enemy bots to just stand and look bewildered. 993 """ 994 self._bot_update_timer = None 995 for botlist in self._bot_lists: 996 for bot in botlist: 997 if bot.node: 998 bot.node.move_left_right = 0 999 bot.node.move_up_down = 0
Tell all bots to stop moving and stops updating their AI.
Useful when players have won and you want the enemy bots to just stand and look bewildered.
1001 def celebrate(self, duration: float) -> None: 1002 """Tell all living bots in the set to celebrate momentarily. 1003 1004 Duration is given in seconds. 1005 """ 1006 msg = ba.CelebrateMessage(duration=duration) 1007 for botlist in self._bot_lists: 1008 for bot in botlist: 1009 if bot: 1010 bot.handlemessage(msg)
Tell all living bots in the set to celebrate momentarily.
Duration is given in seconds.
1012 def final_celebrate(self) -> None: 1013 """Tell all bots in the set to stop what they were doing and celebrate. 1014 1015 Use this when the bots have won a game. 1016 """ 1017 self._bot_update_timer = None 1018 1019 # At this point stop doing anything but jumping and celebrating. 1020 for botlist in self._bot_lists: 1021 for bot in botlist: 1022 if bot: 1023 assert bot.node # (should exist if 'if bot' was True) 1024 bot.node.move_left_right = 0 1025 bot.node.move_up_down = 0 1026 ba.timer(0.5 * random.random(), 1027 ba.Call(bot.handlemessage, ba.CelebrateMessage())) 1028 jump_duration = random.randrange(400, 500) 1029 j = random.randrange(0, 200) 1030 for _i in range(10): 1031 bot.node.jump_pressed = True 1032 bot.node.jump_pressed = False 1033 j += jump_duration 1034 ba.timer(random.uniform(0.0, 1.0), 1035 ba.Call(bot.node.handlemessage, 'attack_sound')) 1036 ba.timer(random.uniform(1.0, 2.0), 1037 ba.Call(bot.node.handlemessage, 'attack_sound')) 1038 ba.timer(random.uniform(2.0, 3.0), 1039 ba.Call(bot.node.handlemessage, 'attack_sound'))
Tell all bots in the set to stop what they were doing and celebrate.
Use this when the bots have won a game.