bascenev1lib.activity.multiteamvictory

Functionality related to the final screen in multi-teams sessions.

  1# Released under the MIT License. See LICENSE for details.
  2#
  3"""Functionality related to the final screen in multi-teams sessions."""
  4
  5from __future__ import annotations
  6
  7from typing import override
  8
  9import bascenev1 as bs
 10
 11from bascenev1lib.activity.multiteamscore import MultiTeamScoreScreenActivity
 12
 13
 14class TeamSeriesVictoryScoreScreenActivity(MultiTeamScoreScreenActivity):
 15    """Final score screen for a team series."""
 16
 17    # Dont' play music by default; (we do manually after a delay).
 18    default_music = None
 19
 20    def __init__(self, settings: dict):
 21        super().__init__(settings=settings)
 22        self._min_view_time = 15.0
 23        self._is_ffa = isinstance(self.session, bs.FreeForAllSession)
 24        self._allow_server_transition = True
 25        self._tips_text = None
 26        self._default_show_tips = False
 27
 28    @override
 29    def on_begin(self) -> None:
 30        # pylint: disable=too-many-branches
 31        # pylint: disable=too-many-locals
 32        # pylint: disable=too-many-statements
 33        from bascenev1lib.actor.text import Text
 34        from bascenev1lib.actor.image import Image
 35
 36        bs.set_analytics_screen(
 37            'FreeForAll Series Victory Screen'
 38            if self._is_ffa
 39            else 'Teams Series Victory Screen'
 40        )
 41        assert bs.app.classic is not None
 42        if bs.app.ui_v1.uiscale is bs.UIScale.LARGE:
 43            sval = bs.Lstr(resource='pressAnyKeyButtonPlayAgainText')
 44        else:
 45            sval = bs.Lstr(resource='pressAnyButtonPlayAgainText')
 46        self._show_up_next = False
 47        self._custom_continue_message = sval
 48        super().on_begin()
 49        winning_sessionteam = self.settings_raw['winner']
 50
 51        # Pause a moment before playing victory music.
 52        bs.timer(0.6, bs.WeakCall(self._play_victory_music))
 53        bs.timer(
 54            4.4, bs.WeakCall(self._show_winner, self.settings_raw['winner'])
 55        )
 56        bs.timer(4.6, self._score_display_sound.play)
 57
 58        # Score / Name / Player-record.
 59        player_entries: list[tuple[int, str, bs.PlayerRecord]] = []
 60
 61        # Note: for ffa, exclude players who haven't entered the game yet.
 62        if self._is_ffa:
 63            for _pkey, prec in self.stats.get_records().items():
 64                if prec.player.in_game:
 65                    player_entries.append(
 66                        (
 67                            prec.player.sessionteam.customdata['score'],
 68                            prec.getname(full=True),
 69                            prec,
 70                        )
 71                    )
 72            player_entries.sort(reverse=True, key=lambda x: x[0])
 73        else:
 74            for _pkey, prec in self.stats.get_records().items():
 75                player_entries.append((prec.score, prec.name_full, prec))
 76            player_entries.sort(reverse=True, key=lambda x: x[0])
 77
 78        ts_height = 300.0
 79        ts_h_offs = -390.0
 80        tval = 6.4
 81        t_incr = 0.12
 82
 83        always_use_first_to = bs.app.lang.get_resource(
 84            'bestOfUseFirstToInstead'
 85        )
 86
 87        session = self.session
 88        if self._is_ffa:
 89            assert isinstance(session, bs.FreeForAllSession)
 90            txt = bs.Lstr(
 91                value='${A}:',
 92                subs=[
 93                    (
 94                        '${A}',
 95                        bs.Lstr(
 96                            resource='firstToFinalText',
 97                            subs=[
 98                                (
 99                                    '${COUNT}',
100                                    str(session.get_ffa_series_length()),
101                                )
102                            ],
103                        ),
104                    )
105                ],
106            )
107        else:
108            assert isinstance(session, bs.MultiTeamSession)
109
110            # Some languages may prefer to always show 'first to X' instead of
111            # 'best of X'.
112            # FIXME: This will affect all clients connected to us even if
113            #  they're not using this language. Should try to come up
114            #  with a wording that works everywhere.
115            if always_use_first_to:
116                txt = bs.Lstr(
117                    value='${A}:',
118                    subs=[
119                        (
120                            '${A}',
121                            bs.Lstr(
122                                resource='firstToFinalText',
123                                subs=[
124                                    (
125                                        '${COUNT}',
126                                        str(
127                                            session.get_series_length() / 2 + 1
128                                        ),
129                                    )
130                                ],
131                            ),
132                        )
133                    ],
134                )
135            else:
136                txt = bs.Lstr(
137                    value='${A}:',
138                    subs=[
139                        (
140                            '${A}',
141                            bs.Lstr(
142                                resource='bestOfFinalText',
143                                subs=[
144                                    (
145                                        '${COUNT}',
146                                        str(session.get_series_length()),
147                                    )
148                                ],
149                            ),
150                        )
151                    ],
152                )
153
154        Text(
155            txt,
156            v_align=Text.VAlign.CENTER,
157            maxwidth=300,
158            color=(0.5, 0.5, 0.5, 1.0),
159            position=(0, 220),
160            scale=1.2,
161            transition=Text.Transition.IN_TOP_SLOW,
162            h_align=Text.HAlign.CENTER,
163            transition_delay=t_incr * 4,
164        ).autoretain()
165
166        win_score = (session.get_series_length() - 1) // 2 + 1
167        lose_score = 0
168        for team in self.teams:
169            if team.sessionteam.customdata['score'] != win_score:
170                lose_score = team.sessionteam.customdata['score']
171
172        if not self._is_ffa:
173            Text(
174                bs.Lstr(
175                    resource='gamesToText',
176                    subs=[
177                        ('${WINCOUNT}', str(win_score)),
178                        ('${LOSECOUNT}', str(lose_score)),
179                    ],
180                ),
181                color=(0.5, 0.5, 0.5, 1.0),
182                maxwidth=160,
183                v_align=Text.VAlign.CENTER,
184                position=(0, -215),
185                scale=1.8,
186                transition=Text.Transition.IN_LEFT,
187                h_align=Text.HAlign.CENTER,
188                transition_delay=4.8 + t_incr * 4,
189            ).autoretain()
190
191        if self._is_ffa:
192            v_extra = 120
193        else:
194            v_extra = 0
195
196        mvp: bs.PlayerRecord | None = None
197        mvp_name: str | None = None
198
199        # Show game MVP.
200        if not self._is_ffa:
201            mvp, mvp_name = None, None
202            for entry in player_entries:
203                if entry[2].team == winning_sessionteam:
204                    mvp = entry[2]
205                    mvp_name = entry[1]
206                    break
207            if mvp is not None:
208                Text(
209                    bs.Lstr(resource='mostValuablePlayerText'),
210                    color=(0.5, 0.5, 0.5, 1.0),
211                    v_align=Text.VAlign.CENTER,
212                    maxwidth=300,
213                    position=(180, ts_height / 2 + 15),
214                    transition=Text.Transition.IN_LEFT,
215                    h_align=Text.HAlign.LEFT,
216                    transition_delay=tval,
217                ).autoretain()
218                tval += 4 * t_incr
219
220                Image(
221                    mvp.get_icon(),
222                    position=(230, ts_height / 2 - 55 + 14 - 5),
223                    scale=(70, 70),
224                    transition=Image.Transition.IN_LEFT,
225                    transition_delay=tval,
226                ).autoretain()
227                assert mvp_name is not None
228                Text(
229                    bs.Lstr(value=mvp_name),
230                    position=(280, ts_height / 2 - 55 + 15 - 5),
231                    h_align=Text.HAlign.LEFT,
232                    v_align=Text.VAlign.CENTER,
233                    maxwidth=170,
234                    scale=1.3,
235                    color=bs.safecolor(mvp.team.color + (1,)),
236                    transition=Text.Transition.IN_LEFT,
237                    transition_delay=tval,
238                ).autoretain()
239                tval += 4 * t_incr
240
241        # Most violent.
242        most_kills = 0
243        for entry in player_entries:
244            if entry[2].kill_count >= most_kills:
245                mvp = entry[2]
246                mvp_name = entry[1]
247                most_kills = entry[2].kill_count
248        if mvp is not None:
249            Text(
250                bs.Lstr(resource='mostViolentPlayerText'),
251                color=(0.5, 0.5, 0.5, 1.0),
252                v_align=Text.VAlign.CENTER,
253                maxwidth=300,
254                position=(180, ts_height / 2 - 150 + v_extra + 15),
255                transition=Text.Transition.IN_LEFT,
256                h_align=Text.HAlign.LEFT,
257                transition_delay=tval,
258            ).autoretain()
259            Text(
260                bs.Lstr(
261                    value='(${A})',
262                    subs=[
263                        (
264                            '${A}',
265                            bs.Lstr(
266                                resource='killsTallyText',
267                                subs=[('${COUNT}', str(most_kills))],
268                            ),
269                        )
270                    ],
271                ),
272                position=(260, ts_height / 2 - 150 - 15 + v_extra),
273                color=(0.3, 0.3, 0.3, 1.0),
274                scale=0.6,
275                h_align=Text.HAlign.LEFT,
276                transition=Text.Transition.IN_LEFT,
277                transition_delay=tval,
278            ).autoretain()
279            tval += 4 * t_incr
280
281            Image(
282                mvp.get_icon(),
283                position=(233, ts_height / 2 - 150 - 30 - 46 + 25 + v_extra),
284                scale=(50, 50),
285                transition=Image.Transition.IN_LEFT,
286                transition_delay=tval,
287            ).autoretain()
288            assert mvp_name is not None
289            Text(
290                bs.Lstr(value=mvp_name),
291                position=(270, ts_height / 2 - 150 - 30 - 36 + v_extra + 15),
292                h_align=Text.HAlign.LEFT,
293                v_align=Text.VAlign.CENTER,
294                maxwidth=180,
295                color=bs.safecolor(mvp.team.color + (1,)),
296                transition=Text.Transition.IN_LEFT,
297                transition_delay=tval,
298            ).autoretain()
299            tval += 4 * t_incr
300
301        # Most killed.
302        most_killed = 0
303        mkp, mkp_name = None, None
304        for entry in player_entries:
305            if entry[2].killed_count >= most_killed:
306                mkp = entry[2]
307                mkp_name = entry[1]
308                most_killed = entry[2].killed_count
309        if mkp is not None:
310            Text(
311                bs.Lstr(resource='mostViolatedPlayerText'),
312                color=(0.5, 0.5, 0.5, 1.0),
313                v_align=Text.VAlign.CENTER,
314                maxwidth=300,
315                position=(180, ts_height / 2 - 300 + v_extra + 15),
316                transition=Text.Transition.IN_LEFT,
317                h_align=Text.HAlign.LEFT,
318                transition_delay=tval,
319            ).autoretain()
320            Text(
321                bs.Lstr(
322                    value='(${A})',
323                    subs=[
324                        (
325                            '${A}',
326                            bs.Lstr(
327                                resource='deathsTallyText',
328                                subs=[('${COUNT}', str(most_killed))],
329                            ),
330                        )
331                    ],
332                ),
333                position=(260, ts_height / 2 - 300 - 15 + v_extra),
334                h_align=Text.HAlign.LEFT,
335                scale=0.6,
336                color=(0.3, 0.3, 0.3, 1.0),
337                transition=Text.Transition.IN_LEFT,
338                transition_delay=tval,
339            ).autoretain()
340            tval += 4 * t_incr
341            Image(
342                mkp.get_icon(),
343                position=(233, ts_height / 2 - 300 - 30 - 46 + 25 + v_extra),
344                scale=(50, 50),
345                transition=Image.Transition.IN_LEFT,
346                transition_delay=tval,
347            ).autoretain()
348            assert mkp_name is not None
349            Text(
350                bs.Lstr(value=mkp_name),
351                position=(270, ts_height / 2 - 300 - 30 - 36 + v_extra + 15),
352                h_align=Text.HAlign.LEFT,
353                v_align=Text.VAlign.CENTER,
354                color=bs.safecolor(mkp.team.color + (1,)),
355                maxwidth=180,
356                transition=Text.Transition.IN_LEFT,
357                transition_delay=tval,
358            ).autoretain()
359            tval += 4 * t_incr
360
361        # Now show individual scores.
362        tdelay = tval
363        Text(
364            bs.Lstr(resource='finalScoresText'),
365            color=(0.5, 0.5, 0.5, 1.0),
366            position=(ts_h_offs, ts_height / 2),
367            transition=Text.Transition.IN_RIGHT,
368            transition_delay=tdelay,
369        ).autoretain()
370        tdelay += 4 * t_incr
371
372        v_offs = 0.0
373        tdelay += len(player_entries) * 8 * t_incr
374        for _score, name, prec in player_entries:
375            tdelay -= 4 * t_incr
376            v_offs -= 40
377            Text(
378                (
379                    str(prec.team.customdata['score'])
380                    if self._is_ffa
381                    else str(prec.score)
382                ),
383                color=(0.5, 0.5, 0.5, 1.0),
384                position=(ts_h_offs + 230, ts_height / 2 + v_offs),
385                h_align=Text.HAlign.RIGHT,
386                transition=Text.Transition.IN_RIGHT,
387                transition_delay=tdelay,
388            ).autoretain()
389            tdelay -= 4 * t_incr
390
391            Image(
392                prec.get_icon(),
393                position=(ts_h_offs - 72, ts_height / 2 + v_offs + 15),
394                scale=(30, 30),
395                transition=Image.Transition.IN_LEFT,
396                transition_delay=tdelay,
397            ).autoretain()
398            Text(
399                bs.Lstr(value=name),
400                position=(ts_h_offs - 50, ts_height / 2 + v_offs + 15),
401                h_align=Text.HAlign.LEFT,
402                v_align=Text.VAlign.CENTER,
403                maxwidth=180,
404                color=bs.safecolor(prec.team.color + (1,)),
405                transition=Text.Transition.IN_RIGHT,
406                transition_delay=tdelay,
407            ).autoretain()
408
409        bs.timer(15.0, bs.WeakCall(self._show_tips))
410
411    def _show_tips(self) -> None:
412        from bascenev1lib.actor.tipstext import TipsText
413
414        self._tips_text = TipsText(offs_y=70)
415
416    def _play_victory_music(self) -> None:
417        # Make sure we don't stomp on the next activity's music choice.
418        if not self.is_transitioning_out():
419            bs.setmusic(bs.MusicType.VICTORY)
420
421    def _show_winner(self, team: bs.SessionTeam) -> None:
422        from bascenev1lib.actor.image import Image
423        from bascenev1lib.actor.zoomtext import ZoomText
424
425        if not self._is_ffa:
426            offs_v = 0.0
427            ZoomText(
428                team.name,
429                position=(0, 97),
430                color=team.color,
431                scale=1.15,
432                jitter=1.0,
433                maxwidth=250,
434            ).autoretain()
435        else:
436            offs_v = -80.0
437            if len(team.players) == 1:
438                i = Image(
439                    team.players[0].get_icon(),
440                    position=(0, 143),
441                    scale=(100, 100),
442                ).autoretain()
443                assert i.node
444                bs.animate(i.node, 'opacity', {0.0: 0.0, 0.25: 1.0})
445                ZoomText(
446                    bs.Lstr(
447                        value=team.players[0].getname(full=True, icon=False)
448                    ),
449                    position=(0, 97 + offs_v),
450                    color=team.color,
451                    scale=1.15,
452                    jitter=1.0,
453                    maxwidth=250,
454                ).autoretain()
455
456        s_extra = 1.0 if self._is_ffa else 1.0
457
458        # Some languages say "FOO WINS" differently for teams vs players.
459        if isinstance(self.session, bs.FreeForAllSession):
460            wins_resource = 'seriesWinLine1PlayerText'
461        else:
462            wins_resource = 'seriesWinLine1TeamText'
463        wins_text = bs.Lstr(resource=wins_resource)
464
465        # Temp - if these come up as the english default, fall-back to the
466        # unified old form which is more likely to be translated.
467        ZoomText(
468            wins_text,
469            position=(0, -10 + offs_v),
470            color=team.color,
471            scale=0.65 * s_extra,
472            jitter=1.0,
473            maxwidth=250,
474        ).autoretain()
475        ZoomText(
476            bs.Lstr(resource='seriesWinLine2Text'),
477            position=(0, -110 + offs_v),
478            scale=1.0 * s_extra,
479            color=team.color,
480            jitter=1.0,
481            maxwidth=250,
482        ).autoretain()
class TeamSeriesVictoryScoreScreenActivity(bascenev1._activity.Activity[bascenev1._player.EmptyPlayer, bascenev1._team.EmptyTeam]):
 15class TeamSeriesVictoryScoreScreenActivity(MultiTeamScoreScreenActivity):
 16    """Final score screen for a team series."""
 17
 18    # Dont' play music by default; (we do manually after a delay).
 19    default_music = None
 20
 21    def __init__(self, settings: dict):
 22        super().__init__(settings=settings)
 23        self._min_view_time = 15.0
 24        self._is_ffa = isinstance(self.session, bs.FreeForAllSession)
 25        self._allow_server_transition = True
 26        self._tips_text = None
 27        self._default_show_tips = False
 28
 29    @override
 30    def on_begin(self) -> None:
 31        # pylint: disable=too-many-branches
 32        # pylint: disable=too-many-locals
 33        # pylint: disable=too-many-statements
 34        from bascenev1lib.actor.text import Text
 35        from bascenev1lib.actor.image import Image
 36
 37        bs.set_analytics_screen(
 38            'FreeForAll Series Victory Screen'
 39            if self._is_ffa
 40            else 'Teams Series Victory Screen'
 41        )
 42        assert bs.app.classic is not None
 43        if bs.app.ui_v1.uiscale is bs.UIScale.LARGE:
 44            sval = bs.Lstr(resource='pressAnyKeyButtonPlayAgainText')
 45        else:
 46            sval = bs.Lstr(resource='pressAnyButtonPlayAgainText')
 47        self._show_up_next = False
 48        self._custom_continue_message = sval
 49        super().on_begin()
 50        winning_sessionteam = self.settings_raw['winner']
 51
 52        # Pause a moment before playing victory music.
 53        bs.timer(0.6, bs.WeakCall(self._play_victory_music))
 54        bs.timer(
 55            4.4, bs.WeakCall(self._show_winner, self.settings_raw['winner'])
 56        )
 57        bs.timer(4.6, self._score_display_sound.play)
 58
 59        # Score / Name / Player-record.
 60        player_entries: list[tuple[int, str, bs.PlayerRecord]] = []
 61
 62        # Note: for ffa, exclude players who haven't entered the game yet.
 63        if self._is_ffa:
 64            for _pkey, prec in self.stats.get_records().items():
 65                if prec.player.in_game:
 66                    player_entries.append(
 67                        (
 68                            prec.player.sessionteam.customdata['score'],
 69                            prec.getname(full=True),
 70                            prec,
 71                        )
 72                    )
 73            player_entries.sort(reverse=True, key=lambda x: x[0])
 74        else:
 75            for _pkey, prec in self.stats.get_records().items():
 76                player_entries.append((prec.score, prec.name_full, prec))
 77            player_entries.sort(reverse=True, key=lambda x: x[0])
 78
 79        ts_height = 300.0
 80        ts_h_offs = -390.0
 81        tval = 6.4
 82        t_incr = 0.12
 83
 84        always_use_first_to = bs.app.lang.get_resource(
 85            'bestOfUseFirstToInstead'
 86        )
 87
 88        session = self.session
 89        if self._is_ffa:
 90            assert isinstance(session, bs.FreeForAllSession)
 91            txt = bs.Lstr(
 92                value='${A}:',
 93                subs=[
 94                    (
 95                        '${A}',
 96                        bs.Lstr(
 97                            resource='firstToFinalText',
 98                            subs=[
 99                                (
100                                    '${COUNT}',
101                                    str(session.get_ffa_series_length()),
102                                )
103                            ],
104                        ),
105                    )
106                ],
107            )
108        else:
109            assert isinstance(session, bs.MultiTeamSession)
110
111            # Some languages may prefer to always show 'first to X' instead of
112            # 'best of X'.
113            # FIXME: This will affect all clients connected to us even if
114            #  they're not using this language. Should try to come up
115            #  with a wording that works everywhere.
116            if always_use_first_to:
117                txt = bs.Lstr(
118                    value='${A}:',
119                    subs=[
120                        (
121                            '${A}',
122                            bs.Lstr(
123                                resource='firstToFinalText',
124                                subs=[
125                                    (
126                                        '${COUNT}',
127                                        str(
128                                            session.get_series_length() / 2 + 1
129                                        ),
130                                    )
131                                ],
132                            ),
133                        )
134                    ],
135                )
136            else:
137                txt = bs.Lstr(
138                    value='${A}:',
139                    subs=[
140                        (
141                            '${A}',
142                            bs.Lstr(
143                                resource='bestOfFinalText',
144                                subs=[
145                                    (
146                                        '${COUNT}',
147                                        str(session.get_series_length()),
148                                    )
149                                ],
150                            ),
151                        )
152                    ],
153                )
154
155        Text(
156            txt,
157            v_align=Text.VAlign.CENTER,
158            maxwidth=300,
159            color=(0.5, 0.5, 0.5, 1.0),
160            position=(0, 220),
161            scale=1.2,
162            transition=Text.Transition.IN_TOP_SLOW,
163            h_align=Text.HAlign.CENTER,
164            transition_delay=t_incr * 4,
165        ).autoretain()
166
167        win_score = (session.get_series_length() - 1) // 2 + 1
168        lose_score = 0
169        for team in self.teams:
170            if team.sessionteam.customdata['score'] != win_score:
171                lose_score = team.sessionteam.customdata['score']
172
173        if not self._is_ffa:
174            Text(
175                bs.Lstr(
176                    resource='gamesToText',
177                    subs=[
178                        ('${WINCOUNT}', str(win_score)),
179                        ('${LOSECOUNT}', str(lose_score)),
180                    ],
181                ),
182                color=(0.5, 0.5, 0.5, 1.0),
183                maxwidth=160,
184                v_align=Text.VAlign.CENTER,
185                position=(0, -215),
186                scale=1.8,
187                transition=Text.Transition.IN_LEFT,
188                h_align=Text.HAlign.CENTER,
189                transition_delay=4.8 + t_incr * 4,
190            ).autoretain()
191
192        if self._is_ffa:
193            v_extra = 120
194        else:
195            v_extra = 0
196
197        mvp: bs.PlayerRecord | None = None
198        mvp_name: str | None = None
199
200        # Show game MVP.
201        if not self._is_ffa:
202            mvp, mvp_name = None, None
203            for entry in player_entries:
204                if entry[2].team == winning_sessionteam:
205                    mvp = entry[2]
206                    mvp_name = entry[1]
207                    break
208            if mvp is not None:
209                Text(
210                    bs.Lstr(resource='mostValuablePlayerText'),
211                    color=(0.5, 0.5, 0.5, 1.0),
212                    v_align=Text.VAlign.CENTER,
213                    maxwidth=300,
214                    position=(180, ts_height / 2 + 15),
215                    transition=Text.Transition.IN_LEFT,
216                    h_align=Text.HAlign.LEFT,
217                    transition_delay=tval,
218                ).autoretain()
219                tval += 4 * t_incr
220
221                Image(
222                    mvp.get_icon(),
223                    position=(230, ts_height / 2 - 55 + 14 - 5),
224                    scale=(70, 70),
225                    transition=Image.Transition.IN_LEFT,
226                    transition_delay=tval,
227                ).autoretain()
228                assert mvp_name is not None
229                Text(
230                    bs.Lstr(value=mvp_name),
231                    position=(280, ts_height / 2 - 55 + 15 - 5),
232                    h_align=Text.HAlign.LEFT,
233                    v_align=Text.VAlign.CENTER,
234                    maxwidth=170,
235                    scale=1.3,
236                    color=bs.safecolor(mvp.team.color + (1,)),
237                    transition=Text.Transition.IN_LEFT,
238                    transition_delay=tval,
239                ).autoretain()
240                tval += 4 * t_incr
241
242        # Most violent.
243        most_kills = 0
244        for entry in player_entries:
245            if entry[2].kill_count >= most_kills:
246                mvp = entry[2]
247                mvp_name = entry[1]
248                most_kills = entry[2].kill_count
249        if mvp is not None:
250            Text(
251                bs.Lstr(resource='mostViolentPlayerText'),
252                color=(0.5, 0.5, 0.5, 1.0),
253                v_align=Text.VAlign.CENTER,
254                maxwidth=300,
255                position=(180, ts_height / 2 - 150 + v_extra + 15),
256                transition=Text.Transition.IN_LEFT,
257                h_align=Text.HAlign.LEFT,
258                transition_delay=tval,
259            ).autoretain()
260            Text(
261                bs.Lstr(
262                    value='(${A})',
263                    subs=[
264                        (
265                            '${A}',
266                            bs.Lstr(
267                                resource='killsTallyText',
268                                subs=[('${COUNT}', str(most_kills))],
269                            ),
270                        )
271                    ],
272                ),
273                position=(260, ts_height / 2 - 150 - 15 + v_extra),
274                color=(0.3, 0.3, 0.3, 1.0),
275                scale=0.6,
276                h_align=Text.HAlign.LEFT,
277                transition=Text.Transition.IN_LEFT,
278                transition_delay=tval,
279            ).autoretain()
280            tval += 4 * t_incr
281
282            Image(
283                mvp.get_icon(),
284                position=(233, ts_height / 2 - 150 - 30 - 46 + 25 + v_extra),
285                scale=(50, 50),
286                transition=Image.Transition.IN_LEFT,
287                transition_delay=tval,
288            ).autoretain()
289            assert mvp_name is not None
290            Text(
291                bs.Lstr(value=mvp_name),
292                position=(270, ts_height / 2 - 150 - 30 - 36 + v_extra + 15),
293                h_align=Text.HAlign.LEFT,
294                v_align=Text.VAlign.CENTER,
295                maxwidth=180,
296                color=bs.safecolor(mvp.team.color + (1,)),
297                transition=Text.Transition.IN_LEFT,
298                transition_delay=tval,
299            ).autoretain()
300            tval += 4 * t_incr
301
302        # Most killed.
303        most_killed = 0
304        mkp, mkp_name = None, None
305        for entry in player_entries:
306            if entry[2].killed_count >= most_killed:
307                mkp = entry[2]
308                mkp_name = entry[1]
309                most_killed = entry[2].killed_count
310        if mkp is not None:
311            Text(
312                bs.Lstr(resource='mostViolatedPlayerText'),
313                color=(0.5, 0.5, 0.5, 1.0),
314                v_align=Text.VAlign.CENTER,
315                maxwidth=300,
316                position=(180, ts_height / 2 - 300 + v_extra + 15),
317                transition=Text.Transition.IN_LEFT,
318                h_align=Text.HAlign.LEFT,
319                transition_delay=tval,
320            ).autoretain()
321            Text(
322                bs.Lstr(
323                    value='(${A})',
324                    subs=[
325                        (
326                            '${A}',
327                            bs.Lstr(
328                                resource='deathsTallyText',
329                                subs=[('${COUNT}', str(most_killed))],
330                            ),
331                        )
332                    ],
333                ),
334                position=(260, ts_height / 2 - 300 - 15 + v_extra),
335                h_align=Text.HAlign.LEFT,
336                scale=0.6,
337                color=(0.3, 0.3, 0.3, 1.0),
338                transition=Text.Transition.IN_LEFT,
339                transition_delay=tval,
340            ).autoretain()
341            tval += 4 * t_incr
342            Image(
343                mkp.get_icon(),
344                position=(233, ts_height / 2 - 300 - 30 - 46 + 25 + v_extra),
345                scale=(50, 50),
346                transition=Image.Transition.IN_LEFT,
347                transition_delay=tval,
348            ).autoretain()
349            assert mkp_name is not None
350            Text(
351                bs.Lstr(value=mkp_name),
352                position=(270, ts_height / 2 - 300 - 30 - 36 + v_extra + 15),
353                h_align=Text.HAlign.LEFT,
354                v_align=Text.VAlign.CENTER,
355                color=bs.safecolor(mkp.team.color + (1,)),
356                maxwidth=180,
357                transition=Text.Transition.IN_LEFT,
358                transition_delay=tval,
359            ).autoretain()
360            tval += 4 * t_incr
361
362        # Now show individual scores.
363        tdelay = tval
364        Text(
365            bs.Lstr(resource='finalScoresText'),
366            color=(0.5, 0.5, 0.5, 1.0),
367            position=(ts_h_offs, ts_height / 2),
368            transition=Text.Transition.IN_RIGHT,
369            transition_delay=tdelay,
370        ).autoretain()
371        tdelay += 4 * t_incr
372
373        v_offs = 0.0
374        tdelay += len(player_entries) * 8 * t_incr
375        for _score, name, prec in player_entries:
376            tdelay -= 4 * t_incr
377            v_offs -= 40
378            Text(
379                (
380                    str(prec.team.customdata['score'])
381                    if self._is_ffa
382                    else str(prec.score)
383                ),
384                color=(0.5, 0.5, 0.5, 1.0),
385                position=(ts_h_offs + 230, ts_height / 2 + v_offs),
386                h_align=Text.HAlign.RIGHT,
387                transition=Text.Transition.IN_RIGHT,
388                transition_delay=tdelay,
389            ).autoretain()
390            tdelay -= 4 * t_incr
391
392            Image(
393                prec.get_icon(),
394                position=(ts_h_offs - 72, ts_height / 2 + v_offs + 15),
395                scale=(30, 30),
396                transition=Image.Transition.IN_LEFT,
397                transition_delay=tdelay,
398            ).autoretain()
399            Text(
400                bs.Lstr(value=name),
401                position=(ts_h_offs - 50, ts_height / 2 + v_offs + 15),
402                h_align=Text.HAlign.LEFT,
403                v_align=Text.VAlign.CENTER,
404                maxwidth=180,
405                color=bs.safecolor(prec.team.color + (1,)),
406                transition=Text.Transition.IN_RIGHT,
407                transition_delay=tdelay,
408            ).autoretain()
409
410        bs.timer(15.0, bs.WeakCall(self._show_tips))
411
412    def _show_tips(self) -> None:
413        from bascenev1lib.actor.tipstext import TipsText
414
415        self._tips_text = TipsText(offs_y=70)
416
417    def _play_victory_music(self) -> None:
418        # Make sure we don't stomp on the next activity's music choice.
419        if not self.is_transitioning_out():
420            bs.setmusic(bs.MusicType.VICTORY)
421
422    def _show_winner(self, team: bs.SessionTeam) -> None:
423        from bascenev1lib.actor.image import Image
424        from bascenev1lib.actor.zoomtext import ZoomText
425
426        if not self._is_ffa:
427            offs_v = 0.0
428            ZoomText(
429                team.name,
430                position=(0, 97),
431                color=team.color,
432                scale=1.15,
433                jitter=1.0,
434                maxwidth=250,
435            ).autoretain()
436        else:
437            offs_v = -80.0
438            if len(team.players) == 1:
439                i = Image(
440                    team.players[0].get_icon(),
441                    position=(0, 143),
442                    scale=(100, 100),
443                ).autoretain()
444                assert i.node
445                bs.animate(i.node, 'opacity', {0.0: 0.0, 0.25: 1.0})
446                ZoomText(
447                    bs.Lstr(
448                        value=team.players[0].getname(full=True, icon=False)
449                    ),
450                    position=(0, 97 + offs_v),
451                    color=team.color,
452                    scale=1.15,
453                    jitter=1.0,
454                    maxwidth=250,
455                ).autoretain()
456
457        s_extra = 1.0 if self._is_ffa else 1.0
458
459        # Some languages say "FOO WINS" differently for teams vs players.
460        if isinstance(self.session, bs.FreeForAllSession):
461            wins_resource = 'seriesWinLine1PlayerText'
462        else:
463            wins_resource = 'seriesWinLine1TeamText'
464        wins_text = bs.Lstr(resource=wins_resource)
465
466        # Temp - if these come up as the english default, fall-back to the
467        # unified old form which is more likely to be translated.
468        ZoomText(
469            wins_text,
470            position=(0, -10 + offs_v),
471            color=team.color,
472            scale=0.65 * s_extra,
473            jitter=1.0,
474            maxwidth=250,
475        ).autoretain()
476        ZoomText(
477            bs.Lstr(resource='seriesWinLine2Text'),
478            position=(0, -110 + offs_v),
479            scale=1.0 * s_extra,
480            color=team.color,
481            jitter=1.0,
482            maxwidth=250,
483        ).autoretain()

Final score screen for a team series.

TeamSeriesVictoryScoreScreenActivity(settings: dict)
21    def __init__(self, settings: dict):
22        super().__init__(settings=settings)
23        self._min_view_time = 15.0
24        self._is_ffa = isinstance(self.session, bs.FreeForAllSession)
25        self._allow_server_transition = True
26        self._tips_text = None
27        self._default_show_tips = False

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.

default_music = None
@override
def on_begin(self) -> None:
 29    @override
 30    def on_begin(self) -> None:
 31        # pylint: disable=too-many-branches
 32        # pylint: disable=too-many-locals
 33        # pylint: disable=too-many-statements
 34        from bascenev1lib.actor.text import Text
 35        from bascenev1lib.actor.image import Image
 36
 37        bs.set_analytics_screen(
 38            'FreeForAll Series Victory Screen'
 39            if self._is_ffa
 40            else 'Teams Series Victory Screen'
 41        )
 42        assert bs.app.classic is not None
 43        if bs.app.ui_v1.uiscale is bs.UIScale.LARGE:
 44            sval = bs.Lstr(resource='pressAnyKeyButtonPlayAgainText')
 45        else:
 46            sval = bs.Lstr(resource='pressAnyButtonPlayAgainText')
 47        self._show_up_next = False
 48        self._custom_continue_message = sval
 49        super().on_begin()
 50        winning_sessionteam = self.settings_raw['winner']
 51
 52        # Pause a moment before playing victory music.
 53        bs.timer(0.6, bs.WeakCall(self._play_victory_music))
 54        bs.timer(
 55            4.4, bs.WeakCall(self._show_winner, self.settings_raw['winner'])
 56        )
 57        bs.timer(4.6, self._score_display_sound.play)
 58
 59        # Score / Name / Player-record.
 60        player_entries: list[tuple[int, str, bs.PlayerRecord]] = []
 61
 62        # Note: for ffa, exclude players who haven't entered the game yet.
 63        if self._is_ffa:
 64            for _pkey, prec in self.stats.get_records().items():
 65                if prec.player.in_game:
 66                    player_entries.append(
 67                        (
 68                            prec.player.sessionteam.customdata['score'],
 69                            prec.getname(full=True),
 70                            prec,
 71                        )
 72                    )
 73            player_entries.sort(reverse=True, key=lambda x: x[0])
 74        else:
 75            for _pkey, prec in self.stats.get_records().items():
 76                player_entries.append((prec.score, prec.name_full, prec))
 77            player_entries.sort(reverse=True, key=lambda x: x[0])
 78
 79        ts_height = 300.0
 80        ts_h_offs = -390.0
 81        tval = 6.4
 82        t_incr = 0.12
 83
 84        always_use_first_to = bs.app.lang.get_resource(
 85            'bestOfUseFirstToInstead'
 86        )
 87
 88        session = self.session
 89        if self._is_ffa:
 90            assert isinstance(session, bs.FreeForAllSession)
 91            txt = bs.Lstr(
 92                value='${A}:',
 93                subs=[
 94                    (
 95                        '${A}',
 96                        bs.Lstr(
 97                            resource='firstToFinalText',
 98                            subs=[
 99                                (
100                                    '${COUNT}',
101                                    str(session.get_ffa_series_length()),
102                                )
103                            ],
104                        ),
105                    )
106                ],
107            )
108        else:
109            assert isinstance(session, bs.MultiTeamSession)
110
111            # Some languages may prefer to always show 'first to X' instead of
112            # 'best of X'.
113            # FIXME: This will affect all clients connected to us even if
114            #  they're not using this language. Should try to come up
115            #  with a wording that works everywhere.
116            if always_use_first_to:
117                txt = bs.Lstr(
118                    value='${A}:',
119                    subs=[
120                        (
121                            '${A}',
122                            bs.Lstr(
123                                resource='firstToFinalText',
124                                subs=[
125                                    (
126                                        '${COUNT}',
127                                        str(
128                                            session.get_series_length() / 2 + 1
129                                        ),
130                                    )
131                                ],
132                            ),
133                        )
134                    ],
135                )
136            else:
137                txt = bs.Lstr(
138                    value='${A}:',
139                    subs=[
140                        (
141                            '${A}',
142                            bs.Lstr(
143                                resource='bestOfFinalText',
144                                subs=[
145                                    (
146                                        '${COUNT}',
147                                        str(session.get_series_length()),
148                                    )
149                                ],
150                            ),
151                        )
152                    ],
153                )
154
155        Text(
156            txt,
157            v_align=Text.VAlign.CENTER,
158            maxwidth=300,
159            color=(0.5, 0.5, 0.5, 1.0),
160            position=(0, 220),
161            scale=1.2,
162            transition=Text.Transition.IN_TOP_SLOW,
163            h_align=Text.HAlign.CENTER,
164            transition_delay=t_incr * 4,
165        ).autoretain()
166
167        win_score = (session.get_series_length() - 1) // 2 + 1
168        lose_score = 0
169        for team in self.teams:
170            if team.sessionteam.customdata['score'] != win_score:
171                lose_score = team.sessionteam.customdata['score']
172
173        if not self._is_ffa:
174            Text(
175                bs.Lstr(
176                    resource='gamesToText',
177                    subs=[
178                        ('${WINCOUNT}', str(win_score)),
179                        ('${LOSECOUNT}', str(lose_score)),
180                    ],
181                ),
182                color=(0.5, 0.5, 0.5, 1.0),
183                maxwidth=160,
184                v_align=Text.VAlign.CENTER,
185                position=(0, -215),
186                scale=1.8,
187                transition=Text.Transition.IN_LEFT,
188                h_align=Text.HAlign.CENTER,
189                transition_delay=4.8 + t_incr * 4,
190            ).autoretain()
191
192        if self._is_ffa:
193            v_extra = 120
194        else:
195            v_extra = 0
196
197        mvp: bs.PlayerRecord | None = None
198        mvp_name: str | None = None
199
200        # Show game MVP.
201        if not self._is_ffa:
202            mvp, mvp_name = None, None
203            for entry in player_entries:
204                if entry[2].team == winning_sessionteam:
205                    mvp = entry[2]
206                    mvp_name = entry[1]
207                    break
208            if mvp is not None:
209                Text(
210                    bs.Lstr(resource='mostValuablePlayerText'),
211                    color=(0.5, 0.5, 0.5, 1.0),
212                    v_align=Text.VAlign.CENTER,
213                    maxwidth=300,
214                    position=(180, ts_height / 2 + 15),
215                    transition=Text.Transition.IN_LEFT,
216                    h_align=Text.HAlign.LEFT,
217                    transition_delay=tval,
218                ).autoretain()
219                tval += 4 * t_incr
220
221                Image(
222                    mvp.get_icon(),
223                    position=(230, ts_height / 2 - 55 + 14 - 5),
224                    scale=(70, 70),
225                    transition=Image.Transition.IN_LEFT,
226                    transition_delay=tval,
227                ).autoretain()
228                assert mvp_name is not None
229                Text(
230                    bs.Lstr(value=mvp_name),
231                    position=(280, ts_height / 2 - 55 + 15 - 5),
232                    h_align=Text.HAlign.LEFT,
233                    v_align=Text.VAlign.CENTER,
234                    maxwidth=170,
235                    scale=1.3,
236                    color=bs.safecolor(mvp.team.color + (1,)),
237                    transition=Text.Transition.IN_LEFT,
238                    transition_delay=tval,
239                ).autoretain()
240                tval += 4 * t_incr
241
242        # Most violent.
243        most_kills = 0
244        for entry in player_entries:
245            if entry[2].kill_count >= most_kills:
246                mvp = entry[2]
247                mvp_name = entry[1]
248                most_kills = entry[2].kill_count
249        if mvp is not None:
250            Text(
251                bs.Lstr(resource='mostViolentPlayerText'),
252                color=(0.5, 0.5, 0.5, 1.0),
253                v_align=Text.VAlign.CENTER,
254                maxwidth=300,
255                position=(180, ts_height / 2 - 150 + v_extra + 15),
256                transition=Text.Transition.IN_LEFT,
257                h_align=Text.HAlign.LEFT,
258                transition_delay=tval,
259            ).autoretain()
260            Text(
261                bs.Lstr(
262                    value='(${A})',
263                    subs=[
264                        (
265                            '${A}',
266                            bs.Lstr(
267                                resource='killsTallyText',
268                                subs=[('${COUNT}', str(most_kills))],
269                            ),
270                        )
271                    ],
272                ),
273                position=(260, ts_height / 2 - 150 - 15 + v_extra),
274                color=(0.3, 0.3, 0.3, 1.0),
275                scale=0.6,
276                h_align=Text.HAlign.LEFT,
277                transition=Text.Transition.IN_LEFT,
278                transition_delay=tval,
279            ).autoretain()
280            tval += 4 * t_incr
281
282            Image(
283                mvp.get_icon(),
284                position=(233, ts_height / 2 - 150 - 30 - 46 + 25 + v_extra),
285                scale=(50, 50),
286                transition=Image.Transition.IN_LEFT,
287                transition_delay=tval,
288            ).autoretain()
289            assert mvp_name is not None
290            Text(
291                bs.Lstr(value=mvp_name),
292                position=(270, ts_height / 2 - 150 - 30 - 36 + v_extra + 15),
293                h_align=Text.HAlign.LEFT,
294                v_align=Text.VAlign.CENTER,
295                maxwidth=180,
296                color=bs.safecolor(mvp.team.color + (1,)),
297                transition=Text.Transition.IN_LEFT,
298                transition_delay=tval,
299            ).autoretain()
300            tval += 4 * t_incr
301
302        # Most killed.
303        most_killed = 0
304        mkp, mkp_name = None, None
305        for entry in player_entries:
306            if entry[2].killed_count >= most_killed:
307                mkp = entry[2]
308                mkp_name = entry[1]
309                most_killed = entry[2].killed_count
310        if mkp is not None:
311            Text(
312                bs.Lstr(resource='mostViolatedPlayerText'),
313                color=(0.5, 0.5, 0.5, 1.0),
314                v_align=Text.VAlign.CENTER,
315                maxwidth=300,
316                position=(180, ts_height / 2 - 300 + v_extra + 15),
317                transition=Text.Transition.IN_LEFT,
318                h_align=Text.HAlign.LEFT,
319                transition_delay=tval,
320            ).autoretain()
321            Text(
322                bs.Lstr(
323                    value='(${A})',
324                    subs=[
325                        (
326                            '${A}',
327                            bs.Lstr(
328                                resource='deathsTallyText',
329                                subs=[('${COUNT}', str(most_killed))],
330                            ),
331                        )
332                    ],
333                ),
334                position=(260, ts_height / 2 - 300 - 15 + v_extra),
335                h_align=Text.HAlign.LEFT,
336                scale=0.6,
337                color=(0.3, 0.3, 0.3, 1.0),
338                transition=Text.Transition.IN_LEFT,
339                transition_delay=tval,
340            ).autoretain()
341            tval += 4 * t_incr
342            Image(
343                mkp.get_icon(),
344                position=(233, ts_height / 2 - 300 - 30 - 46 + 25 + v_extra),
345                scale=(50, 50),
346                transition=Image.Transition.IN_LEFT,
347                transition_delay=tval,
348            ).autoretain()
349            assert mkp_name is not None
350            Text(
351                bs.Lstr(value=mkp_name),
352                position=(270, ts_height / 2 - 300 - 30 - 36 + v_extra + 15),
353                h_align=Text.HAlign.LEFT,
354                v_align=Text.VAlign.CENTER,
355                color=bs.safecolor(mkp.team.color + (1,)),
356                maxwidth=180,
357                transition=Text.Transition.IN_LEFT,
358                transition_delay=tval,
359            ).autoretain()
360            tval += 4 * t_incr
361
362        # Now show individual scores.
363        tdelay = tval
364        Text(
365            bs.Lstr(resource='finalScoresText'),
366            color=(0.5, 0.5, 0.5, 1.0),
367            position=(ts_h_offs, ts_height / 2),
368            transition=Text.Transition.IN_RIGHT,
369            transition_delay=tdelay,
370        ).autoretain()
371        tdelay += 4 * t_incr
372
373        v_offs = 0.0
374        tdelay += len(player_entries) * 8 * t_incr
375        for _score, name, prec in player_entries:
376            tdelay -= 4 * t_incr
377            v_offs -= 40
378            Text(
379                (
380                    str(prec.team.customdata['score'])
381                    if self._is_ffa
382                    else str(prec.score)
383                ),
384                color=(0.5, 0.5, 0.5, 1.0),
385                position=(ts_h_offs + 230, ts_height / 2 + v_offs),
386                h_align=Text.HAlign.RIGHT,
387                transition=Text.Transition.IN_RIGHT,
388                transition_delay=tdelay,
389            ).autoretain()
390            tdelay -= 4 * t_incr
391
392            Image(
393                prec.get_icon(),
394                position=(ts_h_offs - 72, ts_height / 2 + v_offs + 15),
395                scale=(30, 30),
396                transition=Image.Transition.IN_LEFT,
397                transition_delay=tdelay,
398            ).autoretain()
399            Text(
400                bs.Lstr(value=name),
401                position=(ts_h_offs - 50, ts_height / 2 + v_offs + 15),
402                h_align=Text.HAlign.LEFT,
403                v_align=Text.VAlign.CENTER,
404                maxwidth=180,
405                color=bs.safecolor(prec.team.color + (1,)),
406                transition=Text.Transition.IN_RIGHT,
407                transition_delay=tdelay,
408            ).autoretain()
409
410        bs.timer(15.0, bs.WeakCall(self._show_tips))

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.

Inherited Members
bascenev1lib.activity.multiteamscore.MultiTeamScoreScreenActivity
show_player_scores
bascenev1._activitytypes.ScoreScreenActivity
transition_time
inherits_tint
inherits_vr_camera_offset
use_fixed_vr_overlay
on_player_join
on_transition_in
bascenev1._activity.Activity
settings_raw
teams
players
announce_player_deaths
is_joining_activity
allow_pausing
allow_kick_idle_players
slow_motion
inherits_slow_motion
inherits_music
inherits_vr_overlay_center
allow_mid_activity_joins
can_show_ad_on_death
paused_text
preloads
lobby
context
globalsnode
stats
on_expire
customdata
expired
playertype
teamtype
retain_actor
add_actor_weak_ref
session
on_player_leave
on_team_join
on_team_leave
on_transition_out
handlemessage
has_transitioned_in
has_begun
has_ended
is_transitioning_out
transition_out
end
create_player
create_team
bascenev1._dependency.DependencyComponent
dep_is_present
get_dynamic_deps