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.
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
- 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