bascenev1lib.activity.multiteamscore

Functionality related to teams mode score screen.

  1# Released under the MIT License. See LICENSE for details.
  2#
  3"""Functionality related to teams mode score screen."""
  4from __future__ import annotations
  5
  6from typing import override
  7
  8import bascenev1 as bs
  9
 10from bascenev1lib.actor.text import Text
 11from bascenev1lib.actor.image import Image
 12
 13
 14class MultiTeamScoreScreenActivity(bs.ScoreScreenActivity):
 15    """Base class for score screens."""
 16
 17    def __init__(self, settings: dict):
 18        super().__init__(settings=settings)
 19        self._score_display_sound = bs.getsound('scoreHit01')
 20        self._score_display_sound_small = bs.getsound('scoreHit02')
 21
 22        self._show_up_next: bool = True
 23
 24    @override
 25    def on_begin(self) -> None:
 26        super().on_begin()
 27        session = self.session
 28        if self._show_up_next and isinstance(session, bs.MultiTeamSession):
 29            txt = bs.Lstr(
 30                value='${A}   ${B}',
 31                subs=[
 32                    (
 33                        '${A}',
 34                        bs.Lstr(
 35                            resource='upNextText',
 36                            subs=[
 37                                ('${COUNT}', str(session.get_game_number() + 1))
 38                            ],
 39                        ),
 40                    ),
 41                    ('${B}', session.get_next_game_description()),
 42                ],
 43            )
 44            Text(
 45                txt,
 46                maxwidth=900,
 47                h_attach=Text.HAttach.CENTER,
 48                v_attach=Text.VAttach.BOTTOM,
 49                h_align=Text.HAlign.CENTER,
 50                v_align=Text.VAlign.CENTER,
 51                position=(0, 53),
 52                flash=False,
 53                color=(0.3, 0.3, 0.35, 1.0),
 54                transition=Text.Transition.FADE_IN,
 55                transition_delay=2.0,
 56            ).autoretain()
 57
 58    def show_player_scores(
 59        self,
 60        *,
 61        delay: float = 2.5,
 62        results: bs.GameResults | None = None,
 63        scale: float = 1.0,
 64        x_offset: float = 0.0,
 65        y_offset: float = 0.0,
 66    ) -> None:
 67        """Show scores for individual players."""
 68        # pylint: disable=too-many-locals
 69        # pylint: disable=too-many-statements
 70
 71        ts_v_offset = 150.0 + y_offset
 72        ts_h_offs = 80.0 + x_offset
 73        tdelay = delay
 74        spacing = 40
 75
 76        is_free_for_all = isinstance(self.session, bs.FreeForAllSession)
 77
 78        def _get_prec_score(p_rec: bs.PlayerRecord) -> int | None:
 79            if is_free_for_all and results is not None:
 80                assert isinstance(results, bs.GameResults)
 81                assert p_rec.team.activityteam is not None
 82                val = results.get_sessionteam_score(p_rec.team)
 83                return val
 84            return p_rec.accumscore
 85
 86        def _get_prec_score_str(p_rec: bs.PlayerRecord) -> str | bs.Lstr:
 87            if is_free_for_all and results is not None:
 88                assert isinstance(results, bs.GameResults)
 89                assert p_rec.team.activityteam is not None
 90                val = results.get_sessionteam_score_str(p_rec.team)
 91                assert val is not None
 92                return val
 93            return str(p_rec.accumscore)
 94
 95        # stats.get_records() can return players that are no longer in
 96        # the game.. if we're using results we have to filter those out
 97        # (since they're not in results and that's where we pull their
 98        # scores from)
 99        if results is not None:
100            assert isinstance(results, bs.GameResults)
101            player_records = []
102            valid_players = list(self.stats.get_records().items())
103
104            # noinspection PyUnresolvedReferences
105            def _get_player_score_set_entry(
106                player: bs.SessionPlayer,
107            ) -> bs.PlayerRecord | None:
108                for p_rec in valid_players:
109                    if p_rec[1].player is player:
110                        return p_rec[1]
111                return None
112
113            # Results is already sorted; just convert it into a list of
114            # score-set-entries.
115            for winnergroup in results.winnergroups:
116                for team in winnergroup.teams:
117                    if len(team.players) == 1:
118                        player_entry = _get_player_score_set_entry(
119                            team.players[0]
120                        )
121                        if player_entry is not None:
122                            player_records.append(player_entry)
123        else:
124            player_records = []
125            player_records_scores = [
126                (_get_prec_score(p), name, p)
127                for name, p in list(self.stats.get_records().items())
128            ]
129            player_records_scores.sort(reverse=True)
130            player_records = [p[2] for p in player_records_scores]
131
132        voffs = -140.0 + spacing * len(player_records) * 0.5
133
134        def _txt(
135            xoffs: float,
136            yoffs: float,
137            text: bs.Lstr,
138            *,
139            h_align: Text.HAlign = Text.HAlign.RIGHT,
140            extrascale: float = 1.0,
141            maxwidth: float | None = 120.0,
142        ) -> None:
143            Text(
144                text,
145                color=(0.5, 0.5, 0.6, 0.5),
146                position=(
147                    ts_h_offs + xoffs * scale,
148                    ts_v_offset + (voffs + yoffs + 4.0) * scale,
149                ),
150                h_align=h_align,
151                v_align=Text.VAlign.CENTER,
152                scale=0.8 * scale * extrascale,
153                maxwidth=maxwidth,
154                transition=Text.Transition.IN_LEFT,
155                transition_delay=tdelay,
156            ).autoretain()
157
158        session = self.session
159        assert isinstance(session, bs.MultiTeamSession)
160        tval = bs.Lstr(
161            resource='gameLeadersText',
162            subs=[('${COUNT}', str(session.get_game_number()))],
163        )
164        _txt(
165            180,
166            43,
167            tval,
168            h_align=Text.HAlign.CENTER,
169            extrascale=1.4,
170            maxwidth=None,
171        )
172        _txt(-15, 4, bs.Lstr(resource='playerText'), h_align=Text.HAlign.LEFT)
173        _txt(180, 4, bs.Lstr(resource='killsText'))
174        _txt(280, 4, bs.Lstr(resource='deathsText'), maxwidth=100)
175
176        score_label = 'Score' if results is None else results.score_label
177        translated = bs.Lstr(translate=('scoreNames', score_label))
178
179        _txt(390, 0, translated)
180
181        topkillcount = 0
182        topkilledcount = 99999
183        top_score = (
184            0 if not player_records else _get_prec_score(player_records[0])
185        )
186
187        for prec in player_records:
188            topkillcount = max(topkillcount, prec.accum_kill_count)
189            topkilledcount = min(topkilledcount, prec.accum_killed_count)
190
191        def _scoretxt(
192            text: str | bs.Lstr,
193            x_offs: float,
194            highlight: bool,
195            delay2: float,
196            maxwidth: float = 70.0,
197        ) -> None:
198            Text(
199                text,
200                position=(
201                    ts_h_offs + x_offs * scale,
202                    ts_v_offset + (voffs + 15) * scale,
203                ),
204                scale=scale,
205                color=(
206                    (1.0, 0.9, 0.5, 1.0) if highlight else (0.5, 0.5, 0.6, 0.5)
207                ),
208                h_align=Text.HAlign.RIGHT,
209                v_align=Text.VAlign.CENTER,
210                maxwidth=maxwidth,
211                transition=Text.Transition.IN_LEFT,
212                transition_delay=tdelay + delay2,
213            ).autoretain()
214
215        for playerrec in player_records:
216            tdelay += 0.05
217            voffs -= spacing
218            Image(
219                playerrec.get_icon(),
220                position=(
221                    ts_h_offs - 12 * scale,
222                    ts_v_offset + (voffs + 15.0) * scale,
223                ),
224                scale=(30.0 * scale, 30.0 * scale),
225                transition=Image.Transition.IN_LEFT,
226                transition_delay=tdelay,
227            ).autoretain()
228            Text(
229                bs.Lstr(value=playerrec.getname(full=True)),
230                maxwidth=160,
231                scale=0.75 * scale,
232                position=(
233                    ts_h_offs + 10.0 * scale,
234                    ts_v_offset + (voffs + 15) * scale,
235                ),
236                h_align=Text.HAlign.LEFT,
237                v_align=Text.VAlign.CENTER,
238                color=bs.safecolor(playerrec.team.color + (1,)),
239                transition=Text.Transition.IN_LEFT,
240                transition_delay=tdelay,
241            ).autoretain()
242            _scoretxt(
243                str(playerrec.accum_kill_count),
244                180,
245                playerrec.accum_kill_count == topkillcount,
246                0.1,
247            )
248            _scoretxt(
249                str(playerrec.accum_killed_count),
250                280,
251                playerrec.accum_killed_count == topkilledcount,
252                0.1,
253            )
254            _scoretxt(
255                _get_prec_score_str(playerrec),
256                390,
257                _get_prec_score(playerrec) == top_score,
258                0.2,
259            )
class MultiTeamScoreScreenActivity(bascenev1._activity.Activity[bascenev1._player.EmptyPlayer, bascenev1._team.EmptyTeam]):
 15class MultiTeamScoreScreenActivity(bs.ScoreScreenActivity):
 16    """Base class for score screens."""
 17
 18    def __init__(self, settings: dict):
 19        super().__init__(settings=settings)
 20        self._score_display_sound = bs.getsound('scoreHit01')
 21        self._score_display_sound_small = bs.getsound('scoreHit02')
 22
 23        self._show_up_next: bool = True
 24
 25    @override
 26    def on_begin(self) -> None:
 27        super().on_begin()
 28        session = self.session
 29        if self._show_up_next and isinstance(session, bs.MultiTeamSession):
 30            txt = bs.Lstr(
 31                value='${A}   ${B}',
 32                subs=[
 33                    (
 34                        '${A}',
 35                        bs.Lstr(
 36                            resource='upNextText',
 37                            subs=[
 38                                ('${COUNT}', str(session.get_game_number() + 1))
 39                            ],
 40                        ),
 41                    ),
 42                    ('${B}', session.get_next_game_description()),
 43                ],
 44            )
 45            Text(
 46                txt,
 47                maxwidth=900,
 48                h_attach=Text.HAttach.CENTER,
 49                v_attach=Text.VAttach.BOTTOM,
 50                h_align=Text.HAlign.CENTER,
 51                v_align=Text.VAlign.CENTER,
 52                position=(0, 53),
 53                flash=False,
 54                color=(0.3, 0.3, 0.35, 1.0),
 55                transition=Text.Transition.FADE_IN,
 56                transition_delay=2.0,
 57            ).autoretain()
 58
 59    def show_player_scores(
 60        self,
 61        *,
 62        delay: float = 2.5,
 63        results: bs.GameResults | None = None,
 64        scale: float = 1.0,
 65        x_offset: float = 0.0,
 66        y_offset: float = 0.0,
 67    ) -> None:
 68        """Show scores for individual players."""
 69        # pylint: disable=too-many-locals
 70        # pylint: disable=too-many-statements
 71
 72        ts_v_offset = 150.0 + y_offset
 73        ts_h_offs = 80.0 + x_offset
 74        tdelay = delay
 75        spacing = 40
 76
 77        is_free_for_all = isinstance(self.session, bs.FreeForAllSession)
 78
 79        def _get_prec_score(p_rec: bs.PlayerRecord) -> int | None:
 80            if is_free_for_all and results is not None:
 81                assert isinstance(results, bs.GameResults)
 82                assert p_rec.team.activityteam is not None
 83                val = results.get_sessionteam_score(p_rec.team)
 84                return val
 85            return p_rec.accumscore
 86
 87        def _get_prec_score_str(p_rec: bs.PlayerRecord) -> str | bs.Lstr:
 88            if is_free_for_all and results is not None:
 89                assert isinstance(results, bs.GameResults)
 90                assert p_rec.team.activityteam is not None
 91                val = results.get_sessionteam_score_str(p_rec.team)
 92                assert val is not None
 93                return val
 94            return str(p_rec.accumscore)
 95
 96        # stats.get_records() can return players that are no longer in
 97        # the game.. if we're using results we have to filter those out
 98        # (since they're not in results and that's where we pull their
 99        # scores from)
100        if results is not None:
101            assert isinstance(results, bs.GameResults)
102            player_records = []
103            valid_players = list(self.stats.get_records().items())
104
105            # noinspection PyUnresolvedReferences
106            def _get_player_score_set_entry(
107                player: bs.SessionPlayer,
108            ) -> bs.PlayerRecord | None:
109                for p_rec in valid_players:
110                    if p_rec[1].player is player:
111                        return p_rec[1]
112                return None
113
114            # Results is already sorted; just convert it into a list of
115            # score-set-entries.
116            for winnergroup in results.winnergroups:
117                for team in winnergroup.teams:
118                    if len(team.players) == 1:
119                        player_entry = _get_player_score_set_entry(
120                            team.players[0]
121                        )
122                        if player_entry is not None:
123                            player_records.append(player_entry)
124        else:
125            player_records = []
126            player_records_scores = [
127                (_get_prec_score(p), name, p)
128                for name, p in list(self.stats.get_records().items())
129            ]
130            player_records_scores.sort(reverse=True)
131            player_records = [p[2] for p in player_records_scores]
132
133        voffs = -140.0 + spacing * len(player_records) * 0.5
134
135        def _txt(
136            xoffs: float,
137            yoffs: float,
138            text: bs.Lstr,
139            *,
140            h_align: Text.HAlign = Text.HAlign.RIGHT,
141            extrascale: float = 1.0,
142            maxwidth: float | None = 120.0,
143        ) -> None:
144            Text(
145                text,
146                color=(0.5, 0.5, 0.6, 0.5),
147                position=(
148                    ts_h_offs + xoffs * scale,
149                    ts_v_offset + (voffs + yoffs + 4.0) * scale,
150                ),
151                h_align=h_align,
152                v_align=Text.VAlign.CENTER,
153                scale=0.8 * scale * extrascale,
154                maxwidth=maxwidth,
155                transition=Text.Transition.IN_LEFT,
156                transition_delay=tdelay,
157            ).autoretain()
158
159        session = self.session
160        assert isinstance(session, bs.MultiTeamSession)
161        tval = bs.Lstr(
162            resource='gameLeadersText',
163            subs=[('${COUNT}', str(session.get_game_number()))],
164        )
165        _txt(
166            180,
167            43,
168            tval,
169            h_align=Text.HAlign.CENTER,
170            extrascale=1.4,
171            maxwidth=None,
172        )
173        _txt(-15, 4, bs.Lstr(resource='playerText'), h_align=Text.HAlign.LEFT)
174        _txt(180, 4, bs.Lstr(resource='killsText'))
175        _txt(280, 4, bs.Lstr(resource='deathsText'), maxwidth=100)
176
177        score_label = 'Score' if results is None else results.score_label
178        translated = bs.Lstr(translate=('scoreNames', score_label))
179
180        _txt(390, 0, translated)
181
182        topkillcount = 0
183        topkilledcount = 99999
184        top_score = (
185            0 if not player_records else _get_prec_score(player_records[0])
186        )
187
188        for prec in player_records:
189            topkillcount = max(topkillcount, prec.accum_kill_count)
190            topkilledcount = min(topkilledcount, prec.accum_killed_count)
191
192        def _scoretxt(
193            text: str | bs.Lstr,
194            x_offs: float,
195            highlight: bool,
196            delay2: float,
197            maxwidth: float = 70.0,
198        ) -> None:
199            Text(
200                text,
201                position=(
202                    ts_h_offs + x_offs * scale,
203                    ts_v_offset + (voffs + 15) * scale,
204                ),
205                scale=scale,
206                color=(
207                    (1.0, 0.9, 0.5, 1.0) if highlight else (0.5, 0.5, 0.6, 0.5)
208                ),
209                h_align=Text.HAlign.RIGHT,
210                v_align=Text.VAlign.CENTER,
211                maxwidth=maxwidth,
212                transition=Text.Transition.IN_LEFT,
213                transition_delay=tdelay + delay2,
214            ).autoretain()
215
216        for playerrec in player_records:
217            tdelay += 0.05
218            voffs -= spacing
219            Image(
220                playerrec.get_icon(),
221                position=(
222                    ts_h_offs - 12 * scale,
223                    ts_v_offset + (voffs + 15.0) * scale,
224                ),
225                scale=(30.0 * scale, 30.0 * scale),
226                transition=Image.Transition.IN_LEFT,
227                transition_delay=tdelay,
228            ).autoretain()
229            Text(
230                bs.Lstr(value=playerrec.getname(full=True)),
231                maxwidth=160,
232                scale=0.75 * scale,
233                position=(
234                    ts_h_offs + 10.0 * scale,
235                    ts_v_offset + (voffs + 15) * scale,
236                ),
237                h_align=Text.HAlign.LEFT,
238                v_align=Text.VAlign.CENTER,
239                color=bs.safecolor(playerrec.team.color + (1,)),
240                transition=Text.Transition.IN_LEFT,
241                transition_delay=tdelay,
242            ).autoretain()
243            _scoretxt(
244                str(playerrec.accum_kill_count),
245                180,
246                playerrec.accum_kill_count == topkillcount,
247                0.1,
248            )
249            _scoretxt(
250                str(playerrec.accum_killed_count),
251                280,
252                playerrec.accum_killed_count == topkilledcount,
253                0.1,
254            )
255            _scoretxt(
256                _get_prec_score_str(playerrec),
257                390,
258                _get_prec_score(playerrec) == top_score,
259                0.2,
260            )

Base class for score screens.

MultiTeamScoreScreenActivity(settings: dict)
18    def __init__(self, settings: dict):
19        super().__init__(settings=settings)
20        self._score_display_sound = bs.getsound('scoreHit01')
21        self._score_display_sound_small = bs.getsound('scoreHit02')
22
23        self._show_up_next: bool = True

Creates an Activity in the current bascenev1.Session.

The activity will not be actually run until bascenev1.Session.setactivity is called. 'settings' should be a dict of key/value pairs specific to the activity.

Activities should preload as much of their media/etc as possible in their constructor, but none of it should actually be used until they are transitioned in.

@override
def on_begin(self) -> None:
25    @override
26    def on_begin(self) -> None:
27        super().on_begin()
28        session = self.session
29        if self._show_up_next and isinstance(session, bs.MultiTeamSession):
30            txt = bs.Lstr(
31                value='${A}   ${B}',
32                subs=[
33                    (
34                        '${A}',
35                        bs.Lstr(
36                            resource='upNextText',
37                            subs=[
38                                ('${COUNT}', str(session.get_game_number() + 1))
39                            ],
40                        ),
41                    ),
42                    ('${B}', session.get_next_game_description()),
43                ],
44            )
45            Text(
46                txt,
47                maxwidth=900,
48                h_attach=Text.HAttach.CENTER,
49                v_attach=Text.VAttach.BOTTOM,
50                h_align=Text.HAlign.CENTER,
51                v_align=Text.VAlign.CENTER,
52                position=(0, 53),
53                flash=False,
54                color=(0.3, 0.3, 0.35, 1.0),
55                transition=Text.Transition.FADE_IN,
56                transition_delay=2.0,
57            ).autoretain()

Called once the previous Activity has finished transitioning out.

At this point the activity's initial players and teams are filled in and it should begin its actual game logic.

def show_player_scores( self, *, delay: float = 2.5, results: bascenev1.GameResults | None = None, scale: float = 1.0, x_offset: float = 0.0, y_offset: float = 0.0) -> None:
 59    def show_player_scores(
 60        self,
 61        *,
 62        delay: float = 2.5,
 63        results: bs.GameResults | None = None,
 64        scale: float = 1.0,
 65        x_offset: float = 0.0,
 66        y_offset: float = 0.0,
 67    ) -> None:
 68        """Show scores for individual players."""
 69        # pylint: disable=too-many-locals
 70        # pylint: disable=too-many-statements
 71
 72        ts_v_offset = 150.0 + y_offset
 73        ts_h_offs = 80.0 + x_offset
 74        tdelay = delay
 75        spacing = 40
 76
 77        is_free_for_all = isinstance(self.session, bs.FreeForAllSession)
 78
 79        def _get_prec_score(p_rec: bs.PlayerRecord) -> int | None:
 80            if is_free_for_all and results is not None:
 81                assert isinstance(results, bs.GameResults)
 82                assert p_rec.team.activityteam is not None
 83                val = results.get_sessionteam_score(p_rec.team)
 84                return val
 85            return p_rec.accumscore
 86
 87        def _get_prec_score_str(p_rec: bs.PlayerRecord) -> str | bs.Lstr:
 88            if is_free_for_all and results is not None:
 89                assert isinstance(results, bs.GameResults)
 90                assert p_rec.team.activityteam is not None
 91                val = results.get_sessionteam_score_str(p_rec.team)
 92                assert val is not None
 93                return val
 94            return str(p_rec.accumscore)
 95
 96        # stats.get_records() can return players that are no longer in
 97        # the game.. if we're using results we have to filter those out
 98        # (since they're not in results and that's where we pull their
 99        # scores from)
100        if results is not None:
101            assert isinstance(results, bs.GameResults)
102            player_records = []
103            valid_players = list(self.stats.get_records().items())
104
105            # noinspection PyUnresolvedReferences
106            def _get_player_score_set_entry(
107                player: bs.SessionPlayer,
108            ) -> bs.PlayerRecord | None:
109                for p_rec in valid_players:
110                    if p_rec[1].player is player:
111                        return p_rec[1]
112                return None
113
114            # Results is already sorted; just convert it into a list of
115            # score-set-entries.
116            for winnergroup in results.winnergroups:
117                for team in winnergroup.teams:
118                    if len(team.players) == 1:
119                        player_entry = _get_player_score_set_entry(
120                            team.players[0]
121                        )
122                        if player_entry is not None:
123                            player_records.append(player_entry)
124        else:
125            player_records = []
126            player_records_scores = [
127                (_get_prec_score(p), name, p)
128                for name, p in list(self.stats.get_records().items())
129            ]
130            player_records_scores.sort(reverse=True)
131            player_records = [p[2] for p in player_records_scores]
132
133        voffs = -140.0 + spacing * len(player_records) * 0.5
134
135        def _txt(
136            xoffs: float,
137            yoffs: float,
138            text: bs.Lstr,
139            *,
140            h_align: Text.HAlign = Text.HAlign.RIGHT,
141            extrascale: float = 1.0,
142            maxwidth: float | None = 120.0,
143        ) -> None:
144            Text(
145                text,
146                color=(0.5, 0.5, 0.6, 0.5),
147                position=(
148                    ts_h_offs + xoffs * scale,
149                    ts_v_offset + (voffs + yoffs + 4.0) * scale,
150                ),
151                h_align=h_align,
152                v_align=Text.VAlign.CENTER,
153                scale=0.8 * scale * extrascale,
154                maxwidth=maxwidth,
155                transition=Text.Transition.IN_LEFT,
156                transition_delay=tdelay,
157            ).autoretain()
158
159        session = self.session
160        assert isinstance(session, bs.MultiTeamSession)
161        tval = bs.Lstr(
162            resource='gameLeadersText',
163            subs=[('${COUNT}', str(session.get_game_number()))],
164        )
165        _txt(
166            180,
167            43,
168            tval,
169            h_align=Text.HAlign.CENTER,
170            extrascale=1.4,
171            maxwidth=None,
172        )
173        _txt(-15, 4, bs.Lstr(resource='playerText'), h_align=Text.HAlign.LEFT)
174        _txt(180, 4, bs.Lstr(resource='killsText'))
175        _txt(280, 4, bs.Lstr(resource='deathsText'), maxwidth=100)
176
177        score_label = 'Score' if results is None else results.score_label
178        translated = bs.Lstr(translate=('scoreNames', score_label))
179
180        _txt(390, 0, translated)
181
182        topkillcount = 0
183        topkilledcount = 99999
184        top_score = (
185            0 if not player_records else _get_prec_score(player_records[0])
186        )
187
188        for prec in player_records:
189            topkillcount = max(topkillcount, prec.accum_kill_count)
190            topkilledcount = min(topkilledcount, prec.accum_killed_count)
191
192        def _scoretxt(
193            text: str | bs.Lstr,
194            x_offs: float,
195            highlight: bool,
196            delay2: float,
197            maxwidth: float = 70.0,
198        ) -> None:
199            Text(
200                text,
201                position=(
202                    ts_h_offs + x_offs * scale,
203                    ts_v_offset + (voffs + 15) * scale,
204                ),
205                scale=scale,
206                color=(
207                    (1.0, 0.9, 0.5, 1.0) if highlight else (0.5, 0.5, 0.6, 0.5)
208                ),
209                h_align=Text.HAlign.RIGHT,
210                v_align=Text.VAlign.CENTER,
211                maxwidth=maxwidth,
212                transition=Text.Transition.IN_LEFT,
213                transition_delay=tdelay + delay2,
214            ).autoretain()
215
216        for playerrec in player_records:
217            tdelay += 0.05
218            voffs -= spacing
219            Image(
220                playerrec.get_icon(),
221                position=(
222                    ts_h_offs - 12 * scale,
223                    ts_v_offset + (voffs + 15.0) * scale,
224                ),
225                scale=(30.0 * scale, 30.0 * scale),
226                transition=Image.Transition.IN_LEFT,
227                transition_delay=tdelay,
228            ).autoretain()
229            Text(
230                bs.Lstr(value=playerrec.getname(full=True)),
231                maxwidth=160,
232                scale=0.75 * scale,
233                position=(
234                    ts_h_offs + 10.0 * scale,
235                    ts_v_offset + (voffs + 15) * scale,
236                ),
237                h_align=Text.HAlign.LEFT,
238                v_align=Text.VAlign.CENTER,
239                color=bs.safecolor(playerrec.team.color + (1,)),
240                transition=Text.Transition.IN_LEFT,
241                transition_delay=tdelay,
242            ).autoretain()
243            _scoretxt(
244                str(playerrec.accum_kill_count),
245                180,
246                playerrec.accum_kill_count == topkillcount,
247                0.1,
248            )
249            _scoretxt(
250                str(playerrec.accum_killed_count),
251                280,
252                playerrec.accum_killed_count == topkilledcount,
253                0.1,
254            )
255            _scoretxt(
256                _get_prec_score_str(playerrec),
257                390,
258                _get_prec_score(playerrec) == top_score,
259                0.2,
260            )

Show scores for individual players.