Source code for bascenev1lib.activity.freeforallvictory

# Released under the MIT License. See LICENSE for details.
#
"""Functionality related to the final screen in free-for-all games."""

from __future__ import annotations

from typing import TYPE_CHECKING, override

import bascenev1 as bs

from bascenev1lib.activity.multiteamscore import MultiTeamScoreScreenActivity

if TYPE_CHECKING:
    from typing import Any


[docs] class FreeForAllVictoryScoreScreenActivity(MultiTeamScoreScreenActivity): """Score screen shown at after free-for-all rounds.""" def __init__(self, settings: dict): super().__init__(settings=settings) # Keep prev activity alive while we fade in. self.transition_time = 0.5 self._cymbal_sound = bs.getsound('cymbal')
[docs] @override def on_begin(self) -> None: # pylint: disable=too-many-locals # pylint: disable=too-many-statements from bascenev1lib.actor.text import Text from bascenev1lib.actor.image import Image bs.set_analytics_screen('FreeForAll Score Screen') super().on_begin() y_base = 100.0 ts_h_offs = -305.0 tdelay = 1.0 scale = 1.2 spacing = 37.0 # We include name and previous score in the sort to reduce the amount # of random jumping around the list we do in cases of ties. player_order_prev = list(self.players) player_order_prev.sort( reverse=True, key=lambda p: ( p.team.sessionteam.customdata['previous_score'], p.getname(full=True), ), ) player_order = list(self.players) player_order.sort( reverse=True, key=lambda p: ( p.team.sessionteam.customdata['score'], p.team.sessionteam.customdata['score'], p.getname(full=True), ), ) v_offs = -74.0 + spacing * len(player_order_prev) * 0.5 delay1 = 1.3 + 0.1 delay2 = 2.9 + 0.1 delay3 = 2.9 + 0.1 order_change = player_order != player_order_prev if order_change: delay3 += 1.5 bs.timer(0.3, self._score_display_sound.play) results = self.settings_raw['results'] assert isinstance(results, bs.GameResults) self.show_player_scores( delay=0.001, results=results, scale=1.2, x_offset=-110.0 ) sound_times: set[float] = set() def _scoretxt( text: str, x_offs: float, y_offs: float, highlight: bool, delay: float, extrascale: float, flash: bool = False, ) -> Text: # pylint: disable=too-many-positional-arguments return Text( text, position=( ts_h_offs + x_offs * scale, y_base + (y_offs + v_offs + 2.0) * scale, ), scale=scale * extrascale, color=( (1.0, 0.7, 0.3, 1.0) if highlight else (0.7, 0.7, 0.7, 0.7) ), h_align=Text.HAlign.RIGHT, transition=Text.Transition.IN_LEFT, transition_delay=tdelay + delay, flash=flash, ).autoretain() v_offs -= spacing slide_amt = 0.0 transtime = 0.250 transtime2 = 0.250 session = self.session assert isinstance(session, bs.FreeForAllSession) title = Text( bs.Lstr( resource='firstToSeriesText', subs=[('${COUNT}', str(session.get_ffa_series_length()))], ), scale=1.05 * scale, position=( ts_h_offs - 0.0 * scale, y_base + (v_offs + 50.0) * scale, ), h_align=Text.HAlign.CENTER, color=(0.5, 0.5, 0.5, 0.5), transition=Text.Transition.IN_LEFT, transition_delay=tdelay, ).autoretain() v_offs -= 25 v_offs_start = v_offs bs.timer( tdelay + delay3, bs.WeakCall( self._safe_animate, title.position_combine, 'input0', { 0.0: ts_h_offs - 0.0 * scale, transtime2: ts_h_offs - (0.0 + slide_amt) * scale, }, ), ) for i, player in enumerate(player_order_prev): v_offs_2 = v_offs_start - spacing * (player_order.index(player)) bs.timer(tdelay + 0.3, self._score_display_sound_small.play) if order_change: bs.timer(tdelay + delay2 + 0.1, self._cymbal_sound.play) img = Image( player.get_icon(), position=( ts_h_offs - 72.0 * scale, y_base + (v_offs + 15.0) * scale, ), scale=(30.0 * scale, 30.0 * scale), transition=Image.Transition.IN_LEFT, transition_delay=tdelay, ).autoretain() bs.timer( tdelay + delay2, bs.WeakCall( self._safe_animate, img.position_combine, 'input1', { 0: y_base + (v_offs + 15.0) * scale, transtime: y_base + (v_offs_2 + 15.0) * scale, }, ), ) bs.timer( tdelay + delay3, bs.WeakCall( self._safe_animate, img.position_combine, 'input0', { 0: ts_h_offs - 72.0 * scale, transtime2: ts_h_offs - (72.0 + slide_amt) * scale, }, ), ) txt = Text( bs.Lstr(value=player.getname(full=True)), maxwidth=130.0, scale=0.75 * scale, position=( ts_h_offs - 50.0 * scale, y_base + (v_offs + 15.0) * scale, ), h_align=Text.HAlign.LEFT, v_align=Text.VAlign.CENTER, color=bs.safecolor(player.team.color + (1,)), transition=Text.Transition.IN_LEFT, transition_delay=tdelay, ).autoretain() bs.timer( tdelay + delay2, bs.WeakCall( self._safe_animate, txt.position_combine, 'input1', { 0: y_base + (v_offs + 15.0) * scale, transtime: y_base + (v_offs_2 + 15.0) * scale, }, ), ) bs.timer( tdelay + delay3, bs.WeakCall( self._safe_animate, txt.position_combine, 'input0', { 0: ts_h_offs - 50.0 * scale, transtime2: ts_h_offs - (50.0 + slide_amt) * scale, }, ), ) txt_num = Text( '#' + str(i + 1), scale=0.55 * scale, position=( ts_h_offs - 95.0 * scale, y_base + (v_offs + 8.0) * scale, ), h_align=Text.HAlign.RIGHT, color=(0.6, 0.6, 0.6, 0.6), transition=Text.Transition.IN_LEFT, transition_delay=tdelay, ).autoretain() bs.timer( tdelay + delay3, bs.WeakCall( self._safe_animate, txt_num.position_combine, 'input0', { 0: ts_h_offs - 95.0 * scale, transtime2: ts_h_offs - (95.0 + slide_amt) * scale, }, ), ) s_txt = _scoretxt( str(player.team.sessionteam.customdata['previous_score']), 80, 0, False, 0, 1.0, ) bs.timer( tdelay + delay2, bs.WeakCall( self._safe_animate, s_txt.position_combine, 'input1', { 0: y_base + (v_offs + 2.0) * scale, transtime: y_base + (v_offs_2 + 2.0) * scale, }, ), ) bs.timer( tdelay + delay3, bs.WeakCall( self._safe_animate, s_txt.position_combine, 'input0', { 0: ts_h_offs + 80.0 * scale, transtime2: ts_h_offs + (80.0 - slide_amt) * scale, }, ), ) score_change = ( player.team.sessionteam.customdata['score'] - player.team.sessionteam.customdata['previous_score'] ) if score_change > 0: xval = 113 yval = 3.0 s_txt_2 = _scoretxt( '+' + str(score_change), xval, yval, True, 0, 0.7, flash=True, ) bs.timer( tdelay + delay2, bs.WeakCall( self._safe_animate, s_txt_2.position_combine, 'input1', { 0: y_base + (v_offs + yval + 2.0) * scale, transtime: y_base + (v_offs_2 + yval + 2.0) * scale, }, ), ) bs.timer( tdelay + delay3, bs.WeakCall( self._safe_animate, s_txt_2.position_combine, 'input0', { 0: ts_h_offs + xval * scale, transtime2: ts_h_offs + (xval - slide_amt) * scale, }, ), ) def _safesetattr( node: bs.Node | None, attr: str, value: Any ) -> None: if node: setattr(node, attr, value) bs.timer( tdelay + delay1, bs.Call(_safesetattr, s_txt.node, 'color', (1, 1, 1, 1)), ) for j in range(score_change): bs.timer( (tdelay + delay1 + 0.15 * j), bs.Call( _safesetattr, s_txt.node, 'text', str( player.team.sessionteam.customdata[ 'previous_score' ] + j + 1 ), ), ) tfin = tdelay + delay1 + 0.15 * j if tfin not in sound_times: sound_times.add(tfin) bs.timer(tfin, self._score_display_sound_small.play) v_offs -= spacing
def _safe_animate( self, node: bs.Node | None, attr: str, keys: dict[float, float] ) -> None: """Run an animation on a node if the node still exists.""" if node: bs.animate(node, attr, keys)
# 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