コード例 #1
0
    def _show_winner(self, team: ba.SessionTeam) -> None:
        from bastd.actor.image import Image
        from bastd.actor.zoomtext import ZoomText
        if not self._is_ffa:
            offs_v = 0.0
            ZoomText(team.name,
                     position=(0, 97),
                     color=team.color,
                     scale=1.15,
                     jitter=1.0,
                     maxwidth=250).autoretain()
        else:
            offs_v = -80.0
            if len(team.players) == 1:
                i = Image(team.players[0].get_icon(),
                          position=(0, 143),
                          scale=(100, 100)).autoretain()
                assert i.node
                ba.animate(i.node, 'opacity', {0.0: 0.0, 0.25: 1.0})
                ZoomText(ba.Lstr(
                    value=team.players[0].getname(full=True, icon=False)),
                         position=(0, 97 + offs_v),
                         color=team.color,
                         scale=1.15,
                         jitter=1.0,
                         maxwidth=250).autoretain()

        s_extra = 1.0 if self._is_ffa else 1.0

        # Some languages say "FOO WINS" differently for teams vs players.
        if isinstance(self.session, ba.FreeForAllSession):
            wins_resource = 'seriesWinLine1PlayerText'
        else:
            wins_resource = 'seriesWinLine1TeamText'
        wins_text = ba.Lstr(resource=wins_resource)

        # Temp - if these come up as the english default, fall-back to the
        # unified old form which is more likely to be translated.
        ZoomText(wins_text,
                 position=(0, -10 + offs_v),
                 color=team.color,
                 scale=0.65 * s_extra,
                 jitter=1.0,
                 maxwidth=250).autoretain()
        ZoomText(ba.Lstr(resource='seriesWinLine2Text'),
                 position=(0, -110 + offs_v),
                 scale=1.0 * s_extra,
                 color=team.color,
                 jitter=1.0,
                 maxwidth=250).autoretain()
コード例 #2
0
    def on_begin(self) -> None:
        # pylint: disable=too-many-branches
        # pylint: disable=too-many-locals
        # pylint: disable=too-many-statements
        from bastd.actor.text import Text
        from bastd.actor.image import Image
        from ba.deprecated import get_resource
        ba.set_analytics_screen('FreeForAll Series Victory Screen' if self.
                                _is_ffa else 'Teams Series Victory Screen')
        if ba.app.uiscale is ba.UIScale.LARGE:
            sval = ba.Lstr(resource='pressAnyKeyButtonPlayAgainText')
        else:
            sval = ba.Lstr(resource='pressAnyButtonPlayAgainText')
        self._show_up_next = False
        self._custom_continue_message = sval
        super().on_begin()
        winning_sessionteam = self.settings_raw['winner']

        # Pause a moment before playing victory music.
        ba.timer(0.6, ba.WeakCall(self._play_victory_music))
        ba.timer(4.4,
                 ba.WeakCall(self._show_winner, self.settings_raw['winner']))
        ba.timer(4.6, ba.Call(ba.playsound, self._score_display_sound))

        # Score / Name / Player-record.
        player_entries: List[Tuple[int, str, ba.PlayerRecord]] = []

        # Note: for ffa, exclude players who haven't entered the game yet.
        if self._is_ffa:
            for _pkey, prec in self.stats.get_records().items():
                if prec.player.in_game:
                    player_entries.append(
                        (prec.player.sessionteam.customdata['score'],
                         prec.getname(full=True), prec))
            player_entries.sort(reverse=True, key=lambda x: x[0])
        else:
            for _pkey, prec in self.stats.get_records().items():
                player_entries.append((prec.score, prec.name_full, prec))
            player_entries.sort(reverse=True, key=lambda x: x[0])

        ts_height = 300.0
        ts_h_offs = -390.0
        tval = 6.4
        t_incr = 0.12

        always_use_first_to = get_resource('bestOfUseFirstToInstead')

        session = self.session
        if self._is_ffa:
            assert isinstance(session, ba.FreeForAllSession)
            txt = ba.Lstr(
                value='${A}:',
                subs=[('${A}',
                       ba.Lstr(resource='firstToFinalText',
                               subs=[('${COUNT}',
                                      str(session.get_ffa_series_length()))]))
                      ])
        else:
            assert isinstance(session, ba.MultiTeamSession)

            # Some languages may prefer to always show 'first to X' instead of
            # 'best of X'.
            # FIXME: This will affect all clients connected to us even if
            #  they're not using this language. Should try to come up
            #  with a wording that works everywhere.
            if always_use_first_to:
                txt = ba.Lstr(
                    value='${A}:',
                    subs=[
                        ('${A}',
                         ba.Lstr(resource='firstToFinalText',
                                 subs=[
                                     ('${COUNT}',
                                      str(session.get_series_length() / 2 + 1))
                                 ]))
                    ])
            else:
                txt = ba.Lstr(
                    value='${A}:',
                    subs=[('${A}',
                           ba.Lstr(resource='bestOfFinalText',
                                   subs=[('${COUNT}',
                                          str(session.get_series_length()))]))
                          ])

        Text(txt,
             v_align=Text.VAlign.CENTER,
             maxwidth=300,
             color=(0.5, 0.5, 0.5, 1.0),
             position=(0, 220),
             scale=1.2,
             transition=Text.Transition.IN_TOP_SLOW,
             h_align=Text.HAlign.CENTER,
             transition_delay=t_incr * 4).autoretain()

        win_score = (session.get_series_length() - 1) // 2 + 1
        lose_score = 0
        for team in self.teams:
            if team.sessionteam.customdata['score'] != win_score:
                lose_score = team.sessionteam.customdata['score']

        if not self._is_ffa:
            Text(ba.Lstr(resource='gamesToText',
                         subs=[('${WINCOUNT}', str(win_score)),
                               ('${LOSECOUNT}', str(lose_score))]),
                 color=(0.5, 0.5, 0.5, 1.0),
                 maxwidth=160,
                 v_align=Text.VAlign.CENTER,
                 position=(0, -215),
                 scale=1.8,
                 transition=Text.Transition.IN_LEFT,
                 h_align=Text.HAlign.CENTER,
                 transition_delay=4.8 + t_incr * 4).autoretain()

        if self._is_ffa:
            v_extra = 120
        else:
            v_extra = 0

        mvp: Optional[ba.PlayerRecord] = None
        mvp_name: Optional[str] = None

        # Show game MVP.
        if not self._is_ffa:
            mvp, mvp_name = None, None
            for entry in player_entries:
                if entry[2].team == winning_sessionteam:
                    mvp = entry[2]
                    mvp_name = entry[1]
                    break
            if mvp is not None:
                Text(ba.Lstr(resource='mostValuablePlayerText'),
                     color=(0.5, 0.5, 0.5, 1.0),
                     v_align=Text.VAlign.CENTER,
                     maxwidth=300,
                     position=(180, ts_height / 2 + 15),
                     transition=Text.Transition.IN_LEFT,
                     h_align=Text.HAlign.LEFT,
                     transition_delay=tval).autoretain()
                tval += 4 * t_incr

                Image(mvp.get_icon(),
                      position=(230, ts_height / 2 - 55 + 14 - 5),
                      scale=(70, 70),
                      transition=Image.Transition.IN_LEFT,
                      transition_delay=tval).autoretain()
                assert mvp_name is not None
                Text(ba.Lstr(value=mvp_name),
                     position=(280, ts_height / 2 - 55 + 15 - 5),
                     h_align=Text.HAlign.LEFT,
                     v_align=Text.VAlign.CENTER,
                     maxwidth=170,
                     scale=1.3,
                     color=ba.safecolor(mvp.team.color + (1, )),
                     transition=Text.Transition.IN_LEFT,
                     transition_delay=tval).autoretain()
                tval += 4 * t_incr

        # Most violent.
        most_kills = 0
        for entry in player_entries:
            if entry[2].kill_count >= most_kills:
                mvp = entry[2]
                mvp_name = entry[1]
                most_kills = entry[2].kill_count
        if mvp is not None:
            Text(ba.Lstr(resource='mostViolentPlayerText'),
                 color=(0.5, 0.5, 0.5, 1.0),
                 v_align=Text.VAlign.CENTER,
                 maxwidth=300,
                 position=(180, ts_height / 2 - 150 + v_extra + 15),
                 transition=Text.Transition.IN_LEFT,
                 h_align=Text.HAlign.LEFT,
                 transition_delay=tval).autoretain()
            Text(ba.Lstr(value='(${A})',
                         subs=[('${A}',
                                ba.Lstr(resource='killsTallyText',
                                        subs=[('${COUNT}', str(most_kills))]))
                               ]),
                 position=(260, ts_height / 2 - 150 - 15 + v_extra),
                 color=(0.3, 0.3, 0.3, 1.0),
                 scale=0.6,
                 h_align=Text.HAlign.LEFT,
                 transition=Text.Transition.IN_LEFT,
                 transition_delay=tval).autoretain()
            tval += 4 * t_incr

            Image(mvp.get_icon(),
                  position=(233, ts_height / 2 - 150 - 30 - 46 + 25 + v_extra),
                  scale=(50, 50),
                  transition=Image.Transition.IN_LEFT,
                  transition_delay=tval).autoretain()
            assert mvp_name is not None
            Text(ba.Lstr(value=mvp_name),
                 position=(270, ts_height / 2 - 150 - 30 - 36 + v_extra + 15),
                 h_align=Text.HAlign.LEFT,
                 v_align=Text.VAlign.CENTER,
                 maxwidth=180,
                 color=ba.safecolor(mvp.team.color + (1, )),
                 transition=Text.Transition.IN_LEFT,
                 transition_delay=tval).autoretain()
            tval += 4 * t_incr

        # Most killed.
        most_killed = 0
        mkp, mkp_name = None, None
        for entry in player_entries:
            if entry[2].killed_count >= most_killed:
                mkp = entry[2]
                mkp_name = entry[1]
                most_killed = entry[2].killed_count
        if mkp is not None:
            Text(ba.Lstr(resource='mostViolatedPlayerText'),
                 color=(0.5, 0.5, 0.5, 1.0),
                 v_align=Text.VAlign.CENTER,
                 maxwidth=300,
                 position=(180, ts_height / 2 - 300 + v_extra + 15),
                 transition=Text.Transition.IN_LEFT,
                 h_align=Text.HAlign.LEFT,
                 transition_delay=tval).autoretain()
            Text(ba.Lstr(value='(${A})',
                         subs=[('${A}',
                                ba.Lstr(resource='deathsTallyText',
                                        subs=[('${COUNT}', str(most_killed))]))
                               ]),
                 position=(260, ts_height / 2 - 300 - 15 + v_extra),
                 h_align=Text.HAlign.LEFT,
                 scale=0.6,
                 color=(0.3, 0.3, 0.3, 1.0),
                 transition=Text.Transition.IN_LEFT,
                 transition_delay=tval).autoretain()
            tval += 4 * t_incr
            Image(mkp.get_icon(),
                  position=(233, ts_height / 2 - 300 - 30 - 46 + 25 + v_extra),
                  scale=(50, 50),
                  transition=Image.Transition.IN_LEFT,
                  transition_delay=tval).autoretain()
            assert mkp_name is not None
            Text(ba.Lstr(value=mkp_name),
                 position=(270, ts_height / 2 - 300 - 30 - 36 + v_extra + 15),
                 h_align=Text.HAlign.LEFT,
                 v_align=Text.VAlign.CENTER,
                 color=ba.safecolor(mkp.team.color + (1, )),
                 maxwidth=180,
                 transition=Text.Transition.IN_LEFT,
                 transition_delay=tval).autoretain()
            tval += 4 * t_incr

        # Now show individual scores.
        tdelay = tval
        Text(ba.Lstr(resource='finalScoresText'),
             color=(0.5, 0.5, 0.5, 1.0),
             position=(ts_h_offs, ts_height / 2),
             transition=Text.Transition.IN_RIGHT,
             transition_delay=tdelay).autoretain()
        tdelay += 4 * t_incr

        v_offs = 0.0
        tdelay += len(player_entries) * 8 * t_incr
        for _score, name, prec in player_entries:
            tdelay -= 4 * t_incr
            v_offs -= 40
            Text(str(prec.team.customdata['score'])
                 if self._is_ffa else str(prec.score),
                 color=(0.5, 0.5, 0.5, 1.0),
                 position=(ts_h_offs + 230, ts_height / 2 + v_offs),
                 h_align=Text.HAlign.RIGHT,
                 transition=Text.Transition.IN_RIGHT,
                 transition_delay=tdelay).autoretain()
            tdelay -= 4 * t_incr

            Image(prec.get_icon(),
                  position=(ts_h_offs - 72, ts_height / 2 + v_offs + 15),
                  scale=(30, 30),
                  transition=Image.Transition.IN_LEFT,
                  transition_delay=tdelay).autoretain()
            Text(ba.Lstr(value=name),
                 position=(ts_h_offs - 50, ts_height / 2 + v_offs + 15),
                 h_align=Text.HAlign.LEFT,
                 v_align=Text.VAlign.CENTER,
                 maxwidth=180,
                 color=ba.safecolor(prec.team.color + (1, )),
                 transition=Text.Transition.IN_RIGHT,
                 transition_delay=tdelay).autoretain()

        ba.timer(15.0, ba.WeakCall(self._show_tips))
コード例 #3
0
ファイル: multiteamscore.py プロジェクト: bseditor/ballistica
    def show_player_scores(self,
                           delay: float = 2.5,
                           results: Optional[ba.TeamGameResults] = None,
                           scale: float = 1.0,
                           x_offset: float = 0.0,
                           y_offset: float = 0.0) -> None:
        """Show scores for individual players."""
        # pylint: disable=too-many-locals
        # pylint: disable=too-many-statements
        from bastd.actor.text import Text
        from bastd.actor.image import Image

        ts_v_offset = 150.0 + y_offset
        ts_h_offs = 80.0 + x_offset
        tdelay = delay
        spacing = 40

        is_free_for_all = isinstance(self.session, ba.FreeForAllSession)

        def _get_prec_score(p_rec: ba.PlayerRecord) -> Optional[int]:
            if is_free_for_all and results is not None:
                assert isinstance(results, ba.TeamGameResults)
                val = results.get_team_score(p_rec.team)
                return val
            return p_rec.accumscore

        def _get_prec_score_str(p_rec: ba.PlayerRecord) -> Union[str, ba.Lstr]:
            if is_free_for_all and results is not None:
                assert isinstance(results, ba.TeamGameResults)
                val = results.get_team_score_str(p_rec.team)
                assert val is not None
                return val
            return str(p_rec.accumscore)

        # stats.get_records() can return players that are no longer in
        # the game.. if we're using results we have to filter those out
        # (since they're not in results and that's where we pull their
        # scores from)
        if results is not None:
            assert isinstance(results, ba.TeamGameResults)
            player_records = []
            assert self.stats
            valid_players = list(self.stats.get_records().items())

            def _get_player_score_set_entry(
                    player: ba.Player) -> Optional[ba.PlayerRecord]:
                for p_rec in valid_players:
                    # PyCharm incorrectly thinks valid_players is a List[str]
                    # noinspection PyUnresolvedReferences
                    if p_rec[1].player is player:
                        return p_rec[1]
                return None

            # Results is already sorted; just convert it into a list of
            # score-set-entries.
            for winner in results.get_winners():
                for team in winner.teams:
                    if len(team.players) == 1:
                        player_entry = _get_player_score_set_entry(
                            team.players[0])
                        if player_entry is not None:
                            player_records.append(player_entry)
        else:
            player_records = []
            player_records_scores = [
                (_get_prec_score(p), name, p)
                for name, p in list(self.stats.get_records().items())
            ]
            player_records_scores.sort(reverse=True)

            # Just want living player entries.
            player_records = [p[2] for p in player_records_scores if p[2]]

        v_offs = -140.0 + spacing * len(player_records) * 0.5

        def _txt(x_offs: float,
                 y_offs: float,
                 text: ba.Lstr,
                 h_align: Text.HAlign = Text.HAlign.RIGHT,
                 extrascale: float = 1.0,
                 maxwidth: Optional[float] = 120.0) -> None:
            Text(text,
                 color=(0.5, 0.5, 0.6, 0.5),
                 position=(ts_h_offs + x_offs * scale,
                           ts_v_offset + (v_offs + y_offs + 4.0) * scale),
                 h_align=h_align,
                 v_align=Text.VAlign.CENTER,
                 scale=0.8 * scale * extrascale,
                 maxwidth=maxwidth,
                 transition=Text.Transition.IN_LEFT,
                 transition_delay=tdelay).autoretain()

        session = self.session
        assert isinstance(session, ba.MultiTeamSession)
        tval = ba.Lstr(resource='gameLeadersText',
                       subs=[('${COUNT}', str(session.get_game_number()))])
        _txt(180,
             43,
             tval,
             h_align=Text.HAlign.CENTER,
             extrascale=1.4,
             maxwidth=None)
        _txt(-15, 4, ba.Lstr(resource='playerText'), h_align=Text.HAlign.LEFT)
        _txt(180, 4, ba.Lstr(resource='killsText'))
        _txt(280, 4, ba.Lstr(resource='deathsText'), maxwidth=100)

        score_name = 'Score' if results is None else results.get_score_name()
        translated = ba.Lstr(translate=('scoreNames', score_name))

        _txt(390, 0, translated)

        topkillcount = 0
        topkilledcount = 99999
        top_score = 0 if not player_records else _get_prec_score(
            player_records[0])

        for prec in player_records:
            topkillcount = max(topkillcount, prec.accum_kill_count)
            topkilledcount = min(topkilledcount, prec.accum_killed_count)

        def _scoretxt(text: Union[str, ba.Lstr],
                      x_offs: float,
                      highlight: bool,
                      delay2: float,
                      maxwidth: float = 70.0) -> None:
            Text(text,
                 position=(ts_h_offs + x_offs * scale,
                           ts_v_offset + (v_offs + 15) * scale),
                 scale=scale,
                 color=(1.0, 0.9, 0.5, 1.0) if highlight else
                 (0.5, 0.5, 0.6, 0.5),
                 h_align=Text.HAlign.RIGHT,
                 v_align=Text.VAlign.CENTER,
                 maxwidth=maxwidth,
                 transition=Text.Transition.IN_LEFT,
                 transition_delay=tdelay + delay2).autoretain()

        for playerrec in player_records:
            tdelay += 0.05
            v_offs -= spacing
            Image(playerrec.get_icon(),
                  position=(ts_h_offs - 12 * scale,
                            ts_v_offset + (v_offs + 15.0) * scale),
                  scale=(30.0 * scale, 30.0 * scale),
                  transition=Image.Transition.IN_LEFT,
                  transition_delay=tdelay).autoretain()
            Text(ba.Lstr(value=playerrec.get_name(full=True)),
                 maxwidth=160,
                 scale=0.75 * scale,
                 position=(ts_h_offs + 10.0 * scale,
                           ts_v_offset + (v_offs + 15) * scale),
                 h_align=Text.HAlign.LEFT,
                 v_align=Text.VAlign.CENTER,
                 color=ba.safecolor(playerrec.team.color + (1, )),
                 transition=Text.Transition.IN_LEFT,
                 transition_delay=tdelay).autoretain()
            _scoretxt(str(playerrec.accum_kill_count), 180,
                      playerrec.accum_kill_count == topkillcount, 0.1)
            _scoretxt(str(playerrec.accum_killed_count), 280,
                      playerrec.accum_killed_count == topkilledcount, 0.1)
            _scoretxt(_get_prec_score_str(playerrec), 390,
                      _get_prec_score(playerrec) == top_score, 0.2)
コード例 #4
0
    def show_completion_banner(self, sound: bool = True) -> None:
        """Create the banner/sound for an acquired achievement announcement."""
        from ba import _account
        from ba import _gameutils
        from bastd.actor.text import Text
        from bastd.actor.image import Image
        from ba._general import WeakCall
        from ba._lang import Lstr
        from ba._messages import DieMessage
        from ba._enums import TimeType, SpecialChar
        app = _ba.app
        app.last_achievement_display_time = _ba.time(TimeType.REAL)

        # Just piggy-back onto any current activity
        # (should we use the session instead?..)
        activity = _ba.getactivity(doraise=False)

        # If this gets called while this achievement is occupying a slot
        # already, ignore it. (probably should never happen in real
        # life but whatevs).
        if self._completion_banner_slot is not None:
            return

        if activity is None:
            print('show_completion_banner() called with no current activity!')
            return

        if sound:
            _ba.playsound(_ba.getsound('achievement'), host_only=True)
        else:
            _ba.timer(
                0.5,
                lambda: _ba.playsound(_ba.getsound('ding'), host_only=True))

        in_time = 0.300
        out_time = 3.5

        base_vr_depth = 200

        # Find the first free slot.
        i = 0
        while True:
            if i not in app.achievement_completion_banner_slots:
                app.achievement_completion_banner_slots.add(i)
                self._completion_banner_slot = i

                # Remove us from that slot when we close.
                # Use a real-timer in the UI context so the removal runs even
                # if our activity/session dies.
                with _ba.Context('ui'):
                    _ba.timer(in_time + out_time,
                              self._remove_banner_slot,
                              timetype=TimeType.REAL)
                break
            i += 1
        assert self._completion_banner_slot is not None
        y_offs = 110 * self._completion_banner_slot
        objs: List[ba.Actor] = []
        obj = Image(_ba.gettexture('shadow'),
                    position=(-30, 30 + y_offs),
                    front=True,
                    attach=Image.Attach.BOTTOM_CENTER,
                    transition=Image.Transition.IN_BOTTOM,
                    vr_depth=base_vr_depth - 100,
                    transition_delay=in_time,
                    transition_out_delay=out_time,
                    color=(0.0, 0.1, 0, 1),
                    scale=(1000, 300)).autoretain()
        objs.append(obj)
        assert obj.node
        obj.node.host_only = True
        obj = Image(_ba.gettexture('light'),
                    position=(-180, 60 + y_offs),
                    front=True,
                    attach=Image.Attach.BOTTOM_CENTER,
                    vr_depth=base_vr_depth,
                    transition=Image.Transition.IN_BOTTOM,
                    transition_delay=in_time,
                    transition_out_delay=out_time,
                    color=(1.8, 1.8, 1.0, 0.0),
                    scale=(40, 300)).autoretain()
        objs.append(obj)
        assert obj.node
        obj.node.host_only = True
        obj.node.premultiplied = True
        combine = _ba.newnode('combine', owner=obj.node, attrs={'size': 2})
        _gameutils.animate(
            combine, 'input0', {
                in_time: 0,
                in_time + 0.4: 30,
                in_time + 0.5: 40,
                in_time + 0.6: 30,
                in_time + 2.0: 0
            })
        _gameutils.animate(
            combine, 'input1', {
                in_time: 0,
                in_time + 0.4: 200,
                in_time + 0.5: 500,
                in_time + 0.6: 200,
                in_time + 2.0: 0
            })
        combine.connectattr('output', obj.node, 'scale')
        _gameutils.animate(obj.node,
                           'rotate', {
                               0: 0.0,
                               0.35: 360.0
                           },
                           loop=True)
        obj = Image(self.get_icon_texture(True),
                    position=(-180, 60 + y_offs),
                    attach=Image.Attach.BOTTOM_CENTER,
                    front=True,
                    vr_depth=base_vr_depth - 10,
                    transition=Image.Transition.IN_BOTTOM,
                    transition_delay=in_time,
                    transition_out_delay=out_time,
                    scale=(100, 100)).autoretain()
        objs.append(obj)
        assert obj.node
        obj.node.host_only = True

        # Flash.
        color = self.get_icon_color(True)
        combine = _ba.newnode('combine', owner=obj.node, attrs={'size': 3})
        keys = {
            in_time: 1.0 * color[0],
            in_time + 0.4: 1.5 * color[0],
            in_time + 0.5: 6.0 * color[0],
            in_time + 0.6: 1.5 * color[0],
            in_time + 2.0: 1.0 * color[0]
        }
        _gameutils.animate(combine, 'input0', keys)
        keys = {
            in_time: 1.0 * color[1],
            in_time + 0.4: 1.5 * color[1],
            in_time + 0.5: 6.0 * color[1],
            in_time + 0.6: 1.5 * color[1],
            in_time + 2.0: 1.0 * color[1]
        }
        _gameutils.animate(combine, 'input1', keys)
        keys = {
            in_time: 1.0 * color[2],
            in_time + 0.4: 1.5 * color[2],
            in_time + 0.5: 6.0 * color[2],
            in_time + 0.6: 1.5 * color[2],
            in_time + 2.0: 1.0 * color[2]
        }
        _gameutils.animate(combine, 'input2', keys)
        combine.connectattr('output', obj.node, 'color')

        obj = Image(_ba.gettexture('achievementOutline'),
                    model_transparent=_ba.getmodel('achievementOutline'),
                    position=(-180, 60 + y_offs),
                    front=True,
                    attach=Image.Attach.BOTTOM_CENTER,
                    vr_depth=base_vr_depth,
                    transition=Image.Transition.IN_BOTTOM,
                    transition_delay=in_time,
                    transition_out_delay=out_time,
                    scale=(100, 100)).autoretain()
        assert obj.node
        obj.node.host_only = True

        # Flash.
        color = (2, 1.4, 0.4, 1)
        combine = _ba.newnode('combine', owner=obj.node, attrs={'size': 3})
        keys = {
            in_time: 1.0 * color[0],
            in_time + 0.4: 1.5 * color[0],
            in_time + 0.5: 6.0 * color[0],
            in_time + 0.6: 1.5 * color[0],
            in_time + 2.0: 1.0 * color[0]
        }
        _gameutils.animate(combine, 'input0', keys)
        keys = {
            in_time: 1.0 * color[1],
            in_time + 0.4: 1.5 * color[1],
            in_time + 0.5: 6.0 * color[1],
            in_time + 0.6: 1.5 * color[1],
            in_time + 2.0: 1.0 * color[1]
        }
        _gameutils.animate(combine, 'input1', keys)
        keys = {
            in_time: 1.0 * color[2],
            in_time + 0.4: 1.5 * color[2],
            in_time + 0.5: 6.0 * color[2],
            in_time + 0.6: 1.5 * color[2],
            in_time + 2.0: 1.0 * color[2]
        }
        _gameutils.animate(combine, 'input2', keys)
        combine.connectattr('output', obj.node, 'color')
        objs.append(obj)

        objt = Text(Lstr(value='${A}:',
                         subs=[('${A}', Lstr(resource='achievementText'))]),
                    position=(-120, 91 + y_offs),
                    front=True,
                    v_attach=Text.VAttach.BOTTOM,
                    vr_depth=base_vr_depth - 10,
                    transition=Text.Transition.IN_BOTTOM,
                    flatness=0.5,
                    transition_delay=in_time,
                    transition_out_delay=out_time,
                    color=(1, 1, 1, 0.8),
                    scale=0.65).autoretain()
        objs.append(objt)
        assert objt.node
        objt.node.host_only = True

        objt = Text(self.display_name,
                    position=(-120, 50 + y_offs),
                    front=True,
                    v_attach=Text.VAttach.BOTTOM,
                    transition=Text.Transition.IN_BOTTOM,
                    vr_depth=base_vr_depth,
                    flatness=0.5,
                    transition_delay=in_time,
                    transition_out_delay=out_time,
                    flash=True,
                    color=(1, 0.8, 0, 1.0),
                    scale=1.5).autoretain()
        objs.append(objt)
        assert objt.node
        objt.node.host_only = True

        objt = Text(_ba.charstr(SpecialChar.TICKET),
                    position=(-120 - 170 + 5, 75 + y_offs - 20),
                    front=True,
                    v_attach=Text.VAttach.BOTTOM,
                    h_align=Text.HAlign.CENTER,
                    v_align=Text.VAlign.CENTER,
                    transition=Text.Transition.IN_BOTTOM,
                    vr_depth=base_vr_depth,
                    transition_delay=in_time,
                    transition_out_delay=out_time,
                    flash=True,
                    color=(0.5, 0.5, 0.5, 1),
                    scale=3.0).autoretain()
        objs.append(objt)
        assert objt.node
        objt.node.host_only = True

        objt = Text('+' + str(self.get_award_ticket_value()),
                    position=(-120 - 180 + 5, 80 + y_offs - 20),
                    v_attach=Text.VAttach.BOTTOM,
                    front=True,
                    h_align=Text.HAlign.CENTER,
                    v_align=Text.VAlign.CENTER,
                    transition=Text.Transition.IN_BOTTOM,
                    vr_depth=base_vr_depth,
                    flatness=0.5,
                    shadow=1.0,
                    transition_delay=in_time,
                    transition_out_delay=out_time,
                    flash=True,
                    color=(0, 1, 0, 1),
                    scale=1.5).autoretain()
        objs.append(objt)
        assert objt.node
        objt.node.host_only = True

        # Add the 'x 2' if we've got pro.
        if _account.have_pro():
            objt = Text('x 2',
                        position=(-120 - 180 + 45, 80 + y_offs - 50),
                        v_attach=Text.VAttach.BOTTOM,
                        front=True,
                        h_align=Text.HAlign.CENTER,
                        v_align=Text.VAlign.CENTER,
                        transition=Text.Transition.IN_BOTTOM,
                        vr_depth=base_vr_depth,
                        flatness=0.5,
                        shadow=1.0,
                        transition_delay=in_time,
                        transition_out_delay=out_time,
                        flash=True,
                        color=(0.4, 0, 1, 1),
                        scale=0.9).autoretain()
            objs.append(objt)
            assert objt.node
            objt.node.host_only = True

        objt = Text(self.description_complete,
                    position=(-120, 30 + y_offs),
                    front=True,
                    v_attach=Text.VAttach.BOTTOM,
                    transition=Text.Transition.IN_BOTTOM,
                    vr_depth=base_vr_depth - 10,
                    flatness=0.5,
                    transition_delay=in_time,
                    transition_out_delay=out_time,
                    color=(1.0, 0.7, 0.5, 1.0),
                    scale=0.8).autoretain()
        objs.append(objt)
        assert objt.node
        objt.node.host_only = True

        for actor in objs:
            _ba.timer(out_time + 1.000,
                      WeakCall(actor.handlemessage, DieMessage()))
コード例 #5
0
    def create_display(self,
                       x: float,
                       y: float,
                       delay: float,
                       outdelay: float = None,
                       color: Sequence[float] = None,
                       style: str = 'post_game') -> List[ba.Actor]:
        """Create a display for the Achievement.

        Shows the Achievement icon, name, and description.
        """
        # pylint: disable=cyclic-import
        from ba._lang import Lstr
        from ba._enums import SpecialChar
        from ba._coopsession import CoopSession
        from bastd.actor.image import Image
        from bastd.actor.text import Text

        # Yeah this needs cleaning up.
        if style == 'post_game':
            in_game_colors = False
            in_main_menu = False
            h_attach = Text.HAttach.CENTER
            v_attach = Text.VAttach.CENTER
            attach = Image.Attach.CENTER
        elif style == 'in_game':
            in_game_colors = True
            in_main_menu = False
            h_attach = Text.HAttach.LEFT
            v_attach = Text.VAttach.TOP
            attach = Image.Attach.TOP_LEFT
        elif style == 'news':
            in_game_colors = True
            in_main_menu = True
            h_attach = Text.HAttach.CENTER
            v_attach = Text.VAttach.TOP
            attach = Image.Attach.TOP_CENTER
        else:
            raise ValueError('invalid style "' + style + '"')

        # Attempt to determine what campaign we're in
        # (so we know whether to show "hard mode only").
        if in_main_menu:
            hmo = False
        else:
            try:
                session = _ba.getsession()
                if isinstance(session, CoopSession):
                    campaign = session.campaign
                    assert campaign is not None
                    hmo = (self._hard_mode_only and campaign.name == 'Easy')
                else:
                    hmo = False
            except Exception:
                from ba import _error
                _error.print_exception('Error determining campaign')
                hmo = False

        objs: List[ba.Actor]

        if in_game_colors:
            objs = []
            out_delay_fin = (delay +
                             outdelay) if outdelay is not None else None
            if color is not None:
                cl1 = (2.0 * color[0], 2.0 * color[1], 2.0 * color[2],
                       color[3])
                cl2 = color
            else:
                cl1 = (1.5, 1.5, 2, 1.0)
                cl2 = (0.8, 0.8, 1.0, 1.0)

            if hmo:
                cl1 = (cl1[0], cl1[1], cl1[2], cl1[3] * 0.6)
                cl2 = (cl2[0], cl2[1], cl2[2], cl2[3] * 0.2)

            objs.append(
                Image(self.get_icon_texture(False),
                      host_only=True,
                      color=cl1,
                      position=(x - 25, y + 5),
                      attach=attach,
                      transition=Image.Transition.FADE_IN,
                      transition_delay=delay,
                      vr_depth=4,
                      transition_out_delay=out_delay_fin,
                      scale=(40, 40)).autoretain())
            txt = self.display_name
            txt_s = 0.85
            txt_max_w = 300
            objs.append(
                Text(txt,
                     host_only=True,
                     maxwidth=txt_max_w,
                     position=(x, y + 2),
                     transition=Text.Transition.FADE_IN,
                     scale=txt_s,
                     flatness=0.6,
                     shadow=0.5,
                     h_attach=h_attach,
                     v_attach=v_attach,
                     color=cl2,
                     transition_delay=delay + 0.05,
                     transition_out_delay=out_delay_fin).autoretain())
            txt2_s = 0.62
            txt2_max_w = 400
            objs.append(
                Text(self.description_full
                     if in_main_menu else self.description,
                     host_only=True,
                     maxwidth=txt2_max_w,
                     position=(x, y - 14),
                     transition=Text.Transition.FADE_IN,
                     vr_depth=-5,
                     h_attach=h_attach,
                     v_attach=v_attach,
                     scale=txt2_s,
                     flatness=1.0,
                     shadow=0.5,
                     color=cl2,
                     transition_delay=delay + 0.1,
                     transition_out_delay=out_delay_fin).autoretain())

            if hmo:
                txtactor = Text(
                    Lstr(resource='difficultyHardOnlyText'),
                    host_only=True,
                    maxwidth=txt2_max_w * 0.7,
                    position=(x + 60, y + 5),
                    transition=Text.Transition.FADE_IN,
                    vr_depth=-5,
                    h_attach=h_attach,
                    v_attach=v_attach,
                    h_align=Text.HAlign.CENTER,
                    v_align=Text.VAlign.CENTER,
                    scale=txt_s * 0.8,
                    flatness=1.0,
                    shadow=0.5,
                    color=(1, 1, 0.6, 1),
                    transition_delay=delay + 0.1,
                    transition_out_delay=out_delay_fin).autoretain()
                txtactor.node.rotate = 10
                objs.append(txtactor)

            # Ticket-award.
            award_x = -100
            objs.append(
                Text(_ba.charstr(SpecialChar.TICKET),
                     host_only=True,
                     position=(x + award_x + 33, y + 7),
                     transition=Text.Transition.FADE_IN,
                     scale=1.5,
                     h_attach=h_attach,
                     v_attach=v_attach,
                     h_align=Text.HAlign.CENTER,
                     v_align=Text.VAlign.CENTER,
                     color=(1, 1, 1, 0.2 if hmo else 0.4),
                     transition_delay=delay + 0.05,
                     transition_out_delay=out_delay_fin).autoretain())
            objs.append(
                Text('+' + str(self.get_award_ticket_value()),
                     host_only=True,
                     position=(x + award_x + 28, y + 16),
                     transition=Text.Transition.FADE_IN,
                     scale=0.7,
                     flatness=1,
                     h_attach=h_attach,
                     v_attach=v_attach,
                     h_align=Text.HAlign.CENTER,
                     v_align=Text.VAlign.CENTER,
                     color=cl2,
                     transition_delay=delay + 0.05,
                     transition_out_delay=out_delay_fin).autoretain())

        else:
            complete = self.complete
            objs = []
            c_icon = self.get_icon_color(complete)
            if hmo and not complete:
                c_icon = (c_icon[0], c_icon[1], c_icon[2], c_icon[3] * 0.3)
            objs.append(
                Image(self.get_icon_texture(complete),
                      host_only=True,
                      color=c_icon,
                      position=(x - 25, y + 5),
                      attach=attach,
                      vr_depth=4,
                      transition=Image.Transition.IN_RIGHT,
                      transition_delay=delay,
                      transition_out_delay=None,
                      scale=(40, 40)).autoretain())
            if complete:
                objs.append(
                    Image(_ba.gettexture('achievementOutline'),
                          host_only=True,
                          model_transparent=_ba.getmodel('achievementOutline'),
                          color=(2, 1.4, 0.4, 1),
                          vr_depth=8,
                          position=(x - 25, y + 5),
                          attach=attach,
                          transition=Image.Transition.IN_RIGHT,
                          transition_delay=delay,
                          transition_out_delay=None,
                          scale=(40, 40)).autoretain())
            else:
                if not complete:
                    award_x = -100
                    objs.append(
                        Text(_ba.charstr(SpecialChar.TICKET),
                             host_only=True,
                             position=(x + award_x + 33, y + 7),
                             transition=Text.Transition.IN_RIGHT,
                             scale=1.5,
                             h_attach=h_attach,
                             v_attach=v_attach,
                             h_align=Text.HAlign.CENTER,
                             v_align=Text.VAlign.CENTER,
                             color=(1, 1, 1, 0.4) if complete else
                             (1, 1, 1, (0.1 if hmo else 0.2)),
                             transition_delay=delay + 0.05,
                             transition_out_delay=None).autoretain())
                    objs.append(
                        Text('+' + str(self.get_award_ticket_value()),
                             host_only=True,
                             position=(x + award_x + 28, y + 16),
                             transition=Text.Transition.IN_RIGHT,
                             scale=0.7,
                             flatness=1,
                             h_attach=h_attach,
                             v_attach=v_attach,
                             h_align=Text.HAlign.CENTER,
                             v_align=Text.VAlign.CENTER,
                             color=((0.8, 0.93, 0.8, 1.0) if complete else
                                    (0.6, 0.6, 0.6, (0.2 if hmo else 0.4))),
                             transition_delay=delay + 0.05,
                             transition_out_delay=None).autoretain())

                    # Show 'hard-mode-only' only over incomplete achievements
                    # when that's the case.
                    if hmo:
                        txtactor = Text(
                            Lstr(resource='difficultyHardOnlyText'),
                            host_only=True,
                            maxwidth=300 * 0.7,
                            position=(x + 60, y + 5),
                            transition=Text.Transition.FADE_IN,
                            vr_depth=-5,
                            h_attach=h_attach,
                            v_attach=v_attach,
                            h_align=Text.HAlign.CENTER,
                            v_align=Text.VAlign.CENTER,
                            scale=0.85 * 0.8,
                            flatness=1.0,
                            shadow=0.5,
                            color=(1, 1, 0.6, 1),
                            transition_delay=delay + 0.05,
                            transition_out_delay=None).autoretain()
                        assert txtactor.node
                        txtactor.node.rotate = 10
                        objs.append(txtactor)

            objs.append(
                Text(self.display_name,
                     host_only=True,
                     maxwidth=300,
                     position=(x, y + 2),
                     transition=Text.Transition.IN_RIGHT,
                     scale=0.85,
                     flatness=0.6,
                     h_attach=h_attach,
                     v_attach=v_attach,
                     color=((0.8, 0.93, 0.8, 1.0) if complete else
                            (0.6, 0.6, 0.6, (0.2 if hmo else 0.4))),
                     transition_delay=delay + 0.05,
                     transition_out_delay=None).autoretain())
            objs.append(
                Text(self.description_complete
                     if complete else self.description,
                     host_only=True,
                     maxwidth=400,
                     position=(x, y - 14),
                     transition=Text.Transition.IN_RIGHT,
                     vr_depth=-5,
                     h_attach=h_attach,
                     v_attach=v_attach,
                     scale=0.62,
                     flatness=1.0,
                     color=((0.6, 0.6, 0.6, 1.0) if complete else
                            (0.6, 0.6, 0.6, (0.2 if hmo else 0.4))),
                     transition_delay=delay + 0.1,
                     transition_out_delay=None).autoretain())
        return objs
def show_player_scores(self,
                       delay: float = 2.5,
                       results: Optional[ba.GameResults] = None,
                       scale: float = 1.0,
                       x_offset: float = 0.0,
                       y_offset: float = 0.0) -> None:
    """Show scores for individual players."""
    # pylint: disable=too-many-locals
    # pylint: disable=too-many-statements

    ts_v_offset = 150.0 + y_offset
    ts_h_offs = 80.0 + x_offset
    tdelay = delay
    spacing = 40

    is_free_for_all = isinstance(self.session, ba.FreeForAllSession)

    is_two_team = True if len(self.session.sessionteams) == 2 else False

    def _get_prec_score(p_rec: ba.PlayerRecord) -> Optional[int]:
        if is_free_for_all and results is not None:
            assert isinstance(results, ba.GameResults)
            assert p_rec.team.activityteam is not None
            val = results.get_sessionteam_score(p_rec.team)
            return val
        return p_rec.accumscore

    def _get_prec_score_str(p_rec: ba.PlayerRecord) -> Union[str, ba.Lstr]:
        if is_free_for_all and results is not None:
            assert isinstance(results, ba.GameResults)
            assert p_rec.team.activityteam is not None
            val = results.get_sessionteam_score_str(p_rec.team)
            assert val is not None
            return val
        return str(p_rec.accumscore)

    # stats.get_records() can return players that are no longer in
    # the game.. if we're using results we have to filter those out
    # (since they're not in results and that's where we pull their
    # scores from)
    if results is not None:
        assert isinstance(results, ba.GameResults)
        player_records = []
        assert self.stats
        valid_players = list(self.stats.get_records().items())

        def _get_player_score_set_entry(
                player: ba.SessionPlayer) -> Optional[ba.PlayerRecord]:
            for p_rec in valid_players:
                if p_rec[1].player is player:
                    return p_rec[1]
            return None

        # Results is already sorted; just convert it into a list of
        # score-set-entries.
        for winnergroup in results.winnergroups:
            for team in winnergroup.teams:
                if len(team.players) == 1:
                    player_entry = _get_player_score_set_entry(team.players[0])
                    if player_entry is not None:
                        player_records.append(player_entry)
    else:
        player_records = []
        player_records_scores = [
            (_get_prec_score(p), name, p)
            for name, p in list(self.stats.get_records().items())
        ]
        player_records_scores.sort(reverse=True)

        # Just want living player entries.
        player_records = [p[2] for p in player_records_scores if p[2]]

    voffs = -140.0 + spacing * 5 * 0.5

    voffs_team0 = voffs
    tdelay_team0 = tdelay

    def _txt(xoffs: float,
             yoffs: float,
             text: ba.Lstr,
             h_align: Text.HAlign = Text.HAlign.RIGHT,
             extrascale: float = 1.0,
             maxwidth: Optional[float] = 120.0) -> None:
        Text(text,
             color=(0.5, 0.5, 0.6, 0.5),
             position=(ts_h_offs + xoffs * scale,
                       ts_v_offset + (voffs + yoffs + 4.0) * scale),
             h_align=h_align,
             v_align=Text.VAlign.CENTER,
             scale=0.8 * scale * extrascale,
             maxwidth=maxwidth,
             transition=Text.Transition.IN_LEFT,
             transition_delay=tdelay).autoretain()

    session = self.session
    assert isinstance(session, ba.MultiTeamSession)
    if is_two_team:
        tval = "Game " + str(session.get_game_number()) + " Results"
        _txt(-75,
             160,
             tval,
             h_align=Text.HAlign.CENTER,
             extrascale=1.4,
             maxwidth=None)
    _txt(-15, 4, ba.Lstr(resource='playerText'), h_align=Text.HAlign.LEFT)
    _txt(180, 4, ba.Lstr(resource='killsText'))
    _txt(280, 4, ba.Lstr(resource='deathsText'), maxwidth=100)

    score_label = 'Score' if results is None else results.score_label
    translated = ba.Lstr(translate=('scoreNames', score_label))

    _txt(390, 0, translated)

    if is_two_team:
        _txt(-595, 4, ba.Lstr(resource='playerText'), h_align=Text.HAlign.LEFT)
        _txt(-400, 4, ba.Lstr(resource='killsText'))
        _txt(-300, 4, ba.Lstr(resource='deathsText'), maxwidth=100)
        _txt(-190, 0, translated)

    topkillcount = 0
    topkilledcount = 99999
    top_score = 0 if not player_records else _get_prec_score(player_records[0])

    for prec in player_records:
        topkillcount = max(topkillcount, prec.accum_kill_count)
        topkilledcount = min(topkilledcount, prec.accum_killed_count)

    def _scoretxt(text: Union[str, ba.Lstr],
                  x_offs: float,
                  highlight: bool,
                  delay2: float,
                  maxwidth: float = 70.0,
                  team_id=1) -> None:

        Text(text,
             position=(ts_h_offs + x_offs * scale,
                       ts_v_offset + (voffs + 15) * scale) if team_id == 1 else
             (ts_h_offs + x_offs * scale,
              ts_v_offset + (voffs_team0 + 15) * scale),
             scale=scale,
             color=(1.0, 0.9, 0.5, 1.0) if highlight else (0.5, 0.5, 0.6, 0.5),
             h_align=Text.HAlign.RIGHT,
             v_align=Text.VAlign.CENTER,
             maxwidth=maxwidth,
             transition=Text.Transition.IN_LEFT,
             transition_delay=(tdelay + delay2) if team_id == 1 else
             (tdelay_team0 + delay2)).autoretain()

    for playerrec in player_records:
        if is_two_team and playerrec.team.id == 0:
            tdelay_team0 += 0.05
            voffs_team0 -= spacing
            x_image = 617
            x_text = -595
            y = ts_v_offset + (voffs_team0 + 15.0) * scale

        else:
            tdelay += 0.05
            voffs -= spacing
            x_image = 12
            x_text = 10.0
            y = ts_v_offset + (voffs + 15.0) * scale

        Image(playerrec.get_icon(),
              position=(ts_h_offs - x_image * scale, y),
              scale=(30.0 * scale, 30.0 * scale),
              transition=Image.Transition.IN_LEFT,
              transition_delay=tdelay
              if playerrec.team.id == 1 else tdelay_team0).autoretain()
        Text(ba.Lstr(value=playerrec.getname(full=True)),
             maxwidth=160,
             scale=0.75 * scale,
             position=(ts_h_offs + x_text * scale, y),
             h_align=Text.HAlign.LEFT,
             v_align=Text.VAlign.CENTER,
             color=ba.safecolor(playerrec.team.color + (1, )),
             transition=Text.Transition.IN_LEFT,
             transition_delay=tdelay
             if playerrec.team.id == 1 else tdelay_team0).autoretain()

        if is_two_team and playerrec.team.id == 0:
            _scoretxt(str(playerrec.accum_kill_count),
                      -400,
                      playerrec.accum_kill_count == topkillcount,
                      0.1,
                      team_id=0)
            _scoretxt(str(playerrec.accum_killed_count),
                      -300,
                      playerrec.accum_killed_count == topkilledcount,
                      0.1,
                      team_id=0)
            _scoretxt(_get_prec_score_str(playerrec),
                      -190,
                      _get_prec_score(playerrec) == top_score,
                      0.2,
                      team_id=0)
        else:
            _scoretxt(str(playerrec.accum_kill_count), 180,
                      playerrec.accum_kill_count == topkillcount, 0.1)
            _scoretxt(str(playerrec.accum_killed_count), 280,
                      playerrec.accum_killed_count == topkilledcount, 0.1)
            _scoretxt(_get_prec_score_str(playerrec), 390,
                      _get_prec_score(playerrec) == top_score, 0.2)
コード例 #7
0
    def on_begin(self) -> None:
        # pylint: disable=too-many-locals
        # pylint: disable=too-many-statements
        from bastd.actor.text import Text
        from bastd.actor.image import Image
        ba.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

        ba.timer(0.3, ba.Call(ba.playsound, self._score_display_sound))
        results = self.settings_raw['results']
        assert isinstance(results, ba.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, ba.FreeForAllSession)
        title = Text(ba.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

        ba.timer(
            tdelay + delay3,
            ba.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))
            ba.timer(tdelay + 0.3,
                     ba.Call(ba.playsound, self._score_display_sound_small))
            if order_change:
                ba.timer(tdelay + delay2 + 0.1,
                         ba.Call(ba.playsound, self._cymbal_sound))
            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()
            ba.timer(
                tdelay + delay2,
                ba.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
                    }))
            ba.timer(
                tdelay + delay3,
                ba.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(ba.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=ba.safecolor(player.team.color + (1, )),
                       transition=Text.Transition.IN_LEFT,
                       transition_delay=tdelay).autoretain()
            ba.timer(
                tdelay + delay2,
                ba.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
                    }))
            ba.timer(
                tdelay + delay3,
                ba.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()
            ba.timer(
                tdelay + delay3,
                ba.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)
            ba.timer(
                tdelay + delay2,
                ba.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
                    }))
            ba.timer(
                tdelay + delay3,
                ba.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)
                ba.timer(
                    tdelay + delay2,
                    ba.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
                        }))
                ba.timer(
                    tdelay + delay3,
                    ba.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: Optional[ba.Node], attr: str,
                                 value: Any) -> None:
                    if node:
                        setattr(node, attr, value)

                ba.timer(
                    tdelay + delay1,
                    ba.Call(_safesetattr, s_txt.node, 'color', (1, 1, 1, 1)))
                for j in range(score_change):
                    ba.timer((tdelay + delay1 + 0.15 * j),
                             ba.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)
                        ba.timer(
                            tfin,
                            ba.Call(ba.playsound,
                                    self._score_display_sound_small))
            v_offs -= spacing