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

Final score screen for a team series.

TeamSeriesVictoryScoreScreenActivity(settings: dict)
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

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:
 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                str(prec.team.customdata['score'])
379                if self._is_ffa
380                else str(prec.score),
381                color=(0.5, 0.5, 0.5, 1.0),
382                position=(ts_h_offs + 230, ts_height / 2 + v_offs),
383                h_align=Text.HAlign.RIGHT,
384                transition=Text.Transition.IN_RIGHT,
385                transition_delay=tdelay,
386            ).autoretain()
387            tdelay -= 4 * t_incr
388
389            Image(
390                prec.get_icon(),
391                position=(ts_h_offs - 72, ts_height / 2 + v_offs + 15),
392                scale=(30, 30),
393                transition=Image.Transition.IN_LEFT,
394                transition_delay=tdelay,
395            ).autoretain()
396            Text(
397                bs.Lstr(value=name),
398                position=(ts_h_offs - 50, ts_height / 2 + v_offs + 15),
399                h_align=Text.HAlign.LEFT,
400                v_align=Text.VAlign.CENTER,
401                maxwidth=180,
402                color=bs.safecolor(prec.team.color + (1,)),
403                transition=Text.Transition.IN_RIGHT,
404                transition_delay=tdelay,
405            ).autoretain()
406
407        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