class TheLastStandGame(ba.CoopGameActivity[Player, Team]): """Slow motion how-long-can-you-last game.""" name = 'The Last Stand' description = 'Final glorious epic slow motion battle to the death.' tips = [ 'This level never ends, but a high score here\n' 'will earn you eternal respect throughout the world.' ] # Show messages when players die since it matters here. announce_player_deaths = True # And of course the most important part. slow_motion = True default_music = ba.MusicType.EPIC def __init__(self, settings: dict): settings['map'] = 'Rampage' super().__init__(settings) self._new_wave_sound = ba.getsound('scoreHit01') self._winsound = ba.getsound('score') self._cashregistersound = ba.getsound('cashRegister') self._spawn_center = (0, 5.5, -4.14) self._tntspawnpos = (0, 5.5, -6) self._powerup_center = (0, 7, -4.14) self._powerup_spread = (7, 2) self._preset = str(settings.get('preset', 'default')) self._excludepowerups: list[str] = [] self._scoreboard: Optional[Scoreboard] = None self._score = 0 self._bots = SpazBotSet() self._dingsound = ba.getsound('dingSmall') self._dingsoundhigh = ba.getsound('dingSmallHigh') self._tntspawner: Optional[TNTSpawner] = None self._bot_update_interval: Optional[float] = None self._bot_update_timer: Optional[ba.Timer] = None self._powerup_drop_timer = None # For each bot type: [spawnrate, increase, d_increase] self._bot_spawn_types = { BomberBot: SpawnInfo(1.00, 0.00, 0.000), BomberBotPro: SpawnInfo(0.00, 0.05, 0.001), BomberBotProShielded: SpawnInfo(0.00, 0.02, 0.002), BrawlerBot: SpawnInfo(1.00, 0.00, 0.000), BrawlerBotPro: SpawnInfo(0.00, 0.05, 0.001), BrawlerBotProShielded: SpawnInfo(0.00, 0.02, 0.002), TriggerBot: SpawnInfo(0.30, 0.00, 0.000), TriggerBotPro: SpawnInfo(0.00, 0.05, 0.001), TriggerBotProShielded: SpawnInfo(0.00, 0.02, 0.002), ChargerBot: SpawnInfo(0.30, 0.05, 0.000), StickyBot: SpawnInfo(0.10, 0.03, 0.001), ExplodeyBot: SpawnInfo(0.05, 0.02, 0.002) } # yapf: disable def on_transition_in(self) -> None: super().on_transition_in() ba.timer(1.3, ba.Call(ba.playsound, self._new_wave_sound)) self._scoreboard = Scoreboard(label=ba.Lstr(resource='scoreText'), score_split=0.5) def on_begin(self) -> None: super().on_begin() # Spit out a few powerups and start dropping more shortly. self._drop_powerups(standard_points=True) ba.timer(2.0, ba.WeakCall(self._start_powerup_drops)) ba.timer(0.001, ba.WeakCall(self._start_bot_updates)) self.setup_low_life_warning_sound() self._update_scores() self._tntspawner = TNTSpawner(position=self._tntspawnpos, respawn_time=10.0) def spawn_player(self, player: Player) -> ba.Actor: pos = (self._spawn_center[0] + random.uniform(-1.5, 1.5), self._spawn_center[1], self._spawn_center[2] + random.uniform(-1.5, 1.5)) return self.spawn_player_spaz(player, position=pos) def _start_bot_updates(self) -> None: self._bot_update_interval = 3.3 - 0.3 * (len(self.players)) self._update_bots() self._update_bots() if len(self.players) > 2: self._update_bots() if len(self.players) > 3: self._update_bots() self._bot_update_timer = ba.Timer(self._bot_update_interval, ba.WeakCall(self._update_bots)) def _drop_powerup(self, index: int, poweruptype: str = None) -> None: if poweruptype is None: poweruptype = (PowerupBoxFactory.get().get_random_powerup_type( excludetypes=self._excludepowerups)) PowerupBox(position=self.map.powerup_spawn_points[index], poweruptype=poweruptype).autoretain() def _start_powerup_drops(self) -> None: self._powerup_drop_timer = ba.Timer(3.0, ba.WeakCall(self._drop_powerups), repeat=True) def _drop_powerups(self, standard_points: bool = False, force_first: str = None) -> None: """Generic powerup drop.""" from bastd.actor import powerupbox if standard_points: pts = self.map.powerup_spawn_points for i in range(len(pts)): ba.timer( 1.0 + i * 0.5, ba.WeakCall(self._drop_powerup, i, force_first if i == 0 else None)) else: drop_pt = (self._powerup_center[0] + random.uniform( -1.0 * self._powerup_spread[0], 1.0 * self._powerup_spread[0]), self._powerup_center[1], self._powerup_center[2] + random.uniform( -self._powerup_spread[1], self._powerup_spread[1])) # Drop one random one somewhere. powerupbox.PowerupBox( position=drop_pt, poweruptype=PowerupBoxFactory.get().get_random_powerup_type( excludetypes=self._excludepowerups)).autoretain() def do_end(self, outcome: str) -> None: """End the game.""" if outcome == 'defeat': self.fade_to_red() self.end(delay=2.0, results={ 'outcome': outcome, 'score': self._score, 'playerinfos': self.initialplayerinfos }) def _update_bots(self) -> None: assert self._bot_update_interval is not None self._bot_update_interval = max(0.5, self._bot_update_interval * 0.98) self._bot_update_timer = ba.Timer(self._bot_update_interval, ba.WeakCall(self._update_bots)) botspawnpts: list[Sequence[float]] = [[-5.0, 5.5, -4.14], [0.0, 5.5, -4.14], [5.0, 5.5, -4.14]] dists = [0.0, 0.0, 0.0] playerpts: list[Sequence[float]] = [] for player in self.players: try: if player.is_alive(): assert isinstance(player.actor, PlayerSpaz) assert player.actor.node playerpts.append(player.actor.node.position) except Exception: ba.print_exception('Error updating bots.') for i in range(3): for playerpt in playerpts: dists[i] += abs(playerpt[0] - botspawnpts[i][0]) dists[i] += random.random() * 5.0 # Minor random variation. if dists[0] > dists[1] and dists[0] > dists[2]: spawnpt = botspawnpts[0] elif dists[1] > dists[2]: spawnpt = botspawnpts[1] else: spawnpt = botspawnpts[2] spawnpt = (spawnpt[0] + 3.0 * (random.random() - 0.5), spawnpt[1], 2.0 * (random.random() - 0.5) + spawnpt[2]) # Normalize our bot type total and find a random number within that. total = 0.0 for spawninfo in self._bot_spawn_types.values(): total += spawninfo.spawnrate randval = random.random() * total # Now go back through and see where this value falls. total = 0 bottype: Optional[type[SpazBot]] = None for spawntype, spawninfo in self._bot_spawn_types.items(): total += spawninfo.spawnrate if randval <= total: bottype = spawntype break spawn_time = 1.0 assert bottype is not None self._bots.spawn_bot(bottype, pos=spawnpt, spawn_time=spawn_time) # After every spawn we adjust our ratios slightly to get more # difficult. for spawninfo in self._bot_spawn_types.values(): spawninfo.spawnrate += spawninfo.increase spawninfo.increase += spawninfo.dincrease def _update_scores(self) -> None: score = self._score # Achievements apply to the default preset only. if self._preset == 'default': if score >= 250: self._award_achievement('Last Stand Master') if score >= 500: self._award_achievement('Last Stand Wizard') if score >= 1000: self._award_achievement('Last Stand God') assert self._scoreboard is not None self._scoreboard.set_team_value(self.teams[0], score, max_score=None) def handlemessage(self, msg: Any) -> Any: if isinstance(msg, ba.PlayerDiedMessage): player = msg.getplayer(Player) self.stats.player_was_killed(player) ba.timer(0.1, self._checkroundover) elif isinstance(msg, ba.PlayerScoredMessage): self._score += msg.score self._update_scores() elif isinstance(msg, SpazBotDiedMessage): pts, importance = msg.spazbot.get_death_points(msg.how) target: Optional[Sequence[float]] if msg.killerplayer: assert msg.spazbot.node target = msg.spazbot.node.position self.stats.player_scored(msg.killerplayer, pts, target=target, kill=True, screenmessage=False, importance=importance) ba.playsound(self._dingsound if importance == 1 else self._dingsoundhigh, volume=0.6) # Normally we pull scores from the score-set, but if there's no # player lets be explicit. else: self._score += pts self._update_scores() else: super().handlemessage(msg) def _on_got_scores_to_beat(self, scores: list[dict[str, Any]]) -> None: self._show_standard_scores_to_beat_ui(scores) def end_game(self) -> None: # Tell our bots to celebrate just to rub it in. self._bots.final_celebrate() ba.setmusic(None) ba.pushcall(ba.WeakCall(self.do_end, 'defeat')) def _checkroundover(self) -> None: """End the round if conditions are met.""" if not any(player.is_alive() for player in self.teams[0].players): self.end_game()
class FootballCoopGame(ba.CoopGameActivity[Player, Team]): """Co-op variant of football.""" name = 'Football' tips = ['Use the pick-up button to grab the flag < ${PICKUP} >'] scoreconfig = ba.ScoreConfig(scoretype=ba.ScoreType.MILLISECONDS, version='B') default_music = ba.MusicType.FOOTBALL # FIXME: Need to update co-op games to use getscoreconfig. def get_score_type(self) -> str: return 'time' def get_instance_description(self) -> Union[str, Sequence]: touchdowns = self._score_to_win / 7 touchdowns = math.ceil(touchdowns) if touchdowns > 1: return 'Score ${ARG1} touchdowns.', touchdowns return 'Score a touchdown.' def get_instance_description_short(self) -> Union[str, Sequence]: touchdowns = self._score_to_win / 7 touchdowns = math.ceil(touchdowns) if touchdowns > 1: return 'score ${ARG1} touchdowns', touchdowns return 'score a touchdown' def __init__(self, settings: dict): settings['map'] = 'Football Stadium' super().__init__(settings) self._preset = settings.get('preset', 'rookie') # Load some media we need. self._cheer_sound = ba.getsound('cheer') self._boo_sound = ba.getsound('boo') self._chant_sound = ba.getsound('crowdChant') self._score_sound = ba.getsound('score') self._swipsound = ba.getsound('swip') self._whistle_sound = ba.getsound('refWhistle') self._score_to_win = 21 self._score_region_material = ba.Material() self._score_region_material.add_actions( conditions=('they_have_material', FlagFactory.get().flagmaterial), actions=( ('modify_part_collision', 'collide', True), ('modify_part_collision', 'physical', False), ('call', 'at_connect', self._handle_score), )) self._powerup_center = (0, 2, 0) self._powerup_spread = (10, 5.5) self._player_has_dropped_bomb = False self._player_has_punched = False self._scoreboard: Optional[Scoreboard] = None self._flag_spawn_pos: Optional[Sequence[float]] = None self._score_regions: List[ba.NodeActor] = [] self._exclude_powerups: List[str] = [] self._have_tnt = False self._bot_types_initial: Optional[List[Type[SpazBot]]] = None self._bot_types_7: Optional[List[Type[SpazBot]]] = None self._bot_types_14: Optional[List[Type[SpazBot]]] = None self._bot_team: Optional[Team] = None self._starttime_ms: Optional[int] = None self._time_text: Optional[ba.NodeActor] = None self._time_text_input: Optional[ba.NodeActor] = None self._tntspawner: Optional[TNTSpawner] = None self._bots = SpazBotSet() self._bot_spawn_timer: Optional[ba.Timer] = None self._powerup_drop_timer: Optional[ba.Timer] = None self._scoring_team: Optional[Team] = None self._final_time_ms: Optional[int] = None self._time_text_timer: Optional[ba.Timer] = None self._flag_respawn_light: Optional[ba.Actor] = None self._flag: Optional[FootballFlag] = None def on_transition_in(self) -> None: super().on_transition_in() self._scoreboard = Scoreboard() self._flag_spawn_pos = self.map.get_flag_position(None) self._spawn_flag() # Set up the two score regions. defs = self.map.defs self._score_regions.append( ba.NodeActor( ba.newnode('region', attrs={ 'position': defs.boxes['goal1'][0:3], 'scale': defs.boxes['goal1'][6:9], 'type': 'box', 'materials': [self._score_region_material] }))) self._score_regions.append( ba.NodeActor( ba.newnode('region', attrs={ 'position': defs.boxes['goal2'][0:3], 'scale': defs.boxes['goal2'][6:9], 'type': 'box', 'materials': [self._score_region_material] }))) ba.playsound(self._chant_sound) def on_begin(self) -> None: # FIXME: Split this up a bit. # pylint: disable=too-many-statements from bastd.actor import controlsguide super().on_begin() # Show controls help in kiosk mode. if ba.app.demo_mode or ba.app.arcade_mode: controlsguide.ControlsGuide(delay=3.0, lifespan=10.0, bright=True).autoretain() assert self.initialplayerinfos is not None abot: Type[SpazBot] bbot: Type[SpazBot] cbot: Type[SpazBot] if self._preset in ['rookie', 'rookie_easy']: self._exclude_powerups = ['curse'] self._have_tnt = False abot = (BrawlerBotLite if self._preset == 'rookie_easy' else BrawlerBot) self._bot_types_initial = [abot] * len(self.initialplayerinfos) bbot = (BomberBotLite if self._preset == 'rookie_easy' else BomberBot) self._bot_types_7 = ( [bbot] * (1 if len(self.initialplayerinfos) < 3 else 2)) cbot = (BomberBot if self._preset == 'rookie_easy' else TriggerBot) self._bot_types_14 = ( [cbot] * (1 if len(self.initialplayerinfos) < 3 else 2)) elif self._preset == 'tournament': self._exclude_powerups = [] self._have_tnt = True self._bot_types_initial = ( [BrawlerBot] * (1 if len(self.initialplayerinfos) < 2 else 2)) self._bot_types_7 = ( [TriggerBot] * (1 if len(self.initialplayerinfos) < 3 else 2)) self._bot_types_14 = ( [ChargerBot] * (1 if len(self.initialplayerinfos) < 4 else 2)) elif self._preset in ['pro', 'pro_easy', 'tournament_pro']: self._exclude_powerups = ['curse'] self._have_tnt = True self._bot_types_initial = [ChargerBot] * len( self.initialplayerinfos) abot = (BrawlerBot if self._preset == 'pro' else BrawlerBotLite) typed_bot_list: List[Type[SpazBot]] = [] self._bot_types_7 = ( typed_bot_list + [abot] + [BomberBot] * (1 if len(self.initialplayerinfos) < 3 else 2)) bbot = (TriggerBotPro if self._preset == 'pro' else TriggerBot) self._bot_types_14 = ( [bbot] * (1 if len(self.initialplayerinfos) < 3 else 2)) elif self._preset in ['uber', 'uber_easy']: self._exclude_powerups = [] self._have_tnt = True abot = (BrawlerBotPro if self._preset == 'uber' else BrawlerBot) bbot = (TriggerBotPro if self._preset == 'uber' else TriggerBot) typed_bot_list_2: List[Type[SpazBot]] = [] self._bot_types_initial = (typed_bot_list_2 + [StickyBot] + [abot] * len(self.initialplayerinfos)) self._bot_types_7 = ( [bbot] * (1 if len(self.initialplayerinfos) < 3 else 2)) self._bot_types_14 = ( [ExplodeyBot] * (1 if len(self.initialplayerinfos) < 3 else 2)) else: raise Exception() self.setup_low_life_warning_sound() self._drop_powerups(standard_points=True) ba.timer(4.0, self._start_powerup_drops) # Make a bogus team for our bots. bad_team_name = self.get_team_display_string('Bad Guys') self._bot_team = Team() self._bot_team.manual_init(team_id=1, name=bad_team_name, color=(0.5, 0.4, 0.4)) for team in [self.teams[0], self._bot_team]: team.score = 0 self.update_scores() # Time display. starttime_ms = ba.time(timeformat=ba.TimeFormat.MILLISECONDS) assert isinstance(starttime_ms, int) self._starttime_ms = starttime_ms self._time_text = ba.NodeActor( ba.newnode('text', attrs={ 'v_attach': 'top', 'h_attach': 'center', 'h_align': 'center', 'color': (1, 1, 0.5, 1), 'flatness': 0.5, 'shadow': 0.5, 'position': (0, -50), 'scale': 1.3, 'text': '' })) self._time_text_input = ba.NodeActor( ba.newnode('timedisplay', attrs={'showsubseconds': True})) self.globalsnode.connectattr('time', self._time_text_input.node, 'time2') assert self._time_text_input.node assert self._time_text.node self._time_text_input.node.connectattr('output', self._time_text.node, 'text') # Our TNT spawner (if applicable). if self._have_tnt: self._tntspawner = TNTSpawner(position=(0, 1, -1)) self._bots = SpazBotSet() self._bot_spawn_timer = ba.Timer(1.0, self._update_bots, repeat=True) for bottype in self._bot_types_initial: self._spawn_bot(bottype) def _on_got_scores_to_beat(self, scores: List[Dict[str, Any]]) -> None: self._show_standard_scores_to_beat_ui(scores) def _on_bot_spawn(self, spaz: SpazBot) -> None: # We want to move to the left by default. spaz.target_point_default = ba.Vec3(0, 0, 0) def _spawn_bot(self, spaz_type: Type[SpazBot], immediate: bool = False) -> None: assert self._bot_team is not None pos = self.map.get_start_position(self._bot_team.id) self._bots.spawn_bot(spaz_type, pos=pos, spawn_time=0.001 if immediate else 3.0, on_spawn_call=self._on_bot_spawn) def _update_bots(self) -> None: bots = self._bots.get_living_bots() for bot in bots: bot.target_flag = None # If we're waiting on a continue, stop here so they don't keep scoring. if self.is_waiting_for_continue(): self._bots.stop_moving() return # If we've got a flag and no player are holding it, find the closest # bot to it, and make them the designated flag-bearer. assert self._flag is not None if self._flag.node: for player in self.players: if player.actor: assert isinstance(player.actor, PlayerSpaz) if (player.actor.is_alive() and player.actor.node.hold_node == self._flag.node): return flagpos = ba.Vec3(self._flag.node.position) closest_bot: Optional[SpazBot] = None closest_dist = 0.0 # Always gets assigned first time through. for bot in bots: # If a bot is picked up, he should forget about the flag. if bot.held_count > 0: continue assert bot.node botpos = ba.Vec3(bot.node.position) botdist = (botpos - flagpos).length() if closest_bot is None or botdist < closest_dist: closest_bot = bot closest_dist = botdist if closest_bot is not None: closest_bot.target_flag = self._flag def _drop_powerup(self, index: int, poweruptype: str = None) -> None: if poweruptype is None: poweruptype = (PowerupBoxFactory.get().get_random_powerup_type( excludetypes=self._exclude_powerups)) PowerupBox(position=self.map.powerup_spawn_points[index], poweruptype=poweruptype).autoretain() def _start_powerup_drops(self) -> None: self._powerup_drop_timer = ba.Timer(3.0, self._drop_powerups, repeat=True) def _drop_powerups(self, standard_points: bool = False, poweruptype: str = None) -> None: """Generic powerup drop.""" if standard_points: spawnpoints = self.map.powerup_spawn_points for i, _point in enumerate(spawnpoints): ba.timer(1.0 + i * 0.5, ba.Call(self._drop_powerup, i, poweruptype)) else: point = (self._powerup_center[0] + random.uniform( -1.0 * self._powerup_spread[0], 1.0 * self._powerup_spread[0]), self._powerup_center[1], self._powerup_center[2] + random.uniform( -self._powerup_spread[1], self._powerup_spread[1])) # Drop one random one somewhere. PowerupBox( position=point, poweruptype=PowerupBoxFactory.get().get_random_powerup_type( excludetypes=self._exclude_powerups)).autoretain() def _kill_flag(self) -> None: try: assert self._flag is not None self._flag.handlemessage(ba.DieMessage()) except Exception: ba.print_exception('Error in _kill_flag.') def _handle_score(self) -> None: """ a point has been scored """ # FIXME tidy this up # pylint: disable=too-many-branches # Our flag might stick around for a second or two; # we don't want it to be able to score again. assert self._flag is not None if self._flag.scored: return # See which score region it was. region = ba.getcollision().sourcenode i = None for i in range(len(self._score_regions)): if region == self._score_regions[i].node: break for team in [self.teams[0], self._bot_team]: assert team is not None if team.id == i: team.score += 7 # Tell all players (or bots) to celebrate. if i == 0: for player in team.players: if player.actor: player.actor.handlemessage( ba.CelebrateMessage(2.0)) else: self._bots.celebrate(2.0) # If the good guys scored, add more enemies. if i == 0: if self.teams[0].score == 7: assert self._bot_types_7 is not None for bottype in self._bot_types_7: self._spawn_bot(bottype) elif self.teams[0].score == 14: assert self._bot_types_14 is not None for bottype in self._bot_types_14: self._spawn_bot(bottype) ba.playsound(self._score_sound) if i == 0: ba.playsound(self._cheer_sound) else: ba.playsound(self._boo_sound) # Kill the flag (it'll respawn shortly). self._flag.scored = True ba.timer(0.2, self._kill_flag) self.update_scores() light = ba.newnode('light', attrs={ 'position': ba.getcollision().position, 'height_attenuated': False, 'color': (1, 0, 0) }) ba.animate(light, 'intensity', {0: 0, 0.5: 1, 1.0: 0}, loop=True) ba.timer(1.0, light.delete) if i == 0: ba.cameraflash(duration=10.0) def end_game(self) -> None: ba.setmusic(None) self._bots.final_celebrate() ba.timer(0.001, ba.Call(self.do_end, 'defeat')) def on_continue(self) -> None: # Subtract one touchdown from the bots and get them moving again. assert self._bot_team is not None self._bot_team.score -= 7 self._bots.start_moving() self.update_scores() def update_scores(self) -> None: """ update scoreboard and check for winners """ # FIXME: tidy this up # pylint: disable=too-many-nested-blocks have_scoring_team = False win_score = self._score_to_win for team in [self.teams[0], self._bot_team]: assert team is not None assert self._scoreboard is not None self._scoreboard.set_team_value(team, team.score, win_score) if team.score >= win_score: if not have_scoring_team: self._scoring_team = team if team is self._bot_team: self.continue_or_end_game() else: ba.setmusic(ba.MusicType.VICTORY) # Completion achievements. assert self._bot_team is not None if self._preset in ['rookie', 'rookie_easy']: self._award_achievement('Rookie Football Victory', sound=False) if self._bot_team.score == 0: self._award_achievement( 'Rookie Football Shutout', sound=False) elif self._preset in ['pro', 'pro_easy']: self._award_achievement('Pro Football Victory', sound=False) if self._bot_team.score == 0: self._award_achievement('Pro Football Shutout', sound=False) elif self._preset in ['uber', 'uber_easy']: self._award_achievement('Uber Football Victory', sound=False) if self._bot_team.score == 0: self._award_achievement( 'Uber Football Shutout', sound=False) if (not self._player_has_dropped_bomb and not self._player_has_punched): self._award_achievement('Got the Moves', sound=False) self._bots.stop_moving() self.show_zoom_message(ba.Lstr(resource='victoryText'), scale=1.0, duration=4.0) self.celebrate(10.0) assert self._starttime_ms is not None self._final_time_ms = int( ba.time(timeformat=ba.TimeFormat.MILLISECONDS) - self._starttime_ms) self._time_text_timer = None assert (self._time_text_input is not None and self._time_text_input.node) self._time_text_input.node.timemax = ( self._final_time_ms) # FIXME: Does this still need to be deferred? ba.pushcall(ba.Call(self.do_end, 'victory')) def do_end(self, outcome: str) -> None: """End the game with the specified outcome.""" if outcome == 'defeat': self.fade_to_red() assert self._final_time_ms is not None scoreval = (None if outcome == 'defeat' else int(self._final_time_ms // 10)) self.end(delay=3.0, results={ 'outcome': outcome, 'score': scoreval, 'score_order': 'decreasing', 'playerinfos': self.initialplayerinfos }) def handlemessage(self, msg: Any) -> Any: """ handle high-level game messages """ if isinstance(msg, ba.PlayerDiedMessage): # Augment standard behavior. super().handlemessage(msg) # Respawn them shortly. player = msg.getplayer(Player) assert self.initialplayerinfos is not None respawn_time = 2.0 + len(self.initialplayerinfos) * 1.0 player.respawn_timer = ba.Timer( respawn_time, ba.Call(self.spawn_player_if_exists, player)) player.respawn_icon = RespawnIcon(player, respawn_time) elif isinstance(msg, SpazBotDiedMessage): # Every time a bad guy dies, spawn a new one. ba.timer(3.0, ba.Call(self._spawn_bot, (type(msg.spazbot)))) elif isinstance(msg, SpazBotPunchedMessage): if self._preset in ['rookie', 'rookie_easy']: if msg.damage >= 500: self._award_achievement('Super Punch') elif self._preset in ['pro', 'pro_easy']: if msg.damage >= 1000: self._award_achievement('Super Mega Punch') # Respawn dead flags. elif isinstance(msg, FlagDiedMessage): assert isinstance(msg.flag, FootballFlag) msg.flag.respawn_timer = ba.Timer(3.0, self._spawn_flag) self._flag_respawn_light = ba.NodeActor( ba.newnode('light', attrs={ 'position': self._flag_spawn_pos, 'height_attenuated': False, 'radius': 0.15, 'color': (1.0, 1.0, 0.3) })) assert self._flag_respawn_light.node ba.animate(self._flag_respawn_light.node, 'intensity', { 0: 0, 0.25: 0.15, 0.5: 0 }, loop=True) ba.timer(3.0, self._flag_respawn_light.node.delete) else: return super().handlemessage(msg) return None def _handle_player_dropped_bomb(self, player: Spaz, bomb: ba.Actor) -> None: del player, bomb # Unused. self._player_has_dropped_bomb = True def _handle_player_punched(self, player: Spaz) -> None: del player # Unused. self._player_has_punched = True def spawn_player(self, player: Player) -> ba.Actor: spaz = self.spawn_player_spaz(player, position=self.map.get_start_position( player.team.id)) if self._preset in ['rookie_easy', 'pro_easy', 'uber_easy']: spaz.impact_scale = 0.25 spaz.add_dropped_bomb_callback(self._handle_player_dropped_bomb) spaz.punch_callback = self._handle_player_punched return spaz def _flash_flag_spawn(self) -> None: light = ba.newnode('light', attrs={ 'position': self._flag_spawn_pos, 'height_attenuated': False, 'color': (1, 1, 0) }) ba.animate(light, 'intensity', {0: 0, 0.25: 0.25, 0.5: 0}, loop=True) ba.timer(1.0, light.delete) def _spawn_flag(self) -> None: ba.playsound(self._swipsound) ba.playsound(self._whistle_sound) self._flash_flag_spawn() assert self._flag_spawn_pos is not None self._flag = FootballFlag(position=self._flag_spawn_pos)