cleaning up coop ui code

This commit is contained in:
Eric Froemling 2022-07-07 19:31:46 -07:00
parent 4bb10a0d3d
commit e5e54a64e6
No known key found for this signature in database
GPG Key ID: 89C93F0F8D6D5A98
17 changed files with 1523 additions and 1528 deletions

File diff suppressed because it is too large Load Diff

View File

@ -2153,6 +2153,7 @@
<w>sbwht</w>
<w>sbylw</w>
<w>sbytes</w>
<w>scanresults</w>
<w>scenefile</w>
<w>scenefiles</w>
<w>scenename</w>
@ -2567,6 +2568,7 @@
<w>totalwaves</w>
<w>totype</w>
<w>touchpad</w>
<w>tournamentbutton</w>
<w>tournamententry</w>
<w>tournamentscores</w>
<w>tpartial</w>

View File

@ -1,5 +1,6 @@
### 1.7.4 (20637, 2022-07-07)
### 1.7.4 (20638, 2022-07-07)
- Fixed the trophies list showing an incorrect total (Thanks itsre3!)
- ba.app.meta.metascan is now ba.app.meta.scanresults
### 1.7.3 (20634, 2022-07-06)
- Fixed an issue with King of the Hill flag regions not working when players entered them (Thanks itsre3!)

View File

@ -374,9 +374,11 @@
"ba_data/python/bastd/ui/coop/__pycache__/browser.cpython-310.opt-1.pyc",
"ba_data/python/bastd/ui/coop/__pycache__/gamebutton.cpython-310.opt-1.pyc",
"ba_data/python/bastd/ui/coop/__pycache__/level.cpython-310.opt-1.pyc",
"ba_data/python/bastd/ui/coop/__pycache__/tournamentbutton.cpython-310.opt-1.pyc",
"ba_data/python/bastd/ui/coop/browser.py",
"ba_data/python/bastd/ui/coop/gamebutton.py",
"ba_data/python/bastd/ui/coop/level.py",
"ba_data/python/bastd/ui/coop/tournamentbutton.py",
"ba_data/python/bastd/ui/creditslist.py",
"ba_data/python/bastd/ui/debug.py",
"ba_data/python/bastd/ui/feedback.py",

View File

@ -298,6 +298,7 @@ SCRIPT_TARGETS_PY_PUBLIC = \
build/ba_data/python/bastd/ui/coop/browser.py \
build/ba_data/python/bastd/ui/coop/gamebutton.py \
build/ba_data/python/bastd/ui/coop/level.py \
build/ba_data/python/bastd/ui/coop/tournamentbutton.py \
build/ba_data/python/bastd/ui/creditslist.py \
build/ba_data/python/bastd/ui/debug.py \
build/ba_data/python/bastd/ui/feedback.py \
@ -546,6 +547,7 @@ SCRIPT_TARGETS_PYC_PUBLIC = \
build/ba_data/python/bastd/ui/coop/__pycache__/browser.cpython-310.opt-1.pyc \
build/ba_data/python/bastd/ui/coop/__pycache__/gamebutton.cpython-310.opt-1.pyc \
build/ba_data/python/bastd/ui/coop/__pycache__/level.cpython-310.opt-1.pyc \
build/ba_data/python/bastd/ui/coop/__pycache__/tournamentbutton.cpython-310.opt-1.pyc \
build/ba_data/python/bastd/ui/__pycache__/creditslist.cpython-310.opt-1.pyc \
build/ba_data/python/bastd/ui/__pycache__/debug.cpython-310.opt-1.pyc \
build/ba_data/python/bastd/ui/__pycache__/feedback.cpython-310.opt-1.pyc \

View File

@ -28,10 +28,16 @@ class Campaign:
Category: **App Classes**
"""
def __init__(self, name: str, sequential: bool = True):
def __init__(self,
name: str,
sequential: bool = True,
levels: list[ba.Level] | None = None):
self._name = name
self._levels: list[ba.Level] = []
self._sequential = sequential
self._levels: list[ba.Level] = []
if levels is not None:
for level in levels:
self.addlevel(level)
@property
def name(self) -> str:
@ -91,9 +97,8 @@ class Campaign:
def init_campaigns() -> None:
"""Fill out initial default Campaigns."""
# pylint: disable=too-many-statements
# pylint: disable=cyclic-import
from ba import _level
from ba._level import Level
from bastd.game.onslaught import OnslaughtGame
from bastd.game.football import FootballCoopGame
from bastd.game.runaround import RunaroundGame
@ -109,244 +114,218 @@ def init_campaigns() -> None:
# FIXME: Once translations catch up, we can convert these to use the
# generic display-name '${GAME} Training' type stuff.
campaign = Campaign('Easy')
campaign.addlevel(
_level.Level('Onslaught Training',
gametype=OnslaughtGame,
settings={'preset': 'training_easy'},
preview_texture_name='doomShroomPreview'))
campaign.addlevel(
_level.Level('Rookie Onslaught',
gametype=OnslaughtGame,
settings={'preset': 'rookie_easy'},
preview_texture_name='courtyardPreview'))
campaign.addlevel(
_level.Level('Rookie Football',
gametype=FootballCoopGame,
settings={'preset': 'rookie_easy'},
preview_texture_name='footballStadiumPreview'))
campaign.addlevel(
_level.Level('Pro Onslaught',
gametype=OnslaughtGame,
settings={'preset': 'pro_easy'},
preview_texture_name='doomShroomPreview'))
campaign.addlevel(
_level.Level('Pro Football',
gametype=FootballCoopGame,
settings={'preset': 'pro_easy'},
preview_texture_name='footballStadiumPreview'))
campaign.addlevel(
_level.Level('Pro Runaround',
gametype=RunaroundGame,
settings={'preset': 'pro_easy'},
preview_texture_name='towerDPreview'))
campaign.addlevel(
_level.Level('Uber Onslaught',
gametype=OnslaughtGame,
settings={'preset': 'uber_easy'},
preview_texture_name='courtyardPreview'))
campaign.addlevel(
_level.Level('Uber Football',
gametype=FootballCoopGame,
settings={'preset': 'uber_easy'},
preview_texture_name='footballStadiumPreview'))
campaign.addlevel(
_level.Level('Uber Runaround',
gametype=RunaroundGame,
settings={'preset': 'uber_easy'},
preview_texture_name='towerDPreview'))
register_campaign(campaign)
register_campaign(
Campaign(
'Easy',
levels=[
Level('Onslaught Training',
gametype=OnslaughtGame,
settings={'preset': 'training_easy'},
preview_texture_name='doomShroomPreview'),
Level('Rookie Onslaught',
gametype=OnslaughtGame,
settings={'preset': 'rookie_easy'},
preview_texture_name='courtyardPreview'),
Level('Rookie Football',
gametype=FootballCoopGame,
settings={'preset': 'rookie_easy'},
preview_texture_name='footballStadiumPreview'),
Level('Pro Onslaught',
gametype=OnslaughtGame,
settings={'preset': 'pro_easy'},
preview_texture_name='doomShroomPreview'),
Level('Pro Football',
gametype=FootballCoopGame,
settings={'preset': 'pro_easy'},
preview_texture_name='footballStadiumPreview'),
Level('Pro Runaround',
gametype=RunaroundGame,
settings={'preset': 'pro_easy'},
preview_texture_name='towerDPreview'),
Level('Uber Onslaught',
gametype=OnslaughtGame,
settings={'preset': 'uber_easy'},
preview_texture_name='courtyardPreview'),
Level('Uber Football',
gametype=FootballCoopGame,
settings={'preset': 'uber_easy'},
preview_texture_name='footballStadiumPreview'),
Level('Uber Runaround',
gametype=RunaroundGame,
settings={'preset': 'uber_easy'},
preview_texture_name='towerDPreview')
],
))
# "hard" mode
campaign = Campaign('Default')
campaign.addlevel(
_level.Level('Onslaught Training',
gametype=OnslaughtGame,
settings={'preset': 'training'},
preview_texture_name='doomShroomPreview'))
campaign.addlevel(
_level.Level('Rookie Onslaught',
gametype=OnslaughtGame,
settings={'preset': 'rookie'},
preview_texture_name='courtyardPreview'))
campaign.addlevel(
_level.Level('Rookie Football',
gametype=FootballCoopGame,
settings={'preset': 'rookie'},
preview_texture_name='footballStadiumPreview'))
campaign.addlevel(
_level.Level('Pro Onslaught',
gametype=OnslaughtGame,
settings={'preset': 'pro'},
preview_texture_name='doomShroomPreview'))
campaign.addlevel(
_level.Level('Pro Football',
gametype=FootballCoopGame,
settings={'preset': 'pro'},
preview_texture_name='footballStadiumPreview'))
campaign.addlevel(
_level.Level('Pro Runaround',
gametype=RunaroundGame,
settings={'preset': 'pro'},
preview_texture_name='towerDPreview'))
campaign.addlevel(
_level.Level('Uber Onslaught',
gametype=OnslaughtGame,
settings={'preset': 'uber'},
preview_texture_name='courtyardPreview'))
campaign.addlevel(
_level.Level('Uber Football',
gametype=FootballCoopGame,
settings={'preset': 'uber'},
preview_texture_name='footballStadiumPreview'))
campaign.addlevel(
_level.Level('Uber Runaround',
gametype=RunaroundGame,
settings={'preset': 'uber'},
preview_texture_name='towerDPreview'))
campaign.addlevel(
_level.Level('The Last Stand',
gametype=TheLastStandGame,
settings={},
preview_texture_name='rampagePreview'))
register_campaign(campaign)
register_campaign(
Campaign(
'Default',
levels=[
Level('Onslaught Training',
gametype=OnslaughtGame,
settings={'preset': 'training'},
preview_texture_name='doomShroomPreview'),
Level('Rookie Onslaught',
gametype=OnslaughtGame,
settings={'preset': 'rookie'},
preview_texture_name='courtyardPreview'),
Level('Rookie Football',
gametype=FootballCoopGame,
settings={'preset': 'rookie'},
preview_texture_name='footballStadiumPreview'),
Level('Pro Onslaught',
gametype=OnslaughtGame,
settings={'preset': 'pro'},
preview_texture_name='doomShroomPreview'),
Level('Pro Football',
gametype=FootballCoopGame,
settings={'preset': 'pro'},
preview_texture_name='footballStadiumPreview'),
Level('Pro Runaround',
gametype=RunaroundGame,
settings={'preset': 'pro'},
preview_texture_name='towerDPreview'),
Level('Uber Onslaught',
gametype=OnslaughtGame,
settings={'preset': 'uber'},
preview_texture_name='courtyardPreview'),
Level('Uber Football',
gametype=FootballCoopGame,
settings={'preset': 'uber'},
preview_texture_name='footballStadiumPreview'),
Level('Uber Runaround',
gametype=RunaroundGame,
settings={'preset': 'uber'},
preview_texture_name='towerDPreview'),
Level('The Last Stand',
gametype=TheLastStandGame,
settings={},
preview_texture_name='rampagePreview')
],
))
# challenges: our 'official' random extra co-op levels
campaign = Campaign('Challenges', sequential=False)
campaign.addlevel(
_level.Level('Infinite Onslaught',
gametype=OnslaughtGame,
settings={'preset': 'endless'},
preview_texture_name='doomShroomPreview'))
campaign.addlevel(
_level.Level('Infinite Runaround',
gametype=RunaroundGame,
settings={'preset': 'endless'},
preview_texture_name='towerDPreview'))
campaign.addlevel(
_level.Level('Race',
displayname='${GAME}',
gametype=RaceGame,
settings={
'map': 'Big G',
'Laps': 3,
'Bomb Spawning': 0
},
preview_texture_name='bigGPreview'))
campaign.addlevel(
_level.Level('Pro Race',
displayname='Pro ${GAME}',
gametype=RaceGame,
settings={
'map': 'Big G',
'Laps': 3,
'Bomb Spawning': 1000
},
preview_texture_name='bigGPreview'))
campaign.addlevel(
_level.Level('Lake Frigid Race',
displayname='${GAME}',
gametype=RaceGame,
settings={
'map': 'Lake Frigid',
'Laps': 6,
'Mine Spawning': 2000,
'Bomb Spawning': 0
},
preview_texture_name='lakeFrigidPreview'))
campaign.addlevel(
_level.Level('Football',
displayname='${GAME}',
gametype=FootballCoopGame,
settings={'preset': 'tournament'},
preview_texture_name='footballStadiumPreview'))
campaign.addlevel(
_level.Level('Pro Football',
displayname='Pro ${GAME}',
gametype=FootballCoopGame,
settings={'preset': 'tournament_pro'},
preview_texture_name='footballStadiumPreview'))
campaign.addlevel(
_level.Level('Runaround',
displayname='${GAME}',
gametype=RunaroundGame,
settings={'preset': 'tournament'},
preview_texture_name='towerDPreview'))
campaign.addlevel(
_level.Level('Uber Runaround',
displayname='Uber ${GAME}',
gametype=RunaroundGame,
settings={'preset': 'tournament_uber'},
preview_texture_name='towerDPreview'))
campaign.addlevel(
_level.Level('The Last Stand',
displayname='${GAME}',
gametype=TheLastStandGame,
settings={'preset': 'tournament'},
preview_texture_name='rampagePreview'))
campaign.addlevel(
_level.Level('Tournament Infinite Onslaught',
displayname='Infinite Onslaught',
gametype=OnslaughtGame,
settings={'preset': 'endless_tournament'},
preview_texture_name='doomShroomPreview'))
campaign.addlevel(
_level.Level('Tournament Infinite Runaround',
displayname='Infinite Runaround',
gametype=RunaroundGame,
settings={'preset': 'endless_tournament'},
preview_texture_name='towerDPreview'))
campaign.addlevel(
_level.Level('Target Practice',
displayname='Pro ${GAME}',
gametype=TargetPracticeGame,
settings={},
preview_texture_name='doomShroomPreview'))
campaign.addlevel(
_level.Level('Target Practice B',
displayname='${GAME}',
gametype=TargetPracticeGame,
settings={
'Target Count': 2,
'Enable Impact Bombs': False,
'Enable Triple Bombs': False
},
preview_texture_name='doomShroomPreview'))
campaign.addlevel(
_level.Level('Meteor Shower',
displayname='${GAME}',
gametype=MeteorShowerGame,
settings={},
preview_texture_name='rampagePreview'))
campaign.addlevel(
_level.Level('Epic Meteor Shower',
displayname='${GAME}',
gametype=MeteorShowerGame,
settings={'Epic Mode': True},
preview_texture_name='rampagePreview'))
campaign.addlevel(
_level.Level('Easter Egg Hunt',
displayname='${GAME}',
gametype=EasterEggHuntGame,
settings={},
preview_texture_name='towerDPreview'))
campaign.addlevel(
_level.Level('Pro Easter Egg Hunt',
displayname='Pro ${GAME}',
gametype=EasterEggHuntGame,
settings={'Pro Mode': True},
preview_texture_name='towerDPreview'))
campaign.addlevel(
_level.Level(
name='Ninja Fight', # (unique id not seen by player)
displayname='${GAME}', # (readable name seen by player)
gametype=NinjaFightGame,
settings={'preset': 'regular'},
preview_texture_name='courtyardPreview'))
campaign.addlevel(
_level.Level(name='Pro Ninja Fight',
displayname='Pro ${GAME}',
gametype=NinjaFightGame,
settings={'preset': 'pro'},
preview_texture_name='courtyardPreview'))
register_campaign(campaign)
register_campaign(
Campaign(
'Challenges',
sequential=False,
levels=[
Level('Infinite Onslaught',
gametype=OnslaughtGame,
settings={'preset': 'endless'},
preview_texture_name='doomShroomPreview'),
Level('Infinite Runaround',
gametype=RunaroundGame,
settings={'preset': 'endless'},
preview_texture_name='towerDPreview'),
Level('Race',
displayname='${GAME}',
gametype=RaceGame,
settings={
'map': 'Big G',
'Laps': 3,
'Bomb Spawning': 0
},
preview_texture_name='bigGPreview'),
Level('Pro Race',
displayname='Pro ${GAME}',
gametype=RaceGame,
settings={
'map': 'Big G',
'Laps': 3,
'Bomb Spawning': 1000
},
preview_texture_name='bigGPreview'),
Level('Lake Frigid Race',
displayname='${GAME}',
gametype=RaceGame,
settings={
'map': 'Lake Frigid',
'Laps': 6,
'Mine Spawning': 2000,
'Bomb Spawning': 0
},
preview_texture_name='lakeFrigidPreview'),
Level('Football',
displayname='${GAME}',
gametype=FootballCoopGame,
settings={'preset': 'tournament'},
preview_texture_name='footballStadiumPreview'),
Level('Pro Football',
displayname='Pro ${GAME}',
gametype=FootballCoopGame,
settings={'preset': 'tournament_pro'},
preview_texture_name='footballStadiumPreview'),
Level('Runaround',
displayname='${GAME}',
gametype=RunaroundGame,
settings={'preset': 'tournament'},
preview_texture_name='towerDPreview'),
Level('Uber Runaround',
displayname='Uber ${GAME}',
gametype=RunaroundGame,
settings={'preset': 'tournament_uber'},
preview_texture_name='towerDPreview'),
Level('The Last Stand',
displayname='${GAME}',
gametype=TheLastStandGame,
settings={'preset': 'tournament'},
preview_texture_name='rampagePreview'),
Level('Tournament Infinite Onslaught',
displayname='Infinite Onslaught',
gametype=OnslaughtGame,
settings={'preset': 'endless_tournament'},
preview_texture_name='doomShroomPreview'),
Level('Tournament Infinite Runaround',
displayname='Infinite Runaround',
gametype=RunaroundGame,
settings={'preset': 'endless_tournament'},
preview_texture_name='towerDPreview'),
Level('Target Practice',
displayname='Pro ${GAME}',
gametype=TargetPracticeGame,
settings={},
preview_texture_name='doomShroomPreview'),
Level('Target Practice B',
displayname='${GAME}',
gametype=TargetPracticeGame,
settings={
'Target Count': 2,
'Enable Impact Bombs': False,
'Enable Triple Bombs': False
},
preview_texture_name='doomShroomPreview'),
Level('Meteor Shower',
displayname='${GAME}',
gametype=MeteorShowerGame,
settings={},
preview_texture_name='rampagePreview'),
Level('Epic Meteor Shower',
displayname='${GAME}',
gametype=MeteorShowerGame,
settings={'Epic Mode': True},
preview_texture_name='rampagePreview'),
Level('Easter Egg Hunt',
displayname='${GAME}',
gametype=EasterEggHuntGame,
settings={},
preview_texture_name='towerDPreview'),
Level('Pro Easter Egg Hunt',
displayname='Pro ${GAME}',
gametype=EasterEggHuntGame,
settings={'Pro Mode': True},
preview_texture_name='towerDPreview'),
Level(
name='Ninja Fight', # (unique id not seen by player)
displayname='${GAME}', # (readable name seen by player)
gametype=NinjaFightGame,
settings={'preset': 'regular'},
preview_texture_name='courtyardPreview'),
Level(name='Pro Ninja Fight',
displayname='Pro ${GAME}',
gametype=NinjaFightGame,
settings={'preset': 'pro'},
preview_texture_name='courtyardPreview')
],
))

View File

@ -42,7 +42,7 @@ class MetadataSubsystem:
"""
def __init__(self) -> None:
self.metascan: ScanResults | None = None
self.scanresults: ScanResults | None = None
self.extra_scan_dirs: list[str] = []
def on_app_running(self) -> None:
@ -56,7 +56,7 @@ class MetadataSubsystem:
Should be called only once at launch."""
app = _ba.app
if self.metascan is not None:
if self.scanresults is not None:
print('WARNING: meta scan run more than once.')
pythondirs = ([app.python_directory_app, app.python_directory_user] +
self.extra_scan_dirs)
@ -131,7 +131,7 @@ class MetadataSubsystem:
def get_scan_results(self) -> ScanResults:
"""Return meta scan results; block if the scan is not yet complete."""
if self.metascan is None:
if self.scanresults is None:
print('WARNING: ba.meta.get_scan_results()'
' called before scan completed.'
' This can cause hitches.')
@ -139,12 +139,12 @@ class MetadataSubsystem:
# Now wait a bit for the scan to complete.
# Eventually error though if it doesn't.
starttime = time.time()
while self.metascan is None:
while self.scanresults is None:
time.sleep(0.05)
if time.time() - starttime > 10.0:
raise TimeoutError(
'timeout waiting for meta scan to complete.')
return self.metascan
return self.scanresults
def get_game_types(self) -> list[type[ba.GameActivity]]:
"""Return available game types."""
@ -204,7 +204,7 @@ class ScanThread(threading.Thread):
# We also, however, immediately make results available.
# This is because the game thread may be blocked waiting
# for them so we can't push a call or we'd get deadlock.
_ba.app.meta.metascan = results
_ba.app.meta.scanresults = results
class DirectoryScan:

View File

@ -6,7 +6,6 @@
from __future__ import annotations
import copy
from typing import TYPE_CHECKING
import _ba
@ -18,6 +17,8 @@ from bastd.ui.store.browser import StoreBrowserWindow
if TYPE_CHECKING:
from typing import Any
from bastd.ui.coop.tournamentbutton import TournamentButton
class CoopBrowserWindow(ba.Window):
"""Window for browsing co-op levels/games/etc."""
@ -175,8 +176,6 @@ class CoopBrowserWindow(ba.Window):
'Selected Coop Campaign Level', None))
self._selected_custom_level = (cfg.get('Selected Coop Custom Level',
None))
self._selected_challenge_level = (cfg.get(
'Selected Coop Challenge Level', None))
# Don't want initial construction affecting our last-selected.
self._do_selection_callbacks = False
@ -283,6 +282,7 @@ class CoopBrowserWindow(ba.Window):
import bastd.ui.tournamentscores as _unused8
import bastd.ui.tournamententry as _unused9
import bastd.ui.play as _unused10
import bastd.ui.coop.tournamentbutton as _unused11
def _update(self) -> None:
# Do nothing if we've somehow outlived our actual UI.
@ -335,21 +335,21 @@ class CoopBrowserWindow(ba.Window):
# Decrement time on our tournament buttons.
ads_enabled = _ba.have_incentivized_ad()
for tbtn in self._tournament_buttons:
tbtn['time_remaining'] = max(0, tbtn['time_remaining'] - 1)
if tbtn['time_remaining_value_text'] is not None:
tbtn.time_remaining = max(0, tbtn.time_remaining - 1)
if tbtn.time_remaining_value_text is not None:
ba.textwidget(
edit=tbtn['time_remaining_value_text'],
text=ba.timestring(tbtn['time_remaining'],
edit=tbtn.time_remaining_value_text,
text=ba.timestring(tbtn.time_remaining,
centi=False,
suppress_format_warning=True) if
(tbtn['has_time_remaining']
(tbtn.has_time_remaining
and self._tourney_data_up_to_date) else '-')
# Also adjust the ad icon visibility.
if tbtn.get('allow_ads', False) and _ba.has_video_ads():
ba.imagewidget(edit=tbtn['entry_fee_ad_image'],
if tbtn.allow_ads and _ba.has_video_ads():
ba.imagewidget(edit=tbtn.entry_fee_ad_image,
opacity=1.0 if ads_enabled else 0.25)
ba.textwidget(edit=tbtn['entry_fee_text_remaining'],
ba.textwidget(edit=tbtn.entry_fee_text_remaining,
color=(0.6, 0.6, 0.6, 1 if ads_enabled else 0.2))
self._update_hard_mode_lock_image()
@ -363,232 +363,21 @@ class CoopBrowserWindow(ba.Window):
ba.print_exception('Error updating campaign lock.')
def _update_for_data(self, data: list[dict[str, Any]] | None) -> None:
# pylint: disable=too-many-statements
# pylint: disable=too-many-locals
# pylint: disable=too-many-branches
from ba.internal import getcampaign, get_tournament_prize_strings
# If the number of tournaments or challenges in the data differs from
# our current arrangement, refresh with the new number.
if ((data is None and self._tournament_button_count != 0)
or (data is not None and
(len(data) != self._tournament_button_count))):
self._tournament_button_count = len(
data) if data is not None else 0
self._tournament_button_count = (len(data)
if data is not None else 0)
ba.app.config['Tournament Rows'] = self._tournament_button_count
self._refresh()
# Update all of our tourney buttons based on whats in data.
for i, tbtn in enumerate(self._tournament_buttons):
assert data is not None
entry: dict[str, Any] = data[i]
prize_y_offs = (34 if 'prizeRange3' in entry else
20 if 'prizeRange2' in entry else 12)
x_offs = 90
# This seems to be a false alarm.
# pylint: disable=unbalanced-tuple-unpacking
pr1, pv1, pr2, pv2, pr3, pv3 = (
get_tournament_prize_strings(entry))
# pylint: enable=unbalanced-tuple-unpacking
enabled = 'requiredLeague' not in entry
ba.buttonwidget(edit=tbtn['button'],
color=(0.5, 0.7, 0.2) if enabled else
(0.5, 0.5, 0.5))
ba.imagewidget(edit=tbtn['lock_image'],
opacity=0.0 if enabled else 1.0)
ba.textwidget(edit=tbtn['prize_range_1_text'],
text='-' if pr1 == '' else pr1,
position=(tbtn['button_x'] + 365 + x_offs,
tbtn['button_y'] + tbtn['button_scale_y'] -
93 + prize_y_offs))
# We want to draw values containing tickets a bit smaller
# (scratch that; we now draw medals a bit bigger).
ticket_char = ba.charstr(ba.SpecialChar.TICKET_BACKING)
prize_value_scale_large = 1.0
prize_value_scale_small = 1.0
ba.textwidget(edit=tbtn['prize_value_1_text'],
text='-' if pv1 == '' else pv1,
scale=prize_value_scale_large if ticket_char
not in pv1 else prize_value_scale_small,
position=(tbtn['button_x'] + 380 + x_offs,
tbtn['button_y'] + tbtn['button_scale_y'] -
93 + prize_y_offs))
ba.textwidget(edit=tbtn['prize_range_2_text'],
text=pr2,
position=(tbtn['button_x'] + 365 + x_offs,
tbtn['button_y'] + tbtn['button_scale_y'] -
93 - 45 + prize_y_offs))
ba.textwidget(edit=tbtn['prize_value_2_text'],
text=pv2,
scale=prize_value_scale_large if ticket_char
not in pv2 else prize_value_scale_small,
position=(tbtn['button_x'] + 380 + x_offs,
tbtn['button_y'] + tbtn['button_scale_y'] -
93 - 45 + prize_y_offs))
ba.textwidget(edit=tbtn['prize_range_3_text'],
text=pr3,
position=(tbtn['button_x'] + 365 + x_offs,
tbtn['button_y'] + tbtn['button_scale_y'] -
93 - 90 + prize_y_offs))
ba.textwidget(edit=tbtn['prize_value_3_text'],
text=pv3,
scale=prize_value_scale_large if ticket_char
not in pv3 else prize_value_scale_small,
position=(tbtn['button_x'] + 380 + x_offs,
tbtn['button_y'] + tbtn['button_scale_y'] -
93 - 90 + prize_y_offs))
leader_name = '-'
leader_score: str | ba.Lstr = '-'
if entry['scores']:
score = tbtn['leader'] = copy.deepcopy(entry['scores'][0])
leader_name = score[1]
leader_score = (ba.timestring(
score[0] * 10,
centi=True,
timeformat=ba.TimeFormat.MILLISECONDS,
suppress_format_warning=True) if entry['scoreType']
== 'time' else str(score[0]))
else:
tbtn['leader'] = None
ba.textwidget(edit=tbtn['current_leader_name_text'],
text=ba.Lstr(value=leader_name))
self._tournament_leader_score_type = (entry['scoreType'])
ba.textwidget(edit=tbtn['current_leader_score_text'],
text=leader_score)
ba.buttonwidget(edit=tbtn['more_scores_button'],
label=ba.Lstr(resource=self._r + '.seeMoreText'))
out_of_time_text: str | ba.Lstr = (
'-' if 'totalTime' not in entry else ba.Lstr(
resource=self._r + '.ofTotalTimeText',
subs=[('${TOTAL}',
ba.timestring(entry['totalTime'],
centi=False,
suppress_format_warning=True))]))
ba.textwidget(edit=tbtn['time_remaining_out_of_text'],
text=out_of_time_text)
tbtn['time_remaining'] = entry['timeRemaining']
tbtn['has_time_remaining'] = entry is not None
tbtn['tournament_id'] = entry['tournamentID']
tbtn['required_league'] = (None if 'requiredLeague' not in entry
else entry['requiredLeague'])
game = ba.app.accounts_v1.tournament_info[
tbtn['tournament_id']]['game']
if game is None:
ba.textwidget(edit=tbtn['button_text'], text='-')
ba.imagewidget(edit=tbtn['image'],
texture=ba.gettexture('black'),
opacity=0.2)
else:
campaignname, levelname = game.split(':')
campaign = getcampaign(campaignname)
max_players = ba.app.accounts_v1.tournament_info[
tbtn['tournament_id']]['maxPlayers']
txt = ba.Lstr(
value='${A} ${B}',
subs=[('${A}', campaign.getlevel(levelname).displayname),
('${B}',
ba.Lstr(resource='playerCountAbbreviatedText',
subs=[('${COUNT}', str(max_players))]))])
ba.textwidget(edit=tbtn['button_text'], text=txt)
ba.imagewidget(
edit=tbtn['image'],
texture=campaign.getlevel(levelname).get_preview_texture(),
opacity=1.0 if enabled else 0.5)
fee = entry['fee']
if fee is None:
fee_var = None
elif fee == 4:
fee_var = 'price.tournament_entry_4'
elif fee == 3:
fee_var = 'price.tournament_entry_3'
elif fee == 2:
fee_var = 'price.tournament_entry_2'
elif fee == 1:
fee_var = 'price.tournament_entry_1'
else:
if fee != 0:
print('Unknown fee value:', fee)
fee_var = 'price.tournament_entry_0'
tbtn['allow_ads'] = allow_ads = entry['allowAds']
final_fee: int | None = (None if fee_var is None else
_ba.get_v1_account_misc_read_val(
fee_var, '?'))
final_fee_str: str | ba.Lstr
if fee_var is None:
final_fee_str = ''
else:
if final_fee == 0:
final_fee_str = ba.Lstr(
resource='getTicketsWindow.freeText')
else:
final_fee_str = (
ba.charstr(ba.SpecialChar.TICKET_BACKING) +
str(final_fee))
ad_tries_remaining = ba.app.accounts_v1.tournament_info[
tbtn['tournament_id']]['adTriesRemaining']
free_tries_remaining = ba.app.accounts_v1.tournament_info[
tbtn['tournament_id']]['freeTriesRemaining']
# Now, if this fee allows ads and we support video ads, show
# the 'or ad' version.
if allow_ads and _ba.has_video_ads():
ads_enabled = _ba.have_incentivized_ad()
ba.imagewidget(edit=tbtn['entry_fee_ad_image'],
opacity=1.0 if ads_enabled else 0.25)
or_text = ba.Lstr(resource='orText',
subs=[('${A}', ''),
('${B}', '')]).evaluate().strip()
ba.textwidget(edit=tbtn['entry_fee_text_or'], text=or_text)
ba.textwidget(
edit=tbtn['entry_fee_text_top'],
position=(tbtn['button_x'] + 360,
tbtn['button_y'] + tbtn['button_scale_y'] - 60),
scale=1.3,
text=final_fee_str)
# Possibly show number of ad-plays remaining.
ba.textwidget(
edit=tbtn['entry_fee_text_remaining'],
position=(tbtn['button_x'] + 360,
tbtn['button_y'] + tbtn['button_scale_y'] - 146),
text='' if ad_tries_remaining in [None, 0] else
('' + str(ad_tries_remaining)),
color=(0.6, 0.6, 0.6, 1 if ads_enabled else 0.2))
else:
ba.imagewidget(edit=tbtn['entry_fee_ad_image'], opacity=0.0)
ba.textwidget(edit=tbtn['entry_fee_text_or'], text='')
ba.textwidget(
edit=tbtn['entry_fee_text_top'],
position=(tbtn['button_x'] + 360,
tbtn['button_y'] + tbtn['button_scale_y'] - 80),
scale=1.3,
text=final_fee_str)
# Possibly show number of free-plays remaining.
ba.textwidget(
edit=tbtn['entry_fee_text_remaining'],
position=(tbtn['button_x'] + 360,
tbtn['button_y'] + tbtn['button_scale_y'] - 100),
text=('' if (free_tries_remaining in [None, 0]
or final_fee != 0) else
('' + str(free_tries_remaining))),
color=(0.6, 0.6, 0.6, 1))
tbtn.update_for_data(data[i])
def _on_tournament_query_response(self,
data: dict[str, Any] | None) -> None:
@ -715,10 +504,13 @@ class CoopBrowserWindow(ba.Window):
items = [
campaignname + ':Onslaught Training',
campaignname + ':Rookie Onslaught',
campaignname + ':Rookie Football', campaignname + ':Pro Onslaught',
campaignname + ':Pro Football', campaignname + ':Pro Runaround',
campaignname + ':Uber Onslaught', campaignname + ':Uber Football',
campaignname + ':Uber Runaround'
campaignname + ':Rookie Football',
campaignname + ':Pro Onslaught',
campaignname + ':Pro Football',
campaignname + ':Pro Runaround',
campaignname + ':Uber Onslaught',
campaignname + ':Uber Football',
campaignname + ':Uber Runaround',
]
items += [campaignname + ':The Last Stand']
if self._selected_campaign_level is None:
@ -772,6 +564,7 @@ class CoopBrowserWindow(ba.Window):
# pylint: disable=too-many-locals
# pylint: disable=cyclic-import
from bastd.ui.coop.gamebutton import GameButton
from bastd.ui.coop.tournamentbutton import TournamentButton
# (Re)create the sub-container if need be.
if self._subcontainer is not None:
@ -839,7 +632,7 @@ class CoopBrowserWindow(ba.Window):
# Tournaments
self._tournament_buttons: list[dict[str, Any]] = []
self._tournament_buttons: list[TournamentButton] = []
v -= 53
# FIXME shouldn't use hard-coded strings here.
@ -919,7 +712,12 @@ class CoopBrowserWindow(ba.Window):
v2 = -2
is_last_sel = True
self._tournament_buttons.append(
self._tournament_button(sc2, h, v2, is_last_sel))
TournamentButton(sc2,
h,
v2,
is_last_sel,
on_pressed=ba.WeakCall(
self.run_tournament)))
v -= 200
# Custom Games.
@ -949,7 +747,8 @@ class CoopBrowserWindow(ba.Window):
if _ba.get_v1_account_misc_read_val(
'easter', False) or _ba.get_purchased('games.easter_egg_hunt'):
items = [
'Challenges:Easter Egg Hunt', 'Challenges:Pro Easter Egg Hunt'
'Challenges:Easter Egg Hunt',
'Challenges:Pro Easter Egg Hunt',
] + items
# add all custom user levels here..
@ -995,19 +794,19 @@ class CoopBrowserWindow(ba.Window):
for i, tbutton in enumerate(self._tournament_buttons):
ba.widget(
edit=tbutton['button'],
edit=tbutton.button,
up_widget=self._tournament_info_button
if i == 0 else self._tournament_buttons[i - 1]['button'],
down_widget=self._tournament_buttons[(i + 1)]['button']
if i == 0 else self._tournament_buttons[i - 1].button,
down_widget=self._tournament_buttons[(i + 1)].button
if i + 1 < len(self._tournament_buttons) else custom_h_scroll)
ba.widget(
edit=tbutton['more_scores_button'],
edit=tbutton.more_scores_button,
down_widget=self._tournament_buttons[(
i + 1)]['current_leader_name_text']
i + 1)].current_leader_name_text
if i + 1 < len(self._tournament_buttons) else custom_h_scroll)
ba.widget(edit=tbutton['current_leader_name_text'],
ba.widget(edit=tbutton.current_leader_name_text,
up_widget=self._tournament_info_button if i == 0 else
self._tournament_buttons[i - 1]['more_scores_button'])
self._tournament_buttons[i - 1].more_scores_button)
for btn in self._custom_buttons:
try:
@ -1037,314 +836,6 @@ class CoopBrowserWindow(ba.Window):
def _enable_selectable_callback(self) -> None:
self._do_selection_callbacks = True
def _tournament_button(self, parent: ba.Widget, x: float, y: float,
select: bool) -> dict[str, Any]:
sclx = 300
scly = 195.0
data: dict[str, Any] = {
'tournament_id': None,
'time_remaining': 0,
'has_time_remaining': False,
'leader': None
}
data['button'] = btn = ba.buttonwidget(
parent=parent,
position=(x + 23, y + 4),
size=(sclx, scly),
label='',
button_type='square',
autoselect=True,
on_activate_call=lambda: self.run(None, tournament_button=data))
ba.widget(edit=btn,
show_buffer_bottom=50,
show_buffer_top=50,
show_buffer_left=400,
show_buffer_right=200)
if select:
ba.containerwidget(edit=parent,
selected_child=btn,
visible_child=btn)
image_width = sclx * 0.85 * 0.75
data['image'] = ba.imagewidget(
parent=parent,
draw_controller=btn,
position=(x + 21 + sclx * 0.5 - image_width * 0.5, y + scly - 150),
size=(image_width, image_width * 0.5),
model_transparent=self.lsbt,
model_opaque=self.lsbo,
texture=ba.gettexture('black'),
opacity=0.2,
mask_texture=ba.gettexture('mapPreviewMask'))
data['lock_image'] = ba.imagewidget(
parent=parent,
draw_controller=btn,
position=(x + 21 + sclx * 0.5 - image_width * 0.25,
y + scly - 150),
size=(image_width * 0.5, image_width * 0.5),
texture=ba.gettexture('lock'),
opacity=0.0)
data['button_text'] = ba.textwidget(parent=parent,
draw_controller=btn,
position=(x + 20 + sclx * 0.5,
y + scly - 35),
size=(0, 0),
h_align='center',
text='-',
v_align='center',
maxwidth=sclx * 0.76,
scale=0.85,
color=(0.8, 1.0, 0.8, 1.0))
header_color = (0.43, 0.4, 0.5, 1)
value_color = (0.6, 0.6, 0.6, 1)
x_offs = 0
ba.textwidget(parent=parent,
draw_controller=btn,
position=(x + 360, y + scly - 20),
size=(0, 0),
h_align='center',
text=ba.Lstr(resource=self._r + '.entryFeeText'),
v_align='center',
maxwidth=100,
scale=0.9,
color=header_color,
flatness=1.0)
data['entry_fee_text_top'] = ba.textwidget(parent=parent,
draw_controller=btn,
position=(x + 360,
y + scly - 60),
size=(0, 0),
h_align='center',
text='-',
v_align='center',
maxwidth=60,
scale=1.3,
color=value_color,
flatness=1.0)
data['entry_fee_text_or'] = ba.textwidget(parent=parent,
draw_controller=btn,
position=(x + 360,
y + scly - 90),
size=(0, 0),
h_align='center',
text='',
v_align='center',
maxwidth=60,
scale=0.5,
color=value_color,
flatness=1.0)
data['entry_fee_text_remaining'] = ba.textwidget(parent=parent,
draw_controller=btn,
position=(x + 360, y +
scly - 90),
size=(0, 0),
h_align='center',
text='',
v_align='center',
maxwidth=60,
scale=0.5,
color=value_color,
flatness=1.0)
data['entry_fee_ad_image'] = ba.imagewidget(
parent=parent,
size=(40, 40),
draw_controller=btn,
position=(x + 360 - 20, y + scly - 140),
opacity=0.0,
texture=ba.gettexture('tv'))
x_offs += 50
ba.textwidget(parent=parent,
draw_controller=btn,
position=(x + 447 + x_offs, y + scly - 20),
size=(0, 0),
h_align='center',
text=ba.Lstr(resource=self._r + '.prizesText'),
v_align='center',
maxwidth=130,
scale=0.9,
color=header_color,
flatness=1.0)
data['button_x'] = x
data['button_y'] = y
data['button_scale_y'] = scly
xo2 = 0
prize_value_scale = 1.5
data['prize_range_1_text'] = ba.textwidget(
parent=parent,
draw_controller=btn,
position=(x + 355 + xo2 + x_offs, y + scly - 93),
size=(0, 0),
h_align='right',
v_align='center',
maxwidth=50,
text='-',
scale=0.8,
color=header_color,
flatness=1.0)
data['prize_value_1_text'] = ba.textwidget(
parent=parent,
draw_controller=btn,
position=(x + 380 + xo2 + x_offs, y + scly - 93),
size=(0, 0),
h_align='left',
text='-',
v_align='center',
maxwidth=100,
scale=prize_value_scale,
color=value_color,
flatness=1.0)
data['prize_range_2_text'] = ba.textwidget(
parent=parent,
draw_controller=btn,
position=(x + 355 + xo2 + x_offs, y + scly - 93),
size=(0, 0),
h_align='right',
v_align='center',
maxwidth=50,
scale=0.8,
color=header_color,
flatness=1.0)
data['prize_value_2_text'] = ba.textwidget(
parent=parent,
draw_controller=btn,
position=(x + 380 + xo2 + x_offs, y + scly - 93),
size=(0, 0),
h_align='left',
text='',
v_align='center',
maxwidth=100,
scale=prize_value_scale,
color=value_color,
flatness=1.0)
data['prize_range_3_text'] = ba.textwidget(
parent=parent,
draw_controller=btn,
position=(x + 355 + xo2 + x_offs, y + scly - 93),
size=(0, 0),
h_align='right',
v_align='center',
maxwidth=50,
scale=0.8,
color=header_color,
flatness=1.0)
data['prize_value_3_text'] = ba.textwidget(
parent=parent,
draw_controller=btn,
position=(x + 380 + xo2 + x_offs, y + scly - 93),
size=(0, 0),
h_align='left',
text='',
v_align='center',
maxwidth=100,
scale=prize_value_scale,
color=value_color,
flatness=1.0)
ba.textwidget(parent=parent,
draw_controller=btn,
position=(x + 620 + x_offs, y + scly - 20),
size=(0, 0),
h_align='center',
text=ba.Lstr(resource=self._r + '.currentBestText'),
v_align='center',
maxwidth=180,
scale=0.9,
color=header_color,
flatness=1.0)
data['current_leader_name_text'] = ba.textwidget(
parent=parent,
draw_controller=btn,
position=(x + 620 + x_offs - (170 / 1.4) * 0.5,
y + scly - 60 - 40 * 0.5),
selectable=True,
click_activate=True,
autoselect=True,
on_activate_call=lambda: self._show_leader(tournament_button=data),
size=(170 / 1.4, 40),
h_align='center',
text='-',
v_align='center',
maxwidth=170,
scale=1.4,
color=value_color,
flatness=1.0)
data['current_leader_score_text'] = ba.textwidget(
parent=parent,
draw_controller=btn,
position=(x + 620 + x_offs, y + scly - 113 + 10),
size=(0, 0),
h_align='center',
text='-',
v_align='center',
maxwidth=170,
scale=1.8,
color=value_color,
flatness=1.0)
data['more_scores_button'] = ba.buttonwidget(
parent=parent,
position=(x + 620 + x_offs - 60, y + scly - 50 - 125),
color=(0.5, 0.5, 0.6),
textcolor=(0.7, 0.7, 0.8),
label='-',
size=(120, 40),
autoselect=True,
up_widget=data['current_leader_name_text'],
text_scale=0.6,
on_activate_call=lambda: self._show_scores(tournament_button=data))
ba.widget(edit=data['current_leader_name_text'],
down_widget=data['more_scores_button'])
ba.textwidget(parent=parent,
draw_controller=btn,
position=(x + 820 + x_offs, y + scly - 20),
size=(0, 0),
h_align='center',
text=ba.Lstr(resource=self._r + '.timeRemainingText'),
v_align='center',
maxwidth=180,
scale=0.9,
color=header_color,
flatness=1.0)
data['time_remaining_value_text'] = ba.textwidget(
parent=parent,
draw_controller=btn,
position=(x + 820 + x_offs, y + scly - 68),
size=(0, 0),
h_align='center',
text='-',
v_align='center',
maxwidth=180,
scale=2.0,
color=value_color,
flatness=1.0)
data['time_remaining_out_of_text'] = ba.textwidget(
parent=parent,
draw_controller=btn,
position=(x + 820 + x_offs, y + scly - 110),
size=(0, 0),
h_align='center',
text='-',
v_align='center',
maxwidth=120,
scale=0.72,
color=(0.4, 0.4, 0.5),
flatness=1.0)
return data
def _switch_to_league_rankings(self) -> None:
# pylint: disable=cyclic-import
from bastd.ui.account import show_sign_in_prompt
@ -1378,100 +869,20 @@ class CoopBrowserWindow(ba.Window):
show_tab=show_tab,
back_location='CoopBrowserWindow').get_root_widget())
def _show_leader(self, tournament_button: dict[str, Any]) -> None:
# pylint: disable=cyclic-import
from bastd.ui.account.viewer import AccountViewerWindow
tournament_id = tournament_button['tournament_id']
# FIXME: This assumes a single player entry in leader; should expand
# this to work with multiple.
if tournament_id is None or tournament_button['leader'] is None or len(
tournament_button['leader'][2]) != 1:
ba.playsound(ba.getsound('error'))
return
ba.playsound(ba.getsound('swish'))
AccountViewerWindow(
account_id=tournament_button['leader'][2][0].get('a', None),
profile_id=tournament_button['leader'][2][0].get('p', None),
position=tournament_button['current_leader_name_text'].
get_screen_space_center())
def _show_scores(self, tournament_button: dict[str, Any]) -> None:
# pylint: disable=cyclic-import
from bastd.ui.tournamentscores import TournamentScoresWindow
tournament_id = tournament_button['tournament_id']
if tournament_id is None:
ba.playsound(ba.getsound('error'))
return
TournamentScoresWindow(
tournament_id=tournament_id,
position=tournament_button['more_scores_button'].
get_screen_space_center())
def is_tourney_data_up_to_date(self) -> bool:
"""Return whether our tourney data is up to date."""
return self._tourney_data_up_to_date
def run(self,
game: str | None,
tournament_button: dict[str, Any] | None = None) -> None:
def run_game(self, game: str) -> None:
"""Run the provided game."""
# pylint: disable=too-many-branches
# pylint: disable=too-many-statements
# pylint: disable=too-many-return-statements
# pylint: disable=cyclic-import
from bastd.ui.confirm import ConfirmWindow
from bastd.ui.tournamententry import TournamentEntryWindow
from bastd.ui.purchase import PurchaseWindow
from bastd.ui.account import show_sign_in_prompt
args: dict[str, Any] = {}
# Do a bit of pre-flight for tournament options.
if tournament_button is not None:
if _ba.get_v1_account_state() != 'signed_in':
show_sign_in_prompt()
return
if not self._tourney_data_up_to_date:
ba.screenmessage(
ba.Lstr(resource='tournamentCheckingStateText'),
color=(1, 1, 0))
ba.playsound(ba.getsound('error'))
return
if tournament_button['tournament_id'] is None:
ba.screenmessage(
ba.Lstr(resource='internal.unavailableNoConnectionText'),
color=(1, 0, 0))
ba.playsound(ba.getsound('error'))
return
if tournament_button['required_league'] is not None:
ba.screenmessage(ba.Lstr(
resource='league.tournamentLeagueText',
subs=[
('${NAME}',
ba.Lstr(
translate=('leagueNames',
tournament_button['required_league'])))
]),
color=(1, 0, 0))
ba.playsound(ba.getsound('error'))
return
if tournament_button['time_remaining'] <= 0:
ba.screenmessage(ba.Lstr(resource='tournamentEndedText'),
color=(1, 0, 0))
ba.playsound(ba.getsound('error'))
return
# Game is whatever the tournament tells us it is.
game = ba.app.accounts_v1.tournament_info[
tournament_button['tournament_id']]['game']
if tournament_button is None and game == 'Easy:The Last Stand':
if game == 'Easy:The Last Stand':
ConfirmWindow(ba.Lstr(resource='difficultyHardUnlockOnlyText',
fallback_resource='difficultyHardOnlyText'),
cancel_button=False,
@ -1479,12 +890,11 @@ class CoopBrowserWindow(ba.Window):
height=130)
return
# Infinite onslaught/runaround require pro; bring up a store link if
# need be.
if tournament_button is None and game in (
'Challenges:Infinite Runaround',
'Challenges:Infinite Onslaught'
) and not ba.app.accounts_v1.have_pro():
# Infinite onslaught/runaround require pro; bring up a store link
# if need be.
if game in ('Challenges:Infinite Runaround',
'Challenges:Infinite Onslaught'
) and not ba.app.accounts_v1.have_pro():
if _ba.get_v1_account_state() != 'signed_in':
show_sign_in_prompt()
else:
@ -1495,7 +905,8 @@ class CoopBrowserWindow(ba.Window):
if game in ['Challenges:Meteor Shower']:
required_purchase = 'games.meteor_shower'
elif game in [
'Challenges:Target Practice', 'Challenges:Target Practice B'
'Challenges:Target Practice',
'Challenges:Target Practice B',
]:
required_purchase = 'games.target_practice'
elif game in ['Challenges:Ninja Fight']:
@ -1503,13 +914,14 @@ class CoopBrowserWindow(ba.Window):
elif game in ['Challenges:Pro Ninja Fight']:
required_purchase = 'games.ninja_fight'
elif game in [
'Challenges:Easter Egg Hunt', 'Challenges:Pro Easter Egg Hunt'
'Challenges:Easter Egg Hunt',
'Challenges:Pro Easter Egg Hunt',
]:
required_purchase = 'games.easter_egg_hunt'
else:
required_purchase = None
if (tournament_button is None and required_purchase is not None
if (required_purchase is not None
and not _ba.get_purchased(required_purchase)):
if _ba.get_v1_account_state() != 'signed_in':
show_sign_in_prompt()
@ -1519,17 +931,57 @@ class CoopBrowserWindow(ba.Window):
self._save_state()
# For tournaments, we pop up the entry window.
if tournament_button is not None:
TournamentEntryWindow(
tournament_id=tournament_button['tournament_id'],
position=tournament_button['button'].get_screen_space_center())
else:
# Otherwise just dive right in.
assert game is not None
if ba.app.launch_coop_game(game, args=args):
ba.containerwidget(edit=self._root_widget,
transition='out_left')
if ba.app.launch_coop_game(game, args=args):
ba.containerwidget(edit=self._root_widget, transition='out_left')
def run_tournament(self, tournament_button: TournamentButton) -> None:
"""Run the provided tournament game."""
from bastd.ui.account import show_sign_in_prompt
from bastd.ui.tournamententry import TournamentEntryWindow
if _ba.get_v1_account_state() != 'signed_in':
show_sign_in_prompt()
return
if not self._tourney_data_up_to_date:
ba.screenmessage(ba.Lstr(resource='tournamentCheckingStateText'),
color=(1, 1, 0))
ba.playsound(ba.getsound('error'))
return
if tournament_button.tournament_id is None:
ba.screenmessage(
ba.Lstr(resource='internal.unavailableNoConnectionText'),
color=(1, 0, 0))
ba.playsound(ba.getsound('error'))
return
if tournament_button.required_league is not None:
ba.screenmessage(
ba.Lstr(
resource='league.tournamentLeagueText',
subs=[('${NAME}',
ba.Lstr(
translate=('leagueNames',
tournament_button.required_league)))
]),
color=(1, 0, 0),
)
ba.playsound(ba.getsound('error'))
return
if tournament_button.time_remaining <= 0:
ba.screenmessage(ba.Lstr(resource='tournamentEndedText'),
color=(1, 0, 0))
ba.playsound(ba.getsound('error'))
return
self._save_state()
assert tournament_button.tournament_id is not None
TournamentEntryWindow(
tournament_id=tournament_button.tournament_id,
position=tournament_button.button.get_screen_space_center())
def _back(self) -> None:
# pylint: disable=cyclic-import
@ -1542,24 +994,6 @@ class CoopBrowserWindow(ba.Window):
ba.app.ui.set_main_menu_window(
PlayWindow(transition='in_left').get_root_widget())
def _restore_state(self) -> None:
try:
sel_name = ba.app.ui.window_states.get(type(self),
{}).get('sel_name')
if sel_name == 'Back':
sel = self._back_button
elif sel_name == 'Scroll':
sel = self._scrollwidget
elif sel_name == 'PowerRanking':
sel = self._league_rank_button_widget
elif sel_name == 'Store':
sel = self._store_button_widget
else:
sel = self._scrollwidget
ba.containerwidget(edit=self._root_widget, selected_child=sel)
except Exception:
ba.print_exception(f'Error restoring state for {self}.')
def _save_state(self) -> None:
cfg = ba.app.config
try:
@ -1580,16 +1014,31 @@ class CoopBrowserWindow(ba.Window):
cfg['Selected Coop Row'] = self._selected_row
cfg['Selected Coop Custom Level'] = self._selected_custom_level
cfg['Selected Coop Challenge Level'] = self._selected_challenge_level
cfg['Selected Coop Campaign Level'] = self._selected_campaign_level
cfg.commit()
def _restore_state(self) -> None:
try:
sel_name = ba.app.ui.window_states.get(type(self),
{}).get('sel_name')
if sel_name == 'Back':
sel = self._back_button
elif sel_name == 'Scroll':
sel = self._scrollwidget
elif sel_name == 'PowerRanking':
sel = self._league_rank_button_widget
elif sel_name == 'Store':
sel = self._store_button_widget
else:
sel = self._scrollwidget
ba.containerwidget(edit=self._root_widget, selected_child=sel)
except Exception:
ba.print_exception(f'Error restoring state for {self}.')
def sel_change(self, row: str, game: str) -> None:
"""(internal)"""
if self._do_selection_callbacks:
if row == 'custom':
self._selected_custom_level = game
if row == 'challenges':
self._selected_challenge_level = game
elif row == 'campaign':
self._selected_campaign_level = game

View File

@ -55,7 +55,7 @@ class GameButton:
position=(x + 23, y + 4),
size=(sclx, scly),
label='',
on_activate_call=ba.Call(window.run, game),
on_activate_call=ba.Call(window.run_game, game),
button_type='square',
autoselect=True,
on_select_call=ba.Call(window.sel_change, row, game))

View File

@ -0,0 +1,561 @@
# Released under the MIT License. See LICENSE for details.
#
"""Defines button for co-op games."""
from __future__ import annotations
from typing import TYPE_CHECKING
import copy
import ba
import _ba
if TYPE_CHECKING:
from typing import Any, Callable
class TournamentButton:
"""Button showing a tournament in coop window."""
def __init__(self, parent: ba.Widget, x: float, y: float, select: bool,
on_pressed: Callable[[TournamentButton], None]) -> None:
self._r = 'coopSelectWindow'
sclx = 300
scly = 195.0
self.on_pressed = on_pressed
self.lsbt = ba.getmodel('level_select_button_transparent')
self.lsbo = ba.getmodel('level_select_button_opaque')
self.allow_ads = False
self.tournament_id: str | None = None
self.time_remaining: int = 0
self.has_time_remaining: bool = False
self.leader: Any = None
self.required_league: str | None = None
self.button = btn = ba.buttonwidget(
parent=parent,
position=(x + 23, y + 4),
size=(sclx, scly),
label='',
button_type='square',
autoselect=True,
# on_activate_call=lambda: self.run(None, tournament_button=data)
on_activate_call=ba.WeakCall(self._pressed))
ba.widget(edit=btn,
show_buffer_bottom=50,
show_buffer_top=50,
show_buffer_left=400,
show_buffer_right=200)
if select:
ba.containerwidget(edit=parent,
selected_child=btn,
visible_child=btn)
image_width = sclx * 0.85 * 0.75
self.image = ba.imagewidget(
parent=parent,
draw_controller=btn,
position=(x + 21 + sclx * 0.5 - image_width * 0.5, y + scly - 150),
size=(image_width, image_width * 0.5),
model_transparent=self.lsbt,
model_opaque=self.lsbo,
texture=ba.gettexture('black'),
opacity=0.2,
mask_texture=ba.gettexture('mapPreviewMask'))
self.lock_image = ba.imagewidget(
parent=parent,
draw_controller=btn,
position=(x + 21 + sclx * 0.5 - image_width * 0.25,
y + scly - 150),
size=(image_width * 0.5, image_width * 0.5),
texture=ba.gettexture('lock'),
opacity=0.0)
self.button_text = ba.textwidget(parent=parent,
draw_controller=btn,
position=(x + 20 + sclx * 0.5,
y + scly - 35),
size=(0, 0),
h_align='center',
text='-',
v_align='center',
maxwidth=sclx * 0.76,
scale=0.85,
color=(0.8, 1.0, 0.8, 1.0))
header_color = (0.43, 0.4, 0.5, 1)
value_color = (0.6, 0.6, 0.6, 1)
x_offs = 0
ba.textwidget(parent=parent,
draw_controller=btn,
position=(x + 360, y + scly - 20),
size=(0, 0),
h_align='center',
text=ba.Lstr(resource=self._r + '.entryFeeText'),
v_align='center',
maxwidth=100,
scale=0.9,
color=header_color,
flatness=1.0)
self.entry_fee_text_top = ba.textwidget(parent=parent,
draw_controller=btn,
position=(x + 360,
y + scly - 60),
size=(0, 0),
h_align='center',
text='-',
v_align='center',
maxwidth=60,
scale=1.3,
color=value_color,
flatness=1.0)
self.entry_fee_text_or = ba.textwidget(parent=parent,
draw_controller=btn,
position=(x + 360,
y + scly - 90),
size=(0, 0),
h_align='center',
text='',
v_align='center',
maxwidth=60,
scale=0.5,
color=value_color,
flatness=1.0)
self.entry_fee_text_remaining = ba.textwidget(parent=parent,
draw_controller=btn,
position=(x + 360,
y + scly - 90),
size=(0, 0),
h_align='center',
text='',
v_align='center',
maxwidth=60,
scale=0.5,
color=value_color,
flatness=1.0)
self.entry_fee_ad_image = ba.imagewidget(parent=parent,
size=(40, 40),
draw_controller=btn,
position=(x + 360 - 20,
y + scly - 140),
opacity=0.0,
texture=ba.gettexture('tv'))
x_offs += 50
ba.textwidget(parent=parent,
draw_controller=btn,
position=(x + 447 + x_offs, y + scly - 20),
size=(0, 0),
h_align='center',
text=ba.Lstr(resource=self._r + '.prizesText'),
v_align='center',
maxwidth=130,
scale=0.9,
color=header_color,
flatness=1.0)
self.button_x = x
self.button_y = y
self.button_scale_y = scly
xo2 = 0
prize_value_scale = 1.5
self.prize_range_1_text = ba.textwidget(
parent=parent,
draw_controller=btn,
position=(x + 355 + xo2 + x_offs, y + scly - 93),
size=(0, 0),
h_align='right',
v_align='center',
maxwidth=50,
text='-',
scale=0.8,
color=header_color,
flatness=1.0)
self.prize_value_1_text = ba.textwidget(
parent=parent,
draw_controller=btn,
position=(x + 380 + xo2 + x_offs, y + scly - 93),
size=(0, 0),
h_align='left',
text='-',
v_align='center',
maxwidth=100,
scale=prize_value_scale,
color=value_color,
flatness=1.0)
self.prize_range_2_text = ba.textwidget(
parent=parent,
draw_controller=btn,
position=(x + 355 + xo2 + x_offs, y + scly - 93),
size=(0, 0),
h_align='right',
v_align='center',
maxwidth=50,
scale=0.8,
color=header_color,
flatness=1.0)
self.prize_value_2_text = ba.textwidget(
parent=parent,
draw_controller=btn,
position=(x + 380 + xo2 + x_offs, y + scly - 93),
size=(0, 0),
h_align='left',
text='',
v_align='center',
maxwidth=100,
scale=prize_value_scale,
color=value_color,
flatness=1.0)
self.prize_range_3_text = ba.textwidget(
parent=parent,
draw_controller=btn,
position=(x + 355 + xo2 + x_offs, y + scly - 93),
size=(0, 0),
h_align='right',
v_align='center',
maxwidth=50,
scale=0.8,
color=header_color,
flatness=1.0)
self.prize_value_3_text = ba.textwidget(
parent=parent,
draw_controller=btn,
position=(x + 380 + xo2 + x_offs, y + scly - 93),
size=(0, 0),
h_align='left',
text='',
v_align='center',
maxwidth=100,
scale=prize_value_scale,
color=value_color,
flatness=1.0)
ba.textwidget(parent=parent,
draw_controller=btn,
position=(x + 620 + x_offs, y + scly - 20),
size=(0, 0),
h_align='center',
text=ba.Lstr(resource=self._r + '.currentBestText'),
v_align='center',
maxwidth=180,
scale=0.9,
color=header_color,
flatness=1.0)
self.current_leader_name_text = ba.textwidget(
parent=parent,
draw_controller=btn,
position=(x + 620 + x_offs - (170 / 1.4) * 0.5,
y + scly - 60 - 40 * 0.5),
selectable=True,
click_activate=True,
autoselect=True,
on_activate_call=ba.WeakCall(self._show_leader),
size=(170 / 1.4, 40),
h_align='center',
text='-',
v_align='center',
maxwidth=170,
scale=1.4,
color=value_color,
flatness=1.0)
self.current_leader_score_text = ba.textwidget(
parent=parent,
draw_controller=btn,
position=(x + 620 + x_offs, y + scly - 113 + 10),
size=(0, 0),
h_align='center',
text='-',
v_align='center',
maxwidth=170,
scale=1.8,
color=value_color,
flatness=1.0)
self.more_scores_button = ba.buttonwidget(
parent=parent,
position=(x + 620 + x_offs - 60, y + scly - 50 - 125),
color=(0.5, 0.5, 0.6),
textcolor=(0.7, 0.7, 0.8),
label='-',
size=(120, 40),
autoselect=True,
up_widget=self.current_leader_name_text,
text_scale=0.6,
on_activate_call=ba.WeakCall(self._show_scores))
ba.widget(edit=self.current_leader_name_text,
down_widget=self.more_scores_button)
ba.textwidget(parent=parent,
draw_controller=btn,
position=(x + 820 + x_offs, y + scly - 20),
size=(0, 0),
h_align='center',
text=ba.Lstr(resource=self._r + '.timeRemainingText'),
v_align='center',
maxwidth=180,
scale=0.9,
color=header_color,
flatness=1.0)
self.time_remaining_value_text = ba.textwidget(
parent=parent,
draw_controller=btn,
position=(x + 820 + x_offs, y + scly - 68),
size=(0, 0),
h_align='center',
text='-',
v_align='center',
maxwidth=180,
scale=2.0,
color=value_color,
flatness=1.0)
self.time_remaining_out_of_text = ba.textwidget(
parent=parent,
draw_controller=btn,
position=(x + 820 + x_offs, y + scly - 110),
size=(0, 0),
h_align='center',
text='-',
v_align='center',
maxwidth=120,
scale=0.72,
color=(0.4, 0.4, 0.5),
flatness=1.0)
def _pressed(self) -> None:
self.on_pressed(self)
def _show_leader(self) -> None:
# pylint: disable=cyclic-import
from bastd.ui.account.viewer import AccountViewerWindow
tournament_id = self.tournament_id
# FIXME: This assumes a single player entry in leader; should expand
# this to work with multiple.
if tournament_id is None or self.leader is None or len(
self.leader[2]) != 1:
ba.playsound(ba.getsound('error'))
return
ba.playsound(ba.getsound('swish'))
AccountViewerWindow(
account_id=self.leader[2][0].get('a', None),
profile_id=self.leader[2][0].get('p', None),
position=self.current_leader_name_text.get_screen_space_center())
def _show_scores(self) -> None:
# pylint: disable=cyclic-import
from bastd.ui.tournamentscores import TournamentScoresWindow
tournament_id = self.tournament_id
if tournament_id is None:
ba.playsound(ba.getsound('error'))
return
TournamentScoresWindow(
tournament_id=tournament_id,
position=self.more_scores_button.get_screen_space_center())
def update_for_data(self, entry: dict[str, Any]) -> None:
"""Update for new incoming data."""
# pylint: disable=too-many-statements
# pylint: disable=too-many-locals
# pylint: disable=too-many-branches
from ba.internal import getcampaign, get_tournament_prize_strings
prize_y_offs = (34 if 'prizeRange3' in entry else
20 if 'prizeRange2' in entry else 12)
x_offs = 90
# This seems to be a false alarm.
# pylint: disable=unbalanced-tuple-unpacking
pr1, pv1, pr2, pv2, pr3, pv3 = (get_tournament_prize_strings(entry))
# pylint: enable=unbalanced-tuple-unpacking
enabled = 'requiredLeague' not in entry
ba.buttonwidget(edit=self.button,
color=(0.5, 0.7, 0.2) if enabled else (0.5, 0.5, 0.5))
ba.imagewidget(edit=self.lock_image, opacity=0.0 if enabled else 1.0)
ba.textwidget(edit=self.prize_range_1_text,
text='-' if pr1 == '' else pr1,
position=(self.button_x + 365 + x_offs, self.button_y +
self.button_scale_y - 93 + prize_y_offs))
# We want to draw values containing tickets a bit smaller
# (scratch that; we now draw medals a bit bigger).
ticket_char = ba.charstr(ba.SpecialChar.TICKET_BACKING)
prize_value_scale_large = 1.0
prize_value_scale_small = 1.0
ba.textwidget(edit=self.prize_value_1_text,
text='-' if pv1 == '' else pv1,
scale=prize_value_scale_large
if ticket_char not in pv1 else prize_value_scale_small,
position=(self.button_x + 380 + x_offs, self.button_y +
self.button_scale_y - 93 + prize_y_offs))
ba.textwidget(edit=self.prize_range_2_text,
text=pr2,
position=(self.button_x + 365 + x_offs, self.button_y +
self.button_scale_y - 93 - 45 + prize_y_offs))
ba.textwidget(edit=self.prize_value_2_text,
text=pv2,
scale=prize_value_scale_large
if ticket_char not in pv2 else prize_value_scale_small,
position=(self.button_x + 380 + x_offs, self.button_y +
self.button_scale_y - 93 - 45 + prize_y_offs))
ba.textwidget(edit=self.prize_range_3_text,
text=pr3,
position=(self.button_x + 365 + x_offs, self.button_y +
self.button_scale_y - 93 - 90 + prize_y_offs))
ba.textwidget(edit=self.prize_value_3_text,
text=pv3,
scale=prize_value_scale_large
if ticket_char not in pv3 else prize_value_scale_small,
position=(self.button_x + 380 + x_offs, self.button_y +
self.button_scale_y - 93 - 90 + prize_y_offs))
leader_name = '-'
leader_score: str | ba.Lstr = '-'
if entry['scores']:
score = self.leader = copy.deepcopy(entry['scores'][0])
leader_name = score[1]
leader_score = (ba.timestring(
score[0] * 10,
centi=True,
timeformat=ba.TimeFormat.MILLISECONDS,
suppress_format_warning=True)
if entry['scoreType'] == 'time' else str(score[0]))
else:
self.leader = None
ba.textwidget(edit=self.current_leader_name_text,
text=ba.Lstr(value=leader_name))
ba.textwidget(edit=self.current_leader_score_text, text=leader_score)
ba.buttonwidget(edit=self.more_scores_button,
label=ba.Lstr(resource=self._r + '.seeMoreText'))
out_of_time_text: str | ba.Lstr = (
'-' if 'totalTime' not in entry else ba.Lstr(
resource=self._r + '.ofTotalTimeText',
subs=[('${TOTAL}',
ba.timestring(entry['totalTime'],
centi=False,
suppress_format_warning=True))]))
ba.textwidget(edit=self.time_remaining_out_of_text,
text=out_of_time_text)
self.time_remaining = entry['timeRemaining']
self.has_time_remaining = entry is not None
self.tournament_id = entry['tournamentID']
self.required_league = (None if 'requiredLeague' not in entry else
entry['requiredLeague'])
game = ba.app.accounts_v1.tournament_info[self.tournament_id]['game']
if game is None:
ba.textwidget(edit=self.button_text, text='-')
ba.imagewidget(edit=self.image,
texture=ba.gettexture('black'),
opacity=0.2)
else:
campaignname, levelname = game.split(':')
campaign = getcampaign(campaignname)
max_players = ba.app.accounts_v1.tournament_info[
self.tournament_id]['maxPlayers']
txt = ba.Lstr(value='${A} ${B}',
subs=[('${A}',
campaign.getlevel(levelname).displayname),
('${B}',
ba.Lstr(resource='playerCountAbbreviatedText',
subs=[('${COUNT}', str(max_players))
]))])
ba.textwidget(edit=self.button_text, text=txt)
ba.imagewidget(
edit=self.image,
texture=campaign.getlevel(levelname).get_preview_texture(),
opacity=1.0 if enabled else 0.5)
fee = entry['fee']
if fee is None:
fee_var = None
elif fee == 4:
fee_var = 'price.tournament_entry_4'
elif fee == 3:
fee_var = 'price.tournament_entry_3'
elif fee == 2:
fee_var = 'price.tournament_entry_2'
elif fee == 1:
fee_var = 'price.tournament_entry_1'
else:
if fee != 0:
print('Unknown fee value:', fee)
fee_var = 'price.tournament_entry_0'
self.allow_ads = allow_ads = entry['allowAds']
final_fee: int | None = (None if fee_var is None else
_ba.get_v1_account_misc_read_val(
fee_var, '?'))
final_fee_str: str | ba.Lstr
if fee_var is None:
final_fee_str = ''
else:
if final_fee == 0:
final_fee_str = ba.Lstr(resource='getTicketsWindow.freeText')
else:
final_fee_str = (ba.charstr(ba.SpecialChar.TICKET_BACKING) +
str(final_fee))
ad_tries_remaining = ba.app.accounts_v1.tournament_info[
self.tournament_id]['adTriesRemaining']
free_tries_remaining = ba.app.accounts_v1.tournament_info[
self.tournament_id]['freeTriesRemaining']
# Now, if this fee allows ads and we support video ads, show
# the 'or ad' version.
if allow_ads and _ba.has_video_ads():
ads_enabled = _ba.have_incentivized_ad()
ba.imagewidget(edit=self.entry_fee_ad_image,
opacity=1.0 if ads_enabled else 0.25)
or_text = ba.Lstr(resource='orText',
subs=[('${A}', ''),
('${B}', '')]).evaluate().strip()
ba.textwidget(edit=self.entry_fee_text_or, text=or_text)
ba.textwidget(edit=self.entry_fee_text_top,
position=(self.button_x + 360,
self.button_y + self.button_scale_y - 60),
scale=1.3,
text=final_fee_str)
# Possibly show number of ad-plays remaining.
ba.textwidget(edit=self.entry_fee_text_remaining,
position=(self.button_x + 360,
self.button_y + self.button_scale_y - 146),
text='' if ad_tries_remaining in [None, 0] else
('' + str(ad_tries_remaining)),
color=(0.6, 0.6, 0.6, 1 if ads_enabled else 0.2))
else:
ba.imagewidget(edit=self.entry_fee_ad_image, opacity=0.0)
ba.textwidget(edit=self.entry_fee_text_or, text='')
ba.textwidget(edit=self.entry_fee_text_top,
position=(self.button_x + 360,
self.button_y + self.button_scale_y - 80),
scale=1.3,
text=final_fee_str)
# Possibly show number of free-plays remaining.
ba.textwidget(
edit=self.entry_fee_text_remaining,
position=(self.button_x + 360,
self.button_y + self.button_scale_y - 100),
text=('' if (free_tries_remaining in [None, 0]
or final_fee != 0) else
('' + str(free_tries_remaining))),
color=(0.6, 0.6, 0.6, 1),
)

View File

@ -213,8 +213,8 @@ class OnScreenKeyboardWindow(ba.Window):
# Show change instructions only if we have more than one
# keyboard option.
if (ba.app.meta.metascan is not None
and len(ba.app.meta.metascan.keyboards) > 1):
if (ba.app.meta.scanresults is not None
and len(ba.app.meta.scanresults.keyboards) > 1):
ba.textwidget(
parent=self._root_widget,
h_align='center',
@ -238,8 +238,8 @@ class OnScreenKeyboardWindow(ba.Window):
self._refresh()
def _get_keyboard(self) -> ba.Keyboard:
assert ba.app.meta.metascan is not None
classname = ba.app.meta.metascan.keyboards[self._keyboard_index]
assert ba.app.meta.scanresults is not None
classname = ba.app.meta.scanresults.keyboards[self._keyboard_index]
kbclass = ba.getclass(classname, ba.Keyboard)
return kbclass()
@ -317,11 +317,11 @@ class OnScreenKeyboardWindow(ba.Window):
self._refresh()
def _next_keyboard(self) -> None:
assert ba.app.meta.metascan is not None
assert ba.app.meta.scanresults is not None
self._keyboard_index = (self._keyboard_index + 1) % len(
ba.app.meta.metascan.keyboards)
ba.app.meta.scanresults.keyboards)
self._load_keyboard()
if len(ba.app.meta.metascan.keyboards) < 2:
if len(ba.app.meta.scanresults.keyboards) < 2:
ba.playsound(ba.getsound('error'))
ba.screenmessage(ba.Lstr(resource='keyboardNoOthersAvailableText'),
color=(1, 0, 0))

View File

@ -93,7 +93,7 @@ class PluginSettingsWindow(ba.Window):
self._subcontainer = ba.columnwidget(parent=self._scrollwidget,
selection_loops_to_parent=True)
if ba.app.meta.metascan is None:
if ba.app.meta.scanresults is None:
ba.screenmessage('Still scanning plugins; please try again.',
color=(1, 0, 0))
ba.playsound(ba.getsound('error'))

View File

@ -1101,6 +1101,7 @@
<w>savebutton</w>
<w>sbytes</w>
<w>scancode</w>
<w>scanresults</w>
<w>scenetime</w>
<w>screenmessage</w>
<w>scrollwidget</w>
@ -1287,6 +1288,7 @@
<w>topos</w>
<w>touchpad</w>
<w>toucs</w>
<w>tournamentbutton</w>
<w>toutf</w>
<w>tpartial</w>
<w>tpimport</w>

View File

@ -21,7 +21,7 @@
namespace ballistica {
// These are set automatically via script; don't modify them here.
const int kAppBuildNumber = 20637;
const int kAppBuildNumber = 20638;
const char* kAppVersion = "1.7.4";
// Our standalone globals.

View File

@ -1,4 +1,3 @@
#!/usr/bin/env python3.10
# Released under the MIT License. See LICENSE for details.
#
"""Updates assets/Makefile based on source assets present."""

View File

@ -1,4 +1,3 @@
#!/usr/bin/env python3.10
# Released under the MIT License. See LICENSE for details.
#
"""Stage assets for a build."""

View File

@ -1,4 +1,3 @@
#!/usr/bin/env python3.10
# Released under the MIT License. See LICENSE for details.
#
"""General project related functionality."""