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>sbwht</w>
<w>sbylw</w> <w>sbylw</w>
<w>sbytes</w> <w>sbytes</w>
<w>scanresults</w>
<w>scenefile</w> <w>scenefile</w>
<w>scenefiles</w> <w>scenefiles</w>
<w>scenename</w> <w>scenename</w>
@ -2567,6 +2568,7 @@
<w>totalwaves</w> <w>totalwaves</w>
<w>totype</w> <w>totype</w>
<w>touchpad</w> <w>touchpad</w>
<w>tournamentbutton</w>
<w>tournamententry</w> <w>tournamententry</w>
<w>tournamentscores</w> <w>tournamentscores</w>
<w>tpartial</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!) - 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) ### 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!) - 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__/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__/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__/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/browser.py",
"ba_data/python/bastd/ui/coop/gamebutton.py", "ba_data/python/bastd/ui/coop/gamebutton.py",
"ba_data/python/bastd/ui/coop/level.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/creditslist.py",
"ba_data/python/bastd/ui/debug.py", "ba_data/python/bastd/ui/debug.py",
"ba_data/python/bastd/ui/feedback.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/browser.py \
build/ba_data/python/bastd/ui/coop/gamebutton.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/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/creditslist.py \
build/ba_data/python/bastd/ui/debug.py \ build/ba_data/python/bastd/ui/debug.py \
build/ba_data/python/bastd/ui/feedback.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__/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__/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__/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__/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__/debug.cpython-310.opt-1.pyc \
build/ba_data/python/bastd/ui/__pycache__/feedback.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** 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._name = name
self._levels: list[ba.Level] = []
self._sequential = sequential self._sequential = sequential
self._levels: list[ba.Level] = []
if levels is not None:
for level in levels:
self.addlevel(level)
@property @property
def name(self) -> str: def name(self) -> str:
@ -91,9 +97,8 @@ class Campaign:
def init_campaigns() -> None: def init_campaigns() -> None:
"""Fill out initial default Campaigns.""" """Fill out initial default Campaigns."""
# pylint: disable=too-many-statements
# pylint: disable=cyclic-import # pylint: disable=cyclic-import
from ba import _level from ba._level import Level
from bastd.game.onslaught import OnslaughtGame from bastd.game.onslaught import OnslaughtGame
from bastd.game.football import FootballCoopGame from bastd.game.football import FootballCoopGame
from bastd.game.runaround import RunaroundGame 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 # FIXME: Once translations catch up, we can convert these to use the
# generic display-name '${GAME} Training' type stuff. # generic display-name '${GAME} Training' type stuff.
campaign = Campaign('Easy') register_campaign(
campaign.addlevel( Campaign(
_level.Level('Onslaught Training', 'Easy',
gametype=OnslaughtGame, levels=[
settings={'preset': 'training_easy'}, Level('Onslaught Training',
preview_texture_name='doomShroomPreview')) gametype=OnslaughtGame,
campaign.addlevel( settings={'preset': 'training_easy'},
_level.Level('Rookie Onslaught', preview_texture_name='doomShroomPreview'),
gametype=OnslaughtGame, Level('Rookie Onslaught',
settings={'preset': 'rookie_easy'}, gametype=OnslaughtGame,
preview_texture_name='courtyardPreview')) settings={'preset': 'rookie_easy'},
campaign.addlevel( preview_texture_name='courtyardPreview'),
_level.Level('Rookie Football', Level('Rookie Football',
gametype=FootballCoopGame, gametype=FootballCoopGame,
settings={'preset': 'rookie_easy'}, settings={'preset': 'rookie_easy'},
preview_texture_name='footballStadiumPreview')) preview_texture_name='footballStadiumPreview'),
campaign.addlevel( Level('Pro Onslaught',
_level.Level('Pro Onslaught', gametype=OnslaughtGame,
gametype=OnslaughtGame, settings={'preset': 'pro_easy'},
settings={'preset': 'pro_easy'}, preview_texture_name='doomShroomPreview'),
preview_texture_name='doomShroomPreview')) Level('Pro Football',
campaign.addlevel( gametype=FootballCoopGame,
_level.Level('Pro Football', settings={'preset': 'pro_easy'},
gametype=FootballCoopGame, preview_texture_name='footballStadiumPreview'),
settings={'preset': 'pro_easy'}, Level('Pro Runaround',
preview_texture_name='footballStadiumPreview')) gametype=RunaroundGame,
campaign.addlevel( settings={'preset': 'pro_easy'},
_level.Level('Pro Runaround', preview_texture_name='towerDPreview'),
gametype=RunaroundGame, Level('Uber Onslaught',
settings={'preset': 'pro_easy'}, gametype=OnslaughtGame,
preview_texture_name='towerDPreview')) settings={'preset': 'uber_easy'},
campaign.addlevel( preview_texture_name='courtyardPreview'),
_level.Level('Uber Onslaught', Level('Uber Football',
gametype=OnslaughtGame, gametype=FootballCoopGame,
settings={'preset': 'uber_easy'}, settings={'preset': 'uber_easy'},
preview_texture_name='courtyardPreview')) preview_texture_name='footballStadiumPreview'),
campaign.addlevel( Level('Uber Runaround',
_level.Level('Uber Football', gametype=RunaroundGame,
gametype=FootballCoopGame, settings={'preset': 'uber_easy'},
settings={'preset': 'uber_easy'}, preview_texture_name='towerDPreview')
preview_texture_name='footballStadiumPreview')) ],
campaign.addlevel( ))
_level.Level('Uber Runaround',
gametype=RunaroundGame,
settings={'preset': 'uber_easy'},
preview_texture_name='towerDPreview'))
register_campaign(campaign)
# "hard" mode # "hard" mode
campaign = Campaign('Default') register_campaign(
campaign.addlevel( Campaign(
_level.Level('Onslaught Training', 'Default',
gametype=OnslaughtGame, levels=[
settings={'preset': 'training'}, Level('Onslaught Training',
preview_texture_name='doomShroomPreview')) gametype=OnslaughtGame,
campaign.addlevel( settings={'preset': 'training'},
_level.Level('Rookie Onslaught', preview_texture_name='doomShroomPreview'),
gametype=OnslaughtGame, Level('Rookie Onslaught',
settings={'preset': 'rookie'}, gametype=OnslaughtGame,
preview_texture_name='courtyardPreview')) settings={'preset': 'rookie'},
campaign.addlevel( preview_texture_name='courtyardPreview'),
_level.Level('Rookie Football', Level('Rookie Football',
gametype=FootballCoopGame, gametype=FootballCoopGame,
settings={'preset': 'rookie'}, settings={'preset': 'rookie'},
preview_texture_name='footballStadiumPreview')) preview_texture_name='footballStadiumPreview'),
campaign.addlevel( Level('Pro Onslaught',
_level.Level('Pro Onslaught', gametype=OnslaughtGame,
gametype=OnslaughtGame, settings={'preset': 'pro'},
settings={'preset': 'pro'}, preview_texture_name='doomShroomPreview'),
preview_texture_name='doomShroomPreview')) Level('Pro Football',
campaign.addlevel( gametype=FootballCoopGame,
_level.Level('Pro Football', settings={'preset': 'pro'},
gametype=FootballCoopGame, preview_texture_name='footballStadiumPreview'),
settings={'preset': 'pro'}, Level('Pro Runaround',
preview_texture_name='footballStadiumPreview')) gametype=RunaroundGame,
campaign.addlevel( settings={'preset': 'pro'},
_level.Level('Pro Runaround', preview_texture_name='towerDPreview'),
gametype=RunaroundGame, Level('Uber Onslaught',
settings={'preset': 'pro'}, gametype=OnslaughtGame,
preview_texture_name='towerDPreview')) settings={'preset': 'uber'},
campaign.addlevel( preview_texture_name='courtyardPreview'),
_level.Level('Uber Onslaught', Level('Uber Football',
gametype=OnslaughtGame, gametype=FootballCoopGame,
settings={'preset': 'uber'}, settings={'preset': 'uber'},
preview_texture_name='courtyardPreview')) preview_texture_name='footballStadiumPreview'),
campaign.addlevel( Level('Uber Runaround',
_level.Level('Uber Football', gametype=RunaroundGame,
gametype=FootballCoopGame, settings={'preset': 'uber'},
settings={'preset': 'uber'}, preview_texture_name='towerDPreview'),
preview_texture_name='footballStadiumPreview')) Level('The Last Stand',
campaign.addlevel( gametype=TheLastStandGame,
_level.Level('Uber Runaround', settings={},
gametype=RunaroundGame, preview_texture_name='rampagePreview')
settings={'preset': 'uber'}, ],
preview_texture_name='towerDPreview')) ))
campaign.addlevel(
_level.Level('The Last Stand',
gametype=TheLastStandGame,
settings={},
preview_texture_name='rampagePreview'))
register_campaign(campaign)
# challenges: our 'official' random extra co-op levels # challenges: our 'official' random extra co-op levels
campaign = Campaign('Challenges', sequential=False) register_campaign(
campaign.addlevel( Campaign(
_level.Level('Infinite Onslaught', 'Challenges',
gametype=OnslaughtGame, sequential=False,
settings={'preset': 'endless'}, levels=[
preview_texture_name='doomShroomPreview')) Level('Infinite Onslaught',
campaign.addlevel( gametype=OnslaughtGame,
_level.Level('Infinite Runaround', settings={'preset': 'endless'},
gametype=RunaroundGame, preview_texture_name='doomShroomPreview'),
settings={'preset': 'endless'}, Level('Infinite Runaround',
preview_texture_name='towerDPreview')) gametype=RunaroundGame,
campaign.addlevel( settings={'preset': 'endless'},
_level.Level('Race', preview_texture_name='towerDPreview'),
displayname='${GAME}', Level('Race',
gametype=RaceGame, displayname='${GAME}',
settings={ gametype=RaceGame,
'map': 'Big G', settings={
'Laps': 3, 'map': 'Big G',
'Bomb Spawning': 0 'Laps': 3,
}, 'Bomb Spawning': 0
preview_texture_name='bigGPreview')) },
campaign.addlevel( preview_texture_name='bigGPreview'),
_level.Level('Pro Race', Level('Pro Race',
displayname='Pro ${GAME}', displayname='Pro ${GAME}',
gametype=RaceGame, gametype=RaceGame,
settings={ settings={
'map': 'Big G', 'map': 'Big G',
'Laps': 3, 'Laps': 3,
'Bomb Spawning': 1000 'Bomb Spawning': 1000
}, },
preview_texture_name='bigGPreview')) preview_texture_name='bigGPreview'),
campaign.addlevel( Level('Lake Frigid Race',
_level.Level('Lake Frigid Race', displayname='${GAME}',
displayname='${GAME}', gametype=RaceGame,
gametype=RaceGame, settings={
settings={ 'map': 'Lake Frigid',
'map': 'Lake Frigid', 'Laps': 6,
'Laps': 6, 'Mine Spawning': 2000,
'Mine Spawning': 2000, 'Bomb Spawning': 0
'Bomb Spawning': 0 },
}, preview_texture_name='lakeFrigidPreview'),
preview_texture_name='lakeFrigidPreview')) Level('Football',
campaign.addlevel( displayname='${GAME}',
_level.Level('Football', gametype=FootballCoopGame,
displayname='${GAME}', settings={'preset': 'tournament'},
gametype=FootballCoopGame, preview_texture_name='footballStadiumPreview'),
settings={'preset': 'tournament'}, Level('Pro Football',
preview_texture_name='footballStadiumPreview')) displayname='Pro ${GAME}',
campaign.addlevel( gametype=FootballCoopGame,
_level.Level('Pro Football', settings={'preset': 'tournament_pro'},
displayname='Pro ${GAME}', preview_texture_name='footballStadiumPreview'),
gametype=FootballCoopGame, Level('Runaround',
settings={'preset': 'tournament_pro'}, displayname='${GAME}',
preview_texture_name='footballStadiumPreview')) gametype=RunaroundGame,
campaign.addlevel( settings={'preset': 'tournament'},
_level.Level('Runaround', preview_texture_name='towerDPreview'),
displayname='${GAME}', Level('Uber Runaround',
gametype=RunaroundGame, displayname='Uber ${GAME}',
settings={'preset': 'tournament'}, gametype=RunaroundGame,
preview_texture_name='towerDPreview')) settings={'preset': 'tournament_uber'},
campaign.addlevel( preview_texture_name='towerDPreview'),
_level.Level('Uber Runaround', Level('The Last Stand',
displayname='Uber ${GAME}', displayname='${GAME}',
gametype=RunaroundGame, gametype=TheLastStandGame,
settings={'preset': 'tournament_uber'}, settings={'preset': 'tournament'},
preview_texture_name='towerDPreview')) preview_texture_name='rampagePreview'),
campaign.addlevel( Level('Tournament Infinite Onslaught',
_level.Level('The Last Stand', displayname='Infinite Onslaught',
displayname='${GAME}', gametype=OnslaughtGame,
gametype=TheLastStandGame, settings={'preset': 'endless_tournament'},
settings={'preset': 'tournament'}, preview_texture_name='doomShroomPreview'),
preview_texture_name='rampagePreview')) Level('Tournament Infinite Runaround',
campaign.addlevel( displayname='Infinite Runaround',
_level.Level('Tournament Infinite Onslaught', gametype=RunaroundGame,
displayname='Infinite Onslaught', settings={'preset': 'endless_tournament'},
gametype=OnslaughtGame, preview_texture_name='towerDPreview'),
settings={'preset': 'endless_tournament'}, Level('Target Practice',
preview_texture_name='doomShroomPreview')) displayname='Pro ${GAME}',
campaign.addlevel( gametype=TargetPracticeGame,
_level.Level('Tournament Infinite Runaround', settings={},
displayname='Infinite Runaround', preview_texture_name='doomShroomPreview'),
gametype=RunaroundGame, Level('Target Practice B',
settings={'preset': 'endless_tournament'}, displayname='${GAME}',
preview_texture_name='towerDPreview')) gametype=TargetPracticeGame,
campaign.addlevel( settings={
_level.Level('Target Practice', 'Target Count': 2,
displayname='Pro ${GAME}', 'Enable Impact Bombs': False,
gametype=TargetPracticeGame, 'Enable Triple Bombs': False
settings={}, },
preview_texture_name='doomShroomPreview')) preview_texture_name='doomShroomPreview'),
campaign.addlevel( Level('Meteor Shower',
_level.Level('Target Practice B', displayname='${GAME}',
displayname='${GAME}', gametype=MeteorShowerGame,
gametype=TargetPracticeGame, settings={},
settings={ preview_texture_name='rampagePreview'),
'Target Count': 2, Level('Epic Meteor Shower',
'Enable Impact Bombs': False, displayname='${GAME}',
'Enable Triple Bombs': False gametype=MeteorShowerGame,
}, settings={'Epic Mode': True},
preview_texture_name='doomShroomPreview')) preview_texture_name='rampagePreview'),
campaign.addlevel( Level('Easter Egg Hunt',
_level.Level('Meteor Shower', displayname='${GAME}',
displayname='${GAME}', gametype=EasterEggHuntGame,
gametype=MeteorShowerGame, settings={},
settings={}, preview_texture_name='towerDPreview'),
preview_texture_name='rampagePreview')) Level('Pro Easter Egg Hunt',
campaign.addlevel( displayname='Pro ${GAME}',
_level.Level('Epic Meteor Shower', gametype=EasterEggHuntGame,
displayname='${GAME}', settings={'Pro Mode': True},
gametype=MeteorShowerGame, preview_texture_name='towerDPreview'),
settings={'Epic Mode': True}, Level(
preview_texture_name='rampagePreview')) name='Ninja Fight', # (unique id not seen by player)
campaign.addlevel( displayname='${GAME}', # (readable name seen by player)
_level.Level('Easter Egg Hunt', gametype=NinjaFightGame,
displayname='${GAME}', settings={'preset': 'regular'},
gametype=EasterEggHuntGame, preview_texture_name='courtyardPreview'),
settings={}, Level(name='Pro Ninja Fight',
preview_texture_name='towerDPreview')) displayname='Pro ${GAME}',
campaign.addlevel( gametype=NinjaFightGame,
_level.Level('Pro Easter Egg Hunt', settings={'preset': 'pro'},
displayname='Pro ${GAME}', preview_texture_name='courtyardPreview')
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)

View File

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

View File

@ -6,7 +6,6 @@
from __future__ import annotations from __future__ import annotations
import copy
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
import _ba import _ba
@ -18,6 +17,8 @@ from bastd.ui.store.browser import StoreBrowserWindow
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Any from typing import Any
from bastd.ui.coop.tournamentbutton import TournamentButton
class CoopBrowserWindow(ba.Window): class CoopBrowserWindow(ba.Window):
"""Window for browsing co-op levels/games/etc.""" """Window for browsing co-op levels/games/etc."""
@ -175,8 +176,6 @@ class CoopBrowserWindow(ba.Window):
'Selected Coop Campaign Level', None)) 'Selected Coop Campaign Level', None))
self._selected_custom_level = (cfg.get('Selected Coop Custom Level', self._selected_custom_level = (cfg.get('Selected Coop Custom Level',
None)) None))
self._selected_challenge_level = (cfg.get(
'Selected Coop Challenge Level', None))
# Don't want initial construction affecting our last-selected. # Don't want initial construction affecting our last-selected.
self._do_selection_callbacks = False self._do_selection_callbacks = False
@ -283,6 +282,7 @@ class CoopBrowserWindow(ba.Window):
import bastd.ui.tournamentscores as _unused8 import bastd.ui.tournamentscores as _unused8
import bastd.ui.tournamententry as _unused9 import bastd.ui.tournamententry as _unused9
import bastd.ui.play as _unused10 import bastd.ui.play as _unused10
import bastd.ui.coop.tournamentbutton as _unused11
def _update(self) -> None: def _update(self) -> None:
# Do nothing if we've somehow outlived our actual UI. # Do nothing if we've somehow outlived our actual UI.
@ -335,21 +335,21 @@ class CoopBrowserWindow(ba.Window):
# Decrement time on our tournament buttons. # Decrement time on our tournament buttons.
ads_enabled = _ba.have_incentivized_ad() ads_enabled = _ba.have_incentivized_ad()
for tbtn in self._tournament_buttons: for tbtn in self._tournament_buttons:
tbtn['time_remaining'] = max(0, tbtn['time_remaining'] - 1) tbtn.time_remaining = max(0, tbtn.time_remaining - 1)
if tbtn['time_remaining_value_text'] is not None: if tbtn.time_remaining_value_text is not None:
ba.textwidget( ba.textwidget(
edit=tbtn['time_remaining_value_text'], edit=tbtn.time_remaining_value_text,
text=ba.timestring(tbtn['time_remaining'], text=ba.timestring(tbtn.time_remaining,
centi=False, centi=False,
suppress_format_warning=True) if suppress_format_warning=True) if
(tbtn['has_time_remaining'] (tbtn.has_time_remaining
and self._tourney_data_up_to_date) else '-') and self._tourney_data_up_to_date) else '-')
# Also adjust the ad icon visibility. # Also adjust the ad icon visibility.
if tbtn.get('allow_ads', False) and _ba.has_video_ads(): if tbtn.allow_ads and _ba.has_video_ads():
ba.imagewidget(edit=tbtn['entry_fee_ad_image'], ba.imagewidget(edit=tbtn.entry_fee_ad_image,
opacity=1.0 if ads_enabled else 0.25) 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)) color=(0.6, 0.6, 0.6, 1 if ads_enabled else 0.2))
self._update_hard_mode_lock_image() self._update_hard_mode_lock_image()
@ -363,232 +363,21 @@ class CoopBrowserWindow(ba.Window):
ba.print_exception('Error updating campaign lock.') ba.print_exception('Error updating campaign lock.')
def _update_for_data(self, data: list[dict[str, Any]] | None) -> None: 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 # If the number of tournaments or challenges in the data differs from
# our current arrangement, refresh with the new number. # our current arrangement, refresh with the new number.
if ((data is None and self._tournament_button_count != 0) if ((data is None and self._tournament_button_count != 0)
or (data is not None and or (data is not None and
(len(data) != self._tournament_button_count))): (len(data) != self._tournament_button_count))):
self._tournament_button_count = len( self._tournament_button_count = (len(data)
data) if data is not None else 0 if data is not None else 0)
ba.app.config['Tournament Rows'] = self._tournament_button_count ba.app.config['Tournament Rows'] = self._tournament_button_count
self._refresh() self._refresh()
# Update all of our tourney buttons based on whats in data. # Update all of our tourney buttons based on whats in data.
for i, tbtn in enumerate(self._tournament_buttons): for i, tbtn in enumerate(self._tournament_buttons):
assert data is not None assert data is not None
entry: dict[str, Any] = data[i] tbtn.update_for_data(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))
def _on_tournament_query_response(self, def _on_tournament_query_response(self,
data: dict[str, Any] | None) -> None: data: dict[str, Any] | None) -> None:
@ -715,10 +504,13 @@ class CoopBrowserWindow(ba.Window):
items = [ items = [
campaignname + ':Onslaught Training', campaignname + ':Onslaught Training',
campaignname + ':Rookie Onslaught', campaignname + ':Rookie Onslaught',
campaignname + ':Rookie Football', campaignname + ':Pro Onslaught', campaignname + ':Rookie Football',
campaignname + ':Pro Football', campaignname + ':Pro Runaround', campaignname + ':Pro Onslaught',
campaignname + ':Uber Onslaught', campaignname + ':Uber Football', campaignname + ':Pro Football',
campaignname + ':Uber Runaround' campaignname + ':Pro Runaround',
campaignname + ':Uber Onslaught',
campaignname + ':Uber Football',
campaignname + ':Uber Runaround',
] ]
items += [campaignname + ':The Last Stand'] items += [campaignname + ':The Last Stand']
if self._selected_campaign_level is None: if self._selected_campaign_level is None:
@ -772,6 +564,7 @@ class CoopBrowserWindow(ba.Window):
# pylint: disable=too-many-locals # pylint: disable=too-many-locals
# pylint: disable=cyclic-import # pylint: disable=cyclic-import
from bastd.ui.coop.gamebutton import GameButton from bastd.ui.coop.gamebutton import GameButton
from bastd.ui.coop.tournamentbutton import TournamentButton
# (Re)create the sub-container if need be. # (Re)create the sub-container if need be.
if self._subcontainer is not None: if self._subcontainer is not None:
@ -839,7 +632,7 @@ class CoopBrowserWindow(ba.Window):
# Tournaments # Tournaments
self._tournament_buttons: list[dict[str, Any]] = [] self._tournament_buttons: list[TournamentButton] = []
v -= 53 v -= 53
# FIXME shouldn't use hard-coded strings here. # FIXME shouldn't use hard-coded strings here.
@ -919,7 +712,12 @@ class CoopBrowserWindow(ba.Window):
v2 = -2 v2 = -2
is_last_sel = True is_last_sel = True
self._tournament_buttons.append( 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 v -= 200
# Custom Games. # Custom Games.
@ -949,7 +747,8 @@ class CoopBrowserWindow(ba.Window):
if _ba.get_v1_account_misc_read_val( if _ba.get_v1_account_misc_read_val(
'easter', False) or _ba.get_purchased('games.easter_egg_hunt'): 'easter', False) or _ba.get_purchased('games.easter_egg_hunt'):
items = [ items = [
'Challenges:Easter Egg Hunt', 'Challenges:Pro Easter Egg Hunt' 'Challenges:Easter Egg Hunt',
'Challenges:Pro Easter Egg Hunt',
] + items ] + items
# add all custom user levels here.. # add all custom user levels here..
@ -995,19 +794,19 @@ class CoopBrowserWindow(ba.Window):
for i, tbutton in enumerate(self._tournament_buttons): for i, tbutton in enumerate(self._tournament_buttons):
ba.widget( ba.widget(
edit=tbutton['button'], edit=tbutton.button,
up_widget=self._tournament_info_button up_widget=self._tournament_info_button
if i == 0 else self._tournament_buttons[i - 1]['button'], if i == 0 else self._tournament_buttons[i - 1].button,
down_widget=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) if i + 1 < len(self._tournament_buttons) else custom_h_scroll)
ba.widget( ba.widget(
edit=tbutton['more_scores_button'], edit=tbutton.more_scores_button,
down_widget=self._tournament_buttons[( 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) 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 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: for btn in self._custom_buttons:
try: try:
@ -1037,314 +836,6 @@ class CoopBrowserWindow(ba.Window):
def _enable_selectable_callback(self) -> None: def _enable_selectable_callback(self) -> None:
self._do_selection_callbacks = True 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: def _switch_to_league_rankings(self) -> None:
# pylint: disable=cyclic-import # pylint: disable=cyclic-import
from bastd.ui.account import show_sign_in_prompt from bastd.ui.account import show_sign_in_prompt
@ -1378,100 +869,20 @@ class CoopBrowserWindow(ba.Window):
show_tab=show_tab, show_tab=show_tab,
back_location='CoopBrowserWindow').get_root_widget()) 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: def is_tourney_data_up_to_date(self) -> bool:
"""Return whether our tourney data is up to date.""" """Return whether our tourney data is up to date."""
return self._tourney_data_up_to_date return self._tourney_data_up_to_date
def run(self, def run_game(self, game: str) -> None:
game: str | None,
tournament_button: dict[str, Any] | None = None) -> None:
"""Run the provided game.""" """Run the provided game."""
# pylint: disable=too-many-branches # pylint: disable=too-many-branches
# pylint: disable=too-many-statements
# pylint: disable=too-many-return-statements
# pylint: disable=cyclic-import # pylint: disable=cyclic-import
from bastd.ui.confirm import ConfirmWindow from bastd.ui.confirm import ConfirmWindow
from bastd.ui.tournamententry import TournamentEntryWindow
from bastd.ui.purchase import PurchaseWindow from bastd.ui.purchase import PurchaseWindow
from bastd.ui.account import show_sign_in_prompt from bastd.ui.account import show_sign_in_prompt
args: dict[str, Any] = {} args: dict[str, Any] = {}
# Do a bit of pre-flight for tournament options. if game == 'Easy:The Last Stand':
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':
ConfirmWindow(ba.Lstr(resource='difficultyHardUnlockOnlyText', ConfirmWindow(ba.Lstr(resource='difficultyHardUnlockOnlyText',
fallback_resource='difficultyHardOnlyText'), fallback_resource='difficultyHardOnlyText'),
cancel_button=False, cancel_button=False,
@ -1479,12 +890,11 @@ class CoopBrowserWindow(ba.Window):
height=130) height=130)
return return
# Infinite onslaught/runaround require pro; bring up a store link if # Infinite onslaught/runaround require pro; bring up a store link
# need be. # if need be.
if tournament_button is None and game in ( if game in ('Challenges:Infinite Runaround',
'Challenges:Infinite Runaround', 'Challenges:Infinite Onslaught'
'Challenges:Infinite Onslaught' ) and not ba.app.accounts_v1.have_pro():
) and not ba.app.accounts_v1.have_pro():
if _ba.get_v1_account_state() != 'signed_in': if _ba.get_v1_account_state() != 'signed_in':
show_sign_in_prompt() show_sign_in_prompt()
else: else:
@ -1495,7 +905,8 @@ class CoopBrowserWindow(ba.Window):
if game in ['Challenges:Meteor Shower']: if game in ['Challenges:Meteor Shower']:
required_purchase = 'games.meteor_shower' required_purchase = 'games.meteor_shower'
elif game in [ elif game in [
'Challenges:Target Practice', 'Challenges:Target Practice B' 'Challenges:Target Practice',
'Challenges:Target Practice B',
]: ]:
required_purchase = 'games.target_practice' required_purchase = 'games.target_practice'
elif game in ['Challenges:Ninja Fight']: elif game in ['Challenges:Ninja Fight']:
@ -1503,13 +914,14 @@ class CoopBrowserWindow(ba.Window):
elif game in ['Challenges:Pro Ninja Fight']: elif game in ['Challenges:Pro Ninja Fight']:
required_purchase = 'games.ninja_fight' required_purchase = 'games.ninja_fight'
elif game in [ 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' required_purchase = 'games.easter_egg_hunt'
else: else:
required_purchase = None 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)): and not _ba.get_purchased(required_purchase)):
if _ba.get_v1_account_state() != 'signed_in': if _ba.get_v1_account_state() != 'signed_in':
show_sign_in_prompt() show_sign_in_prompt()
@ -1519,17 +931,57 @@ class CoopBrowserWindow(ba.Window):
self._save_state() self._save_state()
# For tournaments, we pop up the entry window. if ba.app.launch_coop_game(game, args=args):
if tournament_button is not None: ba.containerwidget(edit=self._root_widget, transition='out_left')
TournamentEntryWindow(
tournament_id=tournament_button['tournament_id'], def run_tournament(self, tournament_button: TournamentButton) -> None:
position=tournament_button['button'].get_screen_space_center()) """Run the provided tournament game."""
else: from bastd.ui.account import show_sign_in_prompt
# Otherwise just dive right in. from bastd.ui.tournamententry import TournamentEntryWindow
assert game is not None
if ba.app.launch_coop_game(game, args=args): if _ba.get_v1_account_state() != 'signed_in':
ba.containerwidget(edit=self._root_widget, show_sign_in_prompt()
transition='out_left') 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: def _back(self) -> None:
# pylint: disable=cyclic-import # pylint: disable=cyclic-import
@ -1542,24 +994,6 @@ class CoopBrowserWindow(ba.Window):
ba.app.ui.set_main_menu_window( ba.app.ui.set_main_menu_window(
PlayWindow(transition='in_left').get_root_widget()) 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: def _save_state(self) -> None:
cfg = ba.app.config cfg = ba.app.config
try: try:
@ -1580,16 +1014,31 @@ class CoopBrowserWindow(ba.Window):
cfg['Selected Coop Row'] = self._selected_row cfg['Selected Coop Row'] = self._selected_row
cfg['Selected Coop Custom Level'] = self._selected_custom_level 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['Selected Coop Campaign Level'] = self._selected_campaign_level
cfg.commit() 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: def sel_change(self, row: str, game: str) -> None:
"""(internal)""" """(internal)"""
if self._do_selection_callbacks: if self._do_selection_callbacks:
if row == 'custom': if row == 'custom':
self._selected_custom_level = game self._selected_custom_level = game
if row == 'challenges':
self._selected_challenge_level = game
elif row == 'campaign': elif row == 'campaign':
self._selected_campaign_level = game self._selected_campaign_level = game

View File

@ -55,7 +55,7 @@ class GameButton:
position=(x + 23, y + 4), position=(x + 23, y + 4),
size=(sclx, scly), size=(sclx, scly),
label='', label='',
on_activate_call=ba.Call(window.run, game), on_activate_call=ba.Call(window.run_game, game),
button_type='square', button_type='square',
autoselect=True, autoselect=True,
on_select_call=ba.Call(window.sel_change, row, game)) 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 # Show change instructions only if we have more than one
# keyboard option. # keyboard option.
if (ba.app.meta.metascan is not None if (ba.app.meta.scanresults is not None
and len(ba.app.meta.metascan.keyboards) > 1): and len(ba.app.meta.scanresults.keyboards) > 1):
ba.textwidget( ba.textwidget(
parent=self._root_widget, parent=self._root_widget,
h_align='center', h_align='center',
@ -238,8 +238,8 @@ class OnScreenKeyboardWindow(ba.Window):
self._refresh() self._refresh()
def _get_keyboard(self) -> ba.Keyboard: def _get_keyboard(self) -> ba.Keyboard:
assert ba.app.meta.metascan is not None assert ba.app.meta.scanresults is not None
classname = ba.app.meta.metascan.keyboards[self._keyboard_index] classname = ba.app.meta.scanresults.keyboards[self._keyboard_index]
kbclass = ba.getclass(classname, ba.Keyboard) kbclass = ba.getclass(classname, ba.Keyboard)
return kbclass() return kbclass()
@ -317,11 +317,11 @@ class OnScreenKeyboardWindow(ba.Window):
self._refresh() self._refresh()
def _next_keyboard(self) -> None: 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( self._keyboard_index = (self._keyboard_index + 1) % len(
ba.app.meta.metascan.keyboards) ba.app.meta.scanresults.keyboards)
self._load_keyboard() 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.playsound(ba.getsound('error'))
ba.screenmessage(ba.Lstr(resource='keyboardNoOthersAvailableText'), ba.screenmessage(ba.Lstr(resource='keyboardNoOthersAvailableText'),
color=(1, 0, 0)) color=(1, 0, 0))

View File

@ -93,7 +93,7 @@ class PluginSettingsWindow(ba.Window):
self._subcontainer = ba.columnwidget(parent=self._scrollwidget, self._subcontainer = ba.columnwidget(parent=self._scrollwidget,
selection_loops_to_parent=True) 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.', ba.screenmessage('Still scanning plugins; please try again.',
color=(1, 0, 0)) color=(1, 0, 0))
ba.playsound(ba.getsound('error')) ba.playsound(ba.getsound('error'))

View File

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

View File

@ -21,7 +21,7 @@
namespace ballistica { namespace ballistica {
// These are set automatically via script; don't modify them here. // 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"; const char* kAppVersion = "1.7.4";
// Our standalone globals. // Our standalone globals.

View File

@ -1,4 +1,3 @@
#!/usr/bin/env python3.10
# Released under the MIT License. See LICENSE for details. # Released under the MIT License. See LICENSE for details.
# #
"""Updates assets/Makefile based on source assets present.""" """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. # Released under the MIT License. See LICENSE for details.
# #
"""Stage assets for a build.""" """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. # Released under the MIT License. See LICENSE for details.
# #
"""General project related functionality.""" """General project related functionality."""