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