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

default_music: ba._music.MusicType | None = None
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        ba.set_analytics_screen('FreeForAll Series Victory Screen' if self.
 38                                _is_ffa else 'Teams Series Victory Screen')
 39        if ba.app.ui.uiscale is ba.UIScale.LARGE:
 40            sval = ba.Lstr(resource='pressAnyKeyButtonPlayAgainText')
 41        else:
 42            sval = ba.Lstr(resource='pressAnyButtonPlayAgainText')
 43        self._show_up_next = False
 44        self._custom_continue_message = sval
 45        super().on_begin()
 46        winning_sessionteam = self.settings_raw['winner']
 47
 48        # Pause a moment before playing victory music.
 49        ba.timer(0.6, ba.WeakCall(self._play_victory_music))
 50        ba.timer(4.4,
 51                 ba.WeakCall(self._show_winner, self.settings_raw['winner']))
 52        ba.timer(4.6, ba.Call(ba.playsound, self._score_display_sound))
 53
 54        # Score / Name / Player-record.
 55        player_entries: list[tuple[int, str, ba.PlayerRecord]] = []
 56
 57        # Note: for ffa, exclude players who haven't entered the game yet.
 58        if self._is_ffa:
 59            for _pkey, prec in self.stats.get_records().items():
 60                if prec.player.in_game:
 61                    player_entries.append(
 62                        (prec.player.sessionteam.customdata['score'],
 63                         prec.getname(full=True), prec))
 64            player_entries.sort(reverse=True, key=lambda x: x[0])
 65        else:
 66            for _pkey, prec in self.stats.get_records().items():
 67                player_entries.append((prec.score, prec.name_full, prec))
 68            player_entries.sort(reverse=True, key=lambda x: x[0])
 69
 70        ts_height = 300.0
 71        ts_h_offs = -390.0
 72        tval = 6.4
 73        t_incr = 0.12
 74
 75        always_use_first_to = ba.app.lang.get_resource(
 76            'bestOfUseFirstToInstead')
 77
 78        session = self.session
 79        if self._is_ffa:
 80            assert isinstance(session, ba.FreeForAllSession)
 81            txt = ba.Lstr(
 82                value='${A}:',
 83                subs=[('${A}',
 84                       ba.Lstr(resource='firstToFinalText',
 85                               subs=[('${COUNT}',
 86                                      str(session.get_ffa_series_length()))]))
 87                      ])
 88        else:
 89            assert isinstance(session, ba.MultiTeamSession)
 90
 91            # Some languages may prefer to always show 'first to X' instead of
 92            # 'best of X'.
 93            # FIXME: This will affect all clients connected to us even if
 94            #  they're not using this language. Should try to come up
 95            #  with a wording that works everywhere.
 96            if always_use_first_to:
 97                txt = ba.Lstr(
 98                    value='${A}:',
 99                    subs=[
100                        ('${A}',
101                         ba.Lstr(resource='firstToFinalText',
102                                 subs=[
103                                     ('${COUNT}',
104                                      str(session.get_series_length() / 2 + 1))
105                                 ]))
106                    ])
107            else:
108                txt = ba.Lstr(
109                    value='${A}:',
110                    subs=[('${A}',
111                           ba.Lstr(resource='bestOfFinalText',
112                                   subs=[('${COUNT}',
113                                          str(session.get_series_length()))]))
114                          ])
115
116        Text(txt,
117             v_align=Text.VAlign.CENTER,
118             maxwidth=300,
119             color=(0.5, 0.5, 0.5, 1.0),
120             position=(0, 220),
121             scale=1.2,
122             transition=Text.Transition.IN_TOP_SLOW,
123             h_align=Text.HAlign.CENTER,
124             transition_delay=t_incr * 4).autoretain()
125
126        win_score = (session.get_series_length() - 1) // 2 + 1
127        lose_score = 0
128        for team in self.teams:
129            if team.sessionteam.customdata['score'] != win_score:
130                lose_score = team.sessionteam.customdata['score']
131
132        if not self._is_ffa:
133            Text(ba.Lstr(resource='gamesToText',
134                         subs=[('${WINCOUNT}', str(win_score)),
135                               ('${LOSECOUNT}', str(lose_score))]),
136                 color=(0.5, 0.5, 0.5, 1.0),
137                 maxwidth=160,
138                 v_align=Text.VAlign.CENTER,
139                 position=(0, -215),
140                 scale=1.8,
141                 transition=Text.Transition.IN_LEFT,
142                 h_align=Text.HAlign.CENTER,
143                 transition_delay=4.8 + t_incr * 4).autoretain()
144
145        if self._is_ffa:
146            v_extra = 120
147        else:
148            v_extra = 0
149
150        mvp: ba.PlayerRecord | None = None
151        mvp_name: str | None = None
152
153        # Show game MVP.
154        if not self._is_ffa:
155            mvp, mvp_name = None, None
156            for entry in player_entries:
157                if entry[2].team == winning_sessionteam:
158                    mvp = entry[2]
159                    mvp_name = entry[1]
160                    break
161            if mvp is not None:
162                Text(ba.Lstr(resource='mostValuablePlayerText'),
163                     color=(0.5, 0.5, 0.5, 1.0),
164                     v_align=Text.VAlign.CENTER,
165                     maxwidth=300,
166                     position=(180, ts_height / 2 + 15),
167                     transition=Text.Transition.IN_LEFT,
168                     h_align=Text.HAlign.LEFT,
169                     transition_delay=tval).autoretain()
170                tval += 4 * t_incr
171
172                Image(mvp.get_icon(),
173                      position=(230, ts_height / 2 - 55 + 14 - 5),
174                      scale=(70, 70),
175                      transition=Image.Transition.IN_LEFT,
176                      transition_delay=tval).autoretain()
177                assert mvp_name is not None
178                Text(ba.Lstr(value=mvp_name),
179                     position=(280, ts_height / 2 - 55 + 15 - 5),
180                     h_align=Text.HAlign.LEFT,
181                     v_align=Text.VAlign.CENTER,
182                     maxwidth=170,
183                     scale=1.3,
184                     color=ba.safecolor(mvp.team.color + (1, )),
185                     transition=Text.Transition.IN_LEFT,
186                     transition_delay=tval).autoretain()
187                tval += 4 * t_incr
188
189        # Most violent.
190        most_kills = 0
191        for entry in player_entries:
192            if entry[2].kill_count >= most_kills:
193                mvp = entry[2]
194                mvp_name = entry[1]
195                most_kills = entry[2].kill_count
196        if mvp is not None:
197            Text(ba.Lstr(resource='mostViolentPlayerText'),
198                 color=(0.5, 0.5, 0.5, 1.0),
199                 v_align=Text.VAlign.CENTER,
200                 maxwidth=300,
201                 position=(180, ts_height / 2 - 150 + v_extra + 15),
202                 transition=Text.Transition.IN_LEFT,
203                 h_align=Text.HAlign.LEFT,
204                 transition_delay=tval).autoretain()
205            Text(ba.Lstr(value='(${A})',
206                         subs=[('${A}',
207                                ba.Lstr(resource='killsTallyText',
208                                        subs=[('${COUNT}', str(most_kills))]))
209                               ]),
210                 position=(260, ts_height / 2 - 150 - 15 + v_extra),
211                 color=(0.3, 0.3, 0.3, 1.0),
212                 scale=0.6,
213                 h_align=Text.HAlign.LEFT,
214                 transition=Text.Transition.IN_LEFT,
215                 transition_delay=tval).autoretain()
216            tval += 4 * t_incr
217
218            Image(mvp.get_icon(),
219                  position=(233, ts_height / 2 - 150 - 30 - 46 + 25 + v_extra),
220                  scale=(50, 50),
221                  transition=Image.Transition.IN_LEFT,
222                  transition_delay=tval).autoretain()
223            assert mvp_name is not None
224            Text(ba.Lstr(value=mvp_name),
225                 position=(270, ts_height / 2 - 150 - 30 - 36 + v_extra + 15),
226                 h_align=Text.HAlign.LEFT,
227                 v_align=Text.VAlign.CENTER,
228                 maxwidth=180,
229                 color=ba.safecolor(mvp.team.color + (1, )),
230                 transition=Text.Transition.IN_LEFT,
231                 transition_delay=tval).autoretain()
232            tval += 4 * t_incr
233
234        # Most killed.
235        most_killed = 0
236        mkp, mkp_name = None, None
237        for entry in player_entries:
238            if entry[2].killed_count >= most_killed:
239                mkp = entry[2]
240                mkp_name = entry[1]
241                most_killed = entry[2].killed_count
242        if mkp is not None:
243            Text(ba.Lstr(resource='mostViolatedPlayerText'),
244                 color=(0.5, 0.5, 0.5, 1.0),
245                 v_align=Text.VAlign.CENTER,
246                 maxwidth=300,
247                 position=(180, ts_height / 2 - 300 + v_extra + 15),
248                 transition=Text.Transition.IN_LEFT,
249                 h_align=Text.HAlign.LEFT,
250                 transition_delay=tval).autoretain()
251            Text(ba.Lstr(value='(${A})',
252                         subs=[('${A}',
253                                ba.Lstr(resource='deathsTallyText',
254                                        subs=[('${COUNT}', str(most_killed))]))
255                               ]),
256                 position=(260, ts_height / 2 - 300 - 15 + v_extra),
257                 h_align=Text.HAlign.LEFT,
258                 scale=0.6,
259                 color=(0.3, 0.3, 0.3, 1.0),
260                 transition=Text.Transition.IN_LEFT,
261                 transition_delay=tval).autoretain()
262            tval += 4 * t_incr
263            Image(mkp.get_icon(),
264                  position=(233, ts_height / 2 - 300 - 30 - 46 + 25 + v_extra),
265                  scale=(50, 50),
266                  transition=Image.Transition.IN_LEFT,
267                  transition_delay=tval).autoretain()
268            assert mkp_name is not None
269            Text(ba.Lstr(value=mkp_name),
270                 position=(270, ts_height / 2 - 300 - 30 - 36 + v_extra + 15),
271                 h_align=Text.HAlign.LEFT,
272                 v_align=Text.VAlign.CENTER,
273                 color=ba.safecolor(mkp.team.color + (1, )),
274                 maxwidth=180,
275                 transition=Text.Transition.IN_LEFT,
276                 transition_delay=tval).autoretain()
277            tval += 4 * t_incr
278
279        # Now show individual scores.
280        tdelay = tval
281        Text(ba.Lstr(resource='finalScoresText'),
282             color=(0.5, 0.5, 0.5, 1.0),
283             position=(ts_h_offs, ts_height / 2),
284             transition=Text.Transition.IN_RIGHT,
285             transition_delay=tdelay).autoretain()
286        tdelay += 4 * t_incr
287
288        v_offs = 0.0
289        tdelay += len(player_entries) * 8 * t_incr
290        for _score, name, prec in player_entries:
291            tdelay -= 4 * t_incr
292            v_offs -= 40
293            Text(str(prec.team.customdata['score'])
294                 if self._is_ffa else str(prec.score),
295                 color=(0.5, 0.5, 0.5, 1.0),
296                 position=(ts_h_offs + 230, ts_height / 2 + v_offs),
297                 h_align=Text.HAlign.RIGHT,
298                 transition=Text.Transition.IN_RIGHT,
299                 transition_delay=tdelay).autoretain()
300            tdelay -= 4 * t_incr
301
302            Image(prec.get_icon(),
303                  position=(ts_h_offs - 72, ts_height / 2 + v_offs + 15),
304                  scale=(30, 30),
305                  transition=Image.Transition.IN_LEFT,
306                  transition_delay=tdelay).autoretain()
307            Text(ba.Lstr(value=name),
308                 position=(ts_h_offs - 50, ts_height / 2 + v_offs + 15),
309                 h_align=Text.HAlign.LEFT,
310                 v_align=Text.VAlign.CENTER,
311                 maxwidth=180,
312                 color=ba.safecolor(prec.team.color + (1, )),
313                 transition=Text.Transition.IN_RIGHT,
314                 transition_delay=tdelay).autoretain()
315
316        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