bastd.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 TYPE_CHECKING
  8
  9import ba
 10from bastd.activity.multiteamscore import MultiTeamScoreScreenActivity
 11
 12if TYPE_CHECKING:
 13    pass
 14
 15
 16class TeamSeriesVictoryScoreScreenActivity(MultiTeamScoreScreenActivity):
 17    """Final score screen for a team series."""
 18
 19    # Dont' play music by default; (we do manually after a delay).
 20    default_music = None
 21
 22    def __init__(self, settings: dict):
 23        super().__init__(settings=settings)
 24        self._min_view_time = 15.0
 25        self._is_ffa = isinstance(self.session, ba.FreeForAllSession)
 26        self._allow_server_transition = True
 27        self._tips_text = None
 28        self._default_show_tips = False
 29
 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 bastd.actor.text import Text
 35        from bastd.actor.image import Image
 36
 37        ba.set_analytics_screen(
 38            'FreeForAll Series Victory Screen'
 39            if self._is_ffa
 40            else 'Teams Series Victory Screen'
 41        )
 42        if ba.app.ui.uiscale is ba.UIScale.LARGE:
 43            sval = ba.Lstr(resource='pressAnyKeyButtonPlayAgainText')
 44        else:
 45            sval = ba.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        ba.timer(0.6, ba.WeakCall(self._play_victory_music))
 53        ba.timer(
 54            4.4, ba.WeakCall(self._show_winner, self.settings_raw['winner'])
 55        )
 56        ba.timer(4.6, ba.Call(ba.playsound, self._score_display_sound))
 57
 58        # Score / Name / Player-record.
 59        player_entries: list[tuple[int, str, ba.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 = ba.app.lang.get_resource(
 84            'bestOfUseFirstToInstead'
 85        )
 86
 87        session = self.session
 88        if self._is_ffa:
 89            assert isinstance(session, ba.FreeForAllSession)
 90            txt = ba.Lstr(
 91                value='${A}:',
 92                subs=[
 93                    (
 94                        '${A}',
 95                        ba.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, ba.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 = ba.Lstr(
117                    value='${A}:',
118                    subs=[
119                        (
120                            '${A}',
121                            ba.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 = ba.Lstr(
137                    value='${A}:',
138                    subs=[
139                        (
140                            '${A}',
141                            ba.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                ba.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: ba.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                    ba.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                    ba.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=ba.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                ba.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                ba.Lstr(
261                    value='(${A})',
262                    subs=[
263                        (
264                            '${A}',
265                            ba.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                ba.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=ba.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                ba.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                ba.Lstr(
322                    value='(${A})',
323                    subs=[
324                        (
325                            '${A}',
326                            ba.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                ba.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=ba.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            ba.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                ba.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=ba.safecolor(prec.team.color + (1,)),
403                transition=Text.Transition.IN_RIGHT,
404                transition_delay=tdelay,
405            ).autoretain()
406
407        ba.timer(15.0, ba.WeakCall(self._show_tips))
408
409    def _show_tips(self) -> None:
410        from bastd.actor.tipstext import TipsText
411
412        self._tips_text = TipsText(offs_y=70)
413
414    def _play_victory_music(self) -> None:
415
416        # Make sure we don't stomp on the next activity's music choice.
417        if not self.is_transitioning_out():
418            ba.setmusic(ba.MusicType.VICTORY)
419
420    def _show_winner(self, team: ba.SessionTeam) -> None:
421        from bastd.actor.image import Image
422        from bastd.actor.zoomtext import ZoomText
423
424        if not self._is_ffa:
425            offs_v = 0.0
426            ZoomText(
427                team.name,
428                position=(0, 97),
429                color=team.color,
430                scale=1.15,
431                jitter=1.0,
432                maxwidth=250,
433            ).autoretain()
434        else:
435            offs_v = -80.0
436            if len(team.players) == 1:
437                i = Image(
438                    team.players[0].get_icon(),
439                    position=(0, 143),
440                    scale=(100, 100),
441                ).autoretain()
442                assert i.node
443                ba.animate(i.node, 'opacity', {0.0: 0.0, 0.25: 1.0})
444                ZoomText(
445                    ba.Lstr(
446                        value=team.players[0].getname(full=True, icon=False)
447                    ),
448                    position=(0, 97 + offs_v),
449                    color=team.color,
450                    scale=1.15,
451                    jitter=1.0,
452                    maxwidth=250,
453                ).autoretain()
454
455        s_extra = 1.0 if self._is_ffa else 1.0
456
457        # Some languages say "FOO WINS" differently for teams vs players.
458        if isinstance(self.session, ba.FreeForAllSession):
459            wins_resource = 'seriesWinLine1PlayerText'
460        else:
461            wins_resource = 'seriesWinLine1TeamText'
462        wins_text = ba.Lstr(resource=wins_resource)
463
464        # Temp - if these come up as the english default, fall-back to the
465        # unified old form which is more likely to be translated.
466        ZoomText(
467            wins_text,
468            position=(0, -10 + offs_v),
469            color=team.color,
470            scale=0.65 * s_extra,
471            jitter=1.0,
472            maxwidth=250,
473        ).autoretain()
474        ZoomText(
475            ba.Lstr(resource='seriesWinLine2Text'),
476            position=(0, -110 + offs_v),
477            scale=1.0 * s_extra,
478            color=team.color,
479            jitter=1.0,
480            maxwidth=250,
481        ).autoretain()
class TeamSeriesVictoryScoreScreenActivity(ba._activity.Activity[ba._player.EmptyPlayer, ba._team.EmptyTeam]):
 17class TeamSeriesVictoryScoreScreenActivity(MultiTeamScoreScreenActivity):
 18    """Final score screen for a team series."""
 19
 20    # Dont' play music by default; (we do manually after a delay).
 21    default_music = None
 22
 23    def __init__(self, settings: dict):
 24        super().__init__(settings=settings)
 25        self._min_view_time = 15.0
 26        self._is_ffa = isinstance(self.session, ba.FreeForAllSession)
 27        self._allow_server_transition = True
 28        self._tips_text = None
 29        self._default_show_tips = False
 30
 31    def on_begin(self) -> None:
 32        # pylint: disable=too-many-branches
 33        # pylint: disable=too-many-locals
 34        # pylint: disable=too-many-statements
 35        from bastd.actor.text import Text
 36        from bastd.actor.image import Image
 37
 38        ba.set_analytics_screen(
 39            'FreeForAll Series Victory Screen'
 40            if self._is_ffa
 41            else 'Teams Series Victory Screen'
 42        )
 43        if ba.app.ui.uiscale is ba.UIScale.LARGE:
 44            sval = ba.Lstr(resource='pressAnyKeyButtonPlayAgainText')
 45        else:
 46            sval = ba.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        ba.timer(0.6, ba.WeakCall(self._play_victory_music))
 54        ba.timer(
 55            4.4, ba.WeakCall(self._show_winner, self.settings_raw['winner'])
 56        )
 57        ba.timer(4.6, ba.Call(ba.playsound, self._score_display_sound))
 58
 59        # Score / Name / Player-record.
 60        player_entries: list[tuple[int, str, ba.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 = ba.app.lang.get_resource(
 85            'bestOfUseFirstToInstead'
 86        )
 87
 88        session = self.session
 89        if self._is_ffa:
 90            assert isinstance(session, ba.FreeForAllSession)
 91            txt = ba.Lstr(
 92                value='${A}:',
 93                subs=[
 94                    (
 95                        '${A}',
 96                        ba.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, ba.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 = ba.Lstr(
118                    value='${A}:',
119                    subs=[
120                        (
121                            '${A}',
122                            ba.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 = ba.Lstr(
138                    value='${A}:',
139                    subs=[
140                        (
141                            '${A}',
142                            ba.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                ba.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: ba.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                    ba.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                    ba.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=ba.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                ba.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                ba.Lstr(
262                    value='(${A})',
263                    subs=[
264                        (
265                            '${A}',
266                            ba.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                ba.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=ba.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                ba.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                ba.Lstr(
323                    value='(${A})',
324                    subs=[
325                        (
326                            '${A}',
327                            ba.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                ba.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=ba.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            ba.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                str(prec.team.customdata['score'])
380                if self._is_ffa
381                else str(prec.score),
382                color=(0.5, 0.5, 0.5, 1.0),
383                position=(ts_h_offs + 230, ts_height / 2 + v_offs),
384                h_align=Text.HAlign.RIGHT,
385                transition=Text.Transition.IN_RIGHT,
386                transition_delay=tdelay,
387            ).autoretain()
388            tdelay -= 4 * t_incr
389
390            Image(
391                prec.get_icon(),
392                position=(ts_h_offs - 72, ts_height / 2 + v_offs + 15),
393                scale=(30, 30),
394                transition=Image.Transition.IN_LEFT,
395                transition_delay=tdelay,
396            ).autoretain()
397            Text(
398                ba.Lstr(value=name),
399                position=(ts_h_offs - 50, ts_height / 2 + v_offs + 15),
400                h_align=Text.HAlign.LEFT,
401                v_align=Text.VAlign.CENTER,
402                maxwidth=180,
403                color=ba.safecolor(prec.team.color + (1,)),
404                transition=Text.Transition.IN_RIGHT,
405                transition_delay=tdelay,
406            ).autoretain()
407
408        ba.timer(15.0, ba.WeakCall(self._show_tips))
409
410    def _show_tips(self) -> None:
411        from bastd.actor.tipstext import TipsText
412
413        self._tips_text = TipsText(offs_y=70)
414
415    def _play_victory_music(self) -> None:
416
417        # Make sure we don't stomp on the next activity's music choice.
418        if not self.is_transitioning_out():
419            ba.setmusic(ba.MusicType.VICTORY)
420
421    def _show_winner(self, team: ba.SessionTeam) -> None:
422        from bastd.actor.image import Image
423        from bastd.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                ba.animate(i.node, 'opacity', {0.0: 0.0, 0.25: 1.0})
445                ZoomText(
446                    ba.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, ba.FreeForAllSession):
460            wins_resource = 'seriesWinLine1PlayerText'
461        else:
462            wins_resource = 'seriesWinLine1TeamText'
463        wins_text = ba.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            ba.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()

Final score screen for a team series.

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

Creates an Activity in the current ba.Session.

The activity will not be actually run until ba.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.

def on_begin(self) -> None:
 31    def on_begin(self) -> None:
 32        # pylint: disable=too-many-branches
 33        # pylint: disable=too-many-locals
 34        # pylint: disable=too-many-statements
 35        from bastd.actor.text import Text
 36        from bastd.actor.image import Image
 37
 38        ba.set_analytics_screen(
 39            'FreeForAll Series Victory Screen'
 40            if self._is_ffa
 41            else 'Teams Series Victory Screen'
 42        )
 43        if ba.app.ui.uiscale is ba.UIScale.LARGE:
 44            sval = ba.Lstr(resource='pressAnyKeyButtonPlayAgainText')
 45        else:
 46            sval = ba.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        ba.timer(0.6, ba.WeakCall(self._play_victory_music))
 54        ba.timer(
 55            4.4, ba.WeakCall(self._show_winner, self.settings_raw['winner'])
 56        )
 57        ba.timer(4.6, ba.Call(ba.playsound, self._score_display_sound))
 58
 59        # Score / Name / Player-record.
 60        player_entries: list[tuple[int, str, ba.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 = ba.app.lang.get_resource(
 85            'bestOfUseFirstToInstead'
 86        )
 87
 88        session = self.session
 89        if self._is_ffa:
 90            assert isinstance(session, ba.FreeForAllSession)
 91            txt = ba.Lstr(
 92                value='${A}:',
 93                subs=[
 94                    (
 95                        '${A}',
 96                        ba.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, ba.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 = ba.Lstr(
118                    value='${A}:',
119                    subs=[
120                        (
121                            '${A}',
122                            ba.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 = ba.Lstr(
138                    value='${A}:',
139                    subs=[
140                        (
141                            '${A}',
142                            ba.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                ba.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: ba.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                    ba.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                    ba.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=ba.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                ba.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                ba.Lstr(
262                    value='(${A})',
263                    subs=[
264                        (
265                            '${A}',
266                            ba.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                ba.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=ba.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                ba.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                ba.Lstr(
323                    value='(${A})',
324                    subs=[
325                        (
326                            '${A}',
327                            ba.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                ba.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=ba.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            ba.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                str(prec.team.customdata['score'])
380                if self._is_ffa
381                else str(prec.score),
382                color=(0.5, 0.5, 0.5, 1.0),
383                position=(ts_h_offs + 230, ts_height / 2 + v_offs),
384                h_align=Text.HAlign.RIGHT,
385                transition=Text.Transition.IN_RIGHT,
386                transition_delay=tdelay,
387            ).autoretain()
388            tdelay -= 4 * t_incr
389
390            Image(
391                prec.get_icon(),
392                position=(ts_h_offs - 72, ts_height / 2 + v_offs + 15),
393                scale=(30, 30),
394                transition=Image.Transition.IN_LEFT,
395                transition_delay=tdelay,
396            ).autoretain()
397            Text(
398                ba.Lstr(value=name),
399                position=(ts_h_offs - 50, ts_height / 2 + v_offs + 15),
400                h_align=Text.HAlign.LEFT,
401                v_align=Text.VAlign.CENTER,
402                maxwidth=180,
403                color=ba.safecolor(prec.team.color + (1,)),
404                transition=Text.Transition.IN_RIGHT,
405                transition_delay=tdelay,
406            ).autoretain()
407
408        ba.timer(15.0, ba.WeakCall(self._show_tips))

Called once the previous ba.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
bastd.activity.multiteamscore.MultiTeamScoreScreenActivity
show_player_scores
ba._activitytypes.ScoreScreenActivity
transition_time
inherits_tint
inherits_vr_camera_offset
use_fixed_vr_overlay
on_player_join
on_transition_in
ba._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
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
ba._dependency.DependencyComponent
dep_is_present
get_dynamic_deps