Source code for bascenev1._coopgame

# Released under the MIT License. See LICENSE for details.
#
"""Functionality related to co-op games."""
from __future__ import annotations

import logging
from typing import TYPE_CHECKING, TypeVar, override

import babase

import _bascenev1
from bascenev1._gameactivity import GameActivity

if TYPE_CHECKING:
    from typing import Sequence

    from bascenev1lib.actor.playerspaz import PlayerSpaz

    import bascenev1

PlayerT = TypeVar('PlayerT', bound='bascenev1.Player')
TeamT = TypeVar('TeamT', bound='bascenev1.Team')


class CoopGameActivity(GameActivity[PlayerT, TeamT]):
    """Base class for cooperative-mode games.

    Category: **Gameplay Classes**
    """

    # We can assume our session is a CoopSession.
    session: bascenev1.CoopSession

[docs] @override @classmethod def supports_session_type( cls, sessiontype: type[bascenev1.Session] ) -> bool: from bascenev1._coopsession import CoopSession return issubclass(sessiontype, CoopSession)
def __init__(self, settings: dict): super().__init__(settings) # Cache these for efficiency. self._achievements_awarded: set[str] = set() self._life_warning_beep: bascenev1.Actor | None = None self._life_warning_beep_timer: bascenev1.Timer | None = None self._warn_beeps_sound = _bascenev1.getsound('warnBeeps')
[docs] @override def on_begin(self) -> None: super().on_begin() # Show achievements remaining. env = babase.app.env if not (env.demo or env.arcade): _bascenev1.timer( 3.8, babase.WeakCall(self._show_remaining_achievements) ) # Preload achievement images in case we get some. _bascenev1.timer(2.0, babase.WeakCall(self._preload_achievements))
# FIXME: this is now redundant with activityutils.getscoreconfig(); # need to kill this.
[docs] def get_score_type(self) -> str: """ Return the score unit this co-op game uses ('point', 'seconds', etc.) """ return 'points'
def _get_coop_level_name(self) -> str: assert self.session.campaign is not None return self.session.campaign.name + ':' + str(self.settings_raw['name'])
[docs] def celebrate(self, duration: float) -> None: """Tells all existing player-controlled characters to celebrate. Can be useful in co-op games when the good guys score or complete a wave. duration is given in seconds. """ from bascenev1._messages import CelebrateMessage for player in self.players: if player.actor: player.actor.handlemessage(CelebrateMessage(duration))
def _preload_achievements(self) -> None: assert babase.app.classic is not None achievements = babase.app.classic.ach.achievements_for_coop_level( self._get_coop_level_name() ) for ach in achievements: ach.get_icon_texture(True) def _show_remaining_achievements(self) -> None: # pylint: disable=cyclic-import from bascenev1lib.actor.text import Text assert babase.app.classic is not None ts_h_offs = 30 v_offs = -200 achievements = [ a for a in babase.app.classic.ach.achievements_for_coop_level( self._get_coop_level_name() ) if not a.complete ] vrmode = babase.app.env.vr if achievements: Text( babase.Lstr(resource='achievementsRemainingText'), host_only=True, position=(ts_h_offs - 10 + 40, v_offs - 10), transition=Text.Transition.FADE_IN, scale=1.1, h_attach=Text.HAttach.LEFT, v_attach=Text.VAttach.TOP, color=(1, 1, 1.2, 1) if vrmode else (0.8, 0.8, 1.0, 1.0), flatness=1.0 if vrmode else 0.6, shadow=1.0 if vrmode else 0.5, transition_delay=0.0, transition_out_delay=1.3 if self.slow_motion else 4.0, ).autoretain() hval = 70 vval = -50 tdelay = 0.0 for ach in achievements: tdelay += 0.05 ach.create_display( hval + 40, vval + v_offs, 0 + tdelay, outdelay=1.3 if self.slow_motion else 4.0, style='in_game', ) vval -= 55
[docs] @override def spawn_player_spaz( self, player: PlayerT, position: Sequence[float] = (0.0, 0.0, 0.0), angle: float | None = None, ) -> PlayerSpaz: """Spawn and wire up a standard player spaz.""" spaz = super().spawn_player_spaz(player, position, angle) # Deaths are noteworthy in co-op games. spaz.play_big_death_sound = True return spaz
def _award_achievement( self, achievement_name: str, sound: bool = True ) -> None: """Award an achievement. Returns True if a banner will be shown; False otherwise """ classic = babase.app.classic plus = babase.app.plus if classic is None or plus is None: logging.warning( '_award_achievement is a no-op without classic and plus.' ) return if achievement_name in self._achievements_awarded: return ach = classic.ach.get_achievement(achievement_name) # If we're in the easy campaign and this achievement is hard-mode-only, # ignore it. try: campaign = self.session.campaign assert campaign is not None if ach.hard_mode_only and campaign.name == 'Easy': return except Exception: logging.exception('Error in _award_achievement.') # If we haven't awarded this one, check to see if we've got it. # If not, set it through the game service *and* add a transaction # for it. if not ach.complete: self._achievements_awarded.add(achievement_name) # Report new achievements to the game-service. plus.report_achievement(achievement_name) # ...and to our account. plus.add_v1_account_transaction( {'type': 'ACHIEVEMENT', 'name': achievement_name} ) # Now bring up a celebration banner. ach.announce_completion(sound=sound)
[docs] def fade_to_red(self) -> None: """Fade the screen to red; (such as when the good guys have lost).""" from bascenev1 import _gameutils c_existing = self.globalsnode.tint cnode = _bascenev1.newnode( 'combine', attrs={ 'input0': c_existing[0], 'input1': c_existing[1], 'input2': c_existing[2], 'size': 3, }, ) _gameutils.animate(cnode, 'input1', {0: c_existing[1], 2.0: 0}) _gameutils.animate(cnode, 'input2', {0: c_existing[2], 2.0: 0}) cnode.connectattr('output', self.globalsnode, 'tint')
[docs] def setup_low_life_warning_sound(self) -> None: """Set up a beeping noise to play when any players are near death.""" self._life_warning_beep = None self._life_warning_beep_timer = _bascenev1.Timer( 1.0, babase.WeakCall(self._update_life_warning), repeat=True )
def _update_life_warning(self) -> None: # Beep continuously if anyone is close to death. should_beep = False for player in self.players: if player.is_alive(): # FIXME: Should abstract this instead of # reading hitpoints directly. if getattr(player.actor, 'hitpoints', 999) < 200: should_beep = True break if should_beep and self._life_warning_beep is None: from bascenev1._nodeactor import NodeActor self._life_warning_beep = NodeActor( _bascenev1.newnode( 'sound', attrs={ 'sound': self._warn_beeps_sound, 'positional': False, 'loop': True, }, ) ) if self._life_warning_beep is not None and not should_beep: self._life_warning_beep = None