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