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