# Released under the MIT License. See LICENSE for details.
#
"""Session and Activity for displaying the main menu bg."""
from __future__ import annotations
import time
import random
import weakref
from typing import TYPE_CHECKING, override
import bascenev1 as bs
import bauiv1 as bui
if TYPE_CHECKING:
from typing import Any
import bacommon.bs
[docs]
class MainMenuActivity(bs.Activity[bs.Player, bs.Team]):
"""Activity showing the rotating main menu bg stuff."""
_stdassets = bs.Dependency(bs.AssetPackage, 'stdassets@1')
def __init__(self, settings: dict):
super().__init__(settings)
self._logo_node: bs.Node | None = None
self._custom_logo_tex_name: str | None = None
self._word_actors: list[bs.Actor] = []
self.my_name: bs.NodeActor | None = None
self._host_is_navigating_text: bs.NodeActor | None = None
self.version: bs.NodeActor | None = None
self.beta_info: bs.NodeActor | None = None
self.beta_info_2: bs.NodeActor | None = None
self.bottom: bs.NodeActor | None = None
self.vr_bottom_fill: bs.NodeActor | None = None
self.vr_top_fill: bs.NodeActor | None = None
self.terrain: bs.NodeActor | None = None
self.trees: bs.NodeActor | None = None
self.bgterrain: bs.NodeActor | None = None
self._ts = 0.86
self._language: str | None = None
self._update_timer: bs.Timer | None = None
self._news: NewsDisplay | None = None
self._attract_mode_timer: bs.Timer | None = None
self._logo_rotate_timer: bs.Timer | None = None
[docs]
@override
def on_transition_in(self) -> None:
# pylint: disable=too-many-locals
# pylint: disable=too-many-statements
super().on_transition_in()
random.seed(123)
app = bs.app
env = app.env
assert app.classic is not None
plus = bs.app.plus
assert plus is not None
# Throw up some text that only clients can see so they know that
# the host is navigating menus while they're just staring at an
# empty-ish screen.
tval = bs.Lstr(
resource='hostIsNavigatingMenusText',
subs=[('${HOST}', plus.get_v1_account_display_string())],
)
self._host_is_navigating_text = bs.NodeActor(
bs.newnode(
'text',
attrs={
'text': tval,
'client_only': True,
'position': (0, -200),
'flatness': 1.0,
'h_align': 'center',
},
)
)
if (
not app.classic.main_menu_did_initial_transition
and self.my_name is not None
):
assert self.my_name.node
bs.animate(self.my_name.node, 'opacity', {2.3: 0, 3.0: 1.0})
# Throw in test build info.
self.beta_info = self.beta_info_2 = None
if env.test:
pos = (230, 35)
self.beta_info = bs.NodeActor(
bs.newnode(
'text',
attrs={
'v_attach': 'center',
'h_align': 'center',
'color': (1, 1, 1, 1),
'shadow': 0.5,
'flatness': 0.5,
'scale': 1,
'vr_depth': -60,
'position': pos,
'text': bs.Lstr(resource='testBuildText'),
},
)
)
if not app.classic.main_menu_did_initial_transition:
assert self.beta_info.node
bs.animate(self.beta_info.node, 'opacity', {1.3: 0, 1.8: 1.0})
mesh = bs.getmesh('thePadLevel')
trees_mesh = bs.getmesh('trees')
bottom_mesh = bs.getmesh('thePadLevelBottom')
color_texture = bs.gettexture('thePadLevelColor')
trees_texture = bs.gettexture('treesColor')
bgtex = bs.gettexture('menuBG')
bgmesh = bs.getmesh('thePadBG')
# Load these last since most platforms don't use them.
vr_bottom_fill_mesh = bs.getmesh('thePadVRFillBottom')
vr_top_fill_mesh = bs.getmesh('thePadVRFillTop')
gnode = self.globalsnode
gnode.camera_mode = 'rotate'
tint = (1.14, 1.1, 1.0)
gnode.tint = tint
gnode.ambient_color = (1.06, 1.04, 1.03)
gnode.vignette_outer = (0.45, 0.55, 0.54)
gnode.vignette_inner = (0.99, 0.98, 0.98)
self.bottom = bs.NodeActor(
bs.newnode(
'terrain',
attrs={
'mesh': bottom_mesh,
'lighting': False,
'reflection': 'soft',
'reflection_scale': [0.45],
'color_texture': color_texture,
},
)
)
self.vr_bottom_fill = bs.NodeActor(
bs.newnode(
'terrain',
attrs={
'mesh': vr_bottom_fill_mesh,
'lighting': False,
'vr_only': True,
'color_texture': color_texture,
},
)
)
self.vr_top_fill = bs.NodeActor(
bs.newnode(
'terrain',
attrs={
'mesh': vr_top_fill_mesh,
'vr_only': True,
'lighting': False,
'color_texture': bgtex,
},
)
)
self.terrain = bs.NodeActor(
bs.newnode(
'terrain',
attrs={
'mesh': mesh,
'color_texture': color_texture,
'reflection': 'soft',
'reflection_scale': [0.3],
},
)
)
self.trees = bs.NodeActor(
bs.newnode(
'terrain',
attrs={
'mesh': trees_mesh,
'lighting': False,
'reflection': 'char',
'reflection_scale': [0.1],
'color_texture': trees_texture,
},
)
)
self.bgterrain = bs.NodeActor(
bs.newnode(
'terrain',
attrs={
'mesh': bgmesh,
'color': (0.92, 0.91, 0.9),
'lighting': False,
'background': True,
'color_texture': bgtex,
},
)
)
self._update_timer = bs.Timer(1.0, self._update, repeat=True)
self._update()
# Hopefully this won't hitch but lets space these out anyway.
bs.add_clean_frame_callback(bs.WeakCall(self._start_preloads))
random.seed()
# Need to update this for toolbar mode; currenly doesn't fit.
if bool(False):
if not (env.demo or env.arcade):
self._news = NewsDisplay(self)
self._attract_mode_timer = bs.Timer(
3.12, self._update_attract_mode, repeat=True
)
app.classic.invoke_main_menu_ui()
app.classic.main_menu_did_initial_transition = True
def _update(self) -> None:
# pylint: disable=too-many-locals
# pylint: disable=too-many-statements
app = bs.app
env = app.env
assert app.classic is not None
# Update logo in case it changes.
if self._logo_node:
custom_texture = self._get_custom_logo_tex_name()
if custom_texture != self._custom_logo_tex_name:
self._custom_logo_tex_name = custom_texture
self._logo_node.texture = bs.gettexture(
custom_texture if custom_texture is not None else 'logo'
)
self._logo_node.mesh_opaque = (
None if custom_texture is not None else bs.getmesh('logo')
)
self._logo_node.mesh_transparent = (
None
if custom_texture is not None
else bs.getmesh('logoTransparent')
)
# If language has changed, recreate our logo text/graphics.
lang = app.lang.language
if lang != self._language:
self._language = lang
y = 20
base_scale = 1.1
self._word_actors = []
base_delay = 0.8
delay = base_delay
delay_inc = 0.02
# Come on faster after the first time.
if app.classic.main_menu_did_initial_transition:
base_delay = 0.0
delay = base_delay
delay_inc = 0.02
# We draw higher in kiosk mode (make sure to test this
# when making adjustments) for now we're hard-coded for
# a few languages.. should maybe look into generalizing this?..
if app.lang.language == 'Chinese':
base_x = -270.0
x = base_x - 20.0
spacing = 85.0 * base_scale
y_extra = 0.0 if (env.demo or env.arcade) else 0.0
self._make_logo(
x - 110 + 50,
113 + y + 1.2 * y_extra,
0.34 * base_scale,
delay=base_delay + 0.1,
custom_texture='chTitleChar1',
jitter_scale=2.0,
vr_depth_offset=-30,
)
x += spacing
delay += delay_inc
self._make_logo(
x - 10 + 50,
110 + y + 1.2 * y_extra,
0.31 * base_scale,
delay=base_delay + 0.15,
custom_texture='chTitleChar2',
jitter_scale=2.0,
vr_depth_offset=-30,
)
x += 2.0 * spacing
delay += delay_inc
self._make_logo(
x + 180 - 140,
110 + y + 1.2 * y_extra,
0.3 * base_scale,
delay=base_delay + 0.25,
custom_texture='chTitleChar3',
jitter_scale=2.0,
vr_depth_offset=-30,
)
x += spacing
delay += delay_inc
self._make_logo(
x + 241 - 120,
110 + y + 1.2 * y_extra,
0.31 * base_scale,
delay=base_delay + 0.3,
custom_texture='chTitleChar4',
jitter_scale=2.0,
vr_depth_offset=-30,
)
x += spacing
delay += delay_inc
self._make_logo(
x + 300 - 90,
105 + y + 1.2 * y_extra,
0.34 * base_scale,
delay=base_delay + 0.35,
custom_texture='chTitleChar5',
jitter_scale=2.0,
vr_depth_offset=-30,
)
self._make_logo(
base_x + 155,
146 + y + 1.2 * y_extra,
0.28 * base_scale,
delay=base_delay + 0.2,
rotate=-7,
)
else:
base_x = -170
x = base_x - 20
spacing = 55 * base_scale
y_extra = 0 if (env.demo or env.arcade) else 0
xv1 = x
delay1 = delay
for shadow in (True, False):
x = xv1
delay = delay1
self._make_word(
'B',
x - 50,
y - 23 + 0.8 * y_extra,
scale=1.3 * base_scale,
delay=delay,
vr_depth_offset=3,
shadow=shadow,
)
x += spacing
delay += delay_inc
self._make_word(
'm',
x,
y + y_extra,
delay=delay,
scale=base_scale,
shadow=shadow,
)
x += spacing * 1.25
delay += delay_inc
self._make_word(
'b',
x,
y + y_extra - 10,
delay=delay,
scale=1.1 * base_scale,
vr_depth_offset=5,
shadow=shadow,
)
x += spacing * 0.85
delay += delay_inc
self._make_word(
'S',
x,
y - 25 + 0.8 * y_extra,
scale=1.35 * base_scale,
delay=delay,
vr_depth_offset=14,
shadow=shadow,
)
x += spacing
delay += delay_inc
self._make_word(
'q',
x,
y + y_extra,
delay=delay,
scale=base_scale,
shadow=shadow,
)
x += spacing * 0.9
delay += delay_inc
self._make_word(
'u',
x,
y + y_extra,
delay=delay,
scale=base_scale,
vr_depth_offset=7,
shadow=shadow,
)
x += spacing * 0.9
delay += delay_inc
self._make_word(
'a',
x,
y + y_extra,
delay=delay,
scale=base_scale,
shadow=shadow,
)
x += spacing * 0.64
delay += delay_inc
self._make_word(
'd',
x,
y + y_extra - 10,
delay=delay,
scale=1.1 * base_scale,
vr_depth_offset=6,
shadow=shadow,
)
self._make_logo(
base_x - 28,
125 + y + 1.2 * y_extra,
0.32 * base_scale,
delay=base_delay,
)
def _make_word(
self,
word: str,
x: float,
y: float,
*,
scale: float = 1.0,
delay: float = 0.0,
vr_depth_offset: float = 0.0,
shadow: bool = False,
) -> None:
# pylint: disable=too-many-branches
# pylint: disable=too-many-locals
# pylint: disable=too-many-statements
if shadow:
word_obj = bs.NodeActor(
bs.newnode(
'text',
attrs={
'position': (x, y),
'big': True,
'color': (0.0, 0.0, 0.2, 0.08),
'tilt_translate': 0.09,
'opacity_scales_shadow': False,
'shadow': 0.2,
'vr_depth': -130,
'v_align': 'center',
'project_scale': 0.97 * scale,
'scale': 1.0,
'text': word,
},
)
)
self._word_actors.append(word_obj)
else:
word_obj = bs.NodeActor(
bs.newnode(
'text',
attrs={
'position': (x, y),
'big': True,
'color': (1.2, 1.15, 1.15, 1.0),
'tilt_translate': 0.11,
'shadow': 0.2,
'vr_depth': -40 + vr_depth_offset,
'v_align': 'center',
'project_scale': scale,
'scale': 1.0,
'text': word,
},
)
)
self._word_actors.append(word_obj)
# Add a bit of stop-motion-y jitter to the logo (unless we're in
# VR mode in which case its best to leave things still).
if not bs.app.env.vr:
cmb: bs.Node | None
cmb2: bs.Node | None
if not shadow:
cmb = bs.newnode(
'combine', owner=word_obj.node, attrs={'size': 2}
)
else:
cmb = None
if shadow:
cmb2 = bs.newnode(
'combine', owner=word_obj.node, attrs={'size': 2}
)
else:
cmb2 = None
if not shadow:
assert cmb and word_obj.node
cmb.connectattr('output', word_obj.node, 'position')
if shadow:
assert cmb2 and word_obj.node
cmb2.connectattr('output', word_obj.node, 'position')
keys = {}
keys2 = {}
time_v = 0.0
for _i in range(10):
val = x + (random.random() - 0.5) * 0.8
val2 = x + (random.random() - 0.5) * 0.8
keys[time_v * self._ts] = val
keys2[time_v * self._ts] = val2 + 5
time_v += random.random() * 0.1
if cmb is not None:
bs.animate(cmb, 'input0', keys, loop=True)
if cmb2 is not None:
bs.animate(cmb2, 'input0', keys2, loop=True)
keys = {}
keys2 = {}
time_v = 0
for _i in range(10):
val = y + (random.random() - 0.5) * 0.8
val2 = y + (random.random() - 0.5) * 0.8
keys[time_v * self._ts] = val
keys2[time_v * self._ts] = val2 - 9
time_v += random.random() * 0.1
if cmb is not None:
bs.animate(cmb, 'input1', keys, loop=True)
if cmb2 is not None:
bs.animate(cmb2, 'input1', keys2, loop=True)
if not shadow:
assert word_obj.node
bs.animate(
word_obj.node,
'project_scale',
{delay: 0.0, delay + 0.1: scale * 1.1, delay + 0.2: scale},
)
else:
assert word_obj.node
bs.animate(
word_obj.node,
'project_scale',
{delay: 0.0, delay + 0.1: scale * 1.1, delay + 0.2: scale},
)
def _get_custom_logo_tex_name(self) -> str | None:
plus = bui.app.plus
assert plus is not None
if plus.get_v1_account_misc_read_val('easter', False):
return 'logoEaster'
return None
# Pop the logo and menu in.
def _make_logo(
self,
x: float,
y: float,
scale: float,
delay: float,
*,
custom_texture: str | None = None,
jitter_scale: float = 1.0,
rotate: float = 0.0,
vr_depth_offset: float = 0.0,
) -> None:
# pylint: disable=too-many-locals
if custom_texture is None:
custom_texture = self._get_custom_logo_tex_name()
self._custom_logo_tex_name = custom_texture
ltex = bs.gettexture(
custom_texture if custom_texture is not None else 'logo'
)
mopaque = None if custom_texture is not None else bs.getmesh('logo')
mtrans = (
None
if custom_texture is not None
else bs.getmesh('logoTransparent')
)
logo_attrs = {
'position': (x, y),
'texture': ltex,
'mesh_opaque': mopaque,
'mesh_transparent': mtrans,
'vr_depth': -10 + vr_depth_offset,
'rotate': rotate,
'attach': 'center',
'tilt_translate': 0.21,
'absolute_scale': True,
}
if custom_texture is None:
logo_attrs['scale'] = (2000.0, 2000.0)
logo = bs.NodeActor(bs.newnode('image', attrs=logo_attrs))
self._logo_node = logo.node
self._word_actors.append(logo)
# Add a bit of stop-motion-y jitter to the logo (unless we're in
# VR mode in which case its best to leave things still).
assert logo.node
def jitter() -> None:
if not bs.app.env.vr:
cmb = bs.newnode('combine', owner=logo.node, attrs={'size': 2})
cmb.connectattr('output', logo.node, 'position')
keys = {}
time_v = 0.0
# Gen some random keys for that stop-motion-y look
for _i in range(10):
keys[time_v] = (
x + (random.random() - 0.5) * 0.7 * jitter_scale
)
time_v += random.random() * 0.1
bs.animate(cmb, 'input0', keys, loop=True)
keys = {}
time_v = 0.0
for _i in range(10):
keys[time_v * self._ts] = (
y + (random.random() - 0.5) * 0.7 * jitter_scale
)
time_v += random.random() * 0.1
bs.animate(cmb, 'input1', keys, loop=True)
# Do a fun spinny animation on the logo the first time in.
if (
custom_texture is None
and bs.app.classic is not None
and not bs.app.classic.main_menu_did_initial_transition
):
jitter()
cmb = bs.newnode('combine', owner=logo.node, attrs={'size': 2})
delay = 0.0
keys = {
delay: 5000.0 * scale,
delay + 0.4: 530.0 * scale,
delay + 0.45: 620.0 * scale,
delay + 0.5: 590.0 * scale,
delay + 0.55: 605.0 * scale,
delay + 0.6: 600.0 * scale,
}
bs.animate(cmb, 'input0', keys)
bs.animate(cmb, 'input1', keys)
cmb.connectattr('output', logo.node, 'scale')
keys = {
delay: 100.0,
delay + 0.4: 370.0,
delay + 0.45: 357.0,
delay + 0.5: 360.0,
}
bs.animate(logo.node, 'rotate', keys)
else:
# For all other cases do a simple scale up animation.
jitter()
cmb = bs.newnode('combine', owner=logo.node, attrs={'size': 2})
keys = {
delay: 0.0,
delay + 0.1: 700.0 * scale,
delay + 0.2: 600.0 * scale,
}
bs.animate(cmb, 'input0', keys)
bs.animate(cmb, 'input1', keys)
cmb.connectattr('output', logo.node, 'scale')
def _start_preloads(self) -> None:
# FIXME: The func that calls us back doesn't save/restore state
# or check for a dead activity so we have to do that ourself.
if self.expired:
return
with self.context:
_preload1()
def _start_menu_music() -> None:
assert bs.app.classic is not None
bs.setmusic(bs.MusicType.MENU)
bui.apptimer(0.5, _start_menu_music)
def _update_attract_mode(self) -> None:
if bui.app.classic is None:
return
if not bui.app.config.resolve('Show Demos When Idle'):
return
threshold = 20.0
# If we're idle *and* have been in this activity for that long,
# flip over to our cpu demo.
if bui.get_input_idle_time() > threshold and bs.time() > threshold:
bui.app.classic.run_stress_test(
playlist_type='Random',
playlist_name='__default__',
player_count=8,
round_duration=20,
attract_mode=True,
)
[docs]
class NewsDisplay:
"""Wrangles news display."""
def __init__(self, activity: bs.Activity):
self._valid = True
self._message_duration = 10.0
self._message_spacing = 2.0
self._text: bs.NodeActor | None = None
self._activity = weakref.ref(activity)
self._phrases: list[str] = []
self._used_phrases: list[str] = []
self._phrase_change_timer: bs.Timer | None = None
# If we're signed in, fetch news immediately. Otherwise wait
# until we are signed in.
self._fetch_timer: bs.Timer | None = bs.Timer(
1.0, bs.WeakCall(self._try_fetching_news), repeat=True
)
self._try_fetching_news()
# We now want to wait until we're signed in before fetching news.
def _try_fetching_news(self) -> None:
plus = bui.app.plus
assert plus is not None
if plus.get_v1_account_state() == 'signed_in':
self._fetch_news()
self._fetch_timer = None
def _fetch_news(self) -> None:
plus = bui.app.plus
assert plus is not None
assert bs.app.classic is not None
bs.app.classic.main_menu_last_news_fetch_time = time.time()
# UPDATE - We now just pull news from MRVs.
news = plus.get_v1_account_misc_read_val('n', None)
if news is not None:
self._got_news(news)
def _change_phrase(self) -> None:
from bascenev1lib.actor.text import Text
app = bs.app
assert app.classic is not None
# If our news is way out of date, lets re-request it; otherwise,
# rotate our phrase.
assert app.classic.main_menu_last_news_fetch_time is not None
if time.time() - app.classic.main_menu_last_news_fetch_time > 600.0:
self._fetch_news()
self._text = None
else:
if self._text is not None:
if not self._phrases:
for phr in self._used_phrases:
self._phrases.insert(0, phr)
val = self._phrases.pop()
if val == '__ACH__':
vrmode = app.env.vr
Text(
bs.Lstr(resource='nextAchievementsText'),
color=((1, 1, 1, 1) if vrmode else (0.95, 0.9, 1, 0.4)),
host_only=True,
maxwidth=200,
position=(-300, -35),
h_align=Text.HAlign.RIGHT,
transition=Text.Transition.FADE_IN,
scale=0.9 if vrmode else 0.7,
flatness=1.0 if vrmode else 0.6,
shadow=1.0 if vrmode else 0.5,
h_attach=Text.HAttach.CENTER,
v_attach=Text.VAttach.TOP,
transition_delay=1.0,
transition_out_delay=self._message_duration,
).autoretain()
achs = [
a
for a in app.classic.ach.achievements
if not a.complete
]
if achs:
ach = achs.pop(random.randrange(min(4, len(achs))))
ach.create_display(
-180,
-35,
1.0,
outdelay=self._message_duration,
style='news',
)
if achs:
ach = achs.pop(random.randrange(min(8, len(achs))))
ach.create_display(
180,
-35,
1.25,
outdelay=self._message_duration,
style='news',
)
else:
spc = self._message_spacing
keys = {
spc: 0.0,
spc + 1.0: 1.0,
spc + self._message_duration - 1.0: 1.0,
spc + self._message_duration: 0.0,
}
assert self._text.node
bs.animate(self._text.node, 'opacity', keys)
# {k: v
# for k, v in list(keys.items())})
self._text.node.text = val
def _got_news(self, news: str) -> None:
# Run this stuff in the context of our activity since we need to
# make nodes and stuff.. should fix the serverget call so it.
activity = self._activity()
if activity is None or activity.expired:
return
with activity.context:
self._phrases.clear()
# Show upcoming achievements in non-vr versions (currently
# too hard to read in vr).
self._used_phrases = (['__ACH__'] if not bs.app.env.vr else []) + [
s for s in news.split('<br>\n') if s != ''
]
self._phrase_change_timer = bs.Timer(
(self._message_duration + self._message_spacing),
bs.WeakCall(self._change_phrase),
repeat=True,
)
assert bs.app.classic is not None
scl = (
1.2
if (bs.app.ui_v1.uiscale is bs.UIScale.SMALL or bs.app.env.vr)
else 0.8
)
color2 = (1, 1, 1, 1) if bs.app.env.vr else (0.7, 0.65, 0.75, 1.0)
shadow = 1.0 if bs.app.env.vr else 0.4
self._text = bs.NodeActor(
bs.newnode(
'text',
attrs={
'v_attach': 'top',
'h_attach': 'center',
'h_align': 'center',
'vr_depth': -20,
'shadow': shadow,
'flatness': 0.8,
'v_align': 'top',
'color': color2,
'scale': scl,
'maxwidth': 900.0 / scl,
'position': (0, -10),
},
)
)
self._change_phrase()
def _preload1() -> None:
"""Pre-load some assets a second or two into the main menu.
Helps avoid hitches later on.
"""
for mname in [
'plasticEyesTransparent',
'playerLineup1Transparent',
'playerLineup2Transparent',
'playerLineup3Transparent',
'playerLineup4Transparent',
'angryComputerTransparent',
'scrollWidgetShort',
'windowBGBlotch',
]:
bs.getmesh(mname)
for tname in ['playerLineup', 'lock']:
bs.gettexture(tname)
for tex in [
'iconRunaround',
'iconOnslaught',
'medalComplete',
'medalBronze',
'medalSilver',
'medalGold',
'characterIconMask',
]:
bs.gettexture(tex)
bs.gettexture('bg')
from bascenev1lib.actor.powerupbox import PowerupBoxFactory
PowerupBoxFactory.get()
bui.apptimer(0.1, _preload2)
def _preload2() -> None:
# FIXME: Could integrate these loads with the classes that use them
# so they don't have to redundantly call the load
# (even if the actual result is cached).
for mname in ['powerup', 'powerupSimple']:
bs.getmesh(mname)
for tname in [
'powerupBomb',
'powerupSpeed',
'powerupPunch',
'powerupIceBombs',
'powerupStickyBombs',
'powerupShield',
'powerupImpactBombs',
'powerupHealth',
]:
bs.gettexture(tname)
for sname in [
'powerup01',
'boxDrop',
'boxingBell',
'scoreHit01',
'scoreHit02',
'dripity',
'spawn',
'gong',
]:
bs.getsound(sname)
from bascenev1lib.actor.bomb import BombFactory
BombFactory.get()
bui.apptimer(0.1, _preload3)
def _preload3() -> None:
from bascenev1lib.actor.spazfactory import SpazFactory
for mname in ['bomb', 'bombSticky', 'impactBomb']:
bs.getmesh(mname)
for tname in [
'bombColor',
'bombColorIce',
'bombStickyColor',
'impactBombColor',
'impactBombColorLit',
]:
bs.gettexture(tname)
for sname in ['freeze', 'fuse01', 'activateBeep', 'warnBeep']:
bs.getsound(sname)
SpazFactory.get()
bui.apptimer(0.2, _preload4)
def _preload4() -> None:
for tname in ['bar', 'meter', 'null', 'flagColor', 'achievementOutline']:
bs.gettexture(tname)
for mname in ['frameInset', 'meterTransparent', 'achievementOutline']:
bs.getmesh(mname)
for sname in ['metalHit', 'metalSkid', 'refWhistle', 'achievement']:
bs.getsound(sname)
from bascenev1lib.actor.flag import FlagFactory
FlagFactory.get()
[docs]
class MainMenuSession(bs.Session):
"""Session that runs the main menu environment."""
def __init__(self) -> None:
# Gather dependencies we'll need (just our activity).
self._activity_deps = bs.DependencySet(bs.Dependency(MainMenuActivity))
super().__init__([self._activity_deps])
self._locked = False
self.setactivity(bs.newactivity(MainMenuActivity))
[docs]
@override
def on_activity_end(self, activity: bs.Activity, results: Any) -> None:
if self._locked:
bui.unlock_all_input()
# Any ending activity leads us into the main menu one.
self.setactivity(bs.newactivity(MainMenuActivity))
[docs]
@override
def on_player_request(self, player: bs.SessionPlayer) -> bool:
# Reject all player requests.
return False
# Docs-generation hack; import some stuff that we likely only forward-declared
# in our actual source code so that docs tools can find it.
from typing import (Coroutine, Any, Literal, Callable,
Generator, Awaitable, Sequence, Self)
import asyncio
from concurrent.futures import Future
from pathlib import Path
from enum import Enum