bastd.mainmenu
Session and Activity for displaying the main menu bg.
1# Released under the MIT License. See LICENSE for details. 2# 3"""Session and Activity for displaying the main menu bg.""" 4 5from __future__ import annotations 6 7import random 8import time 9import weakref 10from typing import TYPE_CHECKING 11 12import ba 13import _ba 14 15if TYPE_CHECKING: 16 from typing import Any 17 18# FIXME: Clean this up if I ever revisit it. 19# pylint: disable=attribute-defined-outside-init 20# pylint: disable=too-many-branches 21# pylint: disable=too-many-statements 22# pylint: disable=too-many-locals 23# noinspection PyUnreachableCode 24# noinspection PyAttributeOutsideInit 25 26 27class MainMenuActivity(ba.Activity[ba.Player, ba.Team]): 28 """Activity showing the rotating main menu bg stuff.""" 29 30 _stdassets = ba.Dependency(ba.AssetPackage, 'stdassets@1') 31 32 def on_transition_in(self) -> None: 33 super().on_transition_in() 34 random.seed(123) 35 self._logo_node: ba.Node | None = None 36 self._custom_logo_tex_name: str | None = None 37 self._word_actors: list[ba.Actor] = [] 38 app = ba.app 39 40 # FIXME: We shouldn't be doing things conditionally based on whether 41 # the host is VR mode or not (clients may differ in that regard). 42 # Any differences need to happen at the engine level so everyone 43 # sees things in their own optimal way. 44 vr_mode = ba.app.vr_mode 45 46 if not ba.app.toolbar_test: 47 color = ((1.0, 1.0, 1.0, 1.0) if vr_mode else (0.5, 0.6, 0.5, 0.6)) 48 49 # FIXME: Need a node attr for vr-specific-scale. 50 scale = (0.9 if 51 (app.ui.uiscale is ba.UIScale.SMALL or vr_mode) else 0.7) 52 self.my_name = ba.NodeActor( 53 ba.newnode('text', 54 attrs={ 55 'v_attach': 'bottom', 56 'h_align': 'center', 57 'color': color, 58 'flatness': 1.0, 59 'shadow': 1.0 if vr_mode else 0.5, 60 'scale': scale, 61 'position': (0, 10), 62 'vr_depth': -10, 63 'text': '\xa9 2011-2022 Eric Froemling' 64 })) 65 66 # Throw up some text that only clients can see so they know that the 67 # host is navigating menus while they're just staring at an 68 # empty-ish screen. 69 tval = ba.Lstr(resource='hostIsNavigatingMenusText', 70 subs=[('${HOST}', _ba.get_v1_account_display_string())]) 71 self._host_is_navigating_text = ba.NodeActor( 72 ba.newnode('text', 73 attrs={ 74 'text': tval, 75 'client_only': True, 76 'position': (0, -200), 77 'flatness': 1.0, 78 'h_align': 'center' 79 })) 80 if not ba.app.main_menu_did_initial_transition and hasattr( 81 self, 'my_name'): 82 assert self.my_name.node 83 ba.animate(self.my_name.node, 'opacity', {2.3: 0, 3.0: 1.0}) 84 85 # FIXME: We shouldn't be doing things conditionally based on whether 86 # the host is vr mode or not (clients may not be or vice versa). 87 # Any differences need to happen at the engine level so everyone sees 88 # things in their own optimal way. 89 vr_mode = app.vr_mode 90 uiscale = app.ui.uiscale 91 92 # In cases where we're doing lots of dev work lets always show the 93 # build number. 94 force_show_build_number = False 95 96 if not ba.app.toolbar_test: 97 if app.debug_build or app.test_build or force_show_build_number: 98 if app.debug_build: 99 text = ba.Lstr(value='${V} (${B}) (${D})', 100 subs=[ 101 ('${V}', app.version), 102 ('${B}', str(app.build_number)), 103 ('${D}', ba.Lstr(resource='debugText')), 104 ]) 105 else: 106 text = ba.Lstr(value='${V} (${B})', 107 subs=[ 108 ('${V}', app.version), 109 ('${B}', str(app.build_number)), 110 ]) 111 else: 112 text = ba.Lstr(value='${V}', subs=[('${V}', app.version)]) 113 scale = 0.9 if (uiscale is ba.UIScale.SMALL or vr_mode) else 0.7 114 color = (1, 1, 1, 1) if vr_mode else (0.5, 0.6, 0.5, 0.7) 115 self.version = ba.NodeActor( 116 ba.newnode( 117 'text', 118 attrs={ 119 'v_attach': 'bottom', 120 'h_attach': 'right', 121 'h_align': 'right', 122 'flatness': 1.0, 123 'vr_depth': -10, 124 'shadow': 1.0 if vr_mode else 0.5, 125 'color': color, 126 'scale': scale, 127 'position': (-260, 10) if vr_mode else (-10, 10), 128 'text': text 129 })) 130 if not ba.app.main_menu_did_initial_transition: 131 assert self.version.node 132 ba.animate(self.version.node, 'opacity', {2.3: 0, 3.0: 1.0}) 133 134 # Show the iircade logo on our iircade build. 135 if app.iircade_mode: 136 img = ba.NodeActor( 137 ba.newnode('image', 138 attrs={ 139 'texture': ba.gettexture('iircadeLogo'), 140 'attach': 'center', 141 'scale': (250, 250), 142 'position': (0, 0), 143 'tilt_translate': 0.21, 144 'absolute_scale': True 145 })).autoretain() 146 imgdelay = 0.0 if app.main_menu_did_initial_transition else 1.0 147 ba.animate(img.node, 'opacity', { 148 imgdelay + 1.5: 0.0, 149 imgdelay + 2.5: 1.0 150 }) 151 152 # Throw in test build info. 153 self.beta_info = self.beta_info_2 = None 154 if app.test_build and not (app.demo_mode or app.arcade_mode): 155 pos = ((230, 125) if (app.demo_mode or app.arcade_mode) else 156 (230, 35)) 157 self.beta_info = ba.NodeActor( 158 ba.newnode('text', 159 attrs={ 160 'v_attach': 'center', 161 'h_align': 'center', 162 'color': (1, 1, 1, 1), 163 'shadow': 0.5, 164 'flatness': 0.5, 165 'scale': 1, 166 'vr_depth': -60, 167 'position': pos, 168 'text': ba.Lstr(resource='testBuildText') 169 })) 170 if not ba.app.main_menu_did_initial_transition: 171 assert self.beta_info.node 172 ba.animate(self.beta_info.node, 'opacity', {1.3: 0, 1.8: 1.0}) 173 174 model = ba.getmodel('thePadLevel') 175 trees_model = ba.getmodel('trees') 176 bottom_model = ba.getmodel('thePadLevelBottom') 177 color_texture = ba.gettexture('thePadLevelColor') 178 trees_texture = ba.gettexture('treesColor') 179 bgtex = ba.gettexture('menuBG') 180 bgmodel = ba.getmodel('thePadBG') 181 182 # Load these last since most platforms don't use them. 183 vr_bottom_fill_model = ba.getmodel('thePadVRFillBottom') 184 vr_top_fill_model = ba.getmodel('thePadVRFillTop') 185 186 gnode = self.globalsnode 187 gnode.camera_mode = 'rotate' 188 189 tint = (1.14, 1.1, 1.0) 190 gnode.tint = tint 191 gnode.ambient_color = (1.06, 1.04, 1.03) 192 gnode.vignette_outer = (0.45, 0.55, 0.54) 193 gnode.vignette_inner = (0.99, 0.98, 0.98) 194 195 self.bottom = ba.NodeActor( 196 ba.newnode('terrain', 197 attrs={ 198 'model': bottom_model, 199 'lighting': False, 200 'reflection': 'soft', 201 'reflection_scale': [0.45], 202 'color_texture': color_texture 203 })) 204 self.vr_bottom_fill = ba.NodeActor( 205 ba.newnode('terrain', 206 attrs={ 207 'model': vr_bottom_fill_model, 208 'lighting': False, 209 'vr_only': True, 210 'color_texture': color_texture 211 })) 212 self.vr_top_fill = ba.NodeActor( 213 ba.newnode('terrain', 214 attrs={ 215 'model': vr_top_fill_model, 216 'vr_only': True, 217 'lighting': False, 218 'color_texture': bgtex 219 })) 220 self.terrain = ba.NodeActor( 221 ba.newnode('terrain', 222 attrs={ 223 'model': model, 224 'color_texture': color_texture, 225 'reflection': 'soft', 226 'reflection_scale': [0.3] 227 })) 228 self.trees = ba.NodeActor( 229 ba.newnode('terrain', 230 attrs={ 231 'model': trees_model, 232 'lighting': False, 233 'reflection': 'char', 234 'reflection_scale': [0.1], 235 'color_texture': trees_texture 236 })) 237 self.bgterrain = ba.NodeActor( 238 ba.newnode('terrain', 239 attrs={ 240 'model': bgmodel, 241 'color': (0.92, 0.91, 0.9), 242 'lighting': False, 243 'background': True, 244 'color_texture': bgtex 245 })) 246 247 self._ts = 0.86 248 249 self._language: str | None = None 250 self._update_timer = ba.Timer(1.0, self._update, repeat=True) 251 self._update() 252 253 # Hopefully this won't hitch but lets space these out anyway. 254 _ba.add_clean_frame_callback(ba.WeakCall(self._start_preloads)) 255 256 random.seed() 257 258 # On the main menu, also show our news. 259 class News: 260 """Wrangles news display.""" 261 262 def __init__(self, activity: ba.Activity): 263 self._valid = True 264 self._message_duration = 10.0 265 self._message_spacing = 2.0 266 self._text: ba.NodeActor | None = None 267 self._activity = weakref.ref(activity) 268 269 # If we're signed in, fetch news immediately. 270 # Otherwise wait until we are signed in. 271 self._fetch_timer: ba.Timer | None = ba.Timer( 272 1.0, ba.WeakCall(self._try_fetching_news), repeat=True) 273 self._try_fetching_news() 274 275 # We now want to wait until we're signed in before fetching news. 276 def _try_fetching_news(self) -> None: 277 if _ba.get_v1_account_state() == 'signed_in': 278 self._fetch_news() 279 self._fetch_timer = None 280 281 def _fetch_news(self) -> None: 282 ba.app.main_menu_last_news_fetch_time = time.time() 283 284 # UPDATE - We now just pull news from MRVs. 285 news = _ba.get_v1_account_misc_read_val('n', None) 286 if news is not None: 287 self._got_news(news) 288 289 def _change_phrase(self) -> None: 290 from bastd.actor.text import Text 291 292 # If our news is way out of date, lets re-request it; 293 # otherwise, rotate our phrase. 294 assert ba.app.main_menu_last_news_fetch_time is not None 295 if time.time() - ba.app.main_menu_last_news_fetch_time > 600.0: 296 self._fetch_news() 297 self._text = None 298 else: 299 if self._text is not None: 300 if not self._phrases: 301 for phr in self._used_phrases: 302 self._phrases.insert(0, phr) 303 val = self._phrases.pop() 304 if val == '__ACH__': 305 vrmode = app.vr_mode 306 Text(ba.Lstr(resource='nextAchievementsText'), 307 color=((1, 1, 1, 1) if vrmode else 308 (0.95, 0.9, 1, 0.4)), 309 host_only=True, 310 maxwidth=200, 311 position=(-300, -35), 312 h_align=Text.HAlign.RIGHT, 313 transition=Text.Transition.FADE_IN, 314 scale=0.9 if vrmode else 0.7, 315 flatness=1.0 if vrmode else 0.6, 316 shadow=1.0 if vrmode else 0.5, 317 h_attach=Text.HAttach.CENTER, 318 v_attach=Text.VAttach.TOP, 319 transition_delay=1.0, 320 transition_out_delay=self._message_duration 321 ).autoretain() 322 achs = [ 323 a for a in app.ach.achievements 324 if not a.complete 325 ] 326 if achs: 327 ach = achs.pop( 328 random.randrange(min(4, len(achs)))) 329 ach.create_display( 330 -180, 331 -35, 332 1.0, 333 outdelay=self._message_duration, 334 style='news') 335 if achs: 336 ach = achs.pop( 337 random.randrange(min(8, len(achs)))) 338 ach.create_display( 339 180, 340 -35, 341 1.25, 342 outdelay=self._message_duration, 343 style='news') 344 else: 345 spc = self._message_spacing 346 keys = { 347 spc: 0.0, 348 spc + 1.0: 1.0, 349 spc + self._message_duration - 1.0: 1.0, 350 spc + self._message_duration: 0.0 351 } 352 assert self._text.node 353 ba.animate(self._text.node, 'opacity', keys) 354 # {k: v 355 # for k, v in list(keys.items())}) 356 self._text.node.text = val 357 358 def _got_news(self, news: str) -> None: 359 # Run this stuff in the context of our activity since we 360 # need to make nodes and stuff.. should fix the serverget 361 # call so it. 362 activity = self._activity() 363 if activity is None or activity.expired: 364 return 365 with ba.Context(activity): 366 367 self._phrases: list[str] = [] 368 369 # Show upcoming achievements in non-vr versions 370 # (currently too hard to read in vr). 371 self._used_phrases = ( 372 ['__ACH__'] if not ba.app.vr_mode else 373 []) + [s for s in news.split('<br>\n') if s != ''] 374 self._phrase_change_timer = ba.Timer( 375 (self._message_duration + self._message_spacing), 376 ba.WeakCall(self._change_phrase), 377 repeat=True) 378 379 scl = 1.2 if (ba.app.ui.uiscale is ba.UIScale.SMALL 380 or ba.app.vr_mode) else 0.8 381 382 color2 = ((1, 1, 1, 1) if ba.app.vr_mode else 383 (0.7, 0.65, 0.75, 1.0)) 384 shadow = (1.0 if ba.app.vr_mode else 0.4) 385 self._text = ba.NodeActor( 386 ba.newnode('text', 387 attrs={ 388 'v_attach': 'top', 389 'h_attach': 'center', 390 'h_align': 'center', 391 'vr_depth': -20, 392 'shadow': shadow, 393 'flatness': 0.8, 394 'v_align': 'top', 395 'color': color2, 396 'scale': scl, 397 'maxwidth': 900.0 / scl, 398 'position': (0, -10) 399 })) 400 self._change_phrase() 401 402 if not (app.demo_mode or app.arcade_mode) and not app.toolbar_test: 403 self._news = News(self) 404 405 # Bring up the last place we were, or start at the main menu otherwise. 406 with ba.Context('ui'): 407 from bastd.ui import specialoffer 408 if bool(False): 409 uicontroller = ba.app.ui.controller 410 assert uicontroller is not None 411 uicontroller.show_main_menu() 412 else: 413 main_menu_location = ba.app.ui.get_main_menu_location() 414 415 # When coming back from a kiosk-mode game, jump to 416 # the kiosk start screen. 417 if ba.app.demo_mode or ba.app.arcade_mode: 418 # pylint: disable=cyclic-import 419 from bastd.ui.kiosk import KioskWindow 420 ba.app.ui.set_main_menu_window( 421 KioskWindow().get_root_widget()) 422 # ..or in normal cases go back to the main menu 423 else: 424 if main_menu_location == 'Gather': 425 # pylint: disable=cyclic-import 426 from bastd.ui.gather import GatherWindow 427 ba.app.ui.set_main_menu_window( 428 GatherWindow(transition=None).get_root_widget()) 429 elif main_menu_location == 'Watch': 430 # pylint: disable=cyclic-import 431 from bastd.ui.watch import WatchWindow 432 ba.app.ui.set_main_menu_window( 433 WatchWindow(transition=None).get_root_widget()) 434 elif main_menu_location == 'Team Game Select': 435 # pylint: disable=cyclic-import 436 from bastd.ui.playlist.browser import ( 437 PlaylistBrowserWindow) 438 ba.app.ui.set_main_menu_window( 439 PlaylistBrowserWindow( 440 sessiontype=ba.DualTeamSession, 441 transition=None).get_root_widget()) 442 elif main_menu_location == 'Free-for-All Game Select': 443 # pylint: disable=cyclic-import 444 from bastd.ui.playlist.browser import ( 445 PlaylistBrowserWindow) 446 ba.app.ui.set_main_menu_window( 447 PlaylistBrowserWindow( 448 sessiontype=ba.FreeForAllSession, 449 transition=None).get_root_widget()) 450 elif main_menu_location == 'Coop Select': 451 # pylint: disable=cyclic-import 452 from bastd.ui.coop.browser import CoopBrowserWindow 453 ba.app.ui.set_main_menu_window( 454 CoopBrowserWindow( 455 transition=None).get_root_widget()) 456 else: 457 # pylint: disable=cyclic-import 458 from bastd.ui.mainmenu import MainMenuWindow 459 ba.app.ui.set_main_menu_window( 460 MainMenuWindow(transition=None).get_root_widget()) 461 462 # attempt to show any pending offers immediately. 463 # If that doesn't work, try again in a few seconds 464 # (we may not have heard back from the server) 465 # ..if that doesn't work they'll just have to wait 466 # until the next opportunity. 467 if not specialoffer.show_offer(): 468 469 def try_again() -> None: 470 if not specialoffer.show_offer(): 471 # Try one last time.. 472 ba.timer(2.0, 473 specialoffer.show_offer, 474 timetype=ba.TimeType.REAL) 475 476 ba.timer(2.0, try_again, timetype=ba.TimeType.REAL) 477 ba.app.main_menu_did_initial_transition = True 478 479 def _update(self) -> None: 480 app = ba.app 481 482 # Update logo in case it changes. 483 if self._logo_node: 484 custom_texture = self._get_custom_logo_tex_name() 485 if custom_texture != self._custom_logo_tex_name: 486 self._custom_logo_tex_name = custom_texture 487 self._logo_node.texture = ba.gettexture( 488 custom_texture if custom_texture is not None else 'logo') 489 self._logo_node.model_opaque = (None 490 if custom_texture is not None 491 else ba.getmodel('logo')) 492 self._logo_node.model_transparent = ( 493 None if custom_texture is not None else 494 ba.getmodel('logoTransparent')) 495 496 # If language has changed, recreate our logo text/graphics. 497 lang = app.lang.language 498 if lang != self._language: 499 self._language = lang 500 y = 20 501 base_scale = 1.1 502 self._word_actors = [] 503 base_delay = 1.0 504 delay = base_delay 505 delay_inc = 0.02 506 507 # Come on faster after the first time. 508 if app.main_menu_did_initial_transition: 509 base_delay = 0.0 510 delay = base_delay 511 delay_inc = 0.02 512 513 # We draw higher in kiosk mode (make sure to test this 514 # when making adjustments) for now we're hard-coded for 515 # a few languages.. should maybe look into generalizing this?.. 516 if app.lang.language == 'Chinese': 517 base_x = -270.0 518 x = base_x - 20.0 519 spacing = 85.0 * base_scale 520 y_extra = 0.0 if (app.demo_mode or app.arcade_mode) else 0.0 521 self._make_logo(x - 110 + 50, 522 113 + y + 1.2 * y_extra, 523 0.34 * base_scale, 524 delay=base_delay + 0.1, 525 custom_texture='chTitleChar1', 526 jitter_scale=2.0, 527 vr_depth_offset=-30) 528 x += spacing 529 delay += delay_inc 530 self._make_logo(x - 10 + 50, 531 110 + y + 1.2 * y_extra, 532 0.31 * base_scale, 533 delay=base_delay + 0.15, 534 custom_texture='chTitleChar2', 535 jitter_scale=2.0, 536 vr_depth_offset=-30) 537 x += 2.0 * spacing 538 delay += delay_inc 539 self._make_logo(x + 180 - 140, 540 110 + y + 1.2 * y_extra, 541 0.3 * base_scale, 542 delay=base_delay + 0.25, 543 custom_texture='chTitleChar3', 544 jitter_scale=2.0, 545 vr_depth_offset=-30) 546 x += spacing 547 delay += delay_inc 548 self._make_logo(x + 241 - 120, 549 110 + y + 1.2 * y_extra, 550 0.31 * base_scale, 551 delay=base_delay + 0.3, 552 custom_texture='chTitleChar4', 553 jitter_scale=2.0, 554 vr_depth_offset=-30) 555 x += spacing 556 delay += delay_inc 557 self._make_logo(x + 300 - 90, 558 105 + y + 1.2 * y_extra, 559 0.34 * base_scale, 560 delay=base_delay + 0.35, 561 custom_texture='chTitleChar5', 562 jitter_scale=2.0, 563 vr_depth_offset=-30) 564 self._make_logo(base_x + 155, 565 146 + y + 1.2 * y_extra, 566 0.28 * base_scale, 567 delay=base_delay + 0.2, 568 rotate=-7) 569 else: 570 base_x = -170 571 x = base_x - 20 572 spacing = 55 * base_scale 573 y_extra = 0 if (app.demo_mode or app.arcade_mode) else 0 574 xv1 = x 575 delay1 = delay 576 for shadow in (True, False): 577 x = xv1 578 delay = delay1 579 self._make_word('B', 580 x - 50, 581 y - 23 + 0.8 * y_extra, 582 scale=1.3 * base_scale, 583 delay=delay, 584 vr_depth_offset=3, 585 shadow=shadow) 586 x += spacing 587 delay += delay_inc 588 self._make_word('m', 589 x, 590 y + y_extra, 591 delay=delay, 592 scale=base_scale, 593 shadow=shadow) 594 x += spacing * 1.25 595 delay += delay_inc 596 self._make_word('b', 597 x, 598 y + y_extra - 10, 599 delay=delay, 600 scale=1.1 * base_scale, 601 vr_depth_offset=5, 602 shadow=shadow) 603 x += spacing * 0.85 604 delay += delay_inc 605 self._make_word('S', 606 x, 607 y - 25 + 0.8 * y_extra, 608 scale=1.35 * base_scale, 609 delay=delay, 610 vr_depth_offset=14, 611 shadow=shadow) 612 x += spacing 613 delay += delay_inc 614 self._make_word('q', 615 x, 616 y + y_extra, 617 delay=delay, 618 scale=base_scale, 619 shadow=shadow) 620 x += spacing * 0.9 621 delay += delay_inc 622 self._make_word('u', 623 x, 624 y + y_extra, 625 delay=delay, 626 scale=base_scale, 627 vr_depth_offset=7, 628 shadow=shadow) 629 x += spacing * 0.9 630 delay += delay_inc 631 self._make_word('a', 632 x, 633 y + y_extra, 634 delay=delay, 635 scale=base_scale, 636 shadow=shadow) 637 x += spacing * 0.64 638 delay += delay_inc 639 self._make_word('d', 640 x, 641 y + y_extra - 10, 642 delay=delay, 643 scale=1.1 * base_scale, 644 vr_depth_offset=6, 645 shadow=shadow) 646 self._make_logo(base_x - 28, 647 125 + y + 1.2 * y_extra, 648 0.32 * base_scale, 649 delay=base_delay) 650 651 def _make_word(self, 652 word: str, 653 x: float, 654 y: float, 655 scale: float = 1.0, 656 delay: float = 0.0, 657 vr_depth_offset: float = 0.0, 658 shadow: bool = False) -> None: 659 if shadow: 660 word_obj = ba.NodeActor( 661 ba.newnode('text', 662 attrs={ 663 'position': (x, y), 664 'big': True, 665 'color': (0.0, 0.0, 0.2, 0.08), 666 'tilt_translate': 0.09, 667 'opacity_scales_shadow': False, 668 'shadow': 0.2, 669 'vr_depth': -130, 670 'v_align': 'center', 671 'project_scale': 0.97 * scale, 672 'scale': 1.0, 673 'text': word 674 })) 675 self._word_actors.append(word_obj) 676 else: 677 word_obj = ba.NodeActor( 678 ba.newnode('text', 679 attrs={ 680 'position': (x, y), 681 'big': True, 682 'color': (1.2, 1.15, 1.15, 1.0), 683 'tilt_translate': 0.11, 684 'shadow': 0.2, 685 'vr_depth': -40 + vr_depth_offset, 686 'v_align': 'center', 687 'project_scale': scale, 688 'scale': 1.0, 689 'text': word 690 })) 691 self._word_actors.append(word_obj) 692 693 # Add a bit of stop-motion-y jitter to the logo 694 # (unless we're in VR mode in which case its best to 695 # leave things still). 696 if not ba.app.vr_mode: 697 cmb: ba.Node | None 698 cmb2: ba.Node | None 699 if not shadow: 700 cmb = ba.newnode('combine', 701 owner=word_obj.node, 702 attrs={'size': 2}) 703 else: 704 cmb = None 705 if shadow: 706 cmb2 = ba.newnode('combine', 707 owner=word_obj.node, 708 attrs={'size': 2}) 709 else: 710 cmb2 = None 711 if not shadow: 712 assert cmb and word_obj.node 713 cmb.connectattr('output', word_obj.node, 'position') 714 if shadow: 715 assert cmb2 and word_obj.node 716 cmb2.connectattr('output', word_obj.node, 'position') 717 keys = {} 718 keys2 = {} 719 time_v = 0.0 720 for _i in range(10): 721 val = x + (random.random() - 0.5) * 0.8 722 val2 = x + (random.random() - 0.5) * 0.8 723 keys[time_v * self._ts] = val 724 keys2[time_v * self._ts] = val2 + 5 725 time_v += random.random() * 0.1 726 if cmb is not None: 727 ba.animate(cmb, 'input0', keys, loop=True) 728 if cmb2 is not None: 729 ba.animate(cmb2, 'input0', keys2, loop=True) 730 keys = {} 731 keys2 = {} 732 time_v = 0 733 for _i in range(10): 734 val = y + (random.random() - 0.5) * 0.8 735 val2 = y + (random.random() - 0.5) * 0.8 736 keys[time_v * self._ts] = val 737 keys2[time_v * self._ts] = val2 - 9 738 time_v += random.random() * 0.1 739 if cmb is not None: 740 ba.animate(cmb, 'input1', keys, loop=True) 741 if cmb2 is not None: 742 ba.animate(cmb2, 'input1', keys2, loop=True) 743 744 if not shadow: 745 assert word_obj.node 746 ba.animate(word_obj.node, 'project_scale', { 747 delay: 0.0, 748 delay + 0.1: scale * 1.1, 749 delay + 0.2: scale 750 }) 751 else: 752 assert word_obj.node 753 ba.animate(word_obj.node, 'project_scale', { 754 delay: 0.0, 755 delay + 0.1: scale * 1.1, 756 delay + 0.2: scale 757 }) 758 759 def _get_custom_logo_tex_name(self) -> str | None: 760 if _ba.get_v1_account_misc_read_val('easter', False): 761 return 'logoEaster' 762 return None 763 764 # Pop the logo and menu in. 765 def _make_logo(self, 766 x: float, 767 y: float, 768 scale: float, 769 delay: float, 770 custom_texture: str | None = None, 771 jitter_scale: float = 1.0, 772 rotate: float = 0.0, 773 vr_depth_offset: float = 0.0) -> None: 774 775 # Temp easter goodness. 776 if custom_texture is None: 777 custom_texture = self._get_custom_logo_tex_name() 778 self._custom_logo_tex_name = custom_texture 779 ltex = ba.gettexture( 780 custom_texture if custom_texture is not None else 'logo') 781 mopaque = (None if custom_texture is not None else ba.getmodel('logo')) 782 mtrans = (None if custom_texture is not None else 783 ba.getmodel('logoTransparent')) 784 logo = ba.NodeActor( 785 ba.newnode('image', 786 attrs={ 787 'texture': ltex, 788 'model_opaque': mopaque, 789 'model_transparent': mtrans, 790 'vr_depth': -10 + vr_depth_offset, 791 'rotate': rotate, 792 'attach': 'center', 793 'tilt_translate': 0.21, 794 'absolute_scale': True 795 })) 796 self._logo_node = logo.node 797 self._word_actors.append(logo) 798 799 # Add a bit of stop-motion-y jitter to the logo 800 # (unless we're in VR mode in which case its best to 801 # leave things still). 802 assert logo.node 803 if not ba.app.vr_mode: 804 cmb = ba.newnode('combine', owner=logo.node, attrs={'size': 2}) 805 cmb.connectattr('output', logo.node, 'position') 806 keys = {} 807 time_v = 0.0 808 809 # Gen some random keys for that stop-motion-y look 810 for _i in range(10): 811 keys[time_v] = x + (random.random() - 0.5) * 0.7 * jitter_scale 812 time_v += random.random() * 0.1 813 ba.animate(cmb, 'input0', keys, loop=True) 814 keys = {} 815 time_v = 0.0 816 for _i in range(10): 817 keys[time_v * self._ts] = y + (random.random() - 818 0.5) * 0.7 * jitter_scale 819 time_v += random.random() * 0.1 820 ba.animate(cmb, 'input1', keys, loop=True) 821 else: 822 logo.node.position = (x, y) 823 824 cmb = ba.newnode('combine', owner=logo.node, attrs={'size': 2}) 825 826 keys = { 827 delay: 0.0, 828 delay + 0.1: 700.0 * scale, 829 delay + 0.2: 600.0 * scale 830 } 831 ba.animate(cmb, 'input0', keys) 832 ba.animate(cmb, 'input1', keys) 833 cmb.connectattr('output', logo.node, 'scale') 834 835 def _start_preloads(self) -> None: 836 # FIXME: The func that calls us back doesn't save/restore state 837 # or check for a dead activity so we have to do that ourself. 838 if self.expired: 839 return 840 with ba.Context(self): 841 _preload1() 842 843 ba.timer(0.5, lambda: ba.setmusic(ba.MusicType.MENU)) 844 845 846def _preload1() -> None: 847 """Pre-load some assets a second or two into the main menu. 848 849 Helps avoid hitches later on. 850 """ 851 for mname in [ 852 'plasticEyesTransparent', 'playerLineup1Transparent', 853 'playerLineup2Transparent', 'playerLineup3Transparent', 854 'playerLineup4Transparent', 'angryComputerTransparent', 855 'scrollWidgetShort', 'windowBGBlotch' 856 ]: 857 ba.getmodel(mname) 858 for tname in ['playerLineup', 'lock']: 859 ba.gettexture(tname) 860 for tex in [ 861 'iconRunaround', 'iconOnslaught', 'medalComplete', 'medalBronze', 862 'medalSilver', 'medalGold', 'characterIconMask' 863 ]: 864 ba.gettexture(tex) 865 ba.gettexture('bg') 866 from bastd.actor.powerupbox import PowerupBoxFactory 867 PowerupBoxFactory.get() 868 ba.timer(0.1, _preload2) 869 870 871def _preload2() -> None: 872 # FIXME: Could integrate these loads with the classes that use them 873 # so they don't have to redundantly call the load 874 # (even if the actual result is cached). 875 for mname in ['powerup', 'powerupSimple']: 876 ba.getmodel(mname) 877 for tname in [ 878 'powerupBomb', 'powerupSpeed', 'powerupPunch', 'powerupIceBombs', 879 'powerupStickyBombs', 'powerupShield', 'powerupImpactBombs', 880 'powerupHealth' 881 ]: 882 ba.gettexture(tname) 883 for sname in [ 884 'powerup01', 'boxDrop', 'boxingBell', 'scoreHit01', 'scoreHit02', 885 'dripity', 'spawn', 'gong' 886 ]: 887 ba.getsound(sname) 888 from bastd.actor.bomb import BombFactory 889 BombFactory.get() 890 ba.timer(0.1, _preload3) 891 892 893def _preload3() -> None: 894 from bastd.actor.spazfactory import SpazFactory 895 for mname in ['bomb', 'bombSticky', 'impactBomb']: 896 ba.getmodel(mname) 897 for tname in [ 898 'bombColor', 'bombColorIce', 'bombStickyColor', 'impactBombColor', 899 'impactBombColorLit' 900 ]: 901 ba.gettexture(tname) 902 for sname in ['freeze', 'fuse01', 'activateBeep', 'warnBeep']: 903 ba.getsound(sname) 904 SpazFactory.get() 905 ba.timer(0.2, _preload4) 906 907 908def _preload4() -> None: 909 for tname in ['bar', 'meter', 'null', 'flagColor', 'achievementOutline']: 910 ba.gettexture(tname) 911 for mname in ['frameInset', 'meterTransparent', 'achievementOutline']: 912 ba.getmodel(mname) 913 for sname in ['metalHit', 'metalSkid', 'refWhistle', 'achievement']: 914 ba.getsound(sname) 915 from bastd.actor.flag import FlagFactory 916 FlagFactory.get() 917 918 919class MainMenuSession(ba.Session): 920 """Session that runs the main menu environment.""" 921 922 def __init__(self) -> None: 923 924 # Gather dependencies we'll need (just our activity). 925 self._activity_deps = ba.DependencySet(ba.Dependency(MainMenuActivity)) 926 927 super().__init__([self._activity_deps]) 928 self._locked = False 929 self.setactivity(ba.newactivity(MainMenuActivity)) 930 931 def on_activity_end(self, activity: ba.Activity, results: Any) -> None: 932 if self._locked: 933 _ba.unlock_all_input() 934 935 # Any ending activity leads us into the main menu one. 936 self.setactivity(ba.newactivity(MainMenuActivity)) 937 938 def on_player_request(self, player: ba.SessionPlayer) -> bool: 939 # Reject all player requests. 940 return False
28class MainMenuActivity(ba.Activity[ba.Player, ba.Team]): 29 """Activity showing the rotating main menu bg stuff.""" 30 31 _stdassets = ba.Dependency(ba.AssetPackage, 'stdassets@1') 32 33 def on_transition_in(self) -> None: 34 super().on_transition_in() 35 random.seed(123) 36 self._logo_node: ba.Node | None = None 37 self._custom_logo_tex_name: str | None = None 38 self._word_actors: list[ba.Actor] = [] 39 app = ba.app 40 41 # FIXME: We shouldn't be doing things conditionally based on whether 42 # the host is VR mode or not (clients may differ in that regard). 43 # Any differences need to happen at the engine level so everyone 44 # sees things in their own optimal way. 45 vr_mode = ba.app.vr_mode 46 47 if not ba.app.toolbar_test: 48 color = ((1.0, 1.0, 1.0, 1.0) if vr_mode else (0.5, 0.6, 0.5, 0.6)) 49 50 # FIXME: Need a node attr for vr-specific-scale. 51 scale = (0.9 if 52 (app.ui.uiscale is ba.UIScale.SMALL or vr_mode) else 0.7) 53 self.my_name = ba.NodeActor( 54 ba.newnode('text', 55 attrs={ 56 'v_attach': 'bottom', 57 'h_align': 'center', 58 'color': color, 59 'flatness': 1.0, 60 'shadow': 1.0 if vr_mode else 0.5, 61 'scale': scale, 62 'position': (0, 10), 63 'vr_depth': -10, 64 'text': '\xa9 2011-2022 Eric Froemling' 65 })) 66 67 # Throw up some text that only clients can see so they know that the 68 # host is navigating menus while they're just staring at an 69 # empty-ish screen. 70 tval = ba.Lstr(resource='hostIsNavigatingMenusText', 71 subs=[('${HOST}', _ba.get_v1_account_display_string())]) 72 self._host_is_navigating_text = ba.NodeActor( 73 ba.newnode('text', 74 attrs={ 75 'text': tval, 76 'client_only': True, 77 'position': (0, -200), 78 'flatness': 1.0, 79 'h_align': 'center' 80 })) 81 if not ba.app.main_menu_did_initial_transition and hasattr( 82 self, 'my_name'): 83 assert self.my_name.node 84 ba.animate(self.my_name.node, 'opacity', {2.3: 0, 3.0: 1.0}) 85 86 # FIXME: We shouldn't be doing things conditionally based on whether 87 # the host is vr mode or not (clients may not be or vice versa). 88 # Any differences need to happen at the engine level so everyone sees 89 # things in their own optimal way. 90 vr_mode = app.vr_mode 91 uiscale = app.ui.uiscale 92 93 # In cases where we're doing lots of dev work lets always show the 94 # build number. 95 force_show_build_number = False 96 97 if not ba.app.toolbar_test: 98 if app.debug_build or app.test_build or force_show_build_number: 99 if app.debug_build: 100 text = ba.Lstr(value='${V} (${B}) (${D})', 101 subs=[ 102 ('${V}', app.version), 103 ('${B}', str(app.build_number)), 104 ('${D}', ba.Lstr(resource='debugText')), 105 ]) 106 else: 107 text = ba.Lstr(value='${V} (${B})', 108 subs=[ 109 ('${V}', app.version), 110 ('${B}', str(app.build_number)), 111 ]) 112 else: 113 text = ba.Lstr(value='${V}', subs=[('${V}', app.version)]) 114 scale = 0.9 if (uiscale is ba.UIScale.SMALL or vr_mode) else 0.7 115 color = (1, 1, 1, 1) if vr_mode else (0.5, 0.6, 0.5, 0.7) 116 self.version = ba.NodeActor( 117 ba.newnode( 118 'text', 119 attrs={ 120 'v_attach': 'bottom', 121 'h_attach': 'right', 122 'h_align': 'right', 123 'flatness': 1.0, 124 'vr_depth': -10, 125 'shadow': 1.0 if vr_mode else 0.5, 126 'color': color, 127 'scale': scale, 128 'position': (-260, 10) if vr_mode else (-10, 10), 129 'text': text 130 })) 131 if not ba.app.main_menu_did_initial_transition: 132 assert self.version.node 133 ba.animate(self.version.node, 'opacity', {2.3: 0, 3.0: 1.0}) 134 135 # Show the iircade logo on our iircade build. 136 if app.iircade_mode: 137 img = ba.NodeActor( 138 ba.newnode('image', 139 attrs={ 140 'texture': ba.gettexture('iircadeLogo'), 141 'attach': 'center', 142 'scale': (250, 250), 143 'position': (0, 0), 144 'tilt_translate': 0.21, 145 'absolute_scale': True 146 })).autoretain() 147 imgdelay = 0.0 if app.main_menu_did_initial_transition else 1.0 148 ba.animate(img.node, 'opacity', { 149 imgdelay + 1.5: 0.0, 150 imgdelay + 2.5: 1.0 151 }) 152 153 # Throw in test build info. 154 self.beta_info = self.beta_info_2 = None 155 if app.test_build and not (app.demo_mode or app.arcade_mode): 156 pos = ((230, 125) if (app.demo_mode or app.arcade_mode) else 157 (230, 35)) 158 self.beta_info = ba.NodeActor( 159 ba.newnode('text', 160 attrs={ 161 'v_attach': 'center', 162 'h_align': 'center', 163 'color': (1, 1, 1, 1), 164 'shadow': 0.5, 165 'flatness': 0.5, 166 'scale': 1, 167 'vr_depth': -60, 168 'position': pos, 169 'text': ba.Lstr(resource='testBuildText') 170 })) 171 if not ba.app.main_menu_did_initial_transition: 172 assert self.beta_info.node 173 ba.animate(self.beta_info.node, 'opacity', {1.3: 0, 1.8: 1.0}) 174 175 model = ba.getmodel('thePadLevel') 176 trees_model = ba.getmodel('trees') 177 bottom_model = ba.getmodel('thePadLevelBottom') 178 color_texture = ba.gettexture('thePadLevelColor') 179 trees_texture = ba.gettexture('treesColor') 180 bgtex = ba.gettexture('menuBG') 181 bgmodel = ba.getmodel('thePadBG') 182 183 # Load these last since most platforms don't use them. 184 vr_bottom_fill_model = ba.getmodel('thePadVRFillBottom') 185 vr_top_fill_model = ba.getmodel('thePadVRFillTop') 186 187 gnode = self.globalsnode 188 gnode.camera_mode = 'rotate' 189 190 tint = (1.14, 1.1, 1.0) 191 gnode.tint = tint 192 gnode.ambient_color = (1.06, 1.04, 1.03) 193 gnode.vignette_outer = (0.45, 0.55, 0.54) 194 gnode.vignette_inner = (0.99, 0.98, 0.98) 195 196 self.bottom = ba.NodeActor( 197 ba.newnode('terrain', 198 attrs={ 199 'model': bottom_model, 200 'lighting': False, 201 'reflection': 'soft', 202 'reflection_scale': [0.45], 203 'color_texture': color_texture 204 })) 205 self.vr_bottom_fill = ba.NodeActor( 206 ba.newnode('terrain', 207 attrs={ 208 'model': vr_bottom_fill_model, 209 'lighting': False, 210 'vr_only': True, 211 'color_texture': color_texture 212 })) 213 self.vr_top_fill = ba.NodeActor( 214 ba.newnode('terrain', 215 attrs={ 216 'model': vr_top_fill_model, 217 'vr_only': True, 218 'lighting': False, 219 'color_texture': bgtex 220 })) 221 self.terrain = ba.NodeActor( 222 ba.newnode('terrain', 223 attrs={ 224 'model': model, 225 'color_texture': color_texture, 226 'reflection': 'soft', 227 'reflection_scale': [0.3] 228 })) 229 self.trees = ba.NodeActor( 230 ba.newnode('terrain', 231 attrs={ 232 'model': trees_model, 233 'lighting': False, 234 'reflection': 'char', 235 'reflection_scale': [0.1], 236 'color_texture': trees_texture 237 })) 238 self.bgterrain = ba.NodeActor( 239 ba.newnode('terrain', 240 attrs={ 241 'model': bgmodel, 242 'color': (0.92, 0.91, 0.9), 243 'lighting': False, 244 'background': True, 245 'color_texture': bgtex 246 })) 247 248 self._ts = 0.86 249 250 self._language: str | None = None 251 self._update_timer = ba.Timer(1.0, self._update, repeat=True) 252 self._update() 253 254 # Hopefully this won't hitch but lets space these out anyway. 255 _ba.add_clean_frame_callback(ba.WeakCall(self._start_preloads)) 256 257 random.seed() 258 259 # On the main menu, also show our news. 260 class News: 261 """Wrangles news display.""" 262 263 def __init__(self, activity: ba.Activity): 264 self._valid = True 265 self._message_duration = 10.0 266 self._message_spacing = 2.0 267 self._text: ba.NodeActor | None = None 268 self._activity = weakref.ref(activity) 269 270 # If we're signed in, fetch news immediately. 271 # Otherwise wait until we are signed in. 272 self._fetch_timer: ba.Timer | None = ba.Timer( 273 1.0, ba.WeakCall(self._try_fetching_news), repeat=True) 274 self._try_fetching_news() 275 276 # We now want to wait until we're signed in before fetching news. 277 def _try_fetching_news(self) -> None: 278 if _ba.get_v1_account_state() == 'signed_in': 279 self._fetch_news() 280 self._fetch_timer = None 281 282 def _fetch_news(self) -> None: 283 ba.app.main_menu_last_news_fetch_time = time.time() 284 285 # UPDATE - We now just pull news from MRVs. 286 news = _ba.get_v1_account_misc_read_val('n', None) 287 if news is not None: 288 self._got_news(news) 289 290 def _change_phrase(self) -> None: 291 from bastd.actor.text import Text 292 293 # If our news is way out of date, lets re-request it; 294 # otherwise, rotate our phrase. 295 assert ba.app.main_menu_last_news_fetch_time is not None 296 if time.time() - ba.app.main_menu_last_news_fetch_time > 600.0: 297 self._fetch_news() 298 self._text = None 299 else: 300 if self._text is not None: 301 if not self._phrases: 302 for phr in self._used_phrases: 303 self._phrases.insert(0, phr) 304 val = self._phrases.pop() 305 if val == '__ACH__': 306 vrmode = app.vr_mode 307 Text(ba.Lstr(resource='nextAchievementsText'), 308 color=((1, 1, 1, 1) if vrmode else 309 (0.95, 0.9, 1, 0.4)), 310 host_only=True, 311 maxwidth=200, 312 position=(-300, -35), 313 h_align=Text.HAlign.RIGHT, 314 transition=Text.Transition.FADE_IN, 315 scale=0.9 if vrmode else 0.7, 316 flatness=1.0 if vrmode else 0.6, 317 shadow=1.0 if vrmode else 0.5, 318 h_attach=Text.HAttach.CENTER, 319 v_attach=Text.VAttach.TOP, 320 transition_delay=1.0, 321 transition_out_delay=self._message_duration 322 ).autoretain() 323 achs = [ 324 a for a in app.ach.achievements 325 if not a.complete 326 ] 327 if achs: 328 ach = achs.pop( 329 random.randrange(min(4, len(achs)))) 330 ach.create_display( 331 -180, 332 -35, 333 1.0, 334 outdelay=self._message_duration, 335 style='news') 336 if achs: 337 ach = achs.pop( 338 random.randrange(min(8, len(achs)))) 339 ach.create_display( 340 180, 341 -35, 342 1.25, 343 outdelay=self._message_duration, 344 style='news') 345 else: 346 spc = self._message_spacing 347 keys = { 348 spc: 0.0, 349 spc + 1.0: 1.0, 350 spc + self._message_duration - 1.0: 1.0, 351 spc + self._message_duration: 0.0 352 } 353 assert self._text.node 354 ba.animate(self._text.node, 'opacity', keys) 355 # {k: v 356 # for k, v in list(keys.items())}) 357 self._text.node.text = val 358 359 def _got_news(self, news: str) -> None: 360 # Run this stuff in the context of our activity since we 361 # need to make nodes and stuff.. should fix the serverget 362 # call so it. 363 activity = self._activity() 364 if activity is None or activity.expired: 365 return 366 with ba.Context(activity): 367 368 self._phrases: list[str] = [] 369 370 # Show upcoming achievements in non-vr versions 371 # (currently too hard to read in vr). 372 self._used_phrases = ( 373 ['__ACH__'] if not ba.app.vr_mode else 374 []) + [s for s in news.split('<br>\n') if s != ''] 375 self._phrase_change_timer = ba.Timer( 376 (self._message_duration + self._message_spacing), 377 ba.WeakCall(self._change_phrase), 378 repeat=True) 379 380 scl = 1.2 if (ba.app.ui.uiscale is ba.UIScale.SMALL 381 or ba.app.vr_mode) else 0.8 382 383 color2 = ((1, 1, 1, 1) if ba.app.vr_mode else 384 (0.7, 0.65, 0.75, 1.0)) 385 shadow = (1.0 if ba.app.vr_mode else 0.4) 386 self._text = ba.NodeActor( 387 ba.newnode('text', 388 attrs={ 389 'v_attach': 'top', 390 'h_attach': 'center', 391 'h_align': 'center', 392 'vr_depth': -20, 393 'shadow': shadow, 394 'flatness': 0.8, 395 'v_align': 'top', 396 'color': color2, 397 'scale': scl, 398 'maxwidth': 900.0 / scl, 399 'position': (0, -10) 400 })) 401 self._change_phrase() 402 403 if not (app.demo_mode or app.arcade_mode) and not app.toolbar_test: 404 self._news = News(self) 405 406 # Bring up the last place we were, or start at the main menu otherwise. 407 with ba.Context('ui'): 408 from bastd.ui import specialoffer 409 if bool(False): 410 uicontroller = ba.app.ui.controller 411 assert uicontroller is not None 412 uicontroller.show_main_menu() 413 else: 414 main_menu_location = ba.app.ui.get_main_menu_location() 415 416 # When coming back from a kiosk-mode game, jump to 417 # the kiosk start screen. 418 if ba.app.demo_mode or ba.app.arcade_mode: 419 # pylint: disable=cyclic-import 420 from bastd.ui.kiosk import KioskWindow 421 ba.app.ui.set_main_menu_window( 422 KioskWindow().get_root_widget()) 423 # ..or in normal cases go back to the main menu 424 else: 425 if main_menu_location == 'Gather': 426 # pylint: disable=cyclic-import 427 from bastd.ui.gather import GatherWindow 428 ba.app.ui.set_main_menu_window( 429 GatherWindow(transition=None).get_root_widget()) 430 elif main_menu_location == 'Watch': 431 # pylint: disable=cyclic-import 432 from bastd.ui.watch import WatchWindow 433 ba.app.ui.set_main_menu_window( 434 WatchWindow(transition=None).get_root_widget()) 435 elif main_menu_location == 'Team Game Select': 436 # pylint: disable=cyclic-import 437 from bastd.ui.playlist.browser import ( 438 PlaylistBrowserWindow) 439 ba.app.ui.set_main_menu_window( 440 PlaylistBrowserWindow( 441 sessiontype=ba.DualTeamSession, 442 transition=None).get_root_widget()) 443 elif main_menu_location == 'Free-for-All Game Select': 444 # pylint: disable=cyclic-import 445 from bastd.ui.playlist.browser import ( 446 PlaylistBrowserWindow) 447 ba.app.ui.set_main_menu_window( 448 PlaylistBrowserWindow( 449 sessiontype=ba.FreeForAllSession, 450 transition=None).get_root_widget()) 451 elif main_menu_location == 'Coop Select': 452 # pylint: disable=cyclic-import 453 from bastd.ui.coop.browser import CoopBrowserWindow 454 ba.app.ui.set_main_menu_window( 455 CoopBrowserWindow( 456 transition=None).get_root_widget()) 457 else: 458 # pylint: disable=cyclic-import 459 from bastd.ui.mainmenu import MainMenuWindow 460 ba.app.ui.set_main_menu_window( 461 MainMenuWindow(transition=None).get_root_widget()) 462 463 # attempt to show any pending offers immediately. 464 # If that doesn't work, try again in a few seconds 465 # (we may not have heard back from the server) 466 # ..if that doesn't work they'll just have to wait 467 # until the next opportunity. 468 if not specialoffer.show_offer(): 469 470 def try_again() -> None: 471 if not specialoffer.show_offer(): 472 # Try one last time.. 473 ba.timer(2.0, 474 specialoffer.show_offer, 475 timetype=ba.TimeType.REAL) 476 477 ba.timer(2.0, try_again, timetype=ba.TimeType.REAL) 478 ba.app.main_menu_did_initial_transition = True 479 480 def _update(self) -> None: 481 app = ba.app 482 483 # Update logo in case it changes. 484 if self._logo_node: 485 custom_texture = self._get_custom_logo_tex_name() 486 if custom_texture != self._custom_logo_tex_name: 487 self._custom_logo_tex_name = custom_texture 488 self._logo_node.texture = ba.gettexture( 489 custom_texture if custom_texture is not None else 'logo') 490 self._logo_node.model_opaque = (None 491 if custom_texture is not None 492 else ba.getmodel('logo')) 493 self._logo_node.model_transparent = ( 494 None if custom_texture is not None else 495 ba.getmodel('logoTransparent')) 496 497 # If language has changed, recreate our logo text/graphics. 498 lang = app.lang.language 499 if lang != self._language: 500 self._language = lang 501 y = 20 502 base_scale = 1.1 503 self._word_actors = [] 504 base_delay = 1.0 505 delay = base_delay 506 delay_inc = 0.02 507 508 # Come on faster after the first time. 509 if app.main_menu_did_initial_transition: 510 base_delay = 0.0 511 delay = base_delay 512 delay_inc = 0.02 513 514 # We draw higher in kiosk mode (make sure to test this 515 # when making adjustments) for now we're hard-coded for 516 # a few languages.. should maybe look into generalizing this?.. 517 if app.lang.language == 'Chinese': 518 base_x = -270.0 519 x = base_x - 20.0 520 spacing = 85.0 * base_scale 521 y_extra = 0.0 if (app.demo_mode or app.arcade_mode) else 0.0 522 self._make_logo(x - 110 + 50, 523 113 + y + 1.2 * y_extra, 524 0.34 * base_scale, 525 delay=base_delay + 0.1, 526 custom_texture='chTitleChar1', 527 jitter_scale=2.0, 528 vr_depth_offset=-30) 529 x += spacing 530 delay += delay_inc 531 self._make_logo(x - 10 + 50, 532 110 + y + 1.2 * y_extra, 533 0.31 * base_scale, 534 delay=base_delay + 0.15, 535 custom_texture='chTitleChar2', 536 jitter_scale=2.0, 537 vr_depth_offset=-30) 538 x += 2.0 * spacing 539 delay += delay_inc 540 self._make_logo(x + 180 - 140, 541 110 + y + 1.2 * y_extra, 542 0.3 * base_scale, 543 delay=base_delay + 0.25, 544 custom_texture='chTitleChar3', 545 jitter_scale=2.0, 546 vr_depth_offset=-30) 547 x += spacing 548 delay += delay_inc 549 self._make_logo(x + 241 - 120, 550 110 + y + 1.2 * y_extra, 551 0.31 * base_scale, 552 delay=base_delay + 0.3, 553 custom_texture='chTitleChar4', 554 jitter_scale=2.0, 555 vr_depth_offset=-30) 556 x += spacing 557 delay += delay_inc 558 self._make_logo(x + 300 - 90, 559 105 + y + 1.2 * y_extra, 560 0.34 * base_scale, 561 delay=base_delay + 0.35, 562 custom_texture='chTitleChar5', 563 jitter_scale=2.0, 564 vr_depth_offset=-30) 565 self._make_logo(base_x + 155, 566 146 + y + 1.2 * y_extra, 567 0.28 * base_scale, 568 delay=base_delay + 0.2, 569 rotate=-7) 570 else: 571 base_x = -170 572 x = base_x - 20 573 spacing = 55 * base_scale 574 y_extra = 0 if (app.demo_mode or app.arcade_mode) else 0 575 xv1 = x 576 delay1 = delay 577 for shadow in (True, False): 578 x = xv1 579 delay = delay1 580 self._make_word('B', 581 x - 50, 582 y - 23 + 0.8 * y_extra, 583 scale=1.3 * base_scale, 584 delay=delay, 585 vr_depth_offset=3, 586 shadow=shadow) 587 x += spacing 588 delay += delay_inc 589 self._make_word('m', 590 x, 591 y + y_extra, 592 delay=delay, 593 scale=base_scale, 594 shadow=shadow) 595 x += spacing * 1.25 596 delay += delay_inc 597 self._make_word('b', 598 x, 599 y + y_extra - 10, 600 delay=delay, 601 scale=1.1 * base_scale, 602 vr_depth_offset=5, 603 shadow=shadow) 604 x += spacing * 0.85 605 delay += delay_inc 606 self._make_word('S', 607 x, 608 y - 25 + 0.8 * y_extra, 609 scale=1.35 * base_scale, 610 delay=delay, 611 vr_depth_offset=14, 612 shadow=shadow) 613 x += spacing 614 delay += delay_inc 615 self._make_word('q', 616 x, 617 y + y_extra, 618 delay=delay, 619 scale=base_scale, 620 shadow=shadow) 621 x += spacing * 0.9 622 delay += delay_inc 623 self._make_word('u', 624 x, 625 y + y_extra, 626 delay=delay, 627 scale=base_scale, 628 vr_depth_offset=7, 629 shadow=shadow) 630 x += spacing * 0.9 631 delay += delay_inc 632 self._make_word('a', 633 x, 634 y + y_extra, 635 delay=delay, 636 scale=base_scale, 637 shadow=shadow) 638 x += spacing * 0.64 639 delay += delay_inc 640 self._make_word('d', 641 x, 642 y + y_extra - 10, 643 delay=delay, 644 scale=1.1 * base_scale, 645 vr_depth_offset=6, 646 shadow=shadow) 647 self._make_logo(base_x - 28, 648 125 + y + 1.2 * y_extra, 649 0.32 * base_scale, 650 delay=base_delay) 651 652 def _make_word(self, 653 word: str, 654 x: float, 655 y: float, 656 scale: float = 1.0, 657 delay: float = 0.0, 658 vr_depth_offset: float = 0.0, 659 shadow: bool = False) -> None: 660 if shadow: 661 word_obj = ba.NodeActor( 662 ba.newnode('text', 663 attrs={ 664 'position': (x, y), 665 'big': True, 666 'color': (0.0, 0.0, 0.2, 0.08), 667 'tilt_translate': 0.09, 668 'opacity_scales_shadow': False, 669 'shadow': 0.2, 670 'vr_depth': -130, 671 'v_align': 'center', 672 'project_scale': 0.97 * scale, 673 'scale': 1.0, 674 'text': word 675 })) 676 self._word_actors.append(word_obj) 677 else: 678 word_obj = ba.NodeActor( 679 ba.newnode('text', 680 attrs={ 681 'position': (x, y), 682 'big': True, 683 'color': (1.2, 1.15, 1.15, 1.0), 684 'tilt_translate': 0.11, 685 'shadow': 0.2, 686 'vr_depth': -40 + vr_depth_offset, 687 'v_align': 'center', 688 'project_scale': scale, 689 'scale': 1.0, 690 'text': word 691 })) 692 self._word_actors.append(word_obj) 693 694 # Add a bit of stop-motion-y jitter to the logo 695 # (unless we're in VR mode in which case its best to 696 # leave things still). 697 if not ba.app.vr_mode: 698 cmb: ba.Node | None 699 cmb2: ba.Node | None 700 if not shadow: 701 cmb = ba.newnode('combine', 702 owner=word_obj.node, 703 attrs={'size': 2}) 704 else: 705 cmb = None 706 if shadow: 707 cmb2 = ba.newnode('combine', 708 owner=word_obj.node, 709 attrs={'size': 2}) 710 else: 711 cmb2 = None 712 if not shadow: 713 assert cmb and word_obj.node 714 cmb.connectattr('output', word_obj.node, 'position') 715 if shadow: 716 assert cmb2 and word_obj.node 717 cmb2.connectattr('output', word_obj.node, 'position') 718 keys = {} 719 keys2 = {} 720 time_v = 0.0 721 for _i in range(10): 722 val = x + (random.random() - 0.5) * 0.8 723 val2 = x + (random.random() - 0.5) * 0.8 724 keys[time_v * self._ts] = val 725 keys2[time_v * self._ts] = val2 + 5 726 time_v += random.random() * 0.1 727 if cmb is not None: 728 ba.animate(cmb, 'input0', keys, loop=True) 729 if cmb2 is not None: 730 ba.animate(cmb2, 'input0', keys2, loop=True) 731 keys = {} 732 keys2 = {} 733 time_v = 0 734 for _i in range(10): 735 val = y + (random.random() - 0.5) * 0.8 736 val2 = y + (random.random() - 0.5) * 0.8 737 keys[time_v * self._ts] = val 738 keys2[time_v * self._ts] = val2 - 9 739 time_v += random.random() * 0.1 740 if cmb is not None: 741 ba.animate(cmb, 'input1', keys, loop=True) 742 if cmb2 is not None: 743 ba.animate(cmb2, 'input1', keys2, loop=True) 744 745 if not shadow: 746 assert word_obj.node 747 ba.animate(word_obj.node, 'project_scale', { 748 delay: 0.0, 749 delay + 0.1: scale * 1.1, 750 delay + 0.2: scale 751 }) 752 else: 753 assert word_obj.node 754 ba.animate(word_obj.node, 'project_scale', { 755 delay: 0.0, 756 delay + 0.1: scale * 1.1, 757 delay + 0.2: scale 758 }) 759 760 def _get_custom_logo_tex_name(self) -> str | None: 761 if _ba.get_v1_account_misc_read_val('easter', False): 762 return 'logoEaster' 763 return None 764 765 # Pop the logo and menu in. 766 def _make_logo(self, 767 x: float, 768 y: float, 769 scale: float, 770 delay: float, 771 custom_texture: str | None = None, 772 jitter_scale: float = 1.0, 773 rotate: float = 0.0, 774 vr_depth_offset: float = 0.0) -> None: 775 776 # Temp easter goodness. 777 if custom_texture is None: 778 custom_texture = self._get_custom_logo_tex_name() 779 self._custom_logo_tex_name = custom_texture 780 ltex = ba.gettexture( 781 custom_texture if custom_texture is not None else 'logo') 782 mopaque = (None if custom_texture is not None else ba.getmodel('logo')) 783 mtrans = (None if custom_texture is not None else 784 ba.getmodel('logoTransparent')) 785 logo = ba.NodeActor( 786 ba.newnode('image', 787 attrs={ 788 'texture': ltex, 789 'model_opaque': mopaque, 790 'model_transparent': mtrans, 791 'vr_depth': -10 + vr_depth_offset, 792 'rotate': rotate, 793 'attach': 'center', 794 'tilt_translate': 0.21, 795 'absolute_scale': True 796 })) 797 self._logo_node = logo.node 798 self._word_actors.append(logo) 799 800 # Add a bit of stop-motion-y jitter to the logo 801 # (unless we're in VR mode in which case its best to 802 # leave things still). 803 assert logo.node 804 if not ba.app.vr_mode: 805 cmb = ba.newnode('combine', owner=logo.node, attrs={'size': 2}) 806 cmb.connectattr('output', logo.node, 'position') 807 keys = {} 808 time_v = 0.0 809 810 # Gen some random keys for that stop-motion-y look 811 for _i in range(10): 812 keys[time_v] = x + (random.random() - 0.5) * 0.7 * jitter_scale 813 time_v += random.random() * 0.1 814 ba.animate(cmb, 'input0', keys, loop=True) 815 keys = {} 816 time_v = 0.0 817 for _i in range(10): 818 keys[time_v * self._ts] = y + (random.random() - 819 0.5) * 0.7 * jitter_scale 820 time_v += random.random() * 0.1 821 ba.animate(cmb, 'input1', keys, loop=True) 822 else: 823 logo.node.position = (x, y) 824 825 cmb = ba.newnode('combine', owner=logo.node, attrs={'size': 2}) 826 827 keys = { 828 delay: 0.0, 829 delay + 0.1: 700.0 * scale, 830 delay + 0.2: 600.0 * scale 831 } 832 ba.animate(cmb, 'input0', keys) 833 ba.animate(cmb, 'input1', keys) 834 cmb.connectattr('output', logo.node, 'scale') 835 836 def _start_preloads(self) -> None: 837 # FIXME: The func that calls us back doesn't save/restore state 838 # or check for a dead activity so we have to do that ourself. 839 if self.expired: 840 return 841 with ba.Context(self): 842 _preload1() 843 844 ba.timer(0.5, lambda: ba.setmusic(ba.MusicType.MENU))
Activity showing the rotating main menu bg stuff.
33 def on_transition_in(self) -> None: 34 super().on_transition_in() 35 random.seed(123) 36 self._logo_node: ba.Node | None = None 37 self._custom_logo_tex_name: str | None = None 38 self._word_actors: list[ba.Actor] = [] 39 app = ba.app 40 41 # FIXME: We shouldn't be doing things conditionally based on whether 42 # the host is VR mode or not (clients may differ in that regard). 43 # Any differences need to happen at the engine level so everyone 44 # sees things in their own optimal way. 45 vr_mode = ba.app.vr_mode 46 47 if not ba.app.toolbar_test: 48 color = ((1.0, 1.0, 1.0, 1.0) if vr_mode else (0.5, 0.6, 0.5, 0.6)) 49 50 # FIXME: Need a node attr for vr-specific-scale. 51 scale = (0.9 if 52 (app.ui.uiscale is ba.UIScale.SMALL or vr_mode) else 0.7) 53 self.my_name = ba.NodeActor( 54 ba.newnode('text', 55 attrs={ 56 'v_attach': 'bottom', 57 'h_align': 'center', 58 'color': color, 59 'flatness': 1.0, 60 'shadow': 1.0 if vr_mode else 0.5, 61 'scale': scale, 62 'position': (0, 10), 63 'vr_depth': -10, 64 'text': '\xa9 2011-2022 Eric Froemling' 65 })) 66 67 # Throw up some text that only clients can see so they know that the 68 # host is navigating menus while they're just staring at an 69 # empty-ish screen. 70 tval = ba.Lstr(resource='hostIsNavigatingMenusText', 71 subs=[('${HOST}', _ba.get_v1_account_display_string())]) 72 self._host_is_navigating_text = ba.NodeActor( 73 ba.newnode('text', 74 attrs={ 75 'text': tval, 76 'client_only': True, 77 'position': (0, -200), 78 'flatness': 1.0, 79 'h_align': 'center' 80 })) 81 if not ba.app.main_menu_did_initial_transition and hasattr( 82 self, 'my_name'): 83 assert self.my_name.node 84 ba.animate(self.my_name.node, 'opacity', {2.3: 0, 3.0: 1.0}) 85 86 # FIXME: We shouldn't be doing things conditionally based on whether 87 # the host is vr mode or not (clients may not be or vice versa). 88 # Any differences need to happen at the engine level so everyone sees 89 # things in their own optimal way. 90 vr_mode = app.vr_mode 91 uiscale = app.ui.uiscale 92 93 # In cases where we're doing lots of dev work lets always show the 94 # build number. 95 force_show_build_number = False 96 97 if not ba.app.toolbar_test: 98 if app.debug_build or app.test_build or force_show_build_number: 99 if app.debug_build: 100 text = ba.Lstr(value='${V} (${B}) (${D})', 101 subs=[ 102 ('${V}', app.version), 103 ('${B}', str(app.build_number)), 104 ('${D}', ba.Lstr(resource='debugText')), 105 ]) 106 else: 107 text = ba.Lstr(value='${V} (${B})', 108 subs=[ 109 ('${V}', app.version), 110 ('${B}', str(app.build_number)), 111 ]) 112 else: 113 text = ba.Lstr(value='${V}', subs=[('${V}', app.version)]) 114 scale = 0.9 if (uiscale is ba.UIScale.SMALL or vr_mode) else 0.7 115 color = (1, 1, 1, 1) if vr_mode else (0.5, 0.6, 0.5, 0.7) 116 self.version = ba.NodeActor( 117 ba.newnode( 118 'text', 119 attrs={ 120 'v_attach': 'bottom', 121 'h_attach': 'right', 122 'h_align': 'right', 123 'flatness': 1.0, 124 'vr_depth': -10, 125 'shadow': 1.0 if vr_mode else 0.5, 126 'color': color, 127 'scale': scale, 128 'position': (-260, 10) if vr_mode else (-10, 10), 129 'text': text 130 })) 131 if not ba.app.main_menu_did_initial_transition: 132 assert self.version.node 133 ba.animate(self.version.node, 'opacity', {2.3: 0, 3.0: 1.0}) 134 135 # Show the iircade logo on our iircade build. 136 if app.iircade_mode: 137 img = ba.NodeActor( 138 ba.newnode('image', 139 attrs={ 140 'texture': ba.gettexture('iircadeLogo'), 141 'attach': 'center', 142 'scale': (250, 250), 143 'position': (0, 0), 144 'tilt_translate': 0.21, 145 'absolute_scale': True 146 })).autoretain() 147 imgdelay = 0.0 if app.main_menu_did_initial_transition else 1.0 148 ba.animate(img.node, 'opacity', { 149 imgdelay + 1.5: 0.0, 150 imgdelay + 2.5: 1.0 151 }) 152 153 # Throw in test build info. 154 self.beta_info = self.beta_info_2 = None 155 if app.test_build and not (app.demo_mode or app.arcade_mode): 156 pos = ((230, 125) if (app.demo_mode or app.arcade_mode) else 157 (230, 35)) 158 self.beta_info = ba.NodeActor( 159 ba.newnode('text', 160 attrs={ 161 'v_attach': 'center', 162 'h_align': 'center', 163 'color': (1, 1, 1, 1), 164 'shadow': 0.5, 165 'flatness': 0.5, 166 'scale': 1, 167 'vr_depth': -60, 168 'position': pos, 169 'text': ba.Lstr(resource='testBuildText') 170 })) 171 if not ba.app.main_menu_did_initial_transition: 172 assert self.beta_info.node 173 ba.animate(self.beta_info.node, 'opacity', {1.3: 0, 1.8: 1.0}) 174 175 model = ba.getmodel('thePadLevel') 176 trees_model = ba.getmodel('trees') 177 bottom_model = ba.getmodel('thePadLevelBottom') 178 color_texture = ba.gettexture('thePadLevelColor') 179 trees_texture = ba.gettexture('treesColor') 180 bgtex = ba.gettexture('menuBG') 181 bgmodel = ba.getmodel('thePadBG') 182 183 # Load these last since most platforms don't use them. 184 vr_bottom_fill_model = ba.getmodel('thePadVRFillBottom') 185 vr_top_fill_model = ba.getmodel('thePadVRFillTop') 186 187 gnode = self.globalsnode 188 gnode.camera_mode = 'rotate' 189 190 tint = (1.14, 1.1, 1.0) 191 gnode.tint = tint 192 gnode.ambient_color = (1.06, 1.04, 1.03) 193 gnode.vignette_outer = (0.45, 0.55, 0.54) 194 gnode.vignette_inner = (0.99, 0.98, 0.98) 195 196 self.bottom = ba.NodeActor( 197 ba.newnode('terrain', 198 attrs={ 199 'model': bottom_model, 200 'lighting': False, 201 'reflection': 'soft', 202 'reflection_scale': [0.45], 203 'color_texture': color_texture 204 })) 205 self.vr_bottom_fill = ba.NodeActor( 206 ba.newnode('terrain', 207 attrs={ 208 'model': vr_bottom_fill_model, 209 'lighting': False, 210 'vr_only': True, 211 'color_texture': color_texture 212 })) 213 self.vr_top_fill = ba.NodeActor( 214 ba.newnode('terrain', 215 attrs={ 216 'model': vr_top_fill_model, 217 'vr_only': True, 218 'lighting': False, 219 'color_texture': bgtex 220 })) 221 self.terrain = ba.NodeActor( 222 ba.newnode('terrain', 223 attrs={ 224 'model': model, 225 'color_texture': color_texture, 226 'reflection': 'soft', 227 'reflection_scale': [0.3] 228 })) 229 self.trees = ba.NodeActor( 230 ba.newnode('terrain', 231 attrs={ 232 'model': trees_model, 233 'lighting': False, 234 'reflection': 'char', 235 'reflection_scale': [0.1], 236 'color_texture': trees_texture 237 })) 238 self.bgterrain = ba.NodeActor( 239 ba.newnode('terrain', 240 attrs={ 241 'model': bgmodel, 242 'color': (0.92, 0.91, 0.9), 243 'lighting': False, 244 'background': True, 245 'color_texture': bgtex 246 })) 247 248 self._ts = 0.86 249 250 self._language: str | None = None 251 self._update_timer = ba.Timer(1.0, self._update, repeat=True) 252 self._update() 253 254 # Hopefully this won't hitch but lets space these out anyway. 255 _ba.add_clean_frame_callback(ba.WeakCall(self._start_preloads)) 256 257 random.seed() 258 259 # On the main menu, also show our news. 260 class News: 261 """Wrangles news display.""" 262 263 def __init__(self, activity: ba.Activity): 264 self._valid = True 265 self._message_duration = 10.0 266 self._message_spacing = 2.0 267 self._text: ba.NodeActor | None = None 268 self._activity = weakref.ref(activity) 269 270 # If we're signed in, fetch news immediately. 271 # Otherwise wait until we are signed in. 272 self._fetch_timer: ba.Timer | None = ba.Timer( 273 1.0, ba.WeakCall(self._try_fetching_news), repeat=True) 274 self._try_fetching_news() 275 276 # We now want to wait until we're signed in before fetching news. 277 def _try_fetching_news(self) -> None: 278 if _ba.get_v1_account_state() == 'signed_in': 279 self._fetch_news() 280 self._fetch_timer = None 281 282 def _fetch_news(self) -> None: 283 ba.app.main_menu_last_news_fetch_time = time.time() 284 285 # UPDATE - We now just pull news from MRVs. 286 news = _ba.get_v1_account_misc_read_val('n', None) 287 if news is not None: 288 self._got_news(news) 289 290 def _change_phrase(self) -> None: 291 from bastd.actor.text import Text 292 293 # If our news is way out of date, lets re-request it; 294 # otherwise, rotate our phrase. 295 assert ba.app.main_menu_last_news_fetch_time is not None 296 if time.time() - ba.app.main_menu_last_news_fetch_time > 600.0: 297 self._fetch_news() 298 self._text = None 299 else: 300 if self._text is not None: 301 if not self._phrases: 302 for phr in self._used_phrases: 303 self._phrases.insert(0, phr) 304 val = self._phrases.pop() 305 if val == '__ACH__': 306 vrmode = app.vr_mode 307 Text(ba.Lstr(resource='nextAchievementsText'), 308 color=((1, 1, 1, 1) if vrmode else 309 (0.95, 0.9, 1, 0.4)), 310 host_only=True, 311 maxwidth=200, 312 position=(-300, -35), 313 h_align=Text.HAlign.RIGHT, 314 transition=Text.Transition.FADE_IN, 315 scale=0.9 if vrmode else 0.7, 316 flatness=1.0 if vrmode else 0.6, 317 shadow=1.0 if vrmode else 0.5, 318 h_attach=Text.HAttach.CENTER, 319 v_attach=Text.VAttach.TOP, 320 transition_delay=1.0, 321 transition_out_delay=self._message_duration 322 ).autoretain() 323 achs = [ 324 a for a in app.ach.achievements 325 if not a.complete 326 ] 327 if achs: 328 ach = achs.pop( 329 random.randrange(min(4, len(achs)))) 330 ach.create_display( 331 -180, 332 -35, 333 1.0, 334 outdelay=self._message_duration, 335 style='news') 336 if achs: 337 ach = achs.pop( 338 random.randrange(min(8, len(achs)))) 339 ach.create_display( 340 180, 341 -35, 342 1.25, 343 outdelay=self._message_duration, 344 style='news') 345 else: 346 spc = self._message_spacing 347 keys = { 348 spc: 0.0, 349 spc + 1.0: 1.0, 350 spc + self._message_duration - 1.0: 1.0, 351 spc + self._message_duration: 0.0 352 } 353 assert self._text.node 354 ba.animate(self._text.node, 'opacity', keys) 355 # {k: v 356 # for k, v in list(keys.items())}) 357 self._text.node.text = val 358 359 def _got_news(self, news: str) -> None: 360 # Run this stuff in the context of our activity since we 361 # need to make nodes and stuff.. should fix the serverget 362 # call so it. 363 activity = self._activity() 364 if activity is None or activity.expired: 365 return 366 with ba.Context(activity): 367 368 self._phrases: list[str] = [] 369 370 # Show upcoming achievements in non-vr versions 371 # (currently too hard to read in vr). 372 self._used_phrases = ( 373 ['__ACH__'] if not ba.app.vr_mode else 374 []) + [s for s in news.split('<br>\n') if s != ''] 375 self._phrase_change_timer = ba.Timer( 376 (self._message_duration + self._message_spacing), 377 ba.WeakCall(self._change_phrase), 378 repeat=True) 379 380 scl = 1.2 if (ba.app.ui.uiscale is ba.UIScale.SMALL 381 or ba.app.vr_mode) else 0.8 382 383 color2 = ((1, 1, 1, 1) if ba.app.vr_mode else 384 (0.7, 0.65, 0.75, 1.0)) 385 shadow = (1.0 if ba.app.vr_mode else 0.4) 386 self._text = ba.NodeActor( 387 ba.newnode('text', 388 attrs={ 389 'v_attach': 'top', 390 'h_attach': 'center', 391 'h_align': 'center', 392 'vr_depth': -20, 393 'shadow': shadow, 394 'flatness': 0.8, 395 'v_align': 'top', 396 'color': color2, 397 'scale': scl, 398 'maxwidth': 900.0 / scl, 399 'position': (0, -10) 400 })) 401 self._change_phrase() 402 403 if not (app.demo_mode or app.arcade_mode) and not app.toolbar_test: 404 self._news = News(self) 405 406 # Bring up the last place we were, or start at the main menu otherwise. 407 with ba.Context('ui'): 408 from bastd.ui import specialoffer 409 if bool(False): 410 uicontroller = ba.app.ui.controller 411 assert uicontroller is not None 412 uicontroller.show_main_menu() 413 else: 414 main_menu_location = ba.app.ui.get_main_menu_location() 415 416 # When coming back from a kiosk-mode game, jump to 417 # the kiosk start screen. 418 if ba.app.demo_mode or ba.app.arcade_mode: 419 # pylint: disable=cyclic-import 420 from bastd.ui.kiosk import KioskWindow 421 ba.app.ui.set_main_menu_window( 422 KioskWindow().get_root_widget()) 423 # ..or in normal cases go back to the main menu 424 else: 425 if main_menu_location == 'Gather': 426 # pylint: disable=cyclic-import 427 from bastd.ui.gather import GatherWindow 428 ba.app.ui.set_main_menu_window( 429 GatherWindow(transition=None).get_root_widget()) 430 elif main_menu_location == 'Watch': 431 # pylint: disable=cyclic-import 432 from bastd.ui.watch import WatchWindow 433 ba.app.ui.set_main_menu_window( 434 WatchWindow(transition=None).get_root_widget()) 435 elif main_menu_location == 'Team Game Select': 436 # pylint: disable=cyclic-import 437 from bastd.ui.playlist.browser import ( 438 PlaylistBrowserWindow) 439 ba.app.ui.set_main_menu_window( 440 PlaylistBrowserWindow( 441 sessiontype=ba.DualTeamSession, 442 transition=None).get_root_widget()) 443 elif main_menu_location == 'Free-for-All Game Select': 444 # pylint: disable=cyclic-import 445 from bastd.ui.playlist.browser import ( 446 PlaylistBrowserWindow) 447 ba.app.ui.set_main_menu_window( 448 PlaylistBrowserWindow( 449 sessiontype=ba.FreeForAllSession, 450 transition=None).get_root_widget()) 451 elif main_menu_location == 'Coop Select': 452 # pylint: disable=cyclic-import 453 from bastd.ui.coop.browser import CoopBrowserWindow 454 ba.app.ui.set_main_menu_window( 455 CoopBrowserWindow( 456 transition=None).get_root_widget()) 457 else: 458 # pylint: disable=cyclic-import 459 from bastd.ui.mainmenu import MainMenuWindow 460 ba.app.ui.set_main_menu_window( 461 MainMenuWindow(transition=None).get_root_widget()) 462 463 # attempt to show any pending offers immediately. 464 # If that doesn't work, try again in a few seconds 465 # (we may not have heard back from the server) 466 # ..if that doesn't work they'll just have to wait 467 # until the next opportunity. 468 if not specialoffer.show_offer(): 469 470 def try_again() -> None: 471 if not specialoffer.show_offer(): 472 # Try one last time.. 473 ba.timer(2.0, 474 specialoffer.show_offer, 475 timetype=ba.TimeType.REAL) 476 477 ba.timer(2.0, try_again, timetype=ba.TimeType.REAL) 478 ba.app.main_menu_did_initial_transition = True
Called when the Activity is first becoming visible.
Upon this call, the Activity should fade in backgrounds, start playing music, etc. It does not yet have access to players or teams, however. They remain owned by the previous Activity up until ba.Activity.on_begin() is called.
Inherited Members
- ba._activity.Activity
- Activity
- settings_raw
- teams
- players
- announce_player_deaths
- is_joining_activity
- allow_pausing
- allow_kick_idle_players
- use_fixed_vr_overlay
- slow_motion
- inherits_slow_motion
- inherits_music
- inherits_vr_camera_offset
- inherits_vr_overlay_center
- inherits_tint
- allow_mid_activity_joins
- transition_time
- can_show_ad_on_death
- globalsnode
- stats
- on_expire
- customdata
- expired
- playertype
- teamtype
- retain_actor
- add_actor_weak_ref
- session
- on_player_join
- on_player_leave
- on_team_join
- on_team_leave
- on_transition_out
- on_begin
- 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
920class MainMenuSession(ba.Session): 921 """Session that runs the main menu environment.""" 922 923 def __init__(self) -> None: 924 925 # Gather dependencies we'll need (just our activity). 926 self._activity_deps = ba.DependencySet(ba.Dependency(MainMenuActivity)) 927 928 super().__init__([self._activity_deps]) 929 self._locked = False 930 self.setactivity(ba.newactivity(MainMenuActivity)) 931 932 def on_activity_end(self, activity: ba.Activity, results: Any) -> None: 933 if self._locked: 934 _ba.unlock_all_input() 935 936 # Any ending activity leads us into the main menu one. 937 self.setactivity(ba.newactivity(MainMenuActivity)) 938 939 def on_player_request(self, player: ba.SessionPlayer) -> bool: 940 # Reject all player requests. 941 return False
Session that runs the main menu environment.
923 def __init__(self) -> None: 924 925 # Gather dependencies we'll need (just our activity). 926 self._activity_deps = ba.DependencySet(ba.Dependency(MainMenuActivity)) 927 928 super().__init__([self._activity_deps]) 929 self._locked = False 930 self.setactivity(ba.newactivity(MainMenuActivity))
Instantiate a session.
depsets should be a sequence of successfully resolved ba.DependencySet instances; one for each ba.Activity the session may potentially run.
932 def on_activity_end(self, activity: ba.Activity, results: Any) -> None: 933 if self._locked: 934 _ba.unlock_all_input() 935 936 # Any ending activity leads us into the main menu one. 937 self.setactivity(ba.newactivity(MainMenuActivity))
Called when the current ba.Activity has ended.
The ba.Session should look at the results and start another ba.Activity.
939 def on_player_request(self, player: ba.SessionPlayer) -> bool: 940 # Reject all player requests. 941 return False
Called when a new ba.Player wants to join the Session.
This should return True or False to accept/reject.
Inherited Members
- ba._session.Session
- use_teams
- use_team_colors
- lobby
- max_players
- min_players
- sessionplayers
- customdata
- sessionteams
- sessionglobalsnode
- should_allow_mid_activity_joins
- on_player_leave
- end
- on_team_join
- on_team_leave
- end_activity
- handlemessage
- setactivity
- getactivity
- begin_next_activity