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: 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)