Source code for bauiv1lib.coop.tournamentbutton
# 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 bauiv1 as bui
if TYPE_CHECKING:
from typing import Any, Callable
# As of 1.7.37, no longer charging entry fees for tourneys (tourneys now
# reward chests and the game now makes its money from tokens/ads used to
# speed up chest openings).
USE_ENTRY_FEES = False
[docs]
class TournamentButton:
"""Button showing a tournament in coop window."""
def __init__(
self,
parent: bui.Widget,
x: float,
y: float,
select: bool,
on_pressed: Callable[[TournamentButton], None],
) -> None:
# pylint: disable=too-many-positional-arguments
# pylint: disable=too-many-statements
self._r = 'coopSelectWindow'
sclx = 300
scly = 195.0
self.on_pressed = on_pressed
self.lsbt = bui.getmesh('level_select_button_transparent')
self.lsbo = bui.getmesh('level_select_button_opaque')
self.allow_ads = False
self.tournament_id: str | None = None
self.game: 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._base_x_offs = 0 if USE_ENTRY_FEES else -45.0
self.button = btn = bui.buttonwidget(
parent=parent,
position=(x + 23, y + 4),
size=(sclx, scly),
label='',
button_type='square',
autoselect=True,
on_activate_call=bui.WeakCall(self._pressed),
)
bui.widget(
edit=btn,
show_buffer_bottom=50,
show_buffer_top=50,
show_buffer_left=400,
show_buffer_right=200,
)
if select:
bui.containerwidget(
edit=parent, selected_child=btn, visible_child=btn
)
image_width = sclx * 0.85 * 0.75
self.image = bui.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),
mesh_transparent=self.lsbt,
mesh_opaque=self.lsbo,
texture=bui.gettexture('black'),
opacity=0.2,
mask_texture=bui.gettexture('mapPreviewMask'),
)
self.lock_image = bui.imagewidget(
parent=parent,
draw_controller=btn,
position=(x + 21 + sclx * 0.5 - image_width * 0.15, y + scly - 130),
size=(image_width * 0.3, image_width * 0.3),
texture=bui.gettexture('lock'),
opacity=0.0,
)
self.button_text = bui.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 = self._base_x_offs
# No longer using entry fees.
if USE_ENTRY_FEES:
bui.textwidget(
parent=parent,
draw_controller=btn,
position=(x + 360, y + scly - 20),
size=(0, 0),
h_align='center',
text=bui.Lstr(resource=f'{self._r}.entryFeeText'),
v_align='center',
maxwidth=100,
scale=0.9,
color=header_color,
flatness=1.0,
)
self.entry_fee_text_top = bui.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 = bui.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 = bui.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 = bui.imagewidget(
parent=parent,
size=(40, 40),
draw_controller=btn,
position=(x + 360 - 20, y + scly - 140),
opacity=0.0,
texture=bui.gettexture('tv'),
)
x_offs += 50
bui.textwidget(
parent=parent,
draw_controller=btn,
position=(x + 447 + x_offs, y + scly - 20),
size=(0, 0),
h_align='center',
text=bui.Lstr(resource=f'{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
# Offset for prize range/values.
xo2 = 0.0
self.prize_range_1_text = bui.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 = bui.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,
color=value_color,
flatness=1.0,
)
self._chestsz = 50
self.prize_chest_1_image = bui.imagewidget(
parent=parent,
draw_controller=btn,
texture=bui.gettexture('white'),
position=(x + 380 + xo2 + x_offs, y + scly - 93),
size=(self._chestsz, self._chestsz),
opacity=0.0,
)
self.prize_range_2_text = bui.textwidget(
parent=parent,
draw_controller=btn,
position=(x + 355 + xo2 + x_offs, y + scly - 93),
size=(0, 0),
h_align='right',
text='',
v_align='center',
maxwidth=50,
scale=0.8,
color=header_color,
flatness=1.0,
)
self.prize_value_2_text = bui.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,
color=value_color,
flatness=1.0,
)
self.prize_chest_2_image = bui.imagewidget(
parent=parent,
draw_controller=btn,
texture=bui.gettexture('white'),
position=(x + 380 + xo2 + x_offs, y + scly - 93),
size=(self._chestsz, self._chestsz),
opacity=0.0,
)
self.prize_range_3_text = bui.textwidget(
parent=parent,
draw_controller=btn,
position=(x + 355 + xo2 + x_offs, y + scly - 93),
size=(0, 0),
h_align='right',
text='',
v_align='center',
maxwidth=50,
scale=0.8,
color=header_color,
flatness=1.0,
)
self.prize_value_3_text = bui.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,
color=value_color,
flatness=1.0,
)
self.prize_chest_3_image = bui.imagewidget(
parent=parent,
draw_controller=btn,
texture=bui.gettexture('white'),
position=(x + 380 + xo2 + x_offs, y + scly - 93),
size=(self._chestsz, self._chestsz),
opacity=0.0,
)
bui.textwidget(
parent=parent,
draw_controller=btn,
position=(x + 625 + x_offs, y + scly - 20),
size=(0, 0),
h_align='center',
text=bui.Lstr(resource=f'{self._r}.currentBestText'),
v_align='center',
maxwidth=180,
scale=0.9,
color=header_color,
flatness=1.0,
)
self.current_leader_name_text = bui.textwidget(
parent=parent,
draw_controller=btn,
position=(
x + 625 + x_offs - (170 / 1.4) * 0.5,
y + scly - 60 - 40 * 0.5,
),
selectable=True,
click_activate=True,
autoselect=True,
on_activate_call=bui.WeakCall(self._show_leader),
size=(170 / 1.4, 40),
h_align='center',
text='-',
v_align='center',
maxwidth=170,
glow_type='uniform',
scale=1.4,
color=value_color,
flatness=1.0,
)
self.current_leader_score_text = bui.textwidget(
parent=parent,
draw_controller=btn,
position=(x + 625 + 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 = bui.buttonwidget(
parent=parent,
position=(x + 625 + 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=bui.WeakCall(self._show_scores),
)
bui.widget(
edit=self.current_leader_name_text,
down_widget=self.more_scores_button,
)
bui.textwidget(
parent=parent,
draw_controller=btn,
position=(x + 840 + x_offs, y + scly - 20),
size=(0, 0),
h_align='center',
text=bui.Lstr(resource=f'{self._r}.timeRemainingText'),
v_align='center',
maxwidth=180,
scale=0.9,
color=header_color,
flatness=1.0,
)
self.time_remaining_value_text = bui.textwidget(
parent=parent,
draw_controller=btn,
position=(x + 840 + 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 = bui.textwidget(
parent=parent,
draw_controller=btn,
position=(x + 840 + 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,
)
self._lock_update_timer = bui.AppTimer(
1.03, bui.WeakCall(self._update_lock_state), repeat=True
)
def _pressed(self) -> None:
self.on_pressed(self)
def _show_leader(self) -> None:
# pylint: disable=cyclic-import
from bauiv1lib.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
):
bui.getsound('error').play()
return
bui.getsound('swish').play()
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 bauiv1lib.tournamentscores import TournamentScoresWindow
tournament_id = self.tournament_id
if tournament_id is None:
bui.getsound('error').play()
return
TournamentScoresWindow(
tournament_id=tournament_id,
position=self.more_scores_button.get_screen_space_center(),
)
def _update_lock_state(self) -> None:
if self.game is None:
return
assert bui.app.classic is not None
campaignname, levelname = self.game.split(':')
campaign = bui.app.classic.getcampaign(campaignname)
enabled = (
self.required_league is None
and bui.app.classic.is_game_unlocked(self.game)
)
bui.buttonwidget(
edit=self.button,
color=(0.5, 0.7, 0.2) if enabled else (0.5, 0.5, 0.5),
)
bui.imagewidget(edit=self.lock_image, opacity=0.0 if enabled else 1.0)
bui.imagewidget(
edit=self.image,
texture=bui.gettexture(
campaign.getlevel(levelname).preview_texture_name
),
opacity=1.0 if enabled else 0.5,
)
[docs]
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
plus = bui.app.plus
assert plus is not None
classic = bui.app.classic
assert classic is not None
prize_y_offs = (
34
if 'prizeRange3' in entry
else 20 if 'prizeRange2' in entry else 12
)
x_offs = self._base_x_offs + 90
# Special offset for prize ranges/vals.
x_offs2 = x_offs - 20.0
# Special offset for prize chests.
x_offs2c = x_offs2 + 50
# Fetch prize range and trophy strings.
(pr1, pv1, pr2, pv2, pr3, pv3) = classic.get_tournament_prize_strings(
entry, include_tickets=False
)
self.time_remaining = entry['timeRemaining']
self.has_time_remaining = entry is not None
self.tournament_id = entry['tournamentID']
self.required_league = entry.get('requiredLeague')
assert bui.app.classic is not None
self.game = bui.app.classic.accounts.tournament_info[
self.tournament_id
]['game']
assert isinstance(self.game, str)
campaignname, levelname = self.game.split(':')
campaign = bui.app.classic.getcampaign(campaignname)
self._update_lock_state()
bui.textwidget(
edit=self.prize_range_1_text,
text='-' if pr1 == '' else pr1,
position=(
self.button_x + 365 + x_offs2,
self.button_y + self.button_scale_y - 93 + prize_y_offs,
),
)
bui.textwidget(
edit=self.prize_value_1_text,
text='-' if pv1 == '' else pv1,
position=(
self.button_x + 380 + x_offs2,
self.button_y + self.button_scale_y - 93 + prize_y_offs,
),
)
bui.imagewidget(
edit=self.prize_chest_1_image,
position=(
self.button_x + 380 + x_offs2c,
self.button_y
+ self.button_scale_y
- 93
+ prize_y_offs
- 0.5 * self._chestsz,
),
)
classic.set_tournament_prize_image(entry, 0, self.prize_chest_1_image)
bui.textwidget(
edit=self.prize_range_2_text,
text=pr2,
position=(
self.button_x + 365 + x_offs2,
self.button_y + self.button_scale_y - 93 - 45 + prize_y_offs,
),
)
bui.textwidget(
edit=self.prize_value_2_text,
text=pv2,
position=(
self.button_x + 380 + x_offs2,
self.button_y + self.button_scale_y - 93 - 45 + prize_y_offs,
),
)
bui.imagewidget(
edit=self.prize_chest_2_image,
position=(
self.button_x + 380 + x_offs2c,
self.button_y
+ self.button_scale_y
- 93
- 45
+ prize_y_offs
- 0.5 * self._chestsz,
),
)
classic.set_tournament_prize_image(entry, 1, self.prize_chest_2_image)
bui.textwidget(
edit=self.prize_range_3_text,
text=pr3,
position=(
self.button_x + 365 + x_offs2,
self.button_y + self.button_scale_y - 93 - 90 + prize_y_offs,
),
)
bui.textwidget(
edit=self.prize_value_3_text,
text=pv3,
position=(
self.button_x + 380 + x_offs2,
self.button_y + self.button_scale_y - 93 - 90 + prize_y_offs,
),
)
bui.imagewidget(
edit=self.prize_chest_3_image,
position=(
self.button_x + 380 + x_offs2c,
self.button_y
+ self.button_scale_y
- 93
- 90
+ prize_y_offs
- 0.5 * self._chestsz,
),
)
classic.set_tournament_prize_image(entry, 2, self.prize_chest_3_image)
leader_name = '-'
leader_score: str | bui.Lstr = '-'
if entry['scores']:
score = self.leader = copy.deepcopy(entry['scores'][0])
leader_name = score[1]
leader_score = (
bui.timestring((score[0] * 10) / 1000.0, centi=True)
if entry['scoreType'] == 'time'
else str(score[0])
)
else:
self.leader = None
bui.textwidget(
edit=self.current_leader_name_text, text=bui.Lstr(value=leader_name)
)
bui.textwidget(edit=self.current_leader_score_text, text=leader_score)
bui.buttonwidget(
edit=self.more_scores_button,
label=bui.Lstr(resource=f'{self._r}.seeMoreText'),
)
out_of_time_text: str | bui.Lstr = (
'-'
if 'totalTime' not in entry
else bui.Lstr(
resource=f'{self._r}.ofTotalTimeText',
subs=[
(
'${TOTAL}',
bui.timestring(entry['totalTime'], centi=False),
)
],
)
)
bui.textwidget(
edit=self.time_remaining_out_of_text, text=out_of_time_text
)
# if self.game is None:
# bui.textwidget(edit=self.button_text, text='-')
# bui.imagewidget(
# edit=self.image, texture=bui.gettexture('black'), opacity=0.2
# )
# else:
max_players = bui.app.classic.accounts.tournament_info[
self.tournament_id
]['maxPlayers']
txt = bui.Lstr(
value='${A} ${B}',
subs=[
('${A}', campaign.getlevel(levelname).displayname),
(
'${B}',
bui.Lstr(
resource='playerCountAbbreviatedText',
subs=[('${COUNT}', str(max_players))],
),
),
],
)
bui.textwidget(edit=self.button_text, text=txt)
fee = entry['fee']
assert isinstance(fee, int | None)
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'
elif fee == -1:
fee_var = None
else:
if fee != 0:
print('Unknown fee value:', fee)
fee_var = 'price.tournament_entry_0'
self.allow_ads = allow_ads = (
entry['allowAds'] if USE_ENTRY_FEES else False
)
final_fee = (
None
if fee_var is None
else plus.get_v1_account_misc_read_val(fee_var, '?')
)
assert isinstance(final_fee, int | None)
final_fee_str: str | bui.Lstr
if fee_var is None:
final_fee_str = ''
else:
if final_fee == 0:
final_fee_str = bui.Lstr(resource='getTicketsWindow.freeText')
else:
final_fee_str = bui.charstr(
bui.SpecialChar.TICKET_BACKING
) + str(final_fee)
assert bui.app.classic is not None
ad_tries_remaining = bui.app.classic.accounts.tournament_info[
self.tournament_id
]['adTriesRemaining']
assert isinstance(ad_tries_remaining, int | None)
free_tries_remaining = bui.app.classic.accounts.tournament_info[
self.tournament_id
]['freeTriesRemaining']
assert isinstance(free_tries_remaining, int | None)
# Now, if this fee allows ads and we support video ads, show the
# 'or ad' version.
if USE_ENTRY_FEES:
if allow_ads and plus.has_video_ads():
ads_enabled = plus.have_incentivized_ad()
bui.imagewidget(
edit=self.entry_fee_ad_image,
opacity=1.0 if ads_enabled else 0.25,
)
or_text = (
bui.Lstr(
resource='orText', subs=[('${A}', ''), ('${B}', '')]
)
.evaluate()
.strip()
)
bui.textwidget(edit=self.entry_fee_text_or, text=or_text)
bui.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.
bui.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:
bui.imagewidget(edit=self.entry_fee_ad_image, opacity=0.0)
bui.textwidget(edit=self.entry_fee_text_or, text='')
bui.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.
bui.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),
)
# Docs-generation hack; import some stuff that we likely only forward-declared
# in our actual source code so that docs tools can find it.
from typing import (Coroutine, Any, Literal, Callable,
Generator, Awaitable, Sequence, Self)
import asyncio
from concurrent.futures import Future
from pathlib import Path
from enum import Enum