mirror of
https://github.com/imayushsaini/Bombsquad-Ballistica-Modded-Server.git
synced 2026-08-15 13:04:30 +00:00
remving all files
This commit is contained in:
parent
e02f1dbe52
commit
7672d57823
20 changed files with 0 additions and 6151 deletions
227
dist/ba_data/python/baclassic/_ads.py
vendored
227
dist/ba_data/python/baclassic/_ads.py
vendored
|
|
@ -1,227 +0,0 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Functionality related to ads."""
|
||||
from __future__ import annotations
|
||||
|
||||
import time
|
||||
import asyncio
|
||||
import logging
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import babase
|
||||
import bascenev1
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Callable, Any
|
||||
|
||||
|
||||
class AdsSubsystem:
|
||||
"""Subsystem for ads functionality in the app.
|
||||
|
||||
Access the single shared instance of this class at 'ba.app.ads'.
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.last_ad_network = 'unknown'
|
||||
self.last_ad_network_set_time = time.time()
|
||||
self.ad_amt: float | None = None
|
||||
self.last_ad_purpose = 'invalid'
|
||||
self.attempted_first_ad = False
|
||||
self.last_in_game_ad_remove_message_show_time: float | None = None
|
||||
self.last_ad_completion_time: float | None = None
|
||||
self.last_ad_was_short = False
|
||||
self._fallback_task: asyncio.Task | None = None
|
||||
|
||||
def do_remove_in_game_ads_message(self) -> None:
|
||||
"""(internal)"""
|
||||
|
||||
# Print this message once every 10 minutes at most.
|
||||
tval = babase.apptime()
|
||||
if self.last_in_game_ad_remove_message_show_time is None or (
|
||||
tval - self.last_in_game_ad_remove_message_show_time > 60 * 10
|
||||
):
|
||||
self.last_in_game_ad_remove_message_show_time = tval
|
||||
with babase.ContextRef.empty():
|
||||
babase.apptimer(
|
||||
1.0,
|
||||
lambda: babase.screenmessage(
|
||||
babase.Lstr(
|
||||
resource='removeInGameAdsTokenPurchaseText'
|
||||
),
|
||||
color=(1, 1, 0),
|
||||
),
|
||||
)
|
||||
|
||||
def show_ad(
|
||||
self, purpose: str, on_completion_call: Callable[[], Any] | None = None
|
||||
) -> None:
|
||||
"""(internal)"""
|
||||
self.last_ad_purpose = purpose
|
||||
assert babase.app.plus is not None
|
||||
babase.app.plus.show_ad(purpose, on_completion_call)
|
||||
|
||||
def show_ad_2(
|
||||
self,
|
||||
purpose: str,
|
||||
on_completion_call: Callable[[bool], Any] | None = None,
|
||||
) -> None:
|
||||
"""(internal)"""
|
||||
self.last_ad_purpose = purpose
|
||||
assert babase.app.plus is not None
|
||||
babase.app.plus.show_ad_2(purpose, on_completion_call)
|
||||
|
||||
def call_after_ad(self, call: Callable[[], Any]) -> None:
|
||||
"""Run a call after potentially showing an ad."""
|
||||
# pylint: disable=too-many-statements
|
||||
# pylint: disable=too-many-branches
|
||||
# pylint: disable=too-many-locals
|
||||
|
||||
app = babase.app
|
||||
plus = app.plus
|
||||
classic = app.classic
|
||||
assert plus is not None
|
||||
assert classic is not None
|
||||
show = True
|
||||
|
||||
# No ads without net-connections, etc.
|
||||
if not plus.can_show_ad():
|
||||
show = False
|
||||
|
||||
# Pro or other upgrades disable interstitials.
|
||||
if (
|
||||
classic.accounts.have_pro()
|
||||
or classic.gold_pass
|
||||
or classic.remove_ads
|
||||
):
|
||||
show = False
|
||||
try:
|
||||
session = bascenev1.get_foreground_host_session()
|
||||
assert session is not None
|
||||
is_tournament = session.tournament_id is not None
|
||||
except Exception:
|
||||
is_tournament = False
|
||||
if is_tournament:
|
||||
show = False # Never show ads during tournaments.
|
||||
|
||||
if show:
|
||||
interval: float | None
|
||||
launch_count = app.config.get('launchCount', 0)
|
||||
|
||||
# If we're seeing short ads we may want to space them differently.
|
||||
interval_mult = (
|
||||
plus.get_v1_account_misc_read_val('ads.shortIntervalMult', 1.0)
|
||||
if self.last_ad_was_short
|
||||
else 1.0
|
||||
)
|
||||
if self.ad_amt is None:
|
||||
if launch_count <= 1:
|
||||
self.ad_amt = plus.get_v1_account_misc_read_val(
|
||||
'ads.startVal1', 0.99
|
||||
)
|
||||
else:
|
||||
self.ad_amt = plus.get_v1_account_misc_read_val(
|
||||
'ads.startVal2', 1.0
|
||||
)
|
||||
interval = None
|
||||
else:
|
||||
# So far we're cleared to show; now calc our
|
||||
# ad-show-threshold and see if we should *actually* show
|
||||
# (we reach our threshold faster the longer we've been
|
||||
# playing).
|
||||
base = 'ads' if plus.has_video_ads() else 'ads2'
|
||||
min_lc = plus.get_v1_account_misc_read_val(base + '.minLC', 0.0)
|
||||
max_lc = plus.get_v1_account_misc_read_val(base + '.maxLC', 5.0)
|
||||
min_lc_scale = plus.get_v1_account_misc_read_val(
|
||||
base + '.minLCScale', 0.25
|
||||
)
|
||||
max_lc_scale = plus.get_v1_account_misc_read_val(
|
||||
base + '.maxLCScale', 0.34
|
||||
)
|
||||
min_lc_interval = plus.get_v1_account_misc_read_val(
|
||||
base + '.minLCInterval', 360
|
||||
)
|
||||
max_lc_interval = plus.get_v1_account_misc_read_val(
|
||||
base + '.maxLCInterval', 300
|
||||
)
|
||||
if launch_count < min_lc:
|
||||
lc_amt = 0.0
|
||||
elif launch_count > max_lc:
|
||||
lc_amt = 1.0
|
||||
else:
|
||||
lc_amt = (float(launch_count) - min_lc) / (max_lc - min_lc)
|
||||
incr = (1.0 - lc_amt) * min_lc_scale + lc_amt * max_lc_scale
|
||||
interval = (
|
||||
1.0 - lc_amt
|
||||
) * min_lc_interval + lc_amt * max_lc_interval
|
||||
self.ad_amt += incr
|
||||
assert self.ad_amt is not None
|
||||
if self.ad_amt >= 1.0:
|
||||
self.ad_amt = self.ad_amt % 1.0
|
||||
self.attempted_first_ad = True
|
||||
|
||||
# After we've reached the traditional show-threshold once,
|
||||
# try again whenever its been INTERVAL since our last successful
|
||||
# show.
|
||||
elif self.attempted_first_ad and (
|
||||
self.last_ad_completion_time is None
|
||||
or (
|
||||
interval is not None
|
||||
and babase.apptime() - self.last_ad_completion_time
|
||||
> (interval * interval_mult)
|
||||
)
|
||||
):
|
||||
# Reset our other counter too in this case.
|
||||
self.ad_amt = 0.0
|
||||
else:
|
||||
show = False
|
||||
|
||||
# If we're *still* cleared to show, actually tell the system to show.
|
||||
if show:
|
||||
# As a safety-check, we set up an object that will run the
|
||||
# completion callback if we've returned and sat for several
|
||||
# seconds (in case some random ad network doesn't properly
|
||||
# deliver its completion callback).
|
||||
class _Payload:
|
||||
def __init__(self, pcall: Callable[[], Any]):
|
||||
self._call = pcall
|
||||
self._ran = False
|
||||
|
||||
def run(self, fallback: bool = False) -> None:
|
||||
"""Run the payload."""
|
||||
assert app.classic is not None
|
||||
if not self._ran:
|
||||
if fallback:
|
||||
lanst = app.classic.ads.last_ad_network_set_time
|
||||
logging.error(
|
||||
'Relying on fallback ad-callback! '
|
||||
'last network: %s (set %s seconds ago);'
|
||||
' purpose=%s.',
|
||||
app.classic.ads.last_ad_network,
|
||||
time.time() - lanst,
|
||||
app.classic.ads.last_ad_purpose,
|
||||
)
|
||||
babase.pushcall(self._call)
|
||||
self._ran = True
|
||||
|
||||
payload = _Payload(call)
|
||||
|
||||
# Set up our backup.
|
||||
with babase.ContextRef.empty():
|
||||
# Note to self: Previously this was a simple 5 second
|
||||
# timer because the app got totally suspended while ads
|
||||
# were showing (which delayed the timer), but these days
|
||||
# the app may continue to run, so we need to be more
|
||||
# careful and only fire the fallback after we see that
|
||||
# the app has been front-and-center for several seconds.
|
||||
async def add_fallback_task() -> None:
|
||||
activesecs = 5
|
||||
while activesecs > 0:
|
||||
if babase.app.active:
|
||||
activesecs -= 1
|
||||
await asyncio.sleep(1.0)
|
||||
payload.run(fallback=True)
|
||||
|
||||
babase.app.create_async_task(add_fallback_task())
|
||||
self.show_ad('between_game', on_completion_call=payload.run)
|
||||
else:
|
||||
babase.pushcall(call) # Just run the callback without the ad.
|
||||
1062
dist/ba_data/python/bacommon/bs.py
vendored
1062
dist/ba_data/python/bacommon/bs.py
vendored
File diff suppressed because it is too large
Load diff
139
dist/ba_data/python/bacommon/bs/__init__.py
vendored
139
dist/ba_data/python/bacommon/bs/__init__.py
vendored
|
|
@ -1,139 +0,0 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Functionality related to bombsquad classic."""
|
||||
|
||||
from bacommon.bs._account import (
|
||||
ClassicAccountLiveData,
|
||||
)
|
||||
from bacommon.bs._bs import (
|
||||
TOKENS1_COUNT,
|
||||
TOKENS2_COUNT,
|
||||
TOKENS3_COUNT,
|
||||
TOKENS4_COUNT,
|
||||
)
|
||||
from bacommon.bs._chest import (
|
||||
ClassicChestAppearance,
|
||||
)
|
||||
from bacommon.bs._clienteffect import (
|
||||
ClientEffect,
|
||||
ClientEffectChestWaitTimeAnimation,
|
||||
ClientEffectDelay,
|
||||
ClientEffectScreenMessage,
|
||||
ClientEffectSound,
|
||||
ClientEffectTicketsAnimation,
|
||||
ClientEffectTokensAnimation,
|
||||
ClientEffectTypeID,
|
||||
ClientEffectUnknown,
|
||||
)
|
||||
from bacommon.bs._clouddialog import (
|
||||
BasicCloudDialog,
|
||||
BasicCloudDialogComponent,
|
||||
BasicCloudDialogBsClassicTourneyResult,
|
||||
BasicCloudDialogComponentLink,
|
||||
BasicCloudDialogComponentText,
|
||||
BasicCloudDialogComponentTypeID,
|
||||
BasicCloudDialogComponentUnknown,
|
||||
BasicCloudDialogDisplayItems,
|
||||
BasicCloudDialogExpireTime,
|
||||
CloudDialog,
|
||||
CloudDialogAction,
|
||||
CloudDialogTypeID,
|
||||
CloudDialogWrapper,
|
||||
UnknownCloudDialog,
|
||||
)
|
||||
from bacommon.bs._cloudui import CloudUITypeID, CloudUI
|
||||
from bacommon.bs._displayitem import (
|
||||
ChestDisplayItem,
|
||||
DisplayItem,
|
||||
DisplayItemTypeID,
|
||||
DisplayItemWrapper,
|
||||
TestDisplayItem,
|
||||
TicketsDisplayItem,
|
||||
TokensDisplayItem,
|
||||
UnknownDisplayItem,
|
||||
)
|
||||
from bacommon.bs._msg import (
|
||||
ChestActionMessage,
|
||||
ChestActionResponse,
|
||||
ChestInfoMessage,
|
||||
ChestInfoResponse,
|
||||
CloudDialogActionMessage,
|
||||
CloudDialogActionResponse,
|
||||
GetClassicPurchasesMessage,
|
||||
GetClassicPurchasesResponse,
|
||||
GlobalProfileCheckMessage,
|
||||
GlobalProfileCheckResponse,
|
||||
InboxRequestMessage,
|
||||
InboxRequestResponse,
|
||||
LegacyRequest,
|
||||
LegacyResponse,
|
||||
PrivatePartyMessage,
|
||||
PrivatePartyResponse,
|
||||
ScoreSubmitMessage,
|
||||
ScoreSubmitResponse,
|
||||
SendInfoMessage,
|
||||
SendInfoResponse,
|
||||
)
|
||||
|
||||
|
||||
__all__ = [
|
||||
'BasicCloudDialog',
|
||||
'BasicCloudDialogComponent',
|
||||
'BasicCloudDialogBsClassicTourneyResult',
|
||||
'BasicCloudDialogComponentLink',
|
||||
'BasicCloudDialogComponentText',
|
||||
'BasicCloudDialogComponentTypeID',
|
||||
'BasicCloudDialogComponentUnknown',
|
||||
'BasicCloudDialogDisplayItems',
|
||||
'BasicCloudDialogExpireTime',
|
||||
'ChestActionMessage',
|
||||
'ChestActionResponse',
|
||||
'ChestDisplayItem',
|
||||
'ChestInfoMessage',
|
||||
'ChestInfoResponse',
|
||||
'ClassicAccountLiveData',
|
||||
'ClassicChestAppearance',
|
||||
'ClientEffect',
|
||||
'ClientEffectChestWaitTimeAnimation',
|
||||
'ClientEffectDelay',
|
||||
'ClientEffectScreenMessage',
|
||||
'ClientEffectSound',
|
||||
'ClientEffectTicketsAnimation',
|
||||
'ClientEffectTokensAnimation',
|
||||
'ClientEffectTypeID',
|
||||
'ClientEffectUnknown',
|
||||
'CloudDialog',
|
||||
'CloudDialogAction',
|
||||
'CloudDialogActionMessage',
|
||||
'CloudDialogActionResponse',
|
||||
'CloudDialogTypeID',
|
||||
'CloudDialogWrapper',
|
||||
'CloudUI',
|
||||
'CloudUITypeID',
|
||||
'DisplayItem',
|
||||
'DisplayItemTypeID',
|
||||
'DisplayItemWrapper',
|
||||
'GetClassicPurchasesMessage',
|
||||
'GetClassicPurchasesResponse',
|
||||
'GlobalProfileCheckMessage',
|
||||
'GlobalProfileCheckResponse',
|
||||
'InboxRequestMessage',
|
||||
'InboxRequestResponse',
|
||||
'LegacyRequest',
|
||||
'LegacyResponse',
|
||||
'PrivatePartyMessage',
|
||||
'PrivatePartyResponse',
|
||||
'ScoreSubmitMessage',
|
||||
'ScoreSubmitResponse',
|
||||
'SendInfoMessage',
|
||||
'SendInfoResponse',
|
||||
'TestDisplayItem',
|
||||
'TicketsDisplayItem',
|
||||
'TOKENS1_COUNT',
|
||||
'TOKENS2_COUNT',
|
||||
'TOKENS3_COUNT',
|
||||
'TOKENS4_COUNT',
|
||||
'TokensDisplayItem',
|
||||
'UnknownCloudDialog',
|
||||
'UnknownDisplayItem',
|
||||
]
|
||||
73
dist/ba_data/python/bacommon/bs/_account.py
vendored
73
dist/ba_data/python/bacommon/bs/_account.py
vendored
|
|
@ -1,73 +0,0 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""BombSquad specific bits."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import datetime
|
||||
from enum import Enum
|
||||
from dataclasses import dataclass
|
||||
from typing import Annotated
|
||||
|
||||
from efro.dataclassio import ioprepped, IOAttrs
|
||||
from bacommon.bs._chest import ClassicChestAppearance
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class ClassicAccountLiveData:
|
||||
"""Live account data fed to the client in the bs classic app mode."""
|
||||
|
||||
@dataclass
|
||||
class Chest:
|
||||
"""A lovely chest."""
|
||||
|
||||
appearance: Annotated[
|
||||
ClassicChestAppearance,
|
||||
IOAttrs('a', enum_fallback=ClassicChestAppearance.UNKNOWN),
|
||||
]
|
||||
create_time: Annotated[datetime.datetime, IOAttrs('c')]
|
||||
unlock_time: Annotated[datetime.datetime, IOAttrs('t')]
|
||||
unlock_tokens: Annotated[int, IOAttrs('k')]
|
||||
ad_allow_time: Annotated[datetime.datetime | None, IOAttrs('at')]
|
||||
|
||||
class LeagueType(Enum):
|
||||
"""Type of league we are in."""
|
||||
|
||||
BRONZE = 'b'
|
||||
SILVER = 's'
|
||||
GOLD = 'g'
|
||||
DIAMOND = 'd'
|
||||
|
||||
class Flag(Enum):
|
||||
"""Flags set for our account."""
|
||||
|
||||
ASK_FOR_REVIEW = 'r'
|
||||
|
||||
tickets: Annotated[int, IOAttrs('ti')]
|
||||
|
||||
tokens: Annotated[int, IOAttrs('to')]
|
||||
gold_pass: Annotated[bool, IOAttrs('g')]
|
||||
remove_ads: Annotated[bool, IOAttrs('r')]
|
||||
|
||||
achievements: Annotated[int, IOAttrs('a')]
|
||||
achievements_total: Annotated[int, IOAttrs('at')]
|
||||
|
||||
league_type: Annotated[LeagueType | None, IOAttrs('lt')]
|
||||
league_num: Annotated[int | None, IOAttrs('ln')]
|
||||
league_rank: Annotated[int | None, IOAttrs('lr')]
|
||||
|
||||
level: Annotated[int, IOAttrs('lv')]
|
||||
xp: Annotated[int, IOAttrs('xp')]
|
||||
xpmax: Annotated[int, IOAttrs('xpm')]
|
||||
|
||||
inbox_count: Annotated[int, IOAttrs('ibc')]
|
||||
inbox_count_is_max: Annotated[bool, IOAttrs('ibcm')]
|
||||
inbox_contains_prize: Annotated[bool, IOAttrs('icp')]
|
||||
|
||||
chests: Annotated[dict[str, Chest], IOAttrs('c')]
|
||||
|
||||
# State id of our purchases for builds 22459+.
|
||||
purchases_state: Annotated[str | None, IOAttrs('p')]
|
||||
|
||||
flags: Annotated[set[Flag], IOAttrs('f', soft_default_factory=set)]
|
||||
9
dist/ba_data/python/bacommon/bs/_bs.py
vendored
9
dist/ba_data/python/bacommon/bs/_bs.py
vendored
|
|
@ -1,9 +0,0 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""BombSquad specific bits."""
|
||||
|
||||
# Token counts for our various packs.
|
||||
TOKENS1_COUNT = 50
|
||||
TOKENS2_COUNT = 500
|
||||
TOKENS3_COUNT = 1200
|
||||
TOKENS4_COUNT = 2600
|
||||
46
dist/ba_data/python/bacommon/bs/_chest.py
vendored
46
dist/ba_data/python/bacommon/bs/_chest.py
vendored
|
|
@ -1,46 +0,0 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""BombSquad specific bits."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from enum import Enum
|
||||
from typing import assert_never
|
||||
|
||||
|
||||
class ClassicChestAppearance(Enum):
|
||||
"""Appearances bombsquad classic chests can have."""
|
||||
|
||||
UNKNOWN = 'u'
|
||||
DEFAULT = 'd'
|
||||
L1 = 'l1'
|
||||
L2 = 'l2'
|
||||
L3 = 'l3'
|
||||
L4 = 'l4'
|
||||
L5 = 'l5'
|
||||
L6 = 'l6'
|
||||
|
||||
@property
|
||||
def pretty_name(self) -> str:
|
||||
"""Pretty name for the chest in English."""
|
||||
# pylint: disable=too-many-return-statements
|
||||
cls = type(self)
|
||||
|
||||
if self is cls.UNKNOWN:
|
||||
return 'Unknown Chest'
|
||||
if self is cls.DEFAULT:
|
||||
return 'Chest'
|
||||
if self is cls.L1:
|
||||
return 'L1 Chest'
|
||||
if self is cls.L2:
|
||||
return 'L2 Chest'
|
||||
if self is cls.L3:
|
||||
return 'L3 Chest'
|
||||
if self is cls.L4:
|
||||
return 'L4 Chest'
|
||||
if self is cls.L5:
|
||||
return 'L5 Chest'
|
||||
if self is cls.L6:
|
||||
return 'L6 Chest'
|
||||
|
||||
assert_never(self)
|
||||
180
dist/ba_data/python/bacommon/bs/_clienteffect.py
vendored
180
dist/ba_data/python/bacommon/bs/_clienteffect.py
vendored
|
|
@ -1,180 +0,0 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""ClientEffect related functionality."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import datetime
|
||||
from enum import Enum
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Annotated, override, assert_never
|
||||
|
||||
from efro.dataclassio import ioprepped, IOAttrs, IOMultiType
|
||||
|
||||
|
||||
class ClientEffectTypeID(Enum):
|
||||
"""Type ID for each of our subclasses."""
|
||||
|
||||
UNKNOWN = 'u'
|
||||
SCREEN_MESSAGE = 'm'
|
||||
SOUND = 's'
|
||||
DELAY = 'd'
|
||||
CHEST_WAIT_TIME_ANIMATION = 't'
|
||||
TICKETS_ANIMATION = 'ta'
|
||||
TOKENS_ANIMATION = 'toa'
|
||||
|
||||
|
||||
class ClientEffect(IOMultiType[ClientEffectTypeID]):
|
||||
"""Something that can happen on the client.
|
||||
|
||||
This can include screen messages, sounds, visual effects, etc.
|
||||
"""
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> ClientEffectTypeID:
|
||||
# Require child classes to supply this themselves. If we did a
|
||||
# full type registry/lookup here it would require us to import
|
||||
# everything and would prevent lazy loading.
|
||||
raise NotImplementedError()
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type(cls, type_id: ClientEffectTypeID) -> type[ClientEffect]:
|
||||
"""Return the subclass for each of our type-ids."""
|
||||
# pylint: disable=cyclic-import
|
||||
# pylint: disable=too-many-return-statements
|
||||
|
||||
t = ClientEffectTypeID
|
||||
if type_id is t.UNKNOWN:
|
||||
return ClientEffectUnknown
|
||||
if type_id is t.SCREEN_MESSAGE:
|
||||
return ClientEffectScreenMessage
|
||||
if type_id is t.SOUND:
|
||||
return ClientEffectSound
|
||||
if type_id is t.DELAY:
|
||||
return ClientEffectDelay
|
||||
if type_id is t.CHEST_WAIT_TIME_ANIMATION:
|
||||
return ClientEffectChestWaitTimeAnimation
|
||||
if type_id is t.TICKETS_ANIMATION:
|
||||
return ClientEffectTicketsAnimation
|
||||
if type_id is t.TOKENS_ANIMATION:
|
||||
return ClientEffectTokensAnimation
|
||||
|
||||
# Important to make sure we provide all types.
|
||||
assert_never(type_id)
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_unknown_type_fallback(cls) -> ClientEffect:
|
||||
# If we encounter some future message type we don't know
|
||||
# anything about, drop in a placeholder.
|
||||
return ClientEffectUnknown()
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class ClientEffectUnknown(ClientEffect):
|
||||
"""Fallback substitute for types we don't recognize."""
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> ClientEffectTypeID:
|
||||
return ClientEffectTypeID.UNKNOWN
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class ClientEffectScreenMessage(ClientEffect):
|
||||
"""Display a screen-message."""
|
||||
|
||||
message: Annotated[str, IOAttrs('m')]
|
||||
subs: Annotated[list[str], IOAttrs('s')] = field(default_factory=list)
|
||||
color: Annotated[tuple[float, float, float], IOAttrs('c')] = (1.0, 1.0, 1.0)
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> ClientEffectTypeID:
|
||||
return ClientEffectTypeID.SCREEN_MESSAGE
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class ClientEffectSound(ClientEffect):
|
||||
"""Play a sound."""
|
||||
|
||||
class Sound(Enum):
|
||||
"""Sounds that can be made alongside the message."""
|
||||
|
||||
UNKNOWN = 'u'
|
||||
CASH_REGISTER = 'c'
|
||||
ERROR = 'e'
|
||||
POWER_DOWN = 'p'
|
||||
GUN_COCKING = 'g'
|
||||
|
||||
sound: Annotated[Sound, IOAttrs('s', enum_fallback=Sound.UNKNOWN)]
|
||||
volume: Annotated[float, IOAttrs('v')] = 1.0
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> ClientEffectTypeID:
|
||||
return ClientEffectTypeID.SOUND
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class ClientEffectChestWaitTimeAnimation(ClientEffect):
|
||||
"""Animate chest wait time changing."""
|
||||
|
||||
chestid: Annotated[str, IOAttrs('c')]
|
||||
duration: Annotated[float, IOAttrs('u')]
|
||||
startvalue: Annotated[datetime.datetime, IOAttrs('o')]
|
||||
endvalue: Annotated[datetime.datetime, IOAttrs('n')]
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> ClientEffectTypeID:
|
||||
return ClientEffectTypeID.CHEST_WAIT_TIME_ANIMATION
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class ClientEffectTicketsAnimation(ClientEffect):
|
||||
"""Animate tickets count."""
|
||||
|
||||
duration: Annotated[float, IOAttrs('u')]
|
||||
startvalue: Annotated[int, IOAttrs('s')]
|
||||
endvalue: Annotated[int, IOAttrs('e')]
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> ClientEffectTypeID:
|
||||
return ClientEffectTypeID.TICKETS_ANIMATION
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class ClientEffectTokensAnimation(ClientEffect):
|
||||
"""Animate tokens count."""
|
||||
|
||||
duration: Annotated[float, IOAttrs('u')]
|
||||
startvalue: Annotated[int, IOAttrs('s')]
|
||||
endvalue: Annotated[int, IOAttrs('e')]
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> ClientEffectTypeID:
|
||||
return ClientEffectTypeID.TOKENS_ANIMATION
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class ClientEffectDelay(ClientEffect):
|
||||
"""Delay effect processing."""
|
||||
|
||||
seconds: Annotated[float, IOAttrs('s')]
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> ClientEffectTypeID:
|
||||
return ClientEffectTypeID.DELAY
|
||||
308
dist/ba_data/python/bacommon/bs/_clouddialog.py
vendored
308
dist/ba_data/python/bacommon/bs/_clouddialog.py
vendored
|
|
@ -1,308 +0,0 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Simple cloud-defined UIs for things like notifications."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import datetime
|
||||
from enum import Enum
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Annotated, override, assert_never
|
||||
|
||||
from efro.dataclassio import ioprepped, IOAttrs, IOMultiType
|
||||
|
||||
from bacommon.bs._displayitem import DisplayItemWrapper
|
||||
|
||||
|
||||
class CloudDialogTypeID(Enum):
|
||||
"""Type ID for each of our subclasses."""
|
||||
|
||||
UNKNOWN = 'u'
|
||||
BASIC = 'b'
|
||||
|
||||
|
||||
class CloudDialog(IOMultiType[CloudDialogTypeID]):
|
||||
"""Small self-contained ui bit provided by the cloud.
|
||||
|
||||
These take care of updating and/or dismissing themselves based on
|
||||
user input. Useful for things such as inbox messages. For more
|
||||
complex UI construction, look at :class:`CloudUI`.
|
||||
"""
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> CloudDialogTypeID:
|
||||
# Require child classes to supply this themselves. If we did a
|
||||
# full type registry/lookup here it would require us to import
|
||||
# everything and would prevent lazy loading.
|
||||
raise NotImplementedError()
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type(cls, type_id: CloudDialogTypeID) -> type[CloudDialog]:
|
||||
"""Return the subclass for each of our type-ids."""
|
||||
# pylint: disable=cyclic-import
|
||||
out: type[CloudDialog]
|
||||
|
||||
t = CloudDialogTypeID
|
||||
if type_id is t.UNKNOWN:
|
||||
out = UnknownCloudDialog
|
||||
elif type_id is t.BASIC:
|
||||
out = BasicCloudDialog
|
||||
else:
|
||||
# Important to make sure we provide all types.
|
||||
assert_never(type_id)
|
||||
return out
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_unknown_type_fallback(cls) -> CloudDialog:
|
||||
# If we encounter some future message type we don't know
|
||||
# anything about, drop in a placeholder.
|
||||
return UnknownCloudDialog()
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class UnknownCloudDialog(CloudDialog):
|
||||
"""Fallback type for unrecognized entries."""
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> CloudDialogTypeID:
|
||||
return CloudDialogTypeID.UNKNOWN
|
||||
|
||||
|
||||
class BasicCloudDialogComponentTypeID(Enum):
|
||||
"""Type ID for each of our subclasses."""
|
||||
|
||||
UNKNOWN = 'u'
|
||||
TEXT = 't'
|
||||
LINK = 'l'
|
||||
BS_CLASSIC_TOURNEY_RESULT = 'ct'
|
||||
DISPLAY_ITEMS = 'di'
|
||||
EXPIRE_TIME = 'd'
|
||||
|
||||
|
||||
class BasicCloudDialogComponent(IOMultiType[BasicCloudDialogComponentTypeID]):
|
||||
"""Top level class for our multitype."""
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> BasicCloudDialogComponentTypeID:
|
||||
# Require child classes to supply this themselves. If we did a
|
||||
# full type registry/lookup here it would require us to import
|
||||
# everything and would prevent lazy loading.
|
||||
raise NotImplementedError()
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type(
|
||||
cls, type_id: BasicCloudDialogComponentTypeID
|
||||
) -> type[BasicCloudDialogComponent]:
|
||||
"""Return the subclass for each of our type-ids."""
|
||||
# pylint: disable=cyclic-import
|
||||
|
||||
t = BasicCloudDialogComponentTypeID
|
||||
if type_id is t.UNKNOWN:
|
||||
return BasicCloudDialogComponentUnknown
|
||||
if type_id is t.TEXT:
|
||||
return BasicCloudDialogComponentText
|
||||
if type_id is t.LINK:
|
||||
return BasicCloudDialogComponentLink
|
||||
if type_id is t.BS_CLASSIC_TOURNEY_RESULT:
|
||||
return BasicCloudDialogBsClassicTourneyResult
|
||||
if type_id is t.DISPLAY_ITEMS:
|
||||
return BasicCloudDialogDisplayItems
|
||||
if type_id is t.EXPIRE_TIME:
|
||||
return BasicCloudDialogExpireTime
|
||||
|
||||
# Important to make sure we provide all types.
|
||||
assert_never(type_id)
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_unknown_type_fallback(cls) -> BasicCloudDialogComponent:
|
||||
# If we encounter some future message type we don't know
|
||||
# anything about, drop in a placeholder.
|
||||
return BasicCloudDialogComponentUnknown()
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class BasicCloudDialogComponentUnknown(BasicCloudDialogComponent):
|
||||
"""An unknown basic client component type.
|
||||
|
||||
In practice these should never show up since the master-server
|
||||
generates these on the fly for the client and so should not send
|
||||
clients one they can't digest.
|
||||
"""
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> BasicCloudDialogComponentTypeID:
|
||||
return BasicCloudDialogComponentTypeID.UNKNOWN
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class BasicCloudDialogComponentText(BasicCloudDialogComponent):
|
||||
"""Show some text in the inbox message."""
|
||||
|
||||
text: Annotated[str, IOAttrs('t')]
|
||||
subs: Annotated[list[str], IOAttrs('s', store_default=False)] = field(
|
||||
default_factory=list
|
||||
)
|
||||
scale: Annotated[float, IOAttrs('sc', store_default=False)] = 1.0
|
||||
color: Annotated[
|
||||
tuple[float, float, float, float], IOAttrs('c', store_default=False)
|
||||
] = (1.0, 1.0, 1.0, 1.0)
|
||||
spacing_top: Annotated[float, IOAttrs('st', store_default=False)] = 0.0
|
||||
spacing_bottom: Annotated[float, IOAttrs('sb', store_default=False)] = 0.0
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> BasicCloudDialogComponentTypeID:
|
||||
return BasicCloudDialogComponentTypeID.TEXT
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class BasicCloudDialogComponentLink(BasicCloudDialogComponent):
|
||||
"""Show a link in the inbox message."""
|
||||
|
||||
url: Annotated[str, IOAttrs('u')]
|
||||
label: Annotated[str, IOAttrs('l')]
|
||||
subs: Annotated[list[str], IOAttrs('s', store_default=False)] = field(
|
||||
default_factory=list
|
||||
)
|
||||
spacing_top: Annotated[float, IOAttrs('st', store_default=False)] = 0.0
|
||||
spacing_bottom: Annotated[float, IOAttrs('sb', store_default=False)] = 0.0
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> BasicCloudDialogComponentTypeID:
|
||||
return BasicCloudDialogComponentTypeID.LINK
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class BasicCloudDialogBsClassicTourneyResult(BasicCloudDialogComponent):
|
||||
"""Show info about a classic tourney."""
|
||||
|
||||
tournament_id: Annotated[str, IOAttrs('t')]
|
||||
game: Annotated[str, IOAttrs('g')]
|
||||
players: Annotated[int, IOAttrs('p')]
|
||||
rank: Annotated[int, IOAttrs('r')]
|
||||
trophy: Annotated[str | None, IOAttrs('tr')]
|
||||
prizes: Annotated[list[DisplayItemWrapper], IOAttrs('pr')]
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> BasicCloudDialogComponentTypeID:
|
||||
return BasicCloudDialogComponentTypeID.BS_CLASSIC_TOURNEY_RESULT
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class BasicCloudDialogDisplayItems(BasicCloudDialogComponent):
|
||||
"""Show some display-items."""
|
||||
|
||||
items: Annotated[list[DisplayItemWrapper], IOAttrs('d')]
|
||||
width: Annotated[float, IOAttrs('w')] = 100.0
|
||||
spacing_top: Annotated[float, IOAttrs('st', store_default=False)] = 0.0
|
||||
spacing_bottom: Annotated[float, IOAttrs('sb', store_default=False)] = 0.0
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> BasicCloudDialogComponentTypeID:
|
||||
return BasicCloudDialogComponentTypeID.DISPLAY_ITEMS
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class BasicCloudDialogExpireTime(BasicCloudDialogComponent):
|
||||
"""Show expire-time."""
|
||||
|
||||
time: Annotated[datetime.datetime, IOAttrs('d')]
|
||||
spacing_top: Annotated[float, IOAttrs('st', store_default=False)] = 0.0
|
||||
spacing_bottom: Annotated[float, IOAttrs('sb', store_default=False)] = 0.0
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> BasicCloudDialogComponentTypeID:
|
||||
return BasicCloudDialogComponentTypeID.EXPIRE_TIME
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class BasicCloudDialog(CloudDialog):
|
||||
"""A basic UI for the client."""
|
||||
|
||||
class ButtonLabel(Enum):
|
||||
"""Distinct button labels we support."""
|
||||
|
||||
UNKNOWN = 'u'
|
||||
OK = 'o'
|
||||
APPLY = 'a'
|
||||
CANCEL = 'c'
|
||||
ACCEPT = 'ac'
|
||||
DECLINE = 'dn'
|
||||
IGNORE = 'ig'
|
||||
CLAIM = 'cl'
|
||||
DISCARD = 'd'
|
||||
|
||||
class InteractionStyle(Enum):
|
||||
"""Overall interaction styles we support."""
|
||||
|
||||
UNKNOWN = 'u'
|
||||
BUTTON_POSITIVE = 'p'
|
||||
BUTTON_POSITIVE_NEGATIVE = 'pn'
|
||||
|
||||
components: Annotated[list[BasicCloudDialogComponent], IOAttrs('s')]
|
||||
|
||||
interaction_style: Annotated[
|
||||
InteractionStyle, IOAttrs('i', enum_fallback=InteractionStyle.UNKNOWN)
|
||||
] = InteractionStyle.BUTTON_POSITIVE
|
||||
|
||||
button_label_positive: Annotated[
|
||||
ButtonLabel, IOAttrs('p', enum_fallback=ButtonLabel.UNKNOWN)
|
||||
] = ButtonLabel.OK
|
||||
|
||||
button_label_negative: Annotated[
|
||||
ButtonLabel, IOAttrs('n', enum_fallback=ButtonLabel.UNKNOWN)
|
||||
] = ButtonLabel.CANCEL
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> CloudDialogTypeID:
|
||||
return CloudDialogTypeID.BASIC
|
||||
|
||||
def contains_unknown_elements(self) -> bool:
|
||||
"""Whether something within us is an unknown type or enum."""
|
||||
return (
|
||||
self.interaction_style is self.InteractionStyle.UNKNOWN
|
||||
or self.button_label_positive is self.ButtonLabel.UNKNOWN
|
||||
or self.button_label_negative is self.ButtonLabel.UNKNOWN
|
||||
or any(
|
||||
c.get_type_id() is BasicCloudDialogComponentTypeID.UNKNOWN
|
||||
for c in self.components
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class CloudDialogWrapper:
|
||||
"""Wrapper for a CloudDialog and its common data."""
|
||||
|
||||
id: Annotated[str, IOAttrs('i')]
|
||||
createtime: Annotated[datetime.datetime, IOAttrs('c')]
|
||||
ui: Annotated[CloudDialog, IOAttrs('e')]
|
||||
|
||||
|
||||
class CloudDialogAction(Enum):
|
||||
"""Types of actions we can run."""
|
||||
|
||||
BUTTON_PRESS_POSITIVE = 'p'
|
||||
BUTTON_PRESS_NEGATIVE = 'n'
|
||||
180
dist/ba_data/python/bacommon/bs/_cloudui.py
vendored
180
dist/ba_data/python/bacommon/bs/_cloudui.py
vendored
|
|
@ -1,180 +0,0 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Full UIs defined in the cloud - similar to a basic form of html"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from enum import Enum
|
||||
from dataclasses import dataclass
|
||||
from typing import Annotated, override, assert_never
|
||||
|
||||
from efro.dataclassio import ioprepped, IOAttrs, IOMultiType
|
||||
|
||||
|
||||
class CloudUITypeID(Enum):
|
||||
"""Type ID for each of our subclasses."""
|
||||
|
||||
UNKNOWN = 'u'
|
||||
V1 = 'v1'
|
||||
|
||||
|
||||
class CloudUI(IOMultiType[CloudUITypeID]):
|
||||
"""UI defined by the cloud.
|
||||
|
||||
Conceptually similar to a basic html page, except using app UI.
|
||||
"""
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> CloudUITypeID:
|
||||
# Require child classes to supply this themselves. If we did a
|
||||
# full type registry/lookup here it would require us to import
|
||||
# everything and would prevent lazy loading.
|
||||
raise NotImplementedError()
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type(cls, type_id: CloudUITypeID) -> type[CloudUI]:
|
||||
"""Return the subclass for each of our type-ids."""
|
||||
# pylint: disable=cyclic-import
|
||||
out: type[CloudUI]
|
||||
|
||||
t = CloudUITypeID
|
||||
if type_id is t.UNKNOWN:
|
||||
out = UnknownCloudUI
|
||||
elif type_id is t.V1:
|
||||
out = V1CloudUI
|
||||
else:
|
||||
# Important to make sure we provide all types.
|
||||
assert_never(type_id)
|
||||
return out
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_unknown_type_fallback(cls) -> CloudUI:
|
||||
# If we encounter some future message type we don't know
|
||||
# anything about, drop in a placeholder.
|
||||
return UnknownCloudUI()
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class UnknownCloudUI(CloudUI):
|
||||
"""Fallback type for unrecognized UI types.
|
||||
|
||||
Will show the client a 'cannot display this UI' placeholder page.
|
||||
"""
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> CloudUITypeID:
|
||||
return CloudUITypeID.UNKNOWN
|
||||
|
||||
|
||||
class V1CloudUIComponentTypeID(Enum):
|
||||
"""Type ID for each of our subclasses."""
|
||||
|
||||
UNKNOWN = 'u'
|
||||
TEXT = 't'
|
||||
|
||||
|
||||
class V1CloudUIComponent(IOMultiType[V1CloudUIComponentTypeID]):
|
||||
"""Top level class for our multitype."""
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> V1CloudUIComponentTypeID:
|
||||
# Require child classes to supply this themselves. If we did a
|
||||
# full type registry/lookup here it would require us to import
|
||||
# everything and would prevent lazy loading.
|
||||
raise NotImplementedError()
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type(
|
||||
cls, type_id: V1CloudUIComponentTypeID
|
||||
) -> type[V1CloudUIComponent]:
|
||||
"""Return the subclass for each of our type-ids."""
|
||||
# pylint: disable=cyclic-import
|
||||
|
||||
t = V1CloudUIComponentTypeID
|
||||
if type_id is t.UNKNOWN:
|
||||
return V1CloudUIComponentUnknown
|
||||
if type_id is t.TEXT:
|
||||
return V1CloudUIComponentText
|
||||
|
||||
# Important to make sure we provide all types.
|
||||
assert_never(type_id)
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_unknown_type_fallback(cls) -> V1CloudUIComponent:
|
||||
# If we encounter some future message type we don't know
|
||||
# anything about, drop in a placeholder.
|
||||
return V1CloudUIComponentUnknown()
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class V1CloudUIComponentUnknown(V1CloudUIComponent):
|
||||
"""An unknown basic client component type.
|
||||
|
||||
In practice these should never show up since the master-server
|
||||
generates these on the fly for the client and so should not send
|
||||
clients one they can't digest.
|
||||
"""
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> V1CloudUIComponentTypeID:
|
||||
return V1CloudUIComponentTypeID.UNKNOWN
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class V1CloudUIComponentText(V1CloudUIComponent):
|
||||
"""Show some text over a button."""
|
||||
|
||||
text: Annotated[str, IOAttrs('t')]
|
||||
# position: Annotated[float, IOAttrs('p', store_default=False)] = 1.0
|
||||
# scale: Annotated[float, IOAttrs('s', store_default=False)] = 1.0
|
||||
# color: Annotated[
|
||||
# tuple[float, float, float, float], IOAttrs('c', store_default=False)
|
||||
# ] = (1.0, 1.0, 1.0, 1.0)
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> V1CloudUIComponentTypeID:
|
||||
return V1CloudUIComponentTypeID.TEXT
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class V1CloudUIButton:
|
||||
"""A button in our cloud ui."""
|
||||
|
||||
color: Annotated[tuple[float, float, float], IOAttrs('cl')]
|
||||
size: Annotated[tuple[float, float], IOAttrs('sz')]
|
||||
components: Annotated[list[V1CloudUIComponent], IOAttrs('c')]
|
||||
scale: Annotated[float, IOAttrs('sc', store_default=False)] = 1.0
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class V1CloudUIRow:
|
||||
"""A row in our cloud ui."""
|
||||
|
||||
buttons: Annotated[list[V1CloudUIButton], IOAttrs('b')]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class V1CloudUI(CloudUI):
|
||||
"""Version 1 of our cloud-defined UI type."""
|
||||
|
||||
rows: Annotated[list[V1CloudUIRow], IOAttrs('r')]
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> CloudUITypeID:
|
||||
return CloudUITypeID.V1
|
||||
182
dist/ba_data/python/bacommon/bs/_displayitem.py
vendored
182
dist/ba_data/python/bacommon/bs/_displayitem.py
vendored
|
|
@ -1,182 +0,0 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""DisplayItem related functionality."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from enum import Enum
|
||||
from dataclasses import dataclass
|
||||
from typing import Annotated, override, assert_never
|
||||
|
||||
from efro.util import pairs_to_flat
|
||||
from efro.dataclassio import ioprepped, IOAttrs, IOMultiType
|
||||
|
||||
from bacommon.bs._chest import ClassicChestAppearance
|
||||
|
||||
|
||||
class DisplayItemTypeID(Enum):
|
||||
"""Type ID for each of our subclasses."""
|
||||
|
||||
UNKNOWN = 'u'
|
||||
TICKETS = 't'
|
||||
TOKENS = 'k'
|
||||
TEST = 's'
|
||||
CHEST = 'c'
|
||||
|
||||
|
||||
class DisplayItem(IOMultiType[DisplayItemTypeID]):
|
||||
"""Some amount of something that can be shown or described.
|
||||
|
||||
Used to depict chest contents, inventory, rewards, etc.
|
||||
"""
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> DisplayItemTypeID:
|
||||
# Require child classes to supply this themselves. If we did a
|
||||
# full type registry/lookup here it would require us to import
|
||||
# everything and would prevent lazy loading.
|
||||
raise NotImplementedError()
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type(cls, type_id: DisplayItemTypeID) -> type[DisplayItem]:
|
||||
"""Return the subclass for each of our type-ids."""
|
||||
# pylint: disable=cyclic-import
|
||||
|
||||
t = DisplayItemTypeID
|
||||
if type_id is t.UNKNOWN:
|
||||
return UnknownDisplayItem
|
||||
if type_id is t.TICKETS:
|
||||
return TicketsDisplayItem
|
||||
if type_id is t.TOKENS:
|
||||
return TokensDisplayItem
|
||||
if type_id is t.TEST:
|
||||
return TestDisplayItem
|
||||
if type_id is t.CHEST:
|
||||
return ChestDisplayItem
|
||||
|
||||
# Important to make sure we provide all types.
|
||||
assert_never(type_id)
|
||||
|
||||
def get_description(self) -> tuple[str, list[tuple[str, str]]]:
|
||||
"""Return a string description and subs for the item.
|
||||
|
||||
These decriptions are baked into the DisplayItemWrapper and
|
||||
should be accessed from there when available. This allows
|
||||
clients to give descriptions even for newer display items they
|
||||
don't recognize.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
# Implement fallbacks so client can digest item lists even if they
|
||||
# contain unrecognized stuff. DisplayItemWrapper contains basic
|
||||
# baked down info that they can still use in such cases.
|
||||
@override
|
||||
@classmethod
|
||||
def get_unknown_type_fallback(cls) -> DisplayItem:
|
||||
return UnknownDisplayItem()
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class UnknownDisplayItem(DisplayItem):
|
||||
"""Something we don't know how to display."""
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> DisplayItemTypeID:
|
||||
return DisplayItemTypeID.UNKNOWN
|
||||
|
||||
@override
|
||||
def get_description(self) -> tuple[str, list[tuple[str, str]]]:
|
||||
import logging
|
||||
|
||||
# Make noise but don't break.
|
||||
logging.exception(
|
||||
'UnknownDisplayItem.get_description() should never be called.'
|
||||
' Always access descriptions on the DisplayItemWrapper.'
|
||||
)
|
||||
return 'Unknown', []
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class TicketsDisplayItem(DisplayItem):
|
||||
"""Some amount of tickets."""
|
||||
|
||||
count: Annotated[int, IOAttrs('c')]
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> DisplayItemTypeID:
|
||||
return DisplayItemTypeID.TICKETS
|
||||
|
||||
@override
|
||||
def get_description(self) -> tuple[str, list[tuple[str, str]]]:
|
||||
return '${C} Tickets', [('${C}', str(self.count))]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class TokensDisplayItem(DisplayItem):
|
||||
"""Some amount of tokens."""
|
||||
|
||||
count: Annotated[int, IOAttrs('c')]
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> DisplayItemTypeID:
|
||||
return DisplayItemTypeID.TOKENS
|
||||
|
||||
@override
|
||||
def get_description(self) -> tuple[str, list[tuple[str, str]]]:
|
||||
return '${C} Tokens', [('${C}', str(self.count))]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class TestDisplayItem(DisplayItem):
|
||||
"""Fills usable space for a display-item - good for calibration."""
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> DisplayItemTypeID:
|
||||
return DisplayItemTypeID.TEST
|
||||
|
||||
@override
|
||||
def get_description(self) -> tuple[str, list[tuple[str, str]]]:
|
||||
return 'Test Display Item Here', []
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class ChestDisplayItem(DisplayItem):
|
||||
"""Display a chest."""
|
||||
|
||||
appearance: Annotated[ClassicChestAppearance, IOAttrs('a')]
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> DisplayItemTypeID:
|
||||
return DisplayItemTypeID.CHEST
|
||||
|
||||
@override
|
||||
def get_description(self) -> tuple[str, list[tuple[str, str]]]:
|
||||
return self.appearance.pretty_name, []
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class DisplayItemWrapper:
|
||||
"""Wraps a DisplayItem and common info."""
|
||||
|
||||
item: Annotated[DisplayItem, IOAttrs('i')]
|
||||
description: Annotated[str, IOAttrs('d')]
|
||||
description_subs: Annotated[list[str] | None, IOAttrs('s')]
|
||||
|
||||
@classmethod
|
||||
def for_display_item(cls, item: DisplayItem) -> DisplayItemWrapper:
|
||||
"""Convenience method to wrap a DisplayItem."""
|
||||
desc, subs = item.get_description()
|
||||
return DisplayItemWrapper(item, desc, pairs_to_flat(subs))
|
||||
336
dist/ba_data/python/bacommon/bs/_msg.py
vendored
336
dist/ba_data/python/bacommon/bs/_msg.py
vendored
|
|
@ -1,336 +0,0 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""BombSquad specific bits."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import datetime
|
||||
from enum import Enum
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Annotated, override
|
||||
|
||||
from efro.dataclassio import ioprepped, IOAttrs
|
||||
from efro.message import Message, Response
|
||||
|
||||
from bacommon.bs._displayitem import DisplayItemWrapper
|
||||
from bacommon.bs._clienteffect import ClientEffect
|
||||
from bacommon.bs._clouddialog import CloudDialogAction, CloudDialogWrapper
|
||||
from bacommon.bs._chest import ClassicChestAppearance
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class ChestActionMessage(Message):
|
||||
"""Request action about a chest."""
|
||||
|
||||
class Action(Enum):
|
||||
"""Types of actions we can request."""
|
||||
|
||||
# Unlocking (for free or with tokens).
|
||||
UNLOCK = 'u'
|
||||
|
||||
# Watched an ad to reduce wait.
|
||||
AD = 'ad'
|
||||
|
||||
action: Annotated[Action, IOAttrs('a')]
|
||||
|
||||
# Tokens we are paying (only applies to unlock).
|
||||
token_payment: Annotated[int, IOAttrs('t')]
|
||||
|
||||
chest_id: Annotated[str, IOAttrs('i')]
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_response_types(cls) -> list[type[Response] | None]:
|
||||
return [ChestActionResponse]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class ChestActionResponse(Response):
|
||||
"""Here's the results of that action you asked for, boss."""
|
||||
|
||||
# Tokens that were actually charged.
|
||||
tokens_charged: Annotated[int, IOAttrs('t')] = 0
|
||||
|
||||
# If present, signifies the chest has been opened and we should show
|
||||
# the user this stuff that was in it.
|
||||
contents: Annotated[list[DisplayItemWrapper] | None, IOAttrs('c')] = None
|
||||
|
||||
# If contents are present, which of the chest's prize-sets they
|
||||
# represent.
|
||||
prizeindex: Annotated[int, IOAttrs('i')] = 0
|
||||
|
||||
# Printable error if something goes wrong.
|
||||
error: Annotated[str | None, IOAttrs('e')] = None
|
||||
|
||||
# Printable warning. Shown in orange with an error sound. Does not
|
||||
# mean the action failed; only that there's something to tell the
|
||||
# users such as 'It looks like you are faking ad views; stop it or
|
||||
# you won't have ad options anymore.'
|
||||
warning: Annotated[str | None, IOAttrs('w', store_default=False)] = None
|
||||
|
||||
# Printable success message. Shown in green with a cash-register
|
||||
# sound. Can be used for things like successful wait reductions via
|
||||
# ad views. Used in builds earlier than 22311; can remove once
|
||||
# 22311+ is ubiquitous.
|
||||
success_msg: Annotated[str | None, IOAttrs('s', store_default=False)] = None
|
||||
|
||||
# Effects to show on the client. Replaces warning and success_msg in
|
||||
# build 22311 or newer.
|
||||
effects: Annotated[
|
||||
list[ClientEffect], IOAttrs('fx', store_default=False)
|
||||
] = field(default_factory=list)
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class CloudDialogActionMessage(Message):
|
||||
"""Do something to a client ui."""
|
||||
|
||||
id: Annotated[str, IOAttrs('i')]
|
||||
action: Annotated[CloudDialogAction, IOAttrs('a')]
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_response_types(cls) -> list[type[Response] | None]:
|
||||
return [CloudDialogActionResponse]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class CloudDialogActionResponse(Response):
|
||||
"""Did something to that inbox entry, boss."""
|
||||
|
||||
class ErrorType(Enum):
|
||||
"""Types of errors that may have occurred."""
|
||||
|
||||
# Probably a future error type we don't recognize.
|
||||
UNKNOWN = 'u'
|
||||
|
||||
# Something went wrong on the server, but specifics are not
|
||||
# relevant.
|
||||
INTERNAL = 'i'
|
||||
|
||||
# The entry expired on the server. In various cases such as 'ok'
|
||||
# buttons this can generally be ignored.
|
||||
EXPIRED = 'e'
|
||||
|
||||
error_type: Annotated[
|
||||
ErrorType | None, IOAttrs('et', enum_fallback=ErrorType.UNKNOWN)
|
||||
]
|
||||
|
||||
# User facing error message in the case of errors.
|
||||
error_message: Annotated[str | None, IOAttrs('em')]
|
||||
|
||||
effects: Annotated[list[ClientEffect], IOAttrs('fx')]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class GetClassicPurchasesMessage(Message):
|
||||
"""Asking for current account's classic purchases."""
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_response_types(cls) -> list[type[Response] | None]:
|
||||
return [GetClassicPurchasesResponse]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class GetClassicPurchasesResponse(Response):
|
||||
"""Here's those classic purchases ya asked for boss."""
|
||||
|
||||
purchases: Annotated[set[str], IOAttrs('p')]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class GlobalProfileCheckMessage(Message):
|
||||
"""Is this global profile name available?"""
|
||||
|
||||
name: Annotated[str, IOAttrs('n')]
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_response_types(cls) -> list[type[Response] | None]:
|
||||
return [GlobalProfileCheckResponse]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class GlobalProfileCheckResponse(Response):
|
||||
"""Here's that profile check ya asked for boss."""
|
||||
|
||||
available: Annotated[bool, IOAttrs('a')]
|
||||
ticket_cost: Annotated[int, IOAttrs('tc')]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class InboxRequestMessage(Message):
|
||||
"""Message requesting our inbox."""
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_response_types(cls) -> list[type[Response] | None]:
|
||||
return [InboxRequestResponse]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class InboxRequestResponse(Response):
|
||||
"""Here's that inbox contents you asked for, boss."""
|
||||
|
||||
wrappers: Annotated[list[CloudDialogWrapper], IOAttrs('w')]
|
||||
|
||||
# Printable error if something goes wrong.
|
||||
error: Annotated[str | None, IOAttrs('e')] = None
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class LegacyRequest(Message):
|
||||
"""A generic request for the legacy master server."""
|
||||
|
||||
request: Annotated[str, IOAttrs('r')]
|
||||
request_type: Annotated[str, IOAttrs('t')]
|
||||
user_agent_string: Annotated[str, IOAttrs('u')]
|
||||
data: Annotated[str, IOAttrs('d')]
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_response_types(cls) -> list[type[Response] | None]:
|
||||
return [LegacyResponse]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class LegacyResponse(Response):
|
||||
"""Response for generic legacy request."""
|
||||
|
||||
data: Annotated[str | None, IOAttrs('d')]
|
||||
zipped: Annotated[bool, IOAttrs('z')]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class ChestInfoMessage(Message):
|
||||
"""Request info about a chest."""
|
||||
|
||||
chest_id: Annotated[str, IOAttrs('i')]
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_response_types(cls) -> list[type[Response] | None]:
|
||||
return [ChestInfoResponse]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class ChestInfoResponse(Response):
|
||||
"""Here's that chest info you asked for, boss."""
|
||||
|
||||
@dataclass
|
||||
class Chest:
|
||||
"""A lovely chest."""
|
||||
|
||||
@dataclass
|
||||
class PrizeSet:
|
||||
"""A possible set of prizes for this chest."""
|
||||
|
||||
weight: Annotated[float, IOAttrs('w')]
|
||||
contents: Annotated[list[DisplayItemWrapper], IOAttrs('c')]
|
||||
|
||||
appearance: Annotated[
|
||||
ClassicChestAppearance,
|
||||
IOAttrs('a', enum_fallback=ClassicChestAppearance.UNKNOWN),
|
||||
]
|
||||
|
||||
# How much it costs to unlock *now*.
|
||||
unlock_tokens: Annotated[int, IOAttrs('tk')]
|
||||
|
||||
# When it unlocks on its own.
|
||||
unlock_time: Annotated[datetime.datetime, IOAttrs('t')]
|
||||
|
||||
# Possible prizes we contain.
|
||||
prizesets: Annotated[list[PrizeSet], IOAttrs('p')]
|
||||
|
||||
# Are ads allowed now?
|
||||
ad_allow: Annotated[bool, IOAttrs('aa')]
|
||||
|
||||
chest: Annotated[Chest | None, IOAttrs('c')]
|
||||
user_tokens: Annotated[int | None, IOAttrs('t')]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class PrivatePartyMessage(Message):
|
||||
"""Message asking about info we need for private-party UI."""
|
||||
|
||||
need_datacode: Annotated[bool, IOAttrs('d')]
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_response_types(cls) -> list[type[Response] | None]:
|
||||
return [PrivatePartyResponse]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class PrivatePartyResponse(Response):
|
||||
"""Here's that private party UI info you asked for, boss."""
|
||||
|
||||
success: Annotated[bool, IOAttrs('s')]
|
||||
tokens: Annotated[int, IOAttrs('t')]
|
||||
gold_pass: Annotated[bool, IOAttrs('g')]
|
||||
datacode: Annotated[str | None, IOAttrs('d')]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class ScoreSubmitMessage(Message):
|
||||
"""Let the server know we got some score in something."""
|
||||
|
||||
score_token: Annotated[str, IOAttrs('t')]
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_response_types(cls) -> list[type[Response] | None]:
|
||||
return [ScoreSubmitResponse]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class ScoreSubmitResponse(Response):
|
||||
"""Did something to that inbox entry, boss."""
|
||||
|
||||
# Things we should show on our end.
|
||||
effects: Annotated[list[ClientEffect], IOAttrs('fx')]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class SendInfoMessage(Message):
|
||||
"""User is using the send-info function."""
|
||||
|
||||
description: Annotated[str, IOAttrs('c')]
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_response_types(cls) -> list[type[Response] | None]:
|
||||
return [SendInfoResponse]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class SendInfoResponse(Response):
|
||||
"""Response to sending info to the server."""
|
||||
|
||||
handled: Annotated[bool, IOAttrs('v')]
|
||||
message: Annotated[str | None, IOAttrs('m', store_default=False)] = None
|
||||
effects: Annotated[
|
||||
list[ClientEffect], IOAttrs('e', store_default=False)
|
||||
] = field(default_factory=list)
|
||||
legacy_code: Annotated[str | None, IOAttrs('l', store_default=False)] = None
|
||||
304
dist/ba_data/python/bauiv1/_cloudui.py
vendored
304
dist/ba_data/python/bauiv1/_cloudui.py
vendored
|
|
@ -1,304 +0,0 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""UIs provided by the cloud (similar-ish to html in concept)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import random
|
||||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING, override, Annotated
|
||||
|
||||
from efro.dataclassio import ioprepped, IOAttrs
|
||||
import babase
|
||||
from bauiv1._window import MainWindow, BasicMainWindowState
|
||||
import _bauiv1
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from bauiv1._window import MainWindowState
|
||||
|
||||
|
||||
def show_cloud_ui_window() -> None:
|
||||
"""Bust out a cloud-ui window."""
|
||||
|
||||
# Pop up an auxiliary window wherever we are in the nav stack.
|
||||
babase.app.ui_v1.auxiliary_window_activate(
|
||||
win_type=CloudUIWindow,
|
||||
win_create_call=lambda: CloudUIWindow(state=None),
|
||||
)
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class CloudUIButton:
|
||||
"""Represents a button in a cloud-ui."""
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class CloudUIRow:
|
||||
"""Represents a row in a cloud-ui."""
|
||||
|
||||
buttons: Annotated[list[CloudUIButton], IOAttrs('b')]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class CloudUIRoot:
|
||||
"""Represents an entire cloud-ui."""
|
||||
|
||||
title: Annotated[str, IOAttrs('t')]
|
||||
rows: Annotated[list[CloudUIRow], IOAttrs('r')]
|
||||
|
||||
|
||||
class CloudUIWindow(MainWindow):
|
||||
"""An example of a well-behaved main-window."""
|
||||
|
||||
@dataclass
|
||||
class _State:
|
||||
root: CloudUIRoot | None
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
state: _State | None,
|
||||
*,
|
||||
transition: str | None = 'in_right',
|
||||
origin_widget: _bauiv1.Widget | None = None,
|
||||
auxiliary_style: bool = True,
|
||||
):
|
||||
ui = babase.app.ui_v1
|
||||
|
||||
self._state: CloudUIWindow._State | None = None
|
||||
|
||||
# We want to display differently whether we're an auxiliary
|
||||
# window or not, but unfortunately that value is not yet
|
||||
# available until we're added to the main-window-stack so it
|
||||
# must be explicitly passed in.
|
||||
self._auxiliary_style = auxiliary_style
|
||||
|
||||
# Calc scale and size for our backing window. For medium & large
|
||||
# ui-scale we aim for a window small enough to always be fully
|
||||
# visible on-screen and for small mode we aim for a window big
|
||||
# enough that we never see the window edges; only the window
|
||||
# texture covering the whole screen.
|
||||
uiscale = ui.uiscale
|
||||
self._width = 1400 if uiscale is babase.UIScale.SMALL else 750
|
||||
self._height = 1200 if uiscale is babase.UIScale.SMALL else 500
|
||||
scale = (
|
||||
1.5
|
||||
if uiscale is babase.UIScale.SMALL
|
||||
else 1.2 if uiscale is babase.UIScale.MEDIUM else 1.0
|
||||
)
|
||||
|
||||
# Do some fancy math to calculate our visible area; this will be
|
||||
# limited by the screen size in small mode and our backing size
|
||||
# otherwise.
|
||||
screensize = babase.get_virtual_screen_size()
|
||||
self._vis_width = min(self._width - 100, screensize[0] / scale)
|
||||
self._vis_height = min(self._height - 100, screensize[1] / scale)
|
||||
self._vis_top = 0.5 * self._height + 0.5 * self._vis_height
|
||||
self._vis_left = 0.5 * self._width - 0.5 * self._vis_width
|
||||
|
||||
# Nudge our vis area up a bit when we can see the full backing
|
||||
# (visual fudge factor).
|
||||
if uiscale is not babase.UIScale.SMALL:
|
||||
self._vis_top += 12.0
|
||||
|
||||
super().__init__(
|
||||
root_widget=_bauiv1.containerwidget(
|
||||
size=(self._width, self._height),
|
||||
toolbar_visibility='menu_full',
|
||||
toolbar_cancel_button_style=(
|
||||
'close' if auxiliary_style else 'back'
|
||||
),
|
||||
scale=scale,
|
||||
),
|
||||
transition=transition,
|
||||
origin_widget=origin_widget,
|
||||
# We respond to screen size changes only at small ui-scale;
|
||||
# in other cases we assume our window remains fully visible
|
||||
# always (flip to windowed mode and resize the app window to
|
||||
# confirm this).
|
||||
refresh_on_screen_size_changes=uiscale is babase.UIScale.SMALL,
|
||||
)
|
||||
# Avoid complaints if nothing is selected under us.
|
||||
_bauiv1.widget(edit=self._root_widget, allow_preserve_selection=False)
|
||||
|
||||
# Title.
|
||||
self._title = _bauiv1.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(self._width * 0.5, self._vis_top - 20),
|
||||
size=(0, 0),
|
||||
text='',
|
||||
color=ui.title_color,
|
||||
scale=0.9 if uiscale is babase.UIScale.SMALL else 1.0,
|
||||
# Make sure we avoid overlapping meters in small mode.
|
||||
maxwidth=(130 if uiscale is babase.UIScale.SMALL else 200),
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
)
|
||||
|
||||
# For small UI-scale we use the system back/close button;
|
||||
# otherwise we make our own.
|
||||
if uiscale is babase.UIScale.SMALL:
|
||||
_bauiv1.containerwidget(
|
||||
edit=self._root_widget, on_cancel_call=self.main_window_back
|
||||
)
|
||||
else:
|
||||
btn = _bauiv1.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
id=f'{self.main_window_id_prefix}|close',
|
||||
scale=0.8,
|
||||
position=(self._vis_left - 15, self._vis_top - 30),
|
||||
size=(50, 50) if auxiliary_style else (60, 55),
|
||||
extra_touch_border_scale=2.0,
|
||||
button_type=None if auxiliary_style else 'backSmall',
|
||||
on_activate_call=self.main_window_back,
|
||||
autoselect=True,
|
||||
label=babase.charstr(
|
||||
babase.SpecialChar.CLOSE
|
||||
if auxiliary_style
|
||||
else babase.SpecialChar.BACK
|
||||
),
|
||||
)
|
||||
_bauiv1.containerwidget(edit=self._root_widget, cancel_button=btn)
|
||||
|
||||
# Show our vis-area bounds (for debugging).
|
||||
if bool(True):
|
||||
# Skip top-left since its always overlapping back/close
|
||||
# buttons.
|
||||
if bool(False):
|
||||
_bauiv1.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(self._vis_left, self._vis_top),
|
||||
size=(0, 0),
|
||||
color=(1, 1, 1, 0.5),
|
||||
scale=0.5,
|
||||
text='TL',
|
||||
h_align='left',
|
||||
v_align='top',
|
||||
)
|
||||
_bauiv1.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(self._vis_left + self._vis_width, self._vis_top),
|
||||
size=(0, 0),
|
||||
color=(1, 1, 1, 0.5),
|
||||
scale=0.5,
|
||||
text='TR',
|
||||
h_align='right',
|
||||
v_align='top',
|
||||
)
|
||||
_bauiv1.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(self._vis_left, self._vis_top - self._vis_height),
|
||||
size=(0, 0),
|
||||
color=(1, 1, 1, 0.5),
|
||||
scale=0.5,
|
||||
text='BL',
|
||||
h_align='left',
|
||||
v_align='bottom',
|
||||
)
|
||||
_bauiv1.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(
|
||||
self._vis_left + self._vis_width,
|
||||
self._vis_top - self._vis_height,
|
||||
),
|
||||
size=(0, 0),
|
||||
scale=0.5,
|
||||
color=(1, 1, 1, 0.5),
|
||||
text='BR',
|
||||
h_align='right',
|
||||
v_align='bottom',
|
||||
)
|
||||
|
||||
self._spinner: _bauiv1.Widget | None = _bauiv1.spinnerwidget(
|
||||
parent=self._root_widget,
|
||||
position=(
|
||||
self._vis_left + self._vis_width * 0.5,
|
||||
self._vis_top - self._vis_height * 0.5,
|
||||
),
|
||||
size=48,
|
||||
style='bomb',
|
||||
)
|
||||
|
||||
if state is not None:
|
||||
self._set_state(state)
|
||||
else:
|
||||
if random.random() < 0.3:
|
||||
babase.apptimer(1.0, babase.WeakCall(self._on_error_response))
|
||||
else:
|
||||
babase.apptimer(1.0, babase.WeakCall(self._on_response))
|
||||
|
||||
def _on_error_response(self) -> None:
|
||||
self._set_state(self._State(None))
|
||||
|
||||
def _on_response(self) -> None:
|
||||
self._set_state(self._State(CloudUIRoot(title='Testing', rows=[])))
|
||||
|
||||
def _set_state(self, state: _State) -> None:
|
||||
"""Set a final state (error or page contents).
|
||||
|
||||
This state may be instantly restored if the window is recreated
|
||||
(depending on cache lifespan/etc.)
|
||||
"""
|
||||
|
||||
assert self._state is None
|
||||
self._state = state
|
||||
|
||||
if self._spinner:
|
||||
self._spinner.delete()
|
||||
self._spinner = None
|
||||
|
||||
if self._state.root is None:
|
||||
_bauiv1.textwidget(
|
||||
edit=self._title,
|
||||
literal=False, # Allow Lstr.
|
||||
text=babase.Lstr(resource='errorText'),
|
||||
)
|
||||
_bauiv1.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(
|
||||
self._vis_left + 0.5 * self._vis_width,
|
||||
self._vis_top - 0.5 * self._vis_height,
|
||||
),
|
||||
size=(0, 0),
|
||||
scale=0.6,
|
||||
text=babase.Lstr(resource='store.loadErrorText'),
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
)
|
||||
else:
|
||||
_bauiv1.textwidget(
|
||||
edit=self._title,
|
||||
literal=True, # Never interpret as Lstr.
|
||||
text=self._state.root.title,
|
||||
)
|
||||
|
||||
@override
|
||||
def get_main_window_state(self) -> MainWindowState:
|
||||
# Support recreating our window for back/refresh purposes.
|
||||
cls = type(self)
|
||||
|
||||
# IMPORTANT - Pull values from self HERE; if we do it in the
|
||||
# lambda below it'll keep self alive which will lead to
|
||||
# 'ui-not-getting-cleaned-up' warnings and memory leaks.
|
||||
auxiliary_style = self._auxiliary_style
|
||||
state = self._state
|
||||
|
||||
return BasicMainWindowState(
|
||||
create_call=lambda transition, origin_widget: cls(
|
||||
state=state,
|
||||
transition=transition,
|
||||
origin_widget=origin_widget,
|
||||
auxiliary_style=auxiliary_style,
|
||||
),
|
||||
)
|
||||
|
||||
@override
|
||||
def main_window_should_preserve_selection(self) -> bool:
|
||||
return True
|
||||
|
||||
@override
|
||||
def get_main_window_shared_state_id(self) -> str | None:
|
||||
return 'cloudui'
|
||||
198
dist/ba_data/python/bauiv1lib/account/link.py
vendored
198
dist/ba_data/python/bauiv1lib/account/link.py
vendored
|
|
@ -1,198 +0,0 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""UI functionality for linking accounts."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import time
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import bauiv1 as bui
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Any
|
||||
|
||||
|
||||
class AccountLinkWindow(bui.Window):
|
||||
"""Window for linking accounts."""
|
||||
|
||||
def __init__(self, origin_widget: bui.Widget | None = None):
|
||||
plus = bui.app.plus
|
||||
assert plus is not None
|
||||
|
||||
scale_origin: tuple[float, float] | None
|
||||
if origin_widget is not None:
|
||||
self._transition_out = 'out_scale'
|
||||
scale_origin = origin_widget.get_screen_space_center()
|
||||
transition = 'in_scale'
|
||||
else:
|
||||
self._transition_out = 'out_right'
|
||||
scale_origin = None
|
||||
transition = 'in_right'
|
||||
bg_color = (0.4, 0.4, 0.5)
|
||||
self._width = 560
|
||||
self._height = 420
|
||||
assert bui.app.classic is not None
|
||||
uiscale = bui.app.ui_v1.uiscale
|
||||
base_scale = (
|
||||
1.65
|
||||
if uiscale is bui.UIScale.SMALL
|
||||
else 1.5 if uiscale is bui.UIScale.MEDIUM else 1.1
|
||||
)
|
||||
super().__init__(
|
||||
root_widget=bui.containerwidget(
|
||||
size=(self._width, self._height),
|
||||
transition=transition,
|
||||
scale=base_scale,
|
||||
scale_origin_stack_offset=scale_origin,
|
||||
stack_offset=(
|
||||
(0, -10) if uiscale is bui.UIScale.SMALL else (0, 0)
|
||||
),
|
||||
)
|
||||
)
|
||||
self._cancel_button = bui.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
position=(40, self._height - 45),
|
||||
size=(50, 50),
|
||||
scale=0.7,
|
||||
label='',
|
||||
color=bg_color,
|
||||
on_activate_call=self._cancel,
|
||||
autoselect=True,
|
||||
icon=bui.gettexture('crossOut'),
|
||||
iconscale=1.2,
|
||||
)
|
||||
maxlinks = plus.get_v1_account_misc_read_val('maxLinkAccounts', 5)
|
||||
bui.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(self._width * 0.5, self._height * 0.56),
|
||||
size=(0, 0),
|
||||
text=bui.Lstr(
|
||||
resource=(
|
||||
'accountSettingsWindow.linkAccountsInstructionsNewText'
|
||||
),
|
||||
subs=[('${COUNT}', str(maxlinks))],
|
||||
),
|
||||
maxwidth=self._width * 0.9,
|
||||
color=bui.app.ui_v1.infotextcolor,
|
||||
max_height=self._height * 0.6,
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
)
|
||||
bui.containerwidget(
|
||||
edit=self._root_widget, cancel_button=self._cancel_button
|
||||
)
|
||||
bui.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
position=(40, 30),
|
||||
size=(200, 60),
|
||||
label=bui.Lstr(
|
||||
resource='accountSettingsWindow.linkAccountsGenerateCodeText'
|
||||
),
|
||||
autoselect=True,
|
||||
on_activate_call=self._generate_press,
|
||||
)
|
||||
self._enter_code_button = bui.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
position=(self._width - 240, 30),
|
||||
size=(200, 60),
|
||||
label=bui.Lstr(
|
||||
resource='accountSettingsWindow.linkAccountsEnterCodeText'
|
||||
),
|
||||
autoselect=True,
|
||||
on_activate_call=self._enter_code_press,
|
||||
)
|
||||
|
||||
def _generate_press(self) -> None:
|
||||
from bauiv1lib.account.signin import show_sign_in_prompt
|
||||
|
||||
plus = bui.app.plus
|
||||
assert plus is not None
|
||||
|
||||
if plus.get_v1_account_state() != 'signed_in':
|
||||
show_sign_in_prompt()
|
||||
return
|
||||
bui.screenmessage(
|
||||
bui.Lstr(resource='gatherWindow.requestingAPromoCodeText'),
|
||||
color=(0, 1, 0),
|
||||
)
|
||||
plus.add_v1_account_transaction(
|
||||
{
|
||||
'type': 'ACCOUNT_LINK_CODE_REQUEST',
|
||||
'expire_time': time.time() + 5,
|
||||
}
|
||||
)
|
||||
plus.run_v1_account_transactions()
|
||||
|
||||
def _enter_code_press(self) -> None:
|
||||
from bauiv1lib.sendinfo import SendInfoWindow
|
||||
|
||||
SendInfoWindow(
|
||||
modal=True,
|
||||
legacy_code_mode=True,
|
||||
origin_widget=self._enter_code_button,
|
||||
)
|
||||
bui.containerwidget(
|
||||
edit=self._root_widget, transition=self._transition_out
|
||||
)
|
||||
|
||||
def _cancel(self) -> None:
|
||||
bui.containerwidget(
|
||||
edit=self._root_widget, transition=self._transition_out
|
||||
)
|
||||
|
||||
|
||||
class AccountLinkCodeWindow(bui.Window):
|
||||
"""Window showing code for account-linking."""
|
||||
|
||||
def __init__(self, data: dict[str, Any]):
|
||||
self._width = 350
|
||||
self._height = 200
|
||||
assert bui.app.classic is not None
|
||||
uiscale = bui.app.ui_v1.uiscale
|
||||
super().__init__(
|
||||
root_widget=bui.containerwidget(
|
||||
size=(self._width, self._height),
|
||||
color=(0.45, 0.63, 0.15),
|
||||
transition='in_scale',
|
||||
scale=(
|
||||
1.8
|
||||
if uiscale is bui.UIScale.SMALL
|
||||
else 1.35 if uiscale is bui.UIScale.MEDIUM else 1.0
|
||||
),
|
||||
)
|
||||
)
|
||||
self._data = copy.deepcopy(data)
|
||||
bui.getsound('cashRegister').play()
|
||||
bui.getsound('swish').play()
|
||||
self._cancel_button = bui.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
scale=0.5,
|
||||
position=(40, self._height - 40),
|
||||
size=(50, 50),
|
||||
label='',
|
||||
on_activate_call=self.close,
|
||||
autoselect=True,
|
||||
color=(0.45, 0.63, 0.15),
|
||||
icon=bui.gettexture('crossOut'),
|
||||
iconscale=1.2,
|
||||
)
|
||||
bui.containerwidget(
|
||||
edit=self._root_widget, cancel_button=self._cancel_button
|
||||
)
|
||||
bui.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(self._width * 0.5, self._height * 0.5),
|
||||
size=(0, 0),
|
||||
color=(1.0, 3.0, 1.0),
|
||||
scale=2.0,
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
text=data['code'],
|
||||
maxwidth=self._width * 0.85,
|
||||
)
|
||||
|
||||
def close(self) -> None:
|
||||
"""close the window"""
|
||||
bui.containerwidget(edit=self._root_widget, transition='out_scale')
|
||||
152
dist/ba_data/python/bauiv1lib/account/unlink.py
vendored
152
dist/ba_data/python/bauiv1lib/account/unlink.py
vendored
|
|
@ -1,152 +0,0 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""UI functionality for unlinking accounts."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import time
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import bauiv1 as bui
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Any
|
||||
|
||||
|
||||
class AccountUnlinkWindow(bui.Window):
|
||||
"""A window to kick off account unlinks."""
|
||||
|
||||
def __init__(self, origin_widget: bui.Widget | None = None):
|
||||
plus = bui.app.plus
|
||||
assert plus is not None
|
||||
|
||||
scale_origin: tuple[float, float] | None
|
||||
if origin_widget is not None:
|
||||
self._transition_out = 'out_scale'
|
||||
scale_origin = origin_widget.get_screen_space_center()
|
||||
transition = 'in_scale'
|
||||
else:
|
||||
self._transition_out = 'out_right'
|
||||
scale_origin = None
|
||||
transition = 'in_right'
|
||||
bg_color = (0.4, 0.4, 0.5)
|
||||
self._width = 540
|
||||
self._height = 350
|
||||
self._scroll_width = 400
|
||||
self._scroll_height = 200
|
||||
assert bui.app.classic is not None
|
||||
uiscale = bui.app.ui_v1.uiscale
|
||||
base_scale = (
|
||||
2.0
|
||||
if uiscale is bui.UIScale.SMALL
|
||||
else 1.6 if uiscale is bui.UIScale.MEDIUM else 1.1
|
||||
)
|
||||
super().__init__(
|
||||
root_widget=bui.containerwidget(
|
||||
size=(self._width, self._height),
|
||||
transition=transition,
|
||||
scale=base_scale,
|
||||
scale_origin_stack_offset=scale_origin,
|
||||
stack_offset=(
|
||||
(0, -10) if uiscale is bui.UIScale.SMALL else (0, 0)
|
||||
),
|
||||
)
|
||||
)
|
||||
self._cancel_button = bui.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
position=(30, self._height - 50),
|
||||
size=(50, 50),
|
||||
scale=0.7,
|
||||
label='',
|
||||
color=bg_color,
|
||||
on_activate_call=self._cancel,
|
||||
autoselect=True,
|
||||
icon=bui.gettexture('crossOut'),
|
||||
iconscale=1.2,
|
||||
)
|
||||
bui.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(self._width * 0.5, self._height * 0.88),
|
||||
size=(0, 0),
|
||||
text=bui.Lstr(
|
||||
resource='accountSettingsWindow.unlinkAccountsInstructionsText'
|
||||
),
|
||||
maxwidth=self._width * 0.7,
|
||||
color=bui.app.ui_v1.infotextcolor,
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
)
|
||||
bui.containerwidget(
|
||||
edit=self._root_widget, cancel_button=self._cancel_button
|
||||
)
|
||||
|
||||
self._scrollwidget = bui.scrollwidget(
|
||||
parent=self._root_widget,
|
||||
highlight=False,
|
||||
position=(
|
||||
(self._width - self._scroll_width) * 0.5,
|
||||
self._height - 85 - self._scroll_height,
|
||||
),
|
||||
size=(self._scroll_width, self._scroll_height),
|
||||
)
|
||||
bui.containerwidget(edit=self._scrollwidget, claims_left_right=True)
|
||||
self._columnwidget = bui.columnwidget(
|
||||
parent=self._scrollwidget, border=2, margin=0, left_border=10
|
||||
)
|
||||
|
||||
our_login_id = plus.get_v1_account_public_login_id()
|
||||
if our_login_id is None:
|
||||
entries = []
|
||||
else:
|
||||
account_infos = plus.get_v1_account_misc_read_val_2(
|
||||
'linkedAccounts2', []
|
||||
)
|
||||
entries = [
|
||||
{'name': ai['d'], 'id': ai['id']}
|
||||
for ai in account_infos
|
||||
if ai['id'] != our_login_id
|
||||
]
|
||||
|
||||
# (avoid getting our selection stuck on an empty column widget)
|
||||
if not entries:
|
||||
bui.containerwidget(edit=self._scrollwidget, selectable=False)
|
||||
for i, entry in enumerate(entries):
|
||||
txt = bui.textwidget(
|
||||
parent=self._columnwidget,
|
||||
selectable=True,
|
||||
text=entry['name'],
|
||||
size=(self._scroll_width - 30, 30),
|
||||
autoselect=True,
|
||||
click_activate=True,
|
||||
on_activate_call=bui.Call(self._on_entry_selected, entry),
|
||||
)
|
||||
bui.widget(edit=txt, left_widget=self._cancel_button)
|
||||
if i == 0:
|
||||
bui.widget(edit=txt, up_widget=self._cancel_button)
|
||||
|
||||
def _on_entry_selected(self, entry: dict[str, Any]) -> None:
|
||||
plus = bui.app.plus
|
||||
assert plus is not None
|
||||
|
||||
bui.screenmessage(
|
||||
bui.Lstr(
|
||||
resource='pleaseWaitText', fallback_resource='requestingText'
|
||||
),
|
||||
color=(0, 1, 0),
|
||||
)
|
||||
plus.add_v1_account_transaction(
|
||||
{
|
||||
'type': 'ACCOUNT_UNLINK_REQUEST',
|
||||
'accountID': entry['id'],
|
||||
'expire_time': time.time() + 5,
|
||||
}
|
||||
)
|
||||
plus.run_v1_account_transactions()
|
||||
bui.containerwidget(
|
||||
edit=self._root_widget, transition=self._transition_out
|
||||
)
|
||||
|
||||
def _cancel(self) -> None:
|
||||
bui.containerwidget(
|
||||
edit=self._root_widget, transition=self._transition_out
|
||||
)
|
||||
91
dist/ba_data/python/bauiv1lib/feedback.py
vendored
91
dist/ba_data/python/bauiv1lib/feedback.py
vendored
|
|
@ -1,91 +0,0 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""UI functionality related to users rating the game."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import bauiv1 as bui
|
||||
|
||||
|
||||
def ask_for_rating() -> bui.Widget | None:
|
||||
"""(internal)"""
|
||||
app = bui.app
|
||||
assert app.classic is not None
|
||||
platform = app.classic.platform
|
||||
subplatform = app.classic.subplatform
|
||||
|
||||
# FIXME: should whitelist platforms we *do* want this for.
|
||||
if bui.app.env.test:
|
||||
return None
|
||||
if not (
|
||||
platform == 'mac'
|
||||
or (platform == 'android' and subplatform in ['google', 'cardboard'])
|
||||
):
|
||||
return None
|
||||
width = 700
|
||||
height = 400
|
||||
spacing = 40
|
||||
assert bui.app.classic is not None
|
||||
uiscale = bui.app.ui_v1.uiscale
|
||||
dlg = bui.containerwidget(
|
||||
size=(width, height),
|
||||
transition='in_right',
|
||||
scale=(
|
||||
1.6
|
||||
if uiscale is bui.UIScale.SMALL
|
||||
else 1.35 if uiscale is bui.UIScale.MEDIUM else 1.0
|
||||
),
|
||||
)
|
||||
v = height - 50
|
||||
v -= spacing
|
||||
v -= 140
|
||||
bui.imagewidget(
|
||||
parent=dlg,
|
||||
position=(width / 2 - 100, v + 10),
|
||||
size=(200, 200),
|
||||
texture=bui.gettexture('cuteSpaz'),
|
||||
)
|
||||
bui.textwidget(
|
||||
parent=dlg,
|
||||
position=(15, v - 55),
|
||||
size=(width - 30, 30),
|
||||
color=bui.app.ui_v1.infotextcolor,
|
||||
text=bui.Lstr(
|
||||
resource='pleaseRateText',
|
||||
subs=[('${APP_NAME}', bui.Lstr(resource='titleText'))],
|
||||
),
|
||||
maxwidth=width * 0.95,
|
||||
max_height=130,
|
||||
scale=0.85,
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
)
|
||||
|
||||
def do_rating() -> None:
|
||||
# This is not currently in use anywhere.
|
||||
bui.screenmessage(bui.Lstr(resource='error'))
|
||||
# bui.open_url(url)
|
||||
bui.containerwidget(edit=dlg, transition='out_left')
|
||||
|
||||
bui.buttonwidget(
|
||||
parent=dlg,
|
||||
position=(60, 20),
|
||||
size=(200, 60),
|
||||
label=bui.Lstr(resource='wellSureText'),
|
||||
autoselect=True,
|
||||
on_activate_call=do_rating,
|
||||
)
|
||||
|
||||
def close() -> None:
|
||||
bui.containerwidget(edit=dlg, transition='out_left')
|
||||
|
||||
btn = bui.buttonwidget(
|
||||
parent=dlg,
|
||||
position=(width - 270, 20),
|
||||
size=(200, 60),
|
||||
label=bui.Lstr(resource='noThanksText'),
|
||||
autoselect=True,
|
||||
on_activate_call=close,
|
||||
)
|
||||
bui.containerwidget(edit=dlg, cancel_button=btn, selected_child=btn)
|
||||
return dlg
|
||||
476
dist/ba_data/python/bauiv1lib/profile/browser.py
vendored
476
dist/ba_data/python/bauiv1lib/profile/browser.py
vendored
|
|
@ -1,476 +0,0 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""UI functionality related to browsing player profiles."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, override
|
||||
|
||||
import bauiv1 as bui
|
||||
import bascenev1 as bs
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Any, ClassVar
|
||||
|
||||
|
||||
class ProfileBrowserWindow(bui.MainWindow):
|
||||
"""Window for browsing player profiles."""
|
||||
|
||||
# Keep track of this at the class level to share between instances.
|
||||
selected_profile: ClassVar[str | None] = None
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
transition: str | None = 'in_right',
|
||||
selected_profile: str | None = None,
|
||||
origin_widget: bui.Widget | None = None,
|
||||
minimal_toolbar: bool = False,
|
||||
):
|
||||
self._minimal_toolbar = minimal_toolbar
|
||||
back_label = bui.Lstr(resource='backText')
|
||||
assert bui.app.classic is not None
|
||||
uiscale = bui.app.ui_v1.uiscale
|
||||
self._width = 800.0 if uiscale is bui.UIScale.SMALL else 600.0
|
||||
x_inset = 100.0 if uiscale is bui.UIScale.SMALL else 0.0
|
||||
self._height = (
|
||||
360.0
|
||||
if uiscale is bui.UIScale.SMALL
|
||||
else 385.0 if uiscale is bui.UIScale.MEDIUM else 410.0
|
||||
)
|
||||
|
||||
# Need to handle out-transitions ourself for modal mode.
|
||||
if origin_widget is not None:
|
||||
self._transition_out = 'out_scale'
|
||||
else:
|
||||
self._transition_out = 'out_right'
|
||||
|
||||
self._r = 'playerProfilesWindow'
|
||||
|
||||
# Ensure we've got an account-profile in cases where we're signed in.
|
||||
assert bui.app.classic is not None
|
||||
bui.app.classic.accounts.ensure_have_account_player_profile()
|
||||
|
||||
top_extra = 20 if uiscale is bui.UIScale.SMALL else 0
|
||||
|
||||
super().__init__(
|
||||
root_widget=bui.containerwidget(
|
||||
size=(self._width, self._height + top_extra),
|
||||
toolbar_visibility=(
|
||||
'menu_minimal'
|
||||
if (uiscale is bui.UIScale.SMALL or minimal_toolbar)
|
||||
else 'menu_full'
|
||||
),
|
||||
scale=(
|
||||
2.5
|
||||
if uiscale is bui.UIScale.SMALL
|
||||
else 1.5 if uiscale is bui.UIScale.MEDIUM else 1.0
|
||||
),
|
||||
stack_offset=(
|
||||
(0, -14) if uiscale is bui.UIScale.SMALL else (0, 0)
|
||||
),
|
||||
),
|
||||
transition=transition,
|
||||
origin_widget=origin_widget,
|
||||
)
|
||||
|
||||
if bui.app.ui_v1.uiscale is bui.UIScale.SMALL:
|
||||
self._back_button = bui.get_special_widget('back_button')
|
||||
bui.containerwidget(
|
||||
edit=self._root_widget, on_cancel_call=self.main_window_back
|
||||
)
|
||||
else:
|
||||
self._back_button = btn = bui.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
id=f'{self.main_window_id_prefix}|back',
|
||||
position=(40 + x_inset, self._height - 59),
|
||||
size=(120, 60),
|
||||
scale=0.8,
|
||||
label=back_label,
|
||||
button_type='back',
|
||||
autoselect=True,
|
||||
on_activate_call=self.main_window_back,
|
||||
)
|
||||
bui.containerwidget(edit=self._root_widget, cancel_button=btn)
|
||||
bui.buttonwidget(
|
||||
edit=btn,
|
||||
button_type='backSmall',
|
||||
size=(60, 60),
|
||||
label=bui.charstr(bui.SpecialChar.BACK),
|
||||
)
|
||||
|
||||
bui.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(self._width * 0.5, self._height - 36),
|
||||
size=(0, 0),
|
||||
text=bui.Lstr(resource=f'{self._r}.titleText'),
|
||||
maxwidth=300,
|
||||
color=bui.app.ui_v1.title_color,
|
||||
scale=0.9,
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
)
|
||||
|
||||
scroll_height = self._height - 140.0
|
||||
self._scroll_width = self._width - (188 + x_inset * 2)
|
||||
v = self._height - 84.0
|
||||
h = 50 + x_inset
|
||||
b_color = (0.6, 0.53, 0.63)
|
||||
|
||||
scl = (
|
||||
1.055
|
||||
if uiscale is bui.UIScale.SMALL
|
||||
else 1.18 if uiscale is bui.UIScale.MEDIUM else 1.3
|
||||
)
|
||||
v -= 70.0 * scl
|
||||
self._new_button = bui.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
id=f'{self.main_window_id_prefix}|new',
|
||||
position=(h, v),
|
||||
size=(80, 66.0 * scl),
|
||||
on_activate_call=self._new_profile,
|
||||
color=b_color,
|
||||
button_type='square',
|
||||
autoselect=True,
|
||||
textcolor=(0.75, 0.7, 0.8),
|
||||
text_scale=0.7,
|
||||
label=bui.Lstr(resource=f'{self._r}.newButtonText'),
|
||||
)
|
||||
v -= 70.0 * scl
|
||||
self._edit_button = bui.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
id=f'{self.main_window_id_prefix}|edit',
|
||||
position=(h, v),
|
||||
size=(80, 66.0 * scl),
|
||||
on_activate_call=self._edit_profile,
|
||||
color=b_color,
|
||||
button_type='square',
|
||||
autoselect=True,
|
||||
textcolor=(0.75, 0.7, 0.8),
|
||||
text_scale=0.7,
|
||||
label=bui.Lstr(resource=f'{self._r}.editButtonText'),
|
||||
)
|
||||
v -= 70.0 * scl
|
||||
self._delete_button = bui.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
id=f'{self.main_window_id_prefix}|delete',
|
||||
position=(h, v),
|
||||
size=(80, 66.0 * scl),
|
||||
on_activate_call=self._delete_profile,
|
||||
color=b_color,
|
||||
button_type='square',
|
||||
autoselect=True,
|
||||
textcolor=(0.75, 0.7, 0.8),
|
||||
text_scale=0.7,
|
||||
label=bui.Lstr(resource=f'{self._r}.deleteButtonText'),
|
||||
)
|
||||
|
||||
v = self._height - 87
|
||||
|
||||
bui.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(self._width * 0.5, self._height - 71),
|
||||
size=(0, 0),
|
||||
text=bui.Lstr(resource=f'{self._r}.explanationText'),
|
||||
color=bui.app.ui_v1.infotextcolor,
|
||||
maxwidth=self._width * 0.83,
|
||||
scale=0.6,
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
)
|
||||
|
||||
self._scrollwidget = bui.scrollwidget(
|
||||
parent=self._root_widget,
|
||||
highlight=False,
|
||||
position=(140 + x_inset, v - scroll_height),
|
||||
size=(self._scroll_width, scroll_height),
|
||||
)
|
||||
bui.widget(
|
||||
edit=self._scrollwidget,
|
||||
autoselect=True,
|
||||
left_widget=self._new_button,
|
||||
)
|
||||
bui.containerwidget(
|
||||
edit=self._root_widget, selected_child=self._scrollwidget
|
||||
)
|
||||
self._subcontainer = bui.containerwidget(
|
||||
parent=self._scrollwidget,
|
||||
size=(self._scroll_width, 32),
|
||||
background=False,
|
||||
)
|
||||
v -= 255
|
||||
self._profiles: dict[str, dict[str, Any]] | None = None
|
||||
if selected_profile is not None:
|
||||
type(self).selected_profile = selected_profile
|
||||
self._profile_widgets: list[bui.Widget] = []
|
||||
self._refresh()
|
||||
|
||||
@override
|
||||
def get_main_window_state(self) -> bui.MainWindowState:
|
||||
# Support recreating our window for back/refresh purposes.
|
||||
cls = type(self)
|
||||
|
||||
minimal_toolbar = self._minimal_toolbar
|
||||
|
||||
return bui.BasicMainWindowState(
|
||||
create_call=lambda transition, origin_widget: cls(
|
||||
transition=transition,
|
||||
origin_widget=origin_widget,
|
||||
minimal_toolbar=minimal_toolbar,
|
||||
)
|
||||
)
|
||||
|
||||
@override
|
||||
def main_window_should_preserve_selection(self) -> bool:
|
||||
return True
|
||||
|
||||
# @override
|
||||
# def main_window_do_save_shared_state(self, state: dict) -> None:
|
||||
# state['selected_profile'] = self._selected_profile
|
||||
|
||||
# @override
|
||||
# def main_window_do_restore_shared_state(self, state: dict) -> None:
|
||||
# pval = state.get('selected_profile')
|
||||
# if isinstance(pval, str | None):
|
||||
# print('RESTORING', pval)
|
||||
# self._selected_profile = pval
|
||||
|
||||
def _new_profile(self) -> None:
|
||||
# pylint: disable=cyclic-import
|
||||
from bauiv1lib.profile.edit import EditProfileWindow
|
||||
from bauiv1lib.purchase import PurchaseWindow
|
||||
|
||||
# No-op if we're not the in-control main window.
|
||||
if not self.main_window_has_control():
|
||||
return
|
||||
|
||||
plus = bui.app.plus
|
||||
assert plus is not None
|
||||
|
||||
# Limit to a handful profiles if they don't have pro-options.
|
||||
max_non_pro_profiles = plus.get_v1_account_misc_read_val('mnpp', 5)
|
||||
assert self._profiles is not None
|
||||
assert bui.app.classic is not None
|
||||
if (
|
||||
bool(False) # Phasing out pro.
|
||||
and not bui.app.classic.accounts.have_pro_options()
|
||||
and len(self._profiles) >= max_non_pro_profiles
|
||||
):
|
||||
PurchaseWindow(
|
||||
items=['pro'],
|
||||
header_text=bui.Lstr(
|
||||
resource='unlockThisProfilesText',
|
||||
subs=[('${NUM}', str(max_non_pro_profiles))],
|
||||
),
|
||||
)
|
||||
return
|
||||
|
||||
# Clamp at 100 profiles (otherwise the server will and that's less
|
||||
# elegant looking).
|
||||
if len(self._profiles) > 100:
|
||||
bui.screenmessage(
|
||||
bui.Lstr(
|
||||
translate=(
|
||||
'serverResponses',
|
||||
'Max number of profiles reached.',
|
||||
)
|
||||
),
|
||||
color=(1, 0, 0),
|
||||
)
|
||||
bui.getsound('error').play()
|
||||
return
|
||||
|
||||
self.main_window_replace(
|
||||
lambda: EditProfileWindow(existing_profile=None)
|
||||
)
|
||||
|
||||
def _delete_profile(self) -> None:
|
||||
# pylint: disable=cyclic-import
|
||||
from bauiv1lib import confirm
|
||||
|
||||
if self.selected_profile is None:
|
||||
bui.getsound('error').play()
|
||||
bui.screenmessage(
|
||||
bui.Lstr(resource='nothingIsSelectedErrorText'), color=(1, 0, 0)
|
||||
)
|
||||
return
|
||||
if self.selected_profile == '__account__':
|
||||
bui.getsound('error').play()
|
||||
bui.screenmessage(
|
||||
bui.Lstr(resource=f'{self._r}.cantDeleteAccountProfileText'),
|
||||
color=(1, 0, 0),
|
||||
)
|
||||
return
|
||||
confirm.ConfirmWindow(
|
||||
bui.Lstr(
|
||||
resource=f'{self._r}.deleteConfirmText',
|
||||
subs=[('${PROFILE}', self.selected_profile)],
|
||||
),
|
||||
self._do_delete_profile,
|
||||
width=350,
|
||||
)
|
||||
|
||||
def _do_delete_profile(self) -> None:
|
||||
plus = bui.app.plus
|
||||
assert plus is not None
|
||||
|
||||
# Go back to default selection.
|
||||
type(self).selected_profile = None
|
||||
|
||||
plus.add_v1_account_transaction(
|
||||
{'type': 'REMOVE_PLAYER_PROFILE', 'name': self.selected_profile}
|
||||
)
|
||||
plus.run_v1_account_transactions()
|
||||
bui.getsound('shieldDown').play()
|
||||
self._refresh()
|
||||
|
||||
# Select profile list.
|
||||
bui.containerwidget(
|
||||
edit=self._root_widget, selected_child=self._scrollwidget
|
||||
)
|
||||
|
||||
def _edit_profile(self) -> None:
|
||||
# pylint: disable=cyclic-import
|
||||
from bauiv1lib.profile.edit import EditProfileWindow
|
||||
|
||||
# No-op if we're not in control.
|
||||
if not self.main_window_has_control():
|
||||
return
|
||||
|
||||
if self.selected_profile is None:
|
||||
bui.getsound('error').play()
|
||||
bui.screenmessage(
|
||||
bui.Lstr(resource='nothingIsSelectedErrorText'), color=(1, 0, 0)
|
||||
)
|
||||
return
|
||||
|
||||
self.main_window_replace(
|
||||
lambda: EditProfileWindow(self.selected_profile)
|
||||
)
|
||||
|
||||
def _select(self, name: str, index: int) -> None:
|
||||
del index # Unused.
|
||||
type(self).selected_profile = name
|
||||
|
||||
def _refresh(self) -> None:
|
||||
# pylint: disable=too-many-locals
|
||||
# pylint: disable=too-many-statements
|
||||
from efro.util import asserttype
|
||||
from bascenev1 import PlayerProfilesChangedMessage
|
||||
from bascenev1lib.actor import spazappearance
|
||||
|
||||
assert bui.app.classic is not None
|
||||
|
||||
plus = bui.app.plus
|
||||
assert plus is not None
|
||||
|
||||
old_selection = self.selected_profile
|
||||
|
||||
# Delete old.
|
||||
while self._profile_widgets:
|
||||
self._profile_widgets.pop().delete()
|
||||
self._profiles = bui.app.config.get('Player Profiles', {})
|
||||
assert self._profiles is not None
|
||||
items = list(self._profiles.items())
|
||||
items.sort(key=lambda x: asserttype(x[0], str).lower())
|
||||
spazzes = spazappearance.get_appearances()
|
||||
spazzes.sort()
|
||||
icon_textures = [
|
||||
bui.gettexture(bui.app.classic.spaz_appearances[s].icon_texture)
|
||||
for s in spazzes
|
||||
]
|
||||
icon_tint_textures = [
|
||||
bui.gettexture(
|
||||
bui.app.classic.spaz_appearances[s].icon_mask_texture
|
||||
)
|
||||
for s in spazzes
|
||||
]
|
||||
index = 0
|
||||
y_val = 35 * (len(self._profiles) - 1)
|
||||
account_name: str | None
|
||||
if plus.get_v1_account_state() == 'signed_in':
|
||||
account_name = plus.get_v1_account_display_string()
|
||||
else:
|
||||
account_name = None
|
||||
widget_to_select = None
|
||||
for p_name, p_info in items:
|
||||
if p_name == '__account__' and account_name is None:
|
||||
continue
|
||||
color, _highlight = bui.app.classic.get_player_profile_colors(
|
||||
p_name
|
||||
)
|
||||
scl = 1.1
|
||||
tval = (
|
||||
account_name
|
||||
if p_name == '__account__'
|
||||
else bui.app.classic.get_player_profile_icon(p_name) + p_name
|
||||
)
|
||||
|
||||
try:
|
||||
char_index = spazzes.index(p_info['character'])
|
||||
except Exception:
|
||||
char_index = spazzes.index('Spaz')
|
||||
|
||||
assert isinstance(tval, str)
|
||||
txtw = bui.textwidget(
|
||||
parent=self._subcontainer,
|
||||
id=f'{self.main_window_id_prefix}|profile{index}',
|
||||
position=(5, y_val),
|
||||
size=((self._width - 210) / scl, 28),
|
||||
text=bui.Lstr(value=f' {tval}'),
|
||||
h_align='left',
|
||||
v_align='center',
|
||||
on_select_call=bui.WeakCall(self._select, p_name, index),
|
||||
maxwidth=self._scroll_width * 0.86,
|
||||
corner_scale=scl,
|
||||
color=bui.safecolor(color, 0.4),
|
||||
always_highlight=True,
|
||||
on_activate_call=bui.Call(self._edit_button.activate),
|
||||
selectable=True,
|
||||
)
|
||||
# We handle reselection of these manually; no need for ids.
|
||||
bui.widget(edit=txtw, allow_preserve_selection=False)
|
||||
|
||||
character = bui.imagewidget(
|
||||
parent=self._subcontainer,
|
||||
position=(0, y_val),
|
||||
size=(30, 30),
|
||||
color=(1, 1, 1),
|
||||
mask_texture=bui.gettexture('characterIconMask'),
|
||||
tint_color=color,
|
||||
tint2_color=_highlight,
|
||||
texture=icon_textures[char_index],
|
||||
tint_texture=icon_tint_textures[char_index],
|
||||
)
|
||||
if index == 0:
|
||||
bui.widget(edit=txtw, up_widget=self._back_button)
|
||||
if self.selected_profile is None:
|
||||
type(self).selected_profile = p_name
|
||||
bui.widget(edit=txtw, show_buffer_top=40, show_buffer_bottom=40)
|
||||
self._profile_widgets.append(txtw)
|
||||
self._profile_widgets.append(character)
|
||||
|
||||
# Select/show this one if it was previously selected.
|
||||
# (but defer till after this loop since our height is
|
||||
# still changing).
|
||||
if p_name == old_selection or widget_to_select is None:
|
||||
widget_to_select = txtw
|
||||
|
||||
index += 1
|
||||
y_val -= 35
|
||||
|
||||
bui.containerwidget(
|
||||
edit=self._subcontainer,
|
||||
size=(self._scroll_width, index * 35),
|
||||
)
|
||||
if widget_to_select is not None:
|
||||
bui.containerwidget(
|
||||
edit=self._subcontainer,
|
||||
selected_child=widget_to_select,
|
||||
visible_child=widget_to_select,
|
||||
)
|
||||
|
||||
# If there's a team-chooser in existence, tell it the profile-list
|
||||
# has probably changed.
|
||||
session = bs.get_foreground_host_session()
|
||||
if session is not None:
|
||||
session.handlemessage(PlayerProfilesChangedMessage())
|
||||
209
dist/ba_data/python/bauiv1lib/purchase.py
vendored
209
dist/ba_data/python/bauiv1lib/purchase.py
vendored
|
|
@ -1,209 +0,0 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""UI related to purchasing items."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import bauiv1 as bui
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Any
|
||||
|
||||
|
||||
class PurchaseWindow(bui.Window):
|
||||
"""Window for purchasing one or more items."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
items: list[str],
|
||||
origin_widget: bui.Widget | None = None,
|
||||
header_text: bui.Lstr | None = None,
|
||||
):
|
||||
from bauiv1lib.store.item import instantiate_store_item_display
|
||||
|
||||
plus = bui.app.plus
|
||||
assert plus is not None
|
||||
|
||||
assert bui.app.classic is not None
|
||||
store = bui.app.classic.store
|
||||
|
||||
if header_text is None:
|
||||
header_text = bui.Lstr(
|
||||
resource='unlockThisText',
|
||||
fallback_resource='unlockThisInTheStoreText',
|
||||
)
|
||||
if len(items) != 1:
|
||||
raise ValueError('expected exactly 1 item')
|
||||
|
||||
self._idprefix = bui.app.ui_v1.new_id_prefix('purchase')
|
||||
self._items = list(items)
|
||||
self._width = 580
|
||||
self._height = 520
|
||||
uiscale = bui.app.ui_v1.uiscale
|
||||
|
||||
if origin_widget is not None:
|
||||
scale_origin = origin_widget.get_screen_space_center()
|
||||
else:
|
||||
scale_origin = None
|
||||
|
||||
super().__init__(
|
||||
root_widget=bui.containerwidget(
|
||||
parent=bui.get_special_widget('overlay_stack'),
|
||||
size=(self._width, self._height),
|
||||
transition='in_scale',
|
||||
toolbar_visibility='menu_store',
|
||||
scale=(
|
||||
1.2
|
||||
if uiscale is bui.UIScale.SMALL
|
||||
else 1.1 if uiscale is bui.UIScale.MEDIUM else 1.0
|
||||
),
|
||||
scale_origin_stack_offset=scale_origin,
|
||||
stack_offset=(
|
||||
(0, -15) if uiscale is bui.UIScale.SMALL else (0, 0)
|
||||
),
|
||||
darken_behind=True,
|
||||
)
|
||||
)
|
||||
self._is_double = False
|
||||
self._title_text = bui.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(self._width * 0.5, self._height - 30),
|
||||
size=(0, 0),
|
||||
text=header_text,
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
maxwidth=self._width * 0.9 - 120,
|
||||
scale=1.2,
|
||||
color=(1, 0.8, 0.3, 1),
|
||||
)
|
||||
size = store.get_store_item_display_size(items[0])
|
||||
display: dict[str, Any] = {}
|
||||
instantiate_store_item_display(
|
||||
items[0],
|
||||
display,
|
||||
idprefix=self._idprefix,
|
||||
parent_widget=self._root_widget,
|
||||
b_pos=(
|
||||
self._width * 0.5
|
||||
- size[0] * 0.5
|
||||
+ 10
|
||||
- ((size[0] * 0.5 + 30) if self._is_double else 0),
|
||||
self._height * 0.5
|
||||
- size[1] * 0.5
|
||||
+ 30
|
||||
+ (20 if self._is_double else 0),
|
||||
),
|
||||
b_width=size[0],
|
||||
b_height=size[1],
|
||||
button=False,
|
||||
)
|
||||
|
||||
# Wire up the parts we need.
|
||||
if self._is_double:
|
||||
pass # not working
|
||||
else:
|
||||
if self._items == ['pro']:
|
||||
price_str = plus.get_price(self._items[0])
|
||||
pyoffs = -15
|
||||
else:
|
||||
pyoffs = 0
|
||||
price = self._price = plus.get_v1_account_misc_read_val(
|
||||
'price.' + str(items[0]), -1
|
||||
)
|
||||
price_str = bui.charstr(bui.SpecialChar.TICKET) + str(price)
|
||||
self._price_text = bui.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(self._width * 0.5, 150 + pyoffs),
|
||||
size=(0, 0),
|
||||
text=price_str,
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
maxwidth=self._width * 0.9,
|
||||
scale=1.4,
|
||||
color=(0.2, 1, 0.2),
|
||||
)
|
||||
|
||||
self._update_timer = bui.AppTimer(
|
||||
1.0, bui.WeakCall(self._update), repeat=True
|
||||
)
|
||||
|
||||
self._cancel_button = bui.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
position=(50, 40),
|
||||
size=(150, 60),
|
||||
scale=1.0,
|
||||
on_activate_call=self._cancel,
|
||||
autoselect=True,
|
||||
label=bui.Lstr(resource='cancelText'),
|
||||
)
|
||||
self._purchase_button = bui.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
position=(self._width - 200, 40),
|
||||
size=(150, 60),
|
||||
scale=1.0,
|
||||
on_activate_call=self._purchase,
|
||||
autoselect=True,
|
||||
label=bui.Lstr(resource='store.purchaseText'),
|
||||
)
|
||||
|
||||
bui.containerwidget(
|
||||
edit=self._root_widget,
|
||||
cancel_button=self._cancel_button,
|
||||
start_button=self._purchase_button,
|
||||
selected_child=self._purchase_button,
|
||||
)
|
||||
|
||||
def _update(self) -> None:
|
||||
can_die = False
|
||||
|
||||
plus = bui.app.plus
|
||||
assert plus is not None
|
||||
|
||||
# We go away if we see that our target item is owned.
|
||||
if self._items == ['pro']:
|
||||
assert bui.app.classic is not None
|
||||
if bui.app.classic.accounts.have_pro():
|
||||
can_die = True
|
||||
else:
|
||||
assert bui.app.classic is not None
|
||||
if self._items[0] in bui.app.classic.purchases:
|
||||
can_die = True
|
||||
|
||||
if can_die:
|
||||
bui.containerwidget(edit=self._root_widget, transition='out_scale')
|
||||
|
||||
def _purchase(self) -> None:
|
||||
|
||||
plus = bui.app.plus
|
||||
assert plus is not None
|
||||
classic = bui.app.classic
|
||||
assert classic is not None
|
||||
|
||||
if self._items == ['pro']:
|
||||
plus.purchase('pro')
|
||||
else:
|
||||
ticket_count: int | None
|
||||
try:
|
||||
ticket_count = classic.tickets
|
||||
except Exception:
|
||||
ticket_count = None
|
||||
if ticket_count is not None and ticket_count < self._price:
|
||||
bui.getsound('error').play()
|
||||
bui.screenmessage(
|
||||
bui.Lstr(resource='notEnoughTicketsText'),
|
||||
color=(1, 0, 0),
|
||||
)
|
||||
return
|
||||
|
||||
def do_it() -> None:
|
||||
assert plus is not None
|
||||
|
||||
plus.in_game_purchase(self._items[0], self._price)
|
||||
|
||||
bui.getsound('swish').play()
|
||||
do_it()
|
||||
|
||||
def _cancel(self) -> None:
|
||||
bui.containerwidget(edit=self._root_widget, transition='out_scale')
|
||||
|
|
@ -1 +0,0 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
1249
dist/ba_data/python/bauiv1lib/store/browser.py
vendored
1249
dist/ba_data/python/bauiv1lib/store/browser.py
vendored
File diff suppressed because it is too large
Load diff
729
dist/ba_data/python/bauiv1lib/store/item.py
vendored
729
dist/ba_data/python/bauiv1lib/store/item.py
vendored
|
|
@ -1,729 +0,0 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""UI functionality related to UI items."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import bascenev1 as bs
|
||||
import bauiv1 as bui
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Any
|
||||
|
||||
|
||||
def instantiate_store_item_display(
|
||||
item_name: str,
|
||||
item: dict[str, Any],
|
||||
*,
|
||||
parent_widget: bui.Widget,
|
||||
b_pos: tuple[float, float],
|
||||
b_width: float,
|
||||
b_height: float,
|
||||
idprefix: str,
|
||||
boffs_h: float = 0.0,
|
||||
boffs_h2: float = 0.0,
|
||||
boffs_v2: float = 0,
|
||||
delay: float = 0.0,
|
||||
button: bool = True,
|
||||
) -> None:
|
||||
"""(internal)"""
|
||||
# pylint: disable=too-many-statements
|
||||
# pylint: disable=too-many-branches
|
||||
# pylint: disable=too-many-locals
|
||||
assert bui.app.classic is not None
|
||||
store = bui.app.classic.store
|
||||
|
||||
plus = bui.app.plus
|
||||
assert plus is not None
|
||||
|
||||
del boffs_h # unused arg
|
||||
del boffs_h2 # unused arg
|
||||
del boffs_v2 # unused arg
|
||||
item_info = store.get_store_item(item_name)
|
||||
title_v = 0.24
|
||||
price_v = 0.145
|
||||
base_text_scale = 1.0
|
||||
|
||||
item['name'] = title = store.get_store_item_name_translated(item_name)
|
||||
|
||||
btn: bui.Widget | None
|
||||
|
||||
# Hack; showbuffer stuff isn't working well when we're showing merch.
|
||||
showbuffer = 10 if item_name in {'merch', 'pro', 'pro_sale'} else 76.0
|
||||
|
||||
if button:
|
||||
item['button'] = btn = bui.buttonwidget(
|
||||
parent=parent_widget,
|
||||
id=f'{idprefix}|store_item.{item_name}',
|
||||
position=b_pos,
|
||||
transition_delay=delay,
|
||||
show_buffer_top=showbuffer,
|
||||
enable_sound=False,
|
||||
button_type='square',
|
||||
size=(b_width, b_height),
|
||||
autoselect=True,
|
||||
label='',
|
||||
)
|
||||
bui.widget(edit=btn, show_buffer_bottom=showbuffer)
|
||||
else:
|
||||
btn = None
|
||||
|
||||
b_offs_x = -0.015 * b_width
|
||||
check_pos = 0.76
|
||||
|
||||
icon_tex = None
|
||||
tint_tex = None
|
||||
tint_color = None
|
||||
tint2_color = None
|
||||
tex_name: str | None = None
|
||||
desc: bui.Lstr | None = None
|
||||
modes: bui.Lstr | None = None
|
||||
|
||||
if item_name.startswith('characters.'):
|
||||
assert bui.app.classic is not None
|
||||
character = bui.app.classic.spaz_appearances[item_info['character']]
|
||||
tint_color = (
|
||||
item_info['color']
|
||||
if 'color' in item_info
|
||||
else (
|
||||
character.default_color
|
||||
if character.default_color is not None
|
||||
else (1, 1, 1)
|
||||
)
|
||||
)
|
||||
tint2_color = (
|
||||
item_info['highlight']
|
||||
if 'highlight' in item_info
|
||||
else (
|
||||
character.default_highlight
|
||||
if character.default_highlight is not None
|
||||
else (1, 1, 1)
|
||||
)
|
||||
)
|
||||
icon_tex = character.icon_texture
|
||||
tint_tex = character.icon_mask_texture
|
||||
title_v = 0.255
|
||||
price_v = 0.145
|
||||
elif item_name == 'merch':
|
||||
base_text_scale = 0.6
|
||||
title_v = 0.85
|
||||
price_v = 0.15
|
||||
elif item_name in ['upgrades.pro', 'pro']:
|
||||
base_text_scale = 0.6
|
||||
title_v = 0.85
|
||||
price_v = 0.15
|
||||
elif item_name.startswith('maps.'):
|
||||
map_type = item_info['map_type']
|
||||
tex_name = map_type.get_preview_texture_name()
|
||||
title_v = 0.312
|
||||
price_v = 0.17
|
||||
|
||||
elif item_name.startswith('games.'):
|
||||
gametype = item_info['gametype']
|
||||
modes_l = []
|
||||
if gametype.supports_session_type(bs.CoopSession):
|
||||
modes_l.append(bui.Lstr(resource='playModes.coopText'))
|
||||
if gametype.supports_session_type(bs.DualTeamSession):
|
||||
modes_l.append(bui.Lstr(resource='playModes.teamsText'))
|
||||
if gametype.supports_session_type(bs.FreeForAllSession):
|
||||
modes_l.append(bui.Lstr(resource='playModes.freeForAllText'))
|
||||
|
||||
if len(modes_l) == 3:
|
||||
modes = bui.Lstr(
|
||||
value='${A}, ${B}, ${C}',
|
||||
subs=[
|
||||
('${A}', modes_l[0]),
|
||||
('${B}', modes_l[1]),
|
||||
('${C}', modes_l[2]),
|
||||
],
|
||||
)
|
||||
elif len(modes_l) == 2:
|
||||
modes = bui.Lstr(
|
||||
value='${A}, ${B}',
|
||||
subs=[('${A}', modes_l[0]), ('${B}', modes_l[1])],
|
||||
)
|
||||
elif len(modes_l) == 1:
|
||||
modes = modes_l[0]
|
||||
else:
|
||||
raise RuntimeError()
|
||||
desc = gametype.get_description_display_string(bs.CoopSession)
|
||||
tex_name = item_info['previewTex']
|
||||
base_text_scale = 0.8
|
||||
title_v = 0.48
|
||||
price_v = 0.17
|
||||
elif item_name == 'upgrades.infinite_runaround':
|
||||
base_text_scale = 0.8
|
||||
desc = bui.Lstr(
|
||||
translate=(
|
||||
'gameDescriptions',
|
||||
'Prevent enemies from reaching the exit.',
|
||||
)
|
||||
)
|
||||
modes = bui.Lstr(resource='playModes.coopText')
|
||||
tex_name = 'towerDPreview'
|
||||
title_v = 0.48
|
||||
price_v = 0.17
|
||||
elif item_name == 'upgrades.infinite_onslaught':
|
||||
base_text_scale = 0.8
|
||||
desc = bui.Lstr(
|
||||
translate=(
|
||||
'gameDescriptions',
|
||||
'Defeat all enemies.',
|
||||
)
|
||||
)
|
||||
modes = bui.Lstr(resource='playModes.coopText')
|
||||
tex_name = 'doomShroomPreview'
|
||||
title_v = 0.48
|
||||
price_v = 0.17
|
||||
|
||||
elif item_name.startswith('icons.'):
|
||||
base_text_scale = 1.5
|
||||
price_v = 0.2
|
||||
check_pos = 0.6
|
||||
|
||||
if item_name.startswith('characters.'):
|
||||
frame_size = b_width * 0.7
|
||||
im_dim = frame_size * (100.0 / 113.0)
|
||||
im_pos = (
|
||||
b_pos[0] + b_width * 0.5 - im_dim * 0.5 + b_offs_x,
|
||||
b_pos[1] + b_height * 0.57 - im_dim * 0.5,
|
||||
)
|
||||
mask_texture = bui.gettexture('characterIconMask')
|
||||
assert icon_tex is not None
|
||||
assert tint_tex is not None
|
||||
bui.imagewidget(
|
||||
parent=parent_widget,
|
||||
position=im_pos,
|
||||
size=(im_dim, im_dim),
|
||||
color=(1, 1, 1),
|
||||
transition_delay=delay,
|
||||
mask_texture=mask_texture,
|
||||
draw_controller=btn,
|
||||
texture=bui.gettexture(icon_tex),
|
||||
tint_texture=bui.gettexture(tint_tex),
|
||||
tint_color=tint_color,
|
||||
tint2_color=tint2_color,
|
||||
)
|
||||
|
||||
if item_name == 'merch':
|
||||
frame_size = b_width * 0.65
|
||||
im_dim = frame_size * (100.0 / 113.0)
|
||||
im_pos = (
|
||||
b_pos[0] + b_width * 0.5 - im_dim * 0.5 + b_offs_x,
|
||||
b_pos[1] + b_height * 0.47 - im_dim * 0.5,
|
||||
)
|
||||
bui.imagewidget(
|
||||
parent=parent_widget,
|
||||
position=im_pos,
|
||||
size=(im_dim, im_dim),
|
||||
transition_delay=delay,
|
||||
draw_controller=btn,
|
||||
opacity=1.0,
|
||||
texture=bui.gettexture('merch'),
|
||||
)
|
||||
|
||||
if item_name in ['pro', 'upgrades.pro']:
|
||||
frame_size = b_width * 0.5
|
||||
im_dim = frame_size * (100.0 / 113.0)
|
||||
im_pos = (
|
||||
b_pos[0] + b_width * 0.5 - im_dim * 0.5 + b_offs_x,
|
||||
b_pos[1] + b_height * 0.5 - im_dim * 0.5,
|
||||
)
|
||||
bui.imagewidget(
|
||||
parent=parent_widget,
|
||||
position=im_pos,
|
||||
size=(im_dim, im_dim),
|
||||
transition_delay=delay,
|
||||
draw_controller=btn,
|
||||
color=(0.3, 0.0, 0.3),
|
||||
opacity=0.3,
|
||||
texture=bui.gettexture('logo'),
|
||||
)
|
||||
txt = bui.Lstr(resource='store.bombSquadProNewDescriptionText')
|
||||
|
||||
item['descriptionText'] = bui.textwidget(
|
||||
parent=parent_widget,
|
||||
text=txt,
|
||||
position=(b_pos[0] + b_width * 0.5, b_pos[1] + b_height * 0.69),
|
||||
transition_delay=delay,
|
||||
scale=b_width * (1.0 / 230.0) * base_text_scale * 0.75,
|
||||
maxwidth=b_width * 0.75,
|
||||
max_height=b_height * 0.2,
|
||||
size=(0, 0),
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
draw_controller=btn,
|
||||
color=(0.3, 1, 0.3),
|
||||
)
|
||||
|
||||
extra_backings = item['extra_backings'] = []
|
||||
extra_images = item['extra_images'] = []
|
||||
extra_texts = item['extra_texts'] = []
|
||||
extra_texts_2 = item['extra_texts_2'] = []
|
||||
|
||||
backing_color = (0.5, 0.8, 0.3) if button else (0.6, 0.5, 0.65)
|
||||
b_square_texture = bui.gettexture('buttonSquare')
|
||||
char_mask_texture = bui.gettexture('characterIconMask')
|
||||
|
||||
pos = (0.17, 0.43)
|
||||
tile_size = (b_width * 0.16 * 1.2, b_width * 0.2 * 1.2)
|
||||
tile_pos = (b_pos[0] + b_width * pos[0], b_pos[1] + b_height * pos[1])
|
||||
extra_backings.append(
|
||||
bui.imagewidget(
|
||||
parent=parent_widget,
|
||||
position=(
|
||||
tile_pos[0] - tile_size[0] * 0.5,
|
||||
tile_pos[1] - tile_size[1] * 0.5,
|
||||
),
|
||||
size=tile_size,
|
||||
transition_delay=delay,
|
||||
draw_controller=btn,
|
||||
color=backing_color,
|
||||
texture=b_square_texture,
|
||||
)
|
||||
)
|
||||
im_size = tile_size[0] * 0.8
|
||||
extra_images.append(
|
||||
bui.imagewidget(
|
||||
parent=parent_widget,
|
||||
position=(
|
||||
tile_pos[0] - im_size * 0.5,
|
||||
tile_pos[1] - im_size * 0.4,
|
||||
),
|
||||
size=(im_size, im_size),
|
||||
transition_delay=delay,
|
||||
draw_controller=btn,
|
||||
color=(1, 1, 1),
|
||||
texture=bui.gettexture('ticketsMore'),
|
||||
)
|
||||
)
|
||||
bonus_tickets = str(
|
||||
plus.get_v1_account_misc_read_val('proBonusTickets', 100)
|
||||
)
|
||||
extra_texts.append(
|
||||
bui.textwidget(
|
||||
parent=parent_widget,
|
||||
draw_controller=btn,
|
||||
position=(
|
||||
tile_pos[0] - tile_size[0] * 0.03,
|
||||
tile_pos[1] - tile_size[1] * 0.25,
|
||||
),
|
||||
size=(0, 0),
|
||||
color=(0.6, 1, 0.6),
|
||||
transition_delay=delay,
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
maxwidth=tile_size[0] * 0.7,
|
||||
scale=0.55,
|
||||
text=bui.Lstr(
|
||||
resource='getTicketsWindow.ticketsText',
|
||||
subs=[('${COUNT}', bonus_tickets)],
|
||||
),
|
||||
flatness=1.0,
|
||||
shadow=0.0,
|
||||
)
|
||||
)
|
||||
|
||||
for charname, pos in [
|
||||
('Kronk', (0.32, 0.45)),
|
||||
('Zoe', (0.425, 0.4)),
|
||||
('Jack Morgan', (0.555, 0.45)),
|
||||
('Mel', (0.645, 0.4)),
|
||||
]:
|
||||
tile_size = (b_width * 0.16 * 0.9, b_width * 0.2 * 0.9)
|
||||
tile_pos = (
|
||||
b_pos[0] + b_width * pos[0],
|
||||
b_pos[1] + b_height * pos[1],
|
||||
)
|
||||
assert bui.app.classic is not None
|
||||
character = bui.app.classic.spaz_appearances[charname]
|
||||
extra_backings.append(
|
||||
bui.imagewidget(
|
||||
parent=parent_widget,
|
||||
position=(
|
||||
tile_pos[0] - tile_size[0] * 0.5,
|
||||
tile_pos[1] - tile_size[1] * 0.5,
|
||||
),
|
||||
size=tile_size,
|
||||
transition_delay=delay,
|
||||
draw_controller=btn,
|
||||
color=backing_color,
|
||||
texture=b_square_texture,
|
||||
)
|
||||
)
|
||||
im_size = tile_size[0] * 0.7
|
||||
extra_images.append(
|
||||
bui.imagewidget(
|
||||
parent=parent_widget,
|
||||
position=(
|
||||
tile_pos[0] - im_size * 0.53,
|
||||
tile_pos[1] - im_size * 0.35,
|
||||
),
|
||||
size=(im_size, im_size),
|
||||
transition_delay=delay,
|
||||
draw_controller=btn,
|
||||
color=(1, 1, 1),
|
||||
texture=bui.gettexture(character.icon_texture),
|
||||
tint_texture=bui.gettexture(character.icon_mask_texture),
|
||||
tint_color=character.default_color,
|
||||
tint2_color=character.default_highlight,
|
||||
mask_texture=char_mask_texture,
|
||||
)
|
||||
)
|
||||
extra_texts.append(
|
||||
bui.textwidget(
|
||||
parent=parent_widget,
|
||||
draw_controller=btn,
|
||||
position=(
|
||||
tile_pos[0] - im_size * 0.03,
|
||||
tile_pos[1] - im_size * 0.51,
|
||||
),
|
||||
size=(0, 0),
|
||||
color=(0.6, 1, 0.6),
|
||||
transition_delay=delay,
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
maxwidth=tile_size[0] * 0.7,
|
||||
scale=0.55,
|
||||
text=bui.Lstr(translate=('characterNames', charname)),
|
||||
flatness=1.0,
|
||||
shadow=0.0,
|
||||
)
|
||||
)
|
||||
|
||||
# If we have a 'total-worth' item-id for this id, show that price so
|
||||
# the user knows how much this is worth.
|
||||
total_worth_item = plus.get_v1_account_misc_read_val('twrths', {}).get(
|
||||
item_name
|
||||
)
|
||||
total_worth_price: str | None
|
||||
if total_worth_item is not None:
|
||||
price = plus.get_price(total_worth_item)
|
||||
total_worth_price = (
|
||||
store.get_clean_price(price) if price is not None else '??'
|
||||
)
|
||||
else:
|
||||
total_worth_price = None
|
||||
|
||||
if total_worth_price is not None:
|
||||
total_worth_text = bui.Lstr(
|
||||
resource='store.totalWorthText',
|
||||
subs=[('${TOTAL_WORTH}', total_worth_price)],
|
||||
)
|
||||
extra_texts_2.append(
|
||||
bui.textwidget(
|
||||
parent=parent_widget,
|
||||
text=total_worth_text,
|
||||
position=(
|
||||
b_pos[0] + b_width * 0.5 + b_offs_x,
|
||||
b_pos[1] + b_height * 0.25,
|
||||
),
|
||||
transition_delay=delay,
|
||||
scale=b_width * (1.0 / 230.0) * base_text_scale * 0.45,
|
||||
maxwidth=b_width * 0.5,
|
||||
size=(0, 0),
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
shadow=1.0,
|
||||
flatness=1.0,
|
||||
draw_controller=btn,
|
||||
color=(0.3, 1, 1),
|
||||
)
|
||||
)
|
||||
|
||||
mesh_opaque = bui.getmesh('level_select_button_opaque')
|
||||
mesh_transparent = bui.getmesh('level_select_button_transparent')
|
||||
mask_tex = bui.gettexture('mapPreviewMask')
|
||||
for levelname, preview_tex_name, pos in [
|
||||
('Infinite Onslaught', 'doomShroomPreview', (0.80, 0.48)),
|
||||
('Infinite Runaround', 'towerDPreview', (0.80, 0.32)),
|
||||
]:
|
||||
tile_size = (b_width * 0.2, b_width * 0.13)
|
||||
tile_pos = (
|
||||
b_pos[0] + b_width * pos[0],
|
||||
b_pos[1] + b_height * pos[1],
|
||||
)
|
||||
im_size = tile_size[0] * 0.8
|
||||
extra_backings.append(
|
||||
bui.imagewidget(
|
||||
parent=parent_widget,
|
||||
position=(
|
||||
tile_pos[0] - tile_size[0] * 0.5,
|
||||
tile_pos[1] - tile_size[1] * 0.5,
|
||||
),
|
||||
size=tile_size,
|
||||
transition_delay=delay,
|
||||
draw_controller=btn,
|
||||
color=backing_color,
|
||||
texture=b_square_texture,
|
||||
)
|
||||
)
|
||||
|
||||
# Hack - gotta draw two transparent versions to avoid z issues.
|
||||
for mod in mesh_opaque, mesh_transparent:
|
||||
extra_images.append(
|
||||
bui.imagewidget(
|
||||
parent=parent_widget,
|
||||
position=(
|
||||
tile_pos[0] - im_size * 0.52,
|
||||
tile_pos[1] - im_size * 0.2,
|
||||
),
|
||||
size=(im_size, im_size * 0.5),
|
||||
transition_delay=delay,
|
||||
mesh_transparent=mod,
|
||||
mask_texture=mask_tex,
|
||||
draw_controller=btn,
|
||||
texture=bui.gettexture(preview_tex_name),
|
||||
)
|
||||
)
|
||||
|
||||
extra_texts.append(
|
||||
bui.textwidget(
|
||||
parent=parent_widget,
|
||||
draw_controller=btn,
|
||||
position=(
|
||||
tile_pos[0] - im_size * 0.03,
|
||||
tile_pos[1] - im_size * 0.2,
|
||||
),
|
||||
size=(0, 0),
|
||||
color=(0.6, 1, 0.6),
|
||||
transition_delay=delay,
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
maxwidth=tile_size[0] * 0.7,
|
||||
scale=0.55,
|
||||
text=bui.Lstr(translate=('coopLevelNames', levelname)),
|
||||
flatness=1.0,
|
||||
shadow=0.0,
|
||||
)
|
||||
)
|
||||
|
||||
if item_name.startswith('icons.'):
|
||||
item['icon_text'] = bui.textwidget(
|
||||
parent=parent_widget,
|
||||
text=item_info['icon'],
|
||||
position=(b_pos[0] + b_width * 0.5, b_pos[1] + b_height * 0.5),
|
||||
transition_delay=delay,
|
||||
scale=b_width * (1.0 / 230.0) * base_text_scale * 2.0,
|
||||
maxwidth=b_width * 0.9,
|
||||
max_height=b_height * 0.9,
|
||||
size=(0, 0),
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
draw_controller=btn,
|
||||
)
|
||||
|
||||
if item_name.startswith('maps.'):
|
||||
frame_size = b_width * 0.9
|
||||
im_dim = frame_size * (100.0 / 113.0)
|
||||
im_pos = (
|
||||
b_pos[0] + b_width * 0.5 - im_dim * 0.5 + b_offs_x,
|
||||
b_pos[1] + b_height * 0.62 - im_dim * 0.25,
|
||||
)
|
||||
mesh_opaque = bui.getmesh('level_select_button_opaque')
|
||||
mesh_transparent = bui.getmesh('level_select_button_transparent')
|
||||
mask_tex = bui.gettexture('mapPreviewMask')
|
||||
assert tex_name is not None
|
||||
bui.imagewidget(
|
||||
parent=parent_widget,
|
||||
position=im_pos,
|
||||
size=(im_dim, im_dim * 0.5),
|
||||
transition_delay=delay,
|
||||
mesh_opaque=mesh_opaque,
|
||||
mesh_transparent=mesh_transparent,
|
||||
mask_texture=mask_tex,
|
||||
draw_controller=btn,
|
||||
texture=bui.gettexture(tex_name),
|
||||
)
|
||||
|
||||
if item_name.startswith('games.') or item_name in (
|
||||
'upgrades.infinite_runaround',
|
||||
'upgrades.infinite_onslaught',
|
||||
):
|
||||
frame_size = b_width * 0.8
|
||||
im_dim = frame_size * (100.0 / 113.0)
|
||||
im_pos = (
|
||||
b_pos[0] + b_width * 0.5 - im_dim * 0.5 + b_offs_x,
|
||||
b_pos[1] + b_height * 0.72 - im_dim * 0.25,
|
||||
)
|
||||
mesh_opaque = bui.getmesh('level_select_button_opaque')
|
||||
mesh_transparent = bui.getmesh('level_select_button_transparent')
|
||||
mask_tex = bui.gettexture('mapPreviewMask')
|
||||
assert tex_name is not None
|
||||
bui.imagewidget(
|
||||
parent=parent_widget,
|
||||
position=im_pos,
|
||||
size=(im_dim, im_dim * 0.5),
|
||||
transition_delay=delay,
|
||||
mesh_opaque=mesh_opaque,
|
||||
mesh_transparent=mesh_transparent,
|
||||
mask_texture=mask_tex,
|
||||
draw_controller=btn,
|
||||
texture=bui.gettexture(tex_name),
|
||||
)
|
||||
item['descriptionText'] = bui.textwidget(
|
||||
parent=parent_widget,
|
||||
text=desc,
|
||||
position=(b_pos[0] + b_width * 0.5, b_pos[1] + b_height * 0.36),
|
||||
transition_delay=delay,
|
||||
scale=b_width * (1.0 / 230.0) * base_text_scale * 0.78,
|
||||
maxwidth=b_width * 0.8,
|
||||
max_height=b_height * 0.14,
|
||||
size=(0, 0),
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
draw_controller=btn,
|
||||
flatness=1.0,
|
||||
shadow=0.0,
|
||||
color=(0.6, 1, 0.6),
|
||||
)
|
||||
item['gameModesText'] = bui.textwidget(
|
||||
parent=parent_widget,
|
||||
text=modes,
|
||||
position=(b_pos[0] + b_width * 0.5, b_pos[1] + b_height * 0.26),
|
||||
transition_delay=delay,
|
||||
scale=b_width * (1.0 / 230.0) * base_text_scale * 0.65,
|
||||
maxwidth=b_width * 0.8,
|
||||
size=(0, 0),
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
draw_controller=btn,
|
||||
shadow=0,
|
||||
flatness=1.0,
|
||||
color=(0.6, 0.8, 0.6),
|
||||
)
|
||||
|
||||
if not item_name.startswith('icons.'):
|
||||
item['title_text'] = bui.textwidget(
|
||||
parent=parent_widget,
|
||||
text=title,
|
||||
position=(
|
||||
b_pos[0] + b_width * 0.5 + b_offs_x,
|
||||
b_pos[1] + b_height * title_v,
|
||||
),
|
||||
transition_delay=delay,
|
||||
scale=b_width * (1.0 / 230.0) * base_text_scale,
|
||||
maxwidth=b_width * 0.8,
|
||||
size=(0, 0),
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
draw_controller=btn,
|
||||
color=(0.7, 0.9, 0.7, 1.0),
|
||||
)
|
||||
|
||||
item['purchase_check'] = bui.imagewidget(
|
||||
parent=parent_widget,
|
||||
position=(b_pos[0] + b_width * check_pos, b_pos[1] + b_height * 0.05),
|
||||
transition_delay=delay,
|
||||
mesh_transparent=bui.getmesh('checkTransparent'),
|
||||
opacity=0.0,
|
||||
size=(60, 60),
|
||||
color=(0.6, 0.5, 0.8),
|
||||
draw_controller=btn,
|
||||
texture=bui.gettexture('uiAtlas'),
|
||||
)
|
||||
item['price_widget'] = bui.textwidget(
|
||||
parent=parent_widget,
|
||||
text='',
|
||||
position=(
|
||||
b_pos[0] + b_width * 0.5 + b_offs_x,
|
||||
b_pos[1] + b_height * price_v,
|
||||
),
|
||||
transition_delay=delay,
|
||||
scale=b_width * (1.0 / 300.0) * base_text_scale,
|
||||
maxwidth=b_width * 0.9,
|
||||
size=(0, 0),
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
draw_controller=btn,
|
||||
color=(0.2, 1, 0.2, 1.0),
|
||||
)
|
||||
item['price_widget_left'] = bui.textwidget(
|
||||
parent=parent_widget,
|
||||
text='',
|
||||
position=(
|
||||
b_pos[0] + b_width * 0.33 + b_offs_x,
|
||||
b_pos[1] + b_height * price_v,
|
||||
),
|
||||
transition_delay=delay,
|
||||
scale=b_width * (1.0 / 300.0) * base_text_scale,
|
||||
maxwidth=b_width * 0.3,
|
||||
size=(0, 0),
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
draw_controller=btn,
|
||||
color=(0.2, 1, 0.2, 0.5),
|
||||
)
|
||||
item['price_widget_right'] = bui.textwidget(
|
||||
parent=parent_widget,
|
||||
text='',
|
||||
position=(
|
||||
b_pos[0] + b_width * 0.66 + b_offs_x,
|
||||
b_pos[1] + b_height * price_v,
|
||||
),
|
||||
transition_delay=delay,
|
||||
scale=1.1 * b_width * (1.0 / 300.0) * base_text_scale,
|
||||
maxwidth=b_width * 0.3,
|
||||
size=(0, 0),
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
draw_controller=btn,
|
||||
color=(0.2, 1, 0.2, 1.0),
|
||||
)
|
||||
item['price_slash_widget'] = bui.imagewidget(
|
||||
parent=parent_widget,
|
||||
position=(
|
||||
b_pos[0] + b_width * 0.33 + b_offs_x - 36,
|
||||
b_pos[1] + b_height * price_v - 35,
|
||||
),
|
||||
transition_delay=delay,
|
||||
texture=bui.gettexture('slash'),
|
||||
opacity=0.0,
|
||||
size=(70, 70),
|
||||
draw_controller=btn,
|
||||
color=(1, 0, 0),
|
||||
)
|
||||
badge_rad = 44
|
||||
badge_center = (
|
||||
b_pos[0] + b_width * 0.1 + b_offs_x,
|
||||
b_pos[1] + b_height * 0.87,
|
||||
)
|
||||
item['sale_bg_widget'] = bui.imagewidget(
|
||||
parent=parent_widget,
|
||||
position=(badge_center[0] - badge_rad, badge_center[1] - badge_rad),
|
||||
opacity=0.0,
|
||||
transition_delay=delay,
|
||||
texture=bui.gettexture('circleZigZag'),
|
||||
draw_controller=btn,
|
||||
size=(badge_rad * 2, badge_rad * 2),
|
||||
color=(0.5, 0, 1),
|
||||
)
|
||||
item['sale_title_widget'] = bui.textwidget(
|
||||
parent=parent_widget,
|
||||
position=(badge_center[0], badge_center[1] + 12),
|
||||
transition_delay=delay,
|
||||
scale=1.0,
|
||||
maxwidth=badge_rad * 1.6,
|
||||
size=(0, 0),
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
draw_controller=btn,
|
||||
shadow=0.0,
|
||||
flatness=1.0,
|
||||
color=(0, 1, 0),
|
||||
)
|
||||
item['sale_time_widget'] = bui.textwidget(
|
||||
parent=parent_widget,
|
||||
position=(badge_center[0], badge_center[1] - 12),
|
||||
transition_delay=delay,
|
||||
scale=0.7,
|
||||
maxwidth=badge_rad * 1.6,
|
||||
size=(0, 0),
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
draw_controller=btn,
|
||||
shadow=0.0,
|
||||
flatness=1.0,
|
||||
color=(0.0, 1, 0.0, 1),
|
||||
)
|
||||
Loading…
Add table
Add a link
Reference in a new issue