def _handle_score(self) -> None: """A point has been scored.""" assert self._puck is not None assert self._score_regions is not None # Our puck might stick around for a second or two # we don't want it to be able to score again. if self._puck.scored: return region = ba.getcollision().sourcenode index = 0 for index, score_region in enumerate(self._score_regions): if region == score_region.node: break for team in self.teams: if team.id == index: scoring_team = team team.score += 1 # Tell all players to celebrate. for player in team.players: if player.actor: player.actor.handlemessage(ba.CelebrateMessage(2.0)) # If we've got the player from the scoring team that last # touched us, give them points. if (scoring_team.id in self._puck.last_players_to_touch and self._puck.last_players_to_touch[scoring_team.id]): self.stats.player_scored( self._puck.last_players_to_touch[scoring_team.id], 100, big_message=True) # End game if we won. if team.score >= self._score_to_win: self.end_game() ba.playsound(self._foghorn_sound) ba.playsound(self._cheer_sound) self._puck.scored = True # Kill the puck (it'll respawn itself shortly). ba.timer(1.0, self._kill_puck) 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) ba.cameraflash(duration=10.0) self._update_scoreboard()
def _handle_score(self) -> None: """A point has been scored.""" # Our flag might stick around for a second or two # make sure it doesn't score again. assert self._flag is not None if self._flag.scored: return 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: if team.id == i: team.score += 7 # Tell all players to celebrate. for player in team.players: if player.actor: player.actor.handlemessage(ba.CelebrateMessage(2.0)) # If someone on this team was last to touch it, # give them points. assert self._flag is not None if (self._flag.last_holding_player and team == self._flag.last_holding_player.team): self.stats.player_scored(self._flag.last_holding_player, 50, big_message=True) # End the game if we won. if team.score >= self._score_to_win: self.end_game() ba.playsound(self._score_sound) ba.playsound(self._cheer_sound) assert self._flag self._flag.scored = True # Kill the flag (it'll respawn shortly). ba.timer(1.0, self._kill_flag) light = ba.newnode('light', attrs={ 'position': ba.getcollision().position, 'height_attenuated': False, 'color': (1, 0, 0) }) ba.animate(light, 'intensity', {0.0: 0, 0.5: 1, 1.0: 0}, loop=True) ba.timer(1.0, light.delete) ba.cameraflash(duration=10.0) self._update_scoreboard()
def _second_portal_handler(self, node=None, offset=(0, 0, 0)): """Checking a node before teleporting in the second portal.""" if node is None: node = ba.getcollision().opposingnode name = node.getname() if self.already_teleported.get(name): return velocity = node.velocity node.position = (self.first_position[0] + offset[0], self.first_position[1] + offset[1], self.first_position[2] + offset[2]) def velocity_wrapper(): if node: node.velocity = velocity ba.timer(0.01, velocity_wrapper) self.already_teleported[name] = True def wrapper(nodename): self.already_teleported[nodename] = False ba.timer(1, ba.Call(wrapper, name))
def _touch_handler(self): """The action handler of an item if it touches a target.""" node: ba.Node = ba.getcollision().opposingnode if node not in self.cured_nodes: node.handlemessage(ba.PowerupMessage(poweruptype='health')) self.cured_nodes.append(node)
def _handle_flag_player_collide(self) -> None: collision = ba.getcollision() try: flag = collision.sourcenode.getdelegate(ConquestFlag, True) player = collision.opposingnode.getdelegate(PlayerSpaz, True).getplayer( Player, True) except ba.NotFoundError: return assert flag.light if flag.team is not player.team: flag.team = player.team flag.light.color = player.team.color flag.node.color = player.team.color self.stats.player_scored(player, 10, screenmessage=False) ba.playsound(self._swipsound) self._flash_flag(flag) self._update_scores() # Respawn any players on this team that were in limbo due to the # lack of a flag for their team. for otherplayer in self.players: if (otherplayer.team is flag.team and otherplayer.actor is not None and not otherplayer.is_alive() and otherplayer.respawn_timer is None): self.spawn_player(otherplayer)
def _handle_flag_entered_base(self, team: Team) -> None: try: flag = ba.getcollision().opposingnode.getdelegate( StickyStormCTFFlag, True) except ba.NotFoundError: # Don't think this should logically ever happen. print( 'Error getting StickyStormCTFFlag in entering-base callback.') return if flag.team is team: team.home_flag_at_base = True # If the enemy flag is already here, score! if team.enemy_flag_at_base: self._score(team) else: team.enemy_flag_at_base = True if team.home_flag_at_base: # Award points to whoever was carrying the enemy flag. player = flag.last_player_to_hold if player and player.team is team: assert self.stats self.stats.player_scored(player, 50, big_message=True) # Update score and reset flags. self._score(team) # If the home-team flag isn't here, print a message to that effect. else: # Don't want slo-mo affecting this curtime = ba.time(ba.TimeType.BASE) if curtime - self._last_home_flag_notice_print_time > 5.0: self._last_home_flag_notice_print_time = curtime bpos = team.base_pos tval = ba.Lstr(resource='ownFlagAtYourBaseWarning') tnode = ba.newnode('text', attrs={ 'text': tval, 'in_world': True, 'scale': 0.013, 'color': (1, 1, 0, 1), 'h_align': 'center', 'position': (bpos[0], bpos[1] + 3.2, bpos[2]) }) ba.timer(5.1, tnode.delete) ba.animate(tnode, 'scale', { 0.0: 0, 0.2: 0.013, 4.8: 0.013, 5.0: 0 })
def _handle_splat(self) -> None: node = ba.getcollision().opposingnode if (node is not self.owner and ba.time() - self._last_sticky_sound_time > 1.0): self._last_sticky_sound_time = ba.time() assert self.node ba.playsound(BombFactory.get().sticky_impact_sound, 2.0, position=self.node.position)
def _handle_reached_end(self) -> None: spaz = ba.getcollision().opposingnode.getdelegate(SpazBot, True) if not spaz.is_alive(): return # Ignore bodies flying in. self._flawless = False pos = spaz.node.position ba.playsound(self._bad_guy_score_sound, position=pos) light = ba.newnode('light', attrs={ 'position': pos, 'radius': 0.5, 'color': (1, 0, 0) }) ba.animate(light, 'intensity', {0.0: 0, 0.1: 1, 0.5: 0}, loop=False) ba.timer(1.0, light.delete) spaz.handlemessage( ba.DieMessage(immediate=True, how=ba.DeathType.REACHED_GOAL)) if self._lives > 0: self._lives -= 1 if self._lives == 0: self._bots.stop_moving() self.continue_or_end_game() assert self._lives_text is not None assert self._lives_text.node self._lives_text.node.text = str(self._lives) delay = 0.0 def _safesetattr(node: ba.Node, attr: str, value: Any) -> None: if node: setattr(node, attr, value) for _i in range(4): ba.timer( delay, ba.Call(_safesetattr, self._lives_text.node, 'color', (1, 0, 0, 1.0))) assert self._lives_bg is not None assert self._lives_bg.node ba.timer( delay, ba.Call(_safesetattr, self._lives_bg.node, 'opacity', 0.5)) delay += 0.125 ba.timer( delay, ba.Call(_safesetattr, self._lives_text.node, 'color', (1.0, 1.0, 0.0, 1.0))) ba.timer( delay, ba.Call(_safesetattr, self._lives_bg.node, 'opacity', 1.0)) delay += 0.125 ba.timer( delay, ba.Call(_safesetattr, self._lives_text.node, 'color', (0.8, 0.8, 0.8, 1.0)))
def _handle_touching_own_flag(self, team: Team, connecting: bool) -> None: """Called when a player touches or stops touching their own team flag. We keep track of when each player is touching their own flag so we can award points when returned. """ player: Optional[Player] try: player = ba.getcollision().sourcenode.getdelegate( PlayerSpaz, True).getplayer(Player, True) except ba.NotFoundError: # This can happen if the player leaves but his corpse touches/etc. player = None if player: player.touching_own_flag += (1 if connecting else -1) # If return-time is zero, just kill it immediately.. otherwise keep # track of touches and count down. if float(self.flag_touch_return_time) <= 0.0: assert team.flag is not None if (connecting and not team.home_flag_at_base and team.flag.held_count == 0): self._award_players_touching_own_flag(team) ba.getcollision().opposingnode.handlemessage(ba.DieMessage()) # Takes a non-zero amount of time to return. else: if connecting: team.flag_return_touches += 1 if team.flag_return_touches == 1: team.touch_return_timer = ba.Timer( 0.1, call=ba.Call(self._touch_return_update, team), repeat=True) team.touch_return_timer_ticking = None else: team.flag_return_touches -= 1 if team.flag_return_touches == 0: team.touch_return_timer = None team.touch_return_timer_ticking = None if team.flag_return_touches < 0: ba.print_error('CTF flag_return_touches < 0')
def _handle_puck_player_collide(self) -> None: collision = ba.getcollision() try: puck = collision.sourcenode.getdelegate(Puck, True) player = collision.opposingnode.getdelegate(PlayerSpaz, True).getplayer( Player, True) except ba.NotFoundError: return puck.last_players_to_touch[player.team.id] = player
def handlemessage(self, actor: stdbomb.Bomb, msg: Any) -> bool: if isinstance(msg, ba.PickedUpMessage): if actor.node and actor.owner != msg.node: ba.playsound(ba.getsound("corkPop"), position=actor.node.position) actor.explode() return True elif isinstance(msg, SetStickyMessage): node = ba.getcollision().opposingnode self.on_sticky_gift(actor, node) return True return False
def _handle_reset_collide(self) -> None: # If we have a chosen one, ignore these. if self._get_chosen_one_player() is not None: return # Attempt to get a Player controlling a Spaz that we hit. try: player = ba.getcollision().opposingnode.getdelegate( PlayerSpaz, True).getplayer(Player, True) except ba.NotFoundError: return if player.is_alive(): self._set_chosen_one_player(player)
def _handle_impact(self) -> None: node = ba.getcollision().opposingnode # If we're an impact bomb and we came from this node, don't explode... # alternately if we're hitting another impact-bomb from the same # source, don't explode... # try: node_delegate = node.getdelegate(object) if node: if (self.bomb_type == 'impact' and (node is self.owner or (isinstance(node_delegate, Bomb) and node_delegate.bomb_type == 'impact' and node_delegate.owner is self.owner))): return self.handlemessage(ExplodeMessage())
def _handle_impact(self) -> None: node = ba.getcollision().opposingnode # If we're an impact bomb and we came from this node, don't explode. # (otherwise we blow up on our own head when jumping). # Alternately if we're hitting another impact-bomb from the same # source, don't explode. (can cause accidental explosions if rapidly # throwing/etc.) node_delegate = node.getdelegate(object) if node: if (self.bomb_type == 'impact' and (node is self.owner or (isinstance(node_delegate, Bomb) and node_delegate.bomb_type == 'impact' and node_delegate.owner is self.owner))): return self.handlemessage(ExplodeMessage())
def _handle_player_flag_region_collide(self, colliding: bool) -> None: try: player = ba.getcollision().opposingnode.getdelegate( PlayerSpaz, True).getplayer(Player, True) except ba.NotFoundError: return # Different parts of us can collide so a single value isn't enough # also don't count it if we're dead (flying heads shouldn't be able to # win the game :-) if colliding and player.is_alive(): player.time_at_flag += 1 else: player.time_at_flag = max(0, player.time_at_flag - 1) self._update_flag_state()
def _touch_handler(self) -> None: """The action handler of an item if it touches a target.""" node: ba.Node = ba.getcollision().opposingnode node_team: ba.Team = (node.getdelegate(PlayerSpaz).getplayer(ba.Player).team if hasattr(node.getdelegate(PlayerSpaz), 'getplayer') else None) owner_team = self.owner.getdelegate(PlayerSpaz).getplayer(ba.Player).team if (node.exists() and self.owner.exists() and self.item.exists() and node_team is not None and node_team != owner_team and node.getdelegate(PlayerSpaz).is_alive()): self.target = node self.node.delete() self.item.extra_acceleration = (0, 20, 0) self._move_item()
def _handle_flag_left_base(self, team: Team) -> None: cur_time = ba.time() try: flag = ba.getcollision().opposingnode.getdelegate(CTFFlag, True) except ba.NotFoundError: # This can happen if the flag stops touching us due to being # deleted; that's ok. return if flag.team is team: # Check times here to prevent too much flashing. if (team.last_flag_leave_time is None or cur_time - team.last_flag_leave_time > 3.0): ba.playsound(self._alarmsound, position=team.base_pos) self._flash_base(team) team.last_flag_leave_time = cur_time team.home_flag_at_base = False else: team.enemy_flag_at_base = False
def _handle_flag_left_base(self, team: Team) -> None: cur_time = ba.time() try: flag = CTFFlag.from_node(ba.getcollision().opposingnode) except ba.NodeNotFoundError: # We still get this call even if the flag stopped touching us # because it was deleted; that's ok. flag = None if not flag: return if flag.team is team: # Check times here to prevent too much flashing. if (team.last_flag_leave_time is None or cur_time - team.last_flag_leave_time > 3.0): ba.playsound(self._alarmsound, position=team.base_pos) self._flash_base(team) team.last_flag_leave_time = cur_time team.home_flag_at_base = False else: team.enemy_flag_at_base = False
def _second_portal_teleportation(self): """Teleportation of a node that entered the second portal.""" node = ba.getcollision().opposingnode name = node.getname() if self.already_teleported.get(name): return def wrapper(nodename): self.already_teleported[nodename] = False hold_node = node.hold_node node.handlemessage(ba.StandMessage(position=self.first_node.position)) if hold_node: self._second_portal_handler(hold_node, offset=(0, 1, 0)) node.hold_node = hold_node self.already_teleported[name] = True ba.timer(1, ba.Call(wrapper, name))
def _on_egg_player_collide(self) -> None: if self.has_ended(): return collision = ba.getcollision() # Be defensive here; we could be hitting the corpse of a player # who just left/etc. try: egg = collision.sourcenode.getdelegate(Egg, True) player = collision.opposingnode.getdelegate(PlayerSpaz, True).getplayer( Player, True) except ba.NotFoundError: return player.team.score += 1 # Displays a +1 (and adds to individual player score in # teams mode). self.stats.player_scored(player, 1, screenmessage=False) if self._max_eggs < 5: self._max_eggs += 1.0 elif self._max_eggs < 10: self._max_eggs += 0.5 elif self._max_eggs < 30: self._max_eggs += 0.3 self._update_scoreboard() ba.playsound(self._collect_sound, 0.5, position=egg.node.position) # Create a flash. light = ba.newnode('light', attrs={ 'position': egg.node.position, 'height_attenuated': False, 'radius': 0.1, 'color': (1, 1, 0) }) ba.animate(light, 'intensity', {0: 0, 0.1: 1.0, 0.2: 0}, loop=False) ba.timer(0.200, light.delete) egg.handlemessage(ba.DieMessage())
def handlemessage(self, msg: Any) -> Any: assert not self.expired if isinstance(msg, ba.PowerupAcceptMessage): factory = PowerupBoxFactory.get() assert self.node if self.poweruptype == 'health': ba.playsound(factory.health_powerup_sound, 3, position=self.node.position) ba.playsound(factory.powerup_sound, 3, position=self.node.position) self._powersgiven = True self.handlemessage(ba.DieMessage()) elif isinstance(msg, _TouchedMessage): if not self._powersgiven: node = ba.getcollision().opposingnode node.handlemessage( ba.PowerupMessage(self.poweruptype, sourcenode=self.node)) elif isinstance(msg, ba.DieMessage): if self.node: if msg.immediate: self.node.delete() else: ba.animate(self.node, 'model_scale', {0: 1, 0.1: 0}) ba.timer(0.1, self.node.delete) elif isinstance(msg, ba.OutOfBoundsMessage): self.handlemessage(ba.DieMessage()) elif isinstance(msg, ba.HitMessage): # Don't die on punches (that's annoying). if msg.hit_type != 'punch': self.handlemessage(ba.DieMessage()) else: return super().handlemessage(msg) return None
def _handle_impact(self, old_function): mebomb = get_mebomb(self.bomb_type) if mebomb is None: old_function(self) return mebomb.on_impact(self) node = ba.getcollision().opposingnode # if we're an impact bomb and we came from this node, don't explode... # alternately if we're hitting another impact-bomb from the same # source, don't explode... try: node_delegate = node.getdelegate(stdbomb.Bomb) except Exception: node_delegate = None if node: if (mebomb.is_impact and (node is self.owner or (isinstance(node_delegate, stdbomb.Bomb) and get_mebomb(node_delegate.bomb_type) is not None and get_mebomb(node_delegate.bomb_type).is_impact and node_delegate.owner is self.owner))): return self.handlemessage(ExplodeMessage())
def handlemessage(self, msg: Any) -> Any: assert not self.expired if isinstance(msg, ba.DieMessage): if self.node: self.node.delete() elif isinstance(msg, ExplodeHitMessage): node = ba.getcollision().opposingnode assert self.node nodepos = self.node.position mag = 2000.0 if self.blast_type == 'ice': mag *= 0.5 elif self.blast_type == 'land_mine': mag *= 2.5 elif self.blast_type == 'tnt': mag *= 2.0 node.handlemessage( ba.HitMessage(pos=nodepos, velocity=(0, 0, 0), magnitude=mag, hit_type=self.hit_type, hit_subtype=self.hit_subtype, radius=self.radius, source_player=ba.existing(self._source_player))) if self.blast_type == 'ice': ba.playsound(BombFactory.get().freeze_sound, 10, position=nodepos) node.handlemessage(ba.FreezeMessage()) else: return super().handlemessage(msg) return None
def _handle_base_collide(self, team: Team) -> None: try: player = ba.getcollision().opposingnode.getdelegate( PlayerSpaz, True).getplayer(Player, True) except ba.NotFoundError: return if not player.is_alive(): return # If its another team's player, they scored. player_team = player.team if player_team is not team: # Prevent multiple simultaneous scores. if ba.time() != self._last_score_time: self._last_score_time = ba.time() self.stats.player_scored(player, 50, big_message=True) ba.playsound(self._score_sound) self._flash_base(team) # Move all players on the scoring team back to their start # and add flashes of light so its noticeable. for player in player_team.players: if player.is_alive(): pos = player.node.position light = ba.newnode('light', attrs={ 'position': pos, 'color': player_team.color, 'height_attenuated': False, 'radius': 0.4 }) ba.timer(0.5, light.delete) ba.animate(light, 'intensity', { 0: 0, 0.1: 1.0, 0.5: 0 }) new_pos = (self.map.get_start_position(player_team.id)) light = ba.newnode('light', attrs={ 'position': new_pos, 'color': player_team.color, 'radius': 0.4, 'height_attenuated': False }) ba.timer(0.5, light.delete) ba.animate(light, 'intensity', { 0: 0, 0.1: 1.0, 0.5: 0 }) if player.actor: player.actor.handlemessage( ba.StandMessage(new_pos, random.uniform(0, 360))) # Have teammates celebrate. for player in player_team.players: if player.actor: player.actor.handlemessage(ba.CelebrateMessage(2.0)) player_team.score += 1 self._update_scoreboard() if player_team.score >= self._score_to_win: self.end_game()
def _handle_race_point_collide(self) -> None: # FIXME: Tidy this up. # pylint: disable=too-many-statements # pylint: disable=too-many-branches # pylint: disable=too-many-nested-blocks collision = ba.getcollision() try: region = collision.sourcenode.getdelegate(RaceRegion, True) player = collision.opposingnode.getdelegate(PlayerSpaz, True).getplayer( Player, True) except ba.NotFoundError: return last_region = player.last_region this_region = region.index if last_region != this_region: # If a player tries to skip regions, smite them. # Allow a one region leeway though (its plausible players can get # blown over a region, etc). if this_region > last_region + 2: if player.is_alive(): assert player.actor player.actor.handlemessage(ba.DieMessage()) ba.screenmessage(ba.Lstr( translate=('statements', 'Killing ${NAME} for' ' skipping part of the track!'), subs=[('${NAME}', player.getname(full=True))]), color=(1, 0, 0)) else: # If this player is in first, note that this is the # front-most race-point. if player.rank == 0: self._front_race_region = this_region player.last_region = this_region if last_region >= len(self._regions) - 2 and this_region == 0: team = player.team player.lap = min(self._laps, player.lap + 1) # In teams mode with all-must-finish on, the team lap # value is the min of all team players. # Otherwise its the max. if isinstance(self.session, ba.DualTeamSession ) and self._entire_team_must_finish: team.lap = min([p.lap for p in team.players]) else: team.lap = max([p.lap for p in team.players]) # A player is finishing. if player.lap == self._laps: # In teams mode, hand out points based on the order # players come in. if isinstance(self.session, ba.DualTeamSession): assert self._team_finish_pts is not None if self._team_finish_pts > 0: self.stats.player_scored(player, self._team_finish_pts, screenmessage=False) self._team_finish_pts -= 25 # Flash where the player is. self._flash_player(player, 1.0) player.finished = True assert player.actor player.actor.handlemessage( ba.DieMessage(immediate=True)) # Makes sure noone behind them passes them in rank # while finishing. player.distance = 9999.0 # If the whole team has finished the race. if team.lap == self._laps: ba.playsound(self._score_sound) player.team.finished = True assert self._timer is not None elapsed = ba.time() - self._timer.getstarttime() self._last_team_time = player.team.time = elapsed self._check_end_game() # Team has yet to finish. else: ba.playsound(self._swipsound) # They've just finished a lap but not the race. else: ba.playsound(self._swipsound) self._flash_player(player, 0.3) # Print their lap number over their head. try: assert isinstance(player.actor, PlayerSpaz) mathnode = ba.newnode('math', owner=player.actor.node, attrs={ 'input1': (0, 1.9, 0), 'operation': 'add' }) player.actor.node.connectattr( 'torso_position', mathnode, 'input2') tstr = ba.Lstr(resource='lapNumberText', subs=[('${CURRENT}', str(player.lap + 1)), ('${TOTAL}', str(self._laps)) ]) txtnode = ba.newnode('text', owner=mathnode, attrs={ 'text': tstr, 'in_world': True, 'color': (1, 1, 0, 1), 'scale': 0.015, 'h_align': 'center' }) mathnode.connectattr('output', txtnode, 'position') ba.animate(txtnode, 'scale', { 0.0: 0, 0.2: 0.019, 2.0: 0.019, 2.2: 0 }) ba.timer(2.3, mathnode.delete) except Exception: ba.print_exception('Error printing lap.')
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)