def register_player(self, player: ba.Player) -> None: """Register a player with this score-set.""" name = player.get_name() name_full = player.get_name(full=True) try: # If the player already exists, update his character and such as # it may have changed. self._player_records[name].associate_with_player(player) except Exception: # FIXME: Shouldn't use top level Exception catch for logic. # Should only have this as a fallback and always log it. self._player_records[name] = PlayerRecord(name, name_full, player, self)
def on_player_leave(self, player: ba.Player) -> None: ba.TeamGameActivity.on_player_leave(self, player) # A player leaving disqualifies the team if 'Entire Team Must Finish' # is on (otherwise in teams mode everyone could just leave except the # leading player to win). if (isinstance(self.session, ba.DualTeamSession) and self.settings.get('Entire Team Must Finish')): ba.screenmessage(ba.Lstr( translate=('statements', '${TEAM} is disqualified because ${PLAYER} left'), subs=[('${TEAM}', player.team.name), ('${PLAYER}', player.get_name(full=True))]), color=(1, 1, 0)) player.team.gamedata['finished'] = True player.team.gamedata['time'] = None player.team.gamedata['lap'] = 0 ba.playsound(ba.getsound('boo')) for otherplayer in player.team.players: otherplayer.gamedata['lap'] = 0 otherplayer.gamedata['finished'] = True try: if otherplayer.actor is not None: otherplayer.actor.handlemessage(ba.DieMessage()) except Exception: ba.print_exception('Error sending diemessages') # Defer so team/player lists will be updated. ba.pushcall(self._check_end_game)
def player_was_killed(self, player: ba.Player, killed: bool = False, killer: ba.Player = None) -> None: """Should be called when a player is killed.""" from ba._lang import Lstr name = player.get_name() prec = self._player_records[name] prec.streak = 0 if killed: prec.accum_killed_count += 1 prec.killed_count += 1 try: if killed and _ba.getactivity().announce_player_deaths: if killer == player: _ba.screenmessage(Lstr(resource='nameSuicideText', subs=[('${NAME}', name)]), top=True, color=player.color, image=player.get_icon()) elif killer is not None: if killer.team == player.team: _ba.screenmessage(Lstr(resource='nameBetrayedText', subs=[('${NAME}', killer.get_name()), ('${VICTIM}', name)]), top=True, color=killer.color, image=killer.get_icon()) else: _ba.screenmessage(Lstr(resource='nameKilledText', subs=[('${NAME}', killer.get_name()), ('${VICTIM}', name)]), top=True, color=killer.color, image=killer.get_icon()) else: _ba.screenmessage(Lstr(resource='nameDiedText', subs=[('${NAME}', name)]), top=True, color=player.color, image=player.get_icon()) except Exception: from ba import _error _error.print_exception('error announcing kill')
def on_player_join(self, player: ba.Player) -> None: # Don't allow joining after we start # (would enable leave/rejoin tomfoolery). if self.has_begun(): ba.screenmessage(ba.Lstr(resource='playerDelayedJoinText', subs=[('${PLAYER}', player.get_name(full=True))]), color=(0, 1, 0)) # For score purposes, mark them as having died right as the # game started. assert self._timer is not None PlayerData.get(player).death_time = self._timer.getstarttime() return self.spawn_player(player)
def on_player_join(self, player: ba.Player) -> None: # No longer allowing mid-game joiners here; too easy to exploit. if self.has_begun(): player.gamedata['lives'] = 0 player.gamedata['icons'] = [] # Make sure our team has survival seconds set if they're all dead # (otherwise blocked new ffa players would be considered 'still # alive' in score tallying). if self._get_total_team_lives( player.team ) == 0 and player.team.gamedata['survival_seconds'] is None: player.team.gamedata['survival_seconds'] = 0 ba.screenmessage(ba.Lstr(resource='playerDelayedJoinText', subs=[('${PLAYER}', player.get_name(full=True))]), color=(0, 1, 0)) return player.gamedata['lives'] = self.settings['Lives Per Player'] if self._solo_mode: player.gamedata['icons'] = [] player.team.gamedata['spawn_order'].append(player) self._update_solo_mode() else: # Create our icon and spawn. player.gamedata['icons'] = [ Icon(player, position=(0, 50), scale=0.8) ] if player.gamedata['lives'] > 0: self.spawn_player(player) # Don't waste time doing this until begin. if self.has_begun(): self._update_icons()
def on_player_leave(self, player: ba.Player) -> None: """Called when a previously-accepted ba.Player leaves the session.""" # pylint: disable=too-many-statements # pylint: disable=too-many-branches # pylint: disable=cyclic-import from ba._freeforallsession import FreeForAllSession from ba._lang import Lstr from ba import _error # Remove them from the game rosters. if player in self.players: _ba.playsound(_ba.getsound('playerLeft')) team: Optional[ba.Team] # The player will have no team if they are still in the lobby. try: team = player.team except _error.TeamNotFoundError: team = None activity = self._activity_weak() # If he had no team, he's in the lobby. # If we have a current activity with a lobby, ask them to # remove him. if team is None: with _ba.Context(self): try: self.lobby.remove_chooser(player) except Exception: _error.print_exception( 'Error in Lobby.remove_chooser()') # *If* they were actually in the game, announce their departure. if team is not None: _ba.screenmessage( Lstr(resource='playerLeftText', subs=[('${PLAYER}', player.get_name(full=True))])) # Remove him from his team and session lists. # (he may not be on the team list since player are re-added to # team lists every activity) if team is not None and player in team.players: # Testing; can remove this eventually. if isinstance(self, FreeForAllSession): if len(team.players) != 1: _error.print_error('expected 1 player in FFA team') team.players.remove(player) # Remove player from any current activity. if activity is not None and player in activity.players: activity.players.remove(player) # Run the activity callback unless its been expired. if not activity.is_expired(): try: with _ba.Context(activity): activity.on_player_leave(player) except Exception: _error.print_exception( 'exception in on_player_leave for activity', activity) else: _error.print_error('expired activity in on_player_leave;' " shouldn't happen") player.set_activity(None) player.set_node(None) # Reset the player; this will remove its actor-ref and clear # its calls/etc try: with _ba.Context(activity): player.reset() except Exception: _error.print_exception( 'exception in player.reset in' ' on_player_leave for player', player) # If we're a non-team session, remove the player's team completely. if not self._use_teams and team is not None: # If the team's in an activity, call its on_team_leave # callback. if activity is not None and team in activity.teams: activity.teams.remove(team) if not activity.is_expired(): try: with _ba.Context(activity): activity.on_team_leave(team) except Exception: _error.print_exception( 'exception in on_team_leave for activity', activity) else: _error.print_error( 'expired activity in on_player_leave p2' "; shouldn't happen") # Clear the team's game-data (so dying stuff will # have proper context). try: with _ba.Context(activity): team.reset_gamedata() except Exception: _error.print_exception( 'exception clearing gamedata for team:', team, 'for player:', player, 'in activity:', activity) # Remove the team from the session. self.teams.remove(team) try: with _ba.Context(self): self.on_team_leave(team) except Exception: _error.print_exception( 'exception in on_team_leave for session', self) # Clear the team's session-data (so dying stuff will # have proper context). try: with _ba.Context(self): team.reset_sessiondata() except Exception: _error.print_exception( 'exception clearing sessiondata for team:', team, 'in session:', self) # Now remove them from the session list. self.players.remove(player) else: print('ERROR: Session.on_player_leave called' ' for player not in our list.')
def do_hit_at_position(self, pos: Sequence[float], player: ba.Player) -> bool: """Handle a bomb hit at the given position.""" # pylint: disable=too-many-statements from bastd.actor import popuptext activity = self.activity # Ignore hits if the game is over or if we've already been hit if activity.has_ended() or self._hit or not self._nodes: return False diff = (ba.Vec3(pos) - self._position) # Disregard Y difference. Our target point probably isn't exactly # on the ground anyway. diff[1] = 0.0 dist = diff.length() bullseye = False if dist <= self._r3 + self._rfudge: # Inform our activity that we were hit self._hit = True activity.handlemessage(self.TargetHitMessage()) keys: Dict[float, Sequence[float]] = { 0.0: (1.0, 0.0, 0.0), 0.049: (1.0, 0.0, 0.0), 0.05: (1.0, 1.0, 1.0), 0.1: (0.0, 1.0, 0.0) } cdull = (0.3, 0.3, 0.3) popupcolor: Sequence[float] if dist <= self._r1 + self._rfudge: bullseye = True self._nodes[1].color = cdull self._nodes[2].color = cdull ba.animate_array(self._nodes[0], 'color', 3, keys, loop=True) popupscale = 1.8 popupcolor = (1, 1, 0, 1) streak = player.gamedata['streak'] points = 10 + min(20, streak * 2) ba.playsound(ba.getsound('bellHigh')) if streak > 0: ba.playsound( ba.getsound( 'orchestraHit4' if streak > 3 else 'orchestraHit3' if streak > 2 else 'orchestraHit2' if streak > 1 else 'orchestraHit')) elif dist <= self._r2 + self._rfudge: self._nodes[0].color = cdull self._nodes[2].color = cdull ba.animate_array(self._nodes[1], 'color', 3, keys, loop=True) popupscale = 1.25 popupcolor = (1, 0.5, 0.2, 1) points = 4 ba.playsound(ba.getsound('bellMed')) else: self._nodes[0].color = cdull self._nodes[1].color = cdull ba.animate_array(self._nodes[2], 'color', 3, keys, loop=True) popupscale = 1.0 popupcolor = (0.8, 0.3, 0.3, 1) points = 2 ba.playsound(ba.getsound('bellLow')) # Award points/etc.. (technically should probably leave this up # to the activity). popupstr = '+' + str(points) # If there's more than 1 player in the game, include their # names and colors so they know who got the hit. if len(activity.players) > 1: popupcolor = ba.safecolor(player.color, target_intensity=0.75) popupstr += ' ' + player.get_name() popuptext.PopupText(popupstr, position=self._position, color=popupcolor, scale=popupscale).autoretain() # Give this player's team points and update the score-board. player.team.gamedata['score'] += points assert isinstance(activity, TargetPracticeGame) activity.update_scoreboard() # Also give this individual player points # (only applies in teams mode). assert activity.stats is not None activity.stats.player_scored(player, points, showpoints=False, screenmessage=False) ba.animate_array(self._nodes[0], 'size', 1, { 0.8: self._nodes[0].size, 1.0: [0.0] }) ba.animate_array(self._nodes[1], 'size', 1, { 0.85: self._nodes[1].size, 1.05: [0.0] }) ba.animate_array(self._nodes[2], 'size', 1, { 0.9: self._nodes[2].size, 1.1: [0.0] }) ba.timer(1.1, ba.Call(self.handlemessage, ba.DieMessage())) return bullseye
def __init__(self, player: ba.Player, respawn_time: float): """ Instantiate with a given ba.Player and respawn_time (in seconds) """ self._visible = True on_right, offs_extra, respawn_icons = self._get_context(player) try: mask_tex = (player.team.gamedata['_spaz_respawn_icons_mask_tex']) except Exception: mask_tex = player.team.gamedata['_spaz_respawn_icons_mask_tex'] = ( ba.gettexture('characterIconMask')) # Now find the first unused slot and use that. index = 0 while (index in respawn_icons and respawn_icons[index]() is not None and respawn_icons[index]().visible): index += 1 respawn_icons[index] = weakref.ref(self) offs = offs_extra + index * -53 icon = player.get_icon() texture = icon['texture'] h_offs = -10 ipos = (-40 - h_offs if on_right else 40 + h_offs, -180 + offs) self._image: Optional[ba.NodeActor] = ba.NodeActor( ba.newnode('image', attrs={ 'texture': texture, 'tint_texture': icon['tint_texture'], 'tint_color': icon['tint_color'], 'tint2_color': icon['tint2_color'], 'mask_texture': mask_tex, 'position': ipos, 'scale': (32, 32), 'opacity': 1.0, 'absolute_scale': True, 'attach': 'topRight' if on_right else 'topLeft' })) assert self._image.node ba.animate(self._image.node, 'opacity', {0.0: 0, 0.2: 0.7}) npos = (-40 - h_offs if on_right else 40 + h_offs, -205 + 49 + offs) self._name: Optional[ba.NodeActor] = ba.NodeActor( ba.newnode('text', attrs={ 'v_attach': 'top', 'h_attach': 'right' if on_right else 'left', 'text': ba.Lstr(value=player.get_name()), 'maxwidth': 100, 'h_align': 'center', 'v_align': 'center', 'shadow': 1.0, 'flatness': 1.0, 'color': ba.safecolor(icon['tint_color']), 'scale': 0.5, 'position': npos })) assert self._name.node ba.animate(self._name.node, 'scale', {0: 0, 0.1: 0.5}) tpos = (-60 - h_offs if on_right else 60 + h_offs, -192 + offs) self._text: Optional[ba.NodeActor] = ba.NodeActor( ba.newnode('text', attrs={ 'position': tpos, 'h_attach': 'right' if on_right else 'left', 'h_align': 'right' if on_right else 'left', 'scale': 0.9, 'shadow': 0.5, 'flatness': 0.5, 'v_attach': 'top', 'color': ba.safecolor(icon['tint_color']), 'text': '' })) assert self._text.node ba.animate(self._text.node, 'scale', {0: 0, 0.1: 0.9}) self._respawn_time = ba.time() + respawn_time self._update() self._timer: Optional[ba.Timer] = ba.Timer(1.0, ba.WeakCall(self._update), repeat=True)
def player_scored(self, player: ba.Player, base_points: int = 1, target: Sequence[float] = None, kill: bool = False, victim_player: ba.Player = None, scale: float = 1.0, color: Sequence[float] = None, title: Union[str, ba.Lstr] = None, screenmessage: bool = True, display: bool = True, importance: int = 1, showpoints: bool = True, big_message: bool = False) -> int: """Register a score for the player. Return value is actual score with multipliers and such factored in. """ # FIXME: Tidy this up. # pylint: disable=cyclic-import # pylint: disable=too-many-branches # pylint: disable=too-many-locals # pylint: disable=too-many-statements from bastd.actor.popuptext import PopupText from ba import _math from ba._gameactivity import GameActivity from ba._lang import Lstr del victim_player # Currently unused. name = player.get_name() s_player = self._player_records[name] if kill: s_player.submit_kill(showpoints=showpoints) display_color: Sequence[float] = (1.0, 1.0, 1.0, 1.0) if color is not None: display_color = color elif importance != 1: display_color = (1.0, 1.0, 0.4, 1.0) points = base_points # If they want a big announcement, throw a zoom-text up there. if display and big_message: try: assert self._activity is not None activity = self._activity() if isinstance(activity, GameActivity): name_full = player.get_name(full=True, icon=False) activity.show_zoom_message( Lstr(resource='nameScoresText', subs=[('${NAME}', name_full)]), color=_math.normalized_color(player.team.color)) except Exception: print_exception('error showing big_message') # If we currently have a actor, pop up a score over it. if display and showpoints: our_pos = player.node.position if player.node else None if our_pos is not None: if target is None: target = our_pos # If display-pos is *way* lower than us, raise it up # (so we can still see scores from dudes that fell off cliffs). display_pos = (target[0], max(target[1], our_pos[1] - 2.0), min(target[2], our_pos[2] + 2.0)) activity = self.getactivity() if activity is not None: if title is not None: sval = Lstr(value='+${A} ${B}', subs=[('${A}', str(points)), ('${B}', title)]) else: sval = Lstr(value='+${A}', subs=[('${A}', str(points))]) PopupText(sval, color=display_color, scale=1.2 * scale, position=display_pos).autoretain() # Tally kills. if kill: s_player.accum_kill_count += 1 s_player.kill_count += 1 # Report non-kill scorings. try: if screenmessage and not kill: _ba.screenmessage(Lstr(resource='nameScoresText', subs=[('${NAME}', name)]), top=True, color=player.color, image=player.get_icon()) except Exception: print_exception('error announcing score') s_player.score += points s_player.accumscore += points # Inform a running game of the score. if points != 0: activity = self._activity() if self._activity is not None else None if activity is not None: activity.handlemessage(PlayerScoredMessage(score=points)) return points
def player_got_hit(self, player: ba.Player) -> None: """Call this when a player got hit.""" s_player = self._player_records[player.get_name()] s_player.streak = 0
def __init__(self, player: ba.Player, position: Tuple[float, float], scale: float, show_lives: bool = True, show_death: bool = True, name_scale: float = 1.0, name_maxwidth: float = 115.0, flatness: float = 1.0, shadow: float = 1.0): super().__init__() self._player = player self._show_lives = show_lives self._show_death = show_death self._name_scale = name_scale self._outline_tex = ba.gettexture('characterIconMask') icon = player.get_icon() self.node = ba.newnode('image', delegate=self, attrs={ 'texture': icon['texture'], 'tint_texture': icon['tint_texture'], 'tint_color': icon['tint_color'], 'vr_depth': 400, 'tint2_color': icon['tint2_color'], 'mask_texture': self._outline_tex, 'opacity': 1.0, 'absolute_scale': True, 'attach': 'bottomCenter' }) self._name_text = ba.newnode( 'text', owner=self.node, attrs={ 'text': ba.Lstr(value=player.get_name()), 'color': ba.safecolor(player.team.color), 'h_align': 'center', 'v_align': 'center', 'vr_depth': 410, 'maxwidth': name_maxwidth, 'shadow': shadow, 'flatness': flatness, 'h_attach': 'center', 'v_attach': 'bottom' }) if self._show_lives: self._lives_text = ba.newnode('text', owner=self.node, attrs={ 'text': 'x0', 'color': (1, 1, 0.5), 'h_align': 'left', 'vr_depth': 430, 'shadow': 1.0, 'flatness': 1.0, 'h_attach': 'center', 'v_attach': 'bottom' }) self.set_position_and_scale(position, scale)