Source code for bascenev1._teamgame

# Released under the MIT License. See LICENSE for details.
#
"""Functionality related to team games."""

from __future__ import annotations

import logging
from typing import TYPE_CHECKING, override

import babase

import _bascenev1
from bascenev1._freeforallsession import FreeForAllSession
from bascenev1._gameactivity import GameActivity
from bascenev1._gameresults import GameResults
from bascenev1._dualteamsession import DualTeamSession

if TYPE_CHECKING:
    from typing import Any, Sequence

    from bascenev1lib.actor.playerspaz import PlayerSpaz

    import bascenev1


# Note: Need to suppress an undefined variable here because our pylint
# plugin clears type-arg declarations (which we don't require to be
# present at runtime) but keeps parent type-args (which we sometimes use
# at runtime).


[docs] class TeamGameActivity[PlayerT: bascenev1.Player, TeamT: bascenev1.Team]( GameActivity[PlayerT, TeamT] # pylint: disable=undefined-variable ): """Base class for teams and free-for-all mode games. (Free-for-all is essentially just a special case where every player has their own team) """
[docs] @override @classmethod def supports_session_type( cls, sessiontype: type[bascenev1.Session] ) -> bool: # By default, team games support dual-teams and ffa. return issubclass(sessiontype, DualTeamSession) or issubclass( sessiontype, FreeForAllSession )
def __init__(self, settings: dict): super().__init__(settings) # By default we don't show kill-points in free-for-all sessions # (there's usually some activity-specific score and we don't # wanna confuse things). if isinstance(self.session, FreeForAllSession): self.show_kill_points = False
[docs] @override def on_transition_in(self) -> None: # pylint: disable=cyclic-import from bascenev1._coopsession import CoopSession from bascenev1lib.actor.controlsguide import ControlsGuide super().on_transition_in() # On the first game, show the controls UI momentarily (unless # we're being run in co-op mode, in which case we leave it up to # them). if not isinstance(self.session, CoopSession) and getattr( self, 'show_controls_guide', True ): attrname = '_have_shown_ctrl_help_overlay' if not getattr(self.session, attrname, False): delay = 4.0 lifespan = 10.0 if self.slow_motion: lifespan *= 0.3 ControlsGuide( delay=delay, lifespan=lifespan, scale=0.8, position=(380, 200), bright=True, ).autoretain() setattr(self.session, attrname, True)
[docs] @override def on_begin(self) -> None: super().on_begin() try: # Award a few (classic) achievements. if isinstance(self.session, FreeForAllSession): if len(self.players) >= 2: if babase.app.classic is not None: babase.app.classic.ach.award_local_achievement( 'Free Loader' ) elif isinstance(self.session, DualTeamSession): if len(self.players) >= 4: if babase.app.classic is not None: babase.app.classic.ach.award_local_achievement( 'Team Player' ) except Exception: logging.exception('Error in on_begin.')
[docs] @override def spawn_player_spaz( self, player: PlayerT, position: Sequence[float] | None = None, angle: float | None = None, ) -> PlayerSpaz: """Override to spawn and wire up a standard :class:`~bascenev1lib.actor.playerspaz.PlayerSpaz` for a :class:`~bascenev1.Player`. If position or angle is not supplied, a default will be chosen based on the :class:`~bascenev1.Player` and their :class:`~bascenev1.Team`. """ if position is None: # In teams-mode get our team-start-location. if isinstance(self.session, DualTeamSession): position = self.map.get_start_position(player.team.id) else: # Otherwise do free-for-all spawn locations. position = self.map.get_ffa_start_position(self.players) return super().spawn_player_spaz(player, position, angle)
# FIXME: need to unify these arguments with GameActivity.end()
[docs] def end( # type: ignore self, results: Any = None, announce_winning_team: bool = True, announce_delay: float = 0.1, force: bool = False, ) -> None: """ End the game and announce the single winning team unless 'announce_winning_team' is False. (for results without a single most-important winner). """ # pylint: disable=arguments-renamed from bascenev1._coopsession import CoopSession from bascenev1._multiteamsession import MultiTeamSession # Announce win (but only for the first finish() call) # (also don't announce in co-op sessions; we leave that up to them). session = self.session if not isinstance(session, CoopSession): do_announce = not self.has_ended() super().end(results, delay=2.0 + announce_delay, force=force) # Need to do this *after* end end call so that results is valid. assert isinstance(results, GameResults) if do_announce and isinstance(session, MultiTeamSession): session.announce_game_results( self, results, delay=announce_delay, announce_winning_team=announce_winning_team, ) # For co-op we just pass this up the chain with a delay added # (in most cases). Team games expect a delay for the announce # portion in teams/ffa mode so this keeps it consistent. else: # don't want delay on restarts.. if ( isinstance(results, dict) and 'outcome' in results and results['outcome'] == 'restart' ): delay = 0.0 else: delay = 2.0 _bascenev1.timer(0.1, _bascenev1.getsound('boxingBell').play) super().end(results, delay=delay, force=force)
# Docs-generation hack; import some stuff that we likely only forward-declared # in our actual source code so that docs tools can find it. from typing import (Coroutine, Any, Literal, Callable, Generator, Awaitable, Sequence, Self) import asyncio from concurrent.futures import Future from pathlib import Path from enum import Enum