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