mirror of
https://github.com/RYDE-WORK/ballistica.git
synced 2026-01-27 01:13:13 +08:00
Merge branch 'efroemling:main' into main
This commit is contained in:
commit
1f48ac708c
56
.efrocachemap
generated
56
.efrocachemap
generated
@ -4096,26 +4096,26 @@
|
||||
"build/assets/windows/Win32/ucrtbased.dll": "2def5335207d41b21b9823f6805997f1",
|
||||
"build/assets/windows/Win32/vc_redist.x86.exe": "b08a55e2e77623fe657bea24f223a3ae",
|
||||
"build/assets/windows/Win32/vcruntime140d.dll": "865b2af4d1e26a1a8073c89acb06e599",
|
||||
"build/prefab/full/linux_arm64_gui/debug/ballisticakit": "0a166c970637de5e318e4f07e8c0aeae",
|
||||
"build/prefab/full/linux_arm64_gui/release/ballisticakit": "71fee3398234820d038235f02ea7ed6a",
|
||||
"build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "682d622e82fbdf7d41f170146ae8cf6f",
|
||||
"build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "ed9a7e820ab3a59d6326b898973d5cff",
|
||||
"build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "4efa411d0d43debb7c8db1db1d9a4658",
|
||||
"build/prefab/full/linux_x86_64_gui/release/ballisticakit": "8892f774f2d079604d77241adaf3e529",
|
||||
"build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "c479f3ab633f0b19f3e733943b3c1c3b",
|
||||
"build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "2797e7b22388c68b2e41a1eb7c820a62",
|
||||
"build/prefab/full/mac_arm64_gui/debug/ballisticakit": "f58b978d2978fdf6e2f7f0cc13fbb2c7",
|
||||
"build/prefab/full/mac_arm64_gui/release/ballisticakit": "1ad711461c2a65ecb1fbab1aac2aacae",
|
||||
"build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "ec90ed3ec92a6db3f8b9651f2464d8d4",
|
||||
"build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "12cc0539e86a408b01ac3b3a74cff98a",
|
||||
"build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "1f59835f71e01827f056f503302c45c4",
|
||||
"build/prefab/full/mac_x86_64_gui/release/ballisticakit": "c747103d9fe3206f94b54ed9ae5d4aa4",
|
||||
"build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "603f77208cb0df9161b84387631b8778",
|
||||
"build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "00a72972b6b0531b176406ad0e8d243f",
|
||||
"build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "af0063f60bb84a65ca71df83d00b9507",
|
||||
"build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "817b90e953f655e83cd8e69d1feb5526",
|
||||
"build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "c132d48c3b560ac0dcf090a3bce8ba26",
|
||||
"build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "b3c44cd6282d4100b730e997baa3f28e",
|
||||
"build/prefab/full/linux_arm64_gui/debug/ballisticakit": "d6246c930e7e2d2d9a6aff6788f33b69",
|
||||
"build/prefab/full/linux_arm64_gui/release/ballisticakit": "001c67c4d4d33e20755399e0b2ed1593",
|
||||
"build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "6f6bfaf19daf6e866f4fecbc889b8854",
|
||||
"build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "ebc7449903d7868c631c504aed10f371",
|
||||
"build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "91f03e7dbfc0d7eb75568704f681fba5",
|
||||
"build/prefab/full/linux_x86_64_gui/release/ballisticakit": "0b32b6eb05df0a7e23e55ff2e7235a8f",
|
||||
"build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "4ed4b63cc506815f759412a295bfe088",
|
||||
"build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "1890731fb8a2fb43c4cde72af9d7a4e6",
|
||||
"build/prefab/full/mac_arm64_gui/debug/ballisticakit": "1a4764a504dcb20591ac3472c48db8d9",
|
||||
"build/prefab/full/mac_arm64_gui/release/ballisticakit": "9bfd6234aac4ffd7d7601a40017a73be",
|
||||
"build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "941f8fb79d54522ca5df97a735c3babe",
|
||||
"build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "290a3d9840efc5e88532e13b76b3ae6b",
|
||||
"build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "621a6684b54b0a9101f808209bcca1ee",
|
||||
"build/prefab/full/mac_x86_64_gui/release/ballisticakit": "43c8b0b29e5fb257e4cde0f7fce1c680",
|
||||
"build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "9a30c6b88ffc11bdf6765780616d8ba1",
|
||||
"build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "8a8f63850fc296b060a994f4e001d74f",
|
||||
"build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "2864e9ae1d2d566def85f1cdf6e863fe",
|
||||
"build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "dc0db7ca99661a896634fc05187a5c75",
|
||||
"build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "f56bb0fafe0a45cecdcd06a10a6924c9",
|
||||
"build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "e3a58b09fea193d78187f97cf922717d",
|
||||
"build/prefab/lib/linux_arm64_gui/debug/libballisticaplus.a": "73ad3303fe1a82005918fbc5dae3446c",
|
||||
"build/prefab/lib/linux_arm64_gui/release/libballisticaplus.a": "fa659b5d6119acba6570c92ce4d35ae2",
|
||||
"build/prefab/lib/linux_arm64_server/debug/libballisticaplus.a": "73ad3303fe1a82005918fbc5dae3446c",
|
||||
@ -4132,14 +4132,14 @@
|
||||
"build/prefab/lib/mac_x86_64_gui/release/libballisticaplus.a": "3e5c5fd0a09f55ba7b05ce1e2ec7171e",
|
||||
"build/prefab/lib/mac_x86_64_server/debug/libballisticaplus.a": "1659535e95e3047fda529543e265ac97",
|
||||
"build/prefab/lib/mac_x86_64_server/release/libballisticaplus.a": "3e5c5fd0a09f55ba7b05ce1e2ec7171e",
|
||||
"build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "d4aca724c3184c6c2c82e7b11c1a352a",
|
||||
"build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "f4fbab89e11f52329d65a9afcabc2e5e",
|
||||
"build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "09733e1291806ece13f108ad6fb72b68",
|
||||
"build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "099e20f57cfa2c36bf7b44e389d01d9a",
|
||||
"build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "aae5ce60761c700d23a480cac83a2df5",
|
||||
"build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "1fdc6ee85373af9df3a0b5eb601990af",
|
||||
"build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "809e62f1d2ac54c239a3a13c2d2c8ee7",
|
||||
"build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "68b49b66a76b9ab0d1b17de2bab19c04",
|
||||
"build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "6b6aae30362b1e9aaf3f1a8daae0b0b4",
|
||||
"build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "25db08ee2b4c79ca631e517b622c741d",
|
||||
"build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "2ea419644782c4c52d1fa784d0ced86d",
|
||||
"build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "c40627a46ee127997153df041e47eda5",
|
||||
"build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "fff81b29e2d86031ec25c4a672a243e7",
|
||||
"build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "2a87e2f53c658d0be3918476cd8a04ee",
|
||||
"build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "1e91536979b318b9a8325ab0e871ecec",
|
||||
"build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "c6506399a93ef93e8322425f78e2e6b0",
|
||||
"src/assets/ba_data/python/babase/_mgen/__init__.py": "f885fed7f2ed98ff2ba271f9dbe3391c",
|
||||
"src/assets/ba_data/python/babase/_mgen/enums.py": "cb299985623bbcc86015cb103a424ae6",
|
||||
"src/ballistica/base/mgen/pyembed/binding_base.inc": "efa61468cf098f77cc6a234461d8b86d",
|
||||
|
||||
12
CHANGELOG.md
12
CHANGELOG.md
@ -1,6 +1,14 @@
|
||||
### 1.7.37 (build 21968, api 8, 2024-08-29)
|
||||
### 1.7.37 (build 21977, api 9, 2024-08-30)
|
||||
- Bumping api version to 9. As you'll see below, there's some UI changes that
|
||||
will require a bit of work for any UI mods to adapt to. If your mods don't
|
||||
touch UI stuff at all you can simply bump your api version and call it a day.
|
||||
I'm hopeful that api version won't need to be bumped again for along time (if
|
||||
ever).
|
||||
- Playlist customization no longer requires pro.
|
||||
- Soundtrack customization no longer requires pro.
|
||||
- Campaign hard mode no longer requires pro.
|
||||
- Full player profile color customization no longer requires pro.
|
||||
- Removed nag screens for purchasing pro or bundle offers.
|
||||
- Switching over to the new 'toolbar mode' UI that has been in the works for
|
||||
several years. This includes a number of handy things such as consistent
|
||||
buttons and widgets for league status, currencies, inventory, and the store.
|
||||
@ -34,7 +42,7 @@
|
||||
back-button handling are more automatic and windows don't have to hard-code
|
||||
where they came from. There are also other benefits such as better state
|
||||
saving/restoring. When writing a MainWindow, pretty much all navigation should
|
||||
only need too use methods: `can_change_main_window()`, `main_window_back()`,
|
||||
only need too use methods: `main_window_has_control()`, `main_window_back()`,
|
||||
and `main_window_replace()`.
|
||||
- Finally got things updated so language testing works again, and made it a bit
|
||||
spiffier while at it. You now simply point the game at your test language and
|
||||
|
||||
@ -399,7 +399,6 @@
|
||||
"ba_data/python/bauiv1lib/__pycache__/resourcetypeinfo.cpython-312.opt-1.pyc",
|
||||
"ba_data/python/bauiv1lib/__pycache__/sendinfo.cpython-312.opt-1.pyc",
|
||||
"ba_data/python/bauiv1lib/__pycache__/serverdialog.cpython-312.opt-1.pyc",
|
||||
"ba_data/python/bauiv1lib/__pycache__/specialoffer.cpython-312.opt-1.pyc",
|
||||
"ba_data/python/bauiv1lib/__pycache__/tabs.cpython-312.opt-1.pyc",
|
||||
"ba_data/python/bauiv1lib/__pycache__/teamnamescolors.cpython-312.opt-1.pyc",
|
||||
"ba_data/python/bauiv1lib/__pycache__/tournamententry.cpython-312.opt-1.pyc",
|
||||
@ -558,7 +557,6 @@
|
||||
"ba_data/python/bauiv1lib/soundtrack/edit.py",
|
||||
"ba_data/python/bauiv1lib/soundtrack/entrytypeselect.py",
|
||||
"ba_data/python/bauiv1lib/soundtrack/macmusicapp.py",
|
||||
"ba_data/python/bauiv1lib/specialoffer.py",
|
||||
"ba_data/python/bauiv1lib/store/__init__.py",
|
||||
"ba_data/python/bauiv1lib/store/__pycache__/__init__.cpython-312.opt-1.pyc",
|
||||
"ba_data/python/bauiv1lib/store/__pycache__/browser.cpython-312.opt-1.pyc",
|
||||
|
||||
@ -425,7 +425,6 @@ SCRIPT_TARGETS_PY_PUBLIC = \
|
||||
$(BUILD_DIR)/ba_data/python/bauiv1lib/soundtrack/edit.py \
|
||||
$(BUILD_DIR)/ba_data/python/bauiv1lib/soundtrack/entrytypeselect.py \
|
||||
$(BUILD_DIR)/ba_data/python/bauiv1lib/soundtrack/macmusicapp.py \
|
||||
$(BUILD_DIR)/ba_data/python/bauiv1lib/specialoffer.py \
|
||||
$(BUILD_DIR)/ba_data/python/bauiv1lib/store/__init__.py \
|
||||
$(BUILD_DIR)/ba_data/python/bauiv1lib/store/browser.py \
|
||||
$(BUILD_DIR)/ba_data/python/bauiv1lib/store/button.py \
|
||||
@ -704,7 +703,6 @@ SCRIPT_TARGETS_PYC_PUBLIC = \
|
||||
$(BUILD_DIR)/ba_data/python/bauiv1lib/soundtrack/__pycache__/edit.cpython-312.opt-1.pyc \
|
||||
$(BUILD_DIR)/ba_data/python/bauiv1lib/soundtrack/__pycache__/entrytypeselect.cpython-312.opt-1.pyc \
|
||||
$(BUILD_DIR)/ba_data/python/bauiv1lib/soundtrack/__pycache__/macmusicapp.cpython-312.opt-1.pyc \
|
||||
$(BUILD_DIR)/ba_data/python/bauiv1lib/__pycache__/specialoffer.cpython-312.opt-1.pyc \
|
||||
$(BUILD_DIR)/ba_data/python/bauiv1lib/store/__pycache__/__init__.cpython-312.opt-1.pyc \
|
||||
$(BUILD_DIR)/ba_data/python/bauiv1lib/store/__pycache__/browser.cpython-312.opt-1.pyc \
|
||||
$(BUILD_DIR)/ba_data/python/bauiv1lib/store/__pycache__/button.cpython-312.opt-1.pyc \
|
||||
|
||||
@ -62,6 +62,7 @@ from _babase import (
|
||||
have_chars,
|
||||
have_permission,
|
||||
in_logic_thread,
|
||||
in_main_menu,
|
||||
increment_analytics_count,
|
||||
invoke_main_menu,
|
||||
is_os_playing_music,
|
||||
@ -262,6 +263,7 @@ __all__ = [
|
||||
'have_chars',
|
||||
'have_permission',
|
||||
'in_logic_thread',
|
||||
'in_main_menu',
|
||||
'increment_analytics_count',
|
||||
'InputDeviceNotFoundError',
|
||||
'InputType',
|
||||
|
||||
@ -16,7 +16,7 @@ classic submodules directly, it is much harder to make it cleanly handle
|
||||
classic not being present.
|
||||
"""
|
||||
|
||||
# ba_meta require api 8
|
||||
# ba_meta require api 9
|
||||
|
||||
# Note: Code relying on classic should import things from here *only*
|
||||
# for type-checking and use the versions in ba*.app.classic at runtime;
|
||||
|
||||
@ -15,6 +15,7 @@ from babase import (
|
||||
AppIntentDefault,
|
||||
invoke_main_menu,
|
||||
screenmessage,
|
||||
in_main_menu,
|
||||
)
|
||||
|
||||
import _baclassic
|
||||
@ -120,20 +121,28 @@ class ClassicAppMode(AppMode):
|
||||
def _jump_to_main_window(self, window: MainWindow) -> None:
|
||||
"""Jump to a window with the main menu as its parent."""
|
||||
from bauiv1lib.mainmenu import MainMenuWindow
|
||||
from bauiv1lib.ingamemenu import InGameMenuWindow
|
||||
|
||||
ui = app.ui_v1
|
||||
|
||||
old_window = ui.get_main_window()
|
||||
if isinstance(old_window, MainMenuWindow):
|
||||
|
||||
if isinstance(old_window, (MainMenuWindow, InGameMenuWindow)):
|
||||
# If we're currently in the top level menu window, just push
|
||||
# our mainwindow on to the end.
|
||||
old_window.main_window_replace(window)
|
||||
else:
|
||||
# Blow away the window stack.
|
||||
# Blow away the window stack and build a fresh one.
|
||||
ui.clear_main_window()
|
||||
|
||||
ui.set_main_window(
|
||||
window,
|
||||
from_window=False, # Disable from-check.
|
||||
back_state=MainMenuWindow.do_get_main_window_state(),
|
||||
back_state=(
|
||||
MainMenuWindow.do_get_main_window_state()
|
||||
if in_main_menu()
|
||||
else InGameMenuWindow.do_get_main_window_state()
|
||||
),
|
||||
)
|
||||
|
||||
def _root_ui_menu_press(self) -> None:
|
||||
|
||||
@ -216,24 +216,6 @@ class ClassicAppSubsystem(babase.AppSubsystem):
|
||||
cfg['launchCount'] = launch_count
|
||||
cfg.commit()
|
||||
|
||||
# Run a test in a few seconds to see if we should pop up an existing
|
||||
# pending special offer.
|
||||
def check_special_offer() -> None:
|
||||
assert plus is not None
|
||||
|
||||
from bauiv1lib.specialoffer import show_offer
|
||||
|
||||
if (
|
||||
'pendingSpecialOffer' in cfg
|
||||
and plus.get_v1_account_public_login_id()
|
||||
== cfg['pendingSpecialOffer']['a']
|
||||
):
|
||||
self.special_offer = cfg['pendingSpecialOffer']['o']
|
||||
show_offer()
|
||||
|
||||
if babase.app.env.gui:
|
||||
babase.apptimer(3.0, check_special_offer)
|
||||
|
||||
# If there's a leftover log file, attempt to upload it to the
|
||||
# master-server and/or get rid of it.
|
||||
babase.handle_leftover_v1_cloud_log_file()
|
||||
@ -832,7 +814,7 @@ class ClassicAppSubsystem(babase.AppSubsystem):
|
||||
app = bauiv1.app
|
||||
env = app.env
|
||||
with bascenev1.ContextRef.empty():
|
||||
from bauiv1lib import specialoffer
|
||||
# from bauiv1lib import specialoffer
|
||||
|
||||
assert app.classic is not None
|
||||
if app.env.headless:
|
||||
@ -927,11 +909,11 @@ class ClassicAppSubsystem(babase.AppSubsystem):
|
||||
# (we may not have heard back from the server)
|
||||
# ..if that doesn't work they'll just have to wait
|
||||
# until the next opportunity.
|
||||
if not specialoffer.show_offer():
|
||||
# if not specialoffer.show_offer():
|
||||
|
||||
def try_again() -> None:
|
||||
if not specialoffer.show_offer():
|
||||
# Try one last time..
|
||||
bauiv1.apptimer(2.0, specialoffer.show_offer)
|
||||
# def try_again() -> None:
|
||||
# if not specialoffer.show_offer():
|
||||
# # Try one last time..
|
||||
# bauiv1.apptimer(2.0, specialoffer.show_offer)
|
||||
|
||||
bauiv1.apptimer(2.0, try_again)
|
||||
# bauiv1.apptimer(2.0, try_again)
|
||||
|
||||
@ -52,7 +52,7 @@ if TYPE_CHECKING:
|
||||
|
||||
# Build number and version of the ballistica binary we expect to be
|
||||
# using.
|
||||
TARGET_BALLISTICA_BUILD = 21968
|
||||
TARGET_BALLISTICA_BUILD = 21977
|
||||
TARGET_BALLISTICA_VERSION = '1.7.37'
|
||||
|
||||
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
#
|
||||
"""Gameplay-centric api for classic BombSquad."""
|
||||
|
||||
# ba_meta require api 8
|
||||
# ba_meta require api 9
|
||||
|
||||
# The stuff we expose here at the top level is our 'public' api for use
|
||||
# from other modules/packages. Code *within* this package should import
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import random
|
||||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING, NewType
|
||||
|
||||
@ -243,7 +244,6 @@ def cameraflash(duration: float = 999.0) -> None:
|
||||
Duration is in seconds.
|
||||
"""
|
||||
# pylint: disable=too-many-locals
|
||||
import random
|
||||
from bascenev1._nodeactor import NodeActor
|
||||
|
||||
x_spread = 10
|
||||
|
||||
@ -2,4 +2,4 @@
|
||||
#
|
||||
"""Library of stuff using the bascenev1 api: games, actors, etc."""
|
||||
|
||||
# ba_meta require api 8
|
||||
# ba_meta require api 9
|
||||
|
||||
@ -207,20 +207,22 @@ class CoopScoreScreen(bs.Activity[bs.Player, bs.Team]):
|
||||
)
|
||||
|
||||
def _ui_menu(self) -> None:
|
||||
from bauiv1lib import specialoffer
|
||||
# from bauiv1lib import specialoffer
|
||||
|
||||
# if specialoffer.show_offer():
|
||||
# return
|
||||
|
||||
if specialoffer.show_offer():
|
||||
return
|
||||
bui.containerwidget(edit=self._root_ui, transition='out_left')
|
||||
with self.context:
|
||||
bs.timer(0.1, bs.Call(bs.WeakCall(self.session.end)))
|
||||
|
||||
def _ui_restart(self) -> None:
|
||||
from bauiv1lib.tournamententry import TournamentEntryWindow
|
||||
from bauiv1lib import specialoffer
|
||||
|
||||
if specialoffer.show_offer():
|
||||
return
|
||||
# from bauiv1lib import specialoffer
|
||||
|
||||
# if specialoffer.show_offer():
|
||||
# return
|
||||
|
||||
# If we're in a tournament and it looks like there's no time left,
|
||||
# disallow.
|
||||
@ -268,10 +270,10 @@ class CoopScoreScreen(bs.Activity[bs.Player, bs.Team]):
|
||||
self.end({'outcome': 'restart'})
|
||||
|
||||
def _ui_next(self) -> None:
|
||||
from bauiv1lib.specialoffer import show_offer
|
||||
# from bauiv1lib.specialoffer import show_offer
|
||||
|
||||
if show_offer():
|
||||
return
|
||||
# if show_offer():
|
||||
# return
|
||||
|
||||
# If we didn't just complete this level but are choosing to play the
|
||||
# next one, set it as current (this won't happen otherwise).
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
#
|
||||
"""Defines assault minigame."""
|
||||
|
||||
# ba_meta require api 8
|
||||
# ba_meta require api 9
|
||||
# (see https://ballistica.net/wiki/meta-tag-system)
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
#
|
||||
"""Defines a capture-the-flag game."""
|
||||
|
||||
# ba_meta require api 8
|
||||
# ba_meta require api 9
|
||||
# (see https://ballistica.net/wiki/meta-tag-system)
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
#
|
||||
"""Provides the chosen-one mini-game."""
|
||||
|
||||
# ba_meta require api 8
|
||||
# ba_meta require api 9
|
||||
# (see https://ballistica.net/wiki/meta-tag-system)
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
#
|
||||
"""Provides the Conquest game."""
|
||||
|
||||
# ba_meta require api 8
|
||||
# ba_meta require api 9
|
||||
# (see https://ballistica.net/wiki/meta-tag-system)
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
#
|
||||
"""DeathMatch game and support classes."""
|
||||
|
||||
# ba_meta require api 8
|
||||
# ba_meta require api 9
|
||||
# (see https://ballistica.net/wiki/meta-tag-system)
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
#
|
||||
"""Provides an easter egg hunt game."""
|
||||
|
||||
# ba_meta require api 8
|
||||
# ba_meta require api 9
|
||||
# (see https://ballistica.net/wiki/meta-tag-system)
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
#
|
||||
"""Elimination mini-game."""
|
||||
|
||||
# ba_meta require api 8
|
||||
# ba_meta require api 9
|
||||
# (see https://ballistica.net/wiki/meta-tag-system)
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
# pylint: disable=too-many-lines
|
||||
"""Implements football games (both co-op and teams varieties)."""
|
||||
|
||||
# ba_meta require api 8
|
||||
# ba_meta require api 9
|
||||
# (see https://ballistica.net/wiki/meta-tag-system)
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
#
|
||||
"""Hockey game and support classes."""
|
||||
|
||||
# ba_meta require api 8
|
||||
# ba_meta require api 9
|
||||
# (see https://ballistica.net/wiki/meta-tag-system)
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
#
|
||||
"""Defines a keep-away game type."""
|
||||
|
||||
# ba_meta require api 8
|
||||
# ba_meta require api 9
|
||||
# (see https://ballistica.net/wiki/meta-tag-system)
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
#
|
||||
"""Defines the King of the Hill game."""
|
||||
|
||||
# ba_meta require api 8
|
||||
# ba_meta require api 9
|
||||
# (see https://ballistica.net/wiki/meta-tag-system)
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
#
|
||||
"""Defines a bomb-dodging mini-game."""
|
||||
|
||||
# ba_meta require api 8
|
||||
# ba_meta require api 9
|
||||
# (see https://ballistica.net/wiki/meta-tag-system)
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
#
|
||||
"""Provides Ninja Fight mini-game."""
|
||||
|
||||
# ba_meta require api 8
|
||||
# ba_meta require api 9
|
||||
# (see https://ballistica.net/wiki/meta-tag-system)
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
# Yes this is a long one..
|
||||
# pylint: disable=too-many-lines
|
||||
|
||||
# ba_meta require api 8
|
||||
# ba_meta require api 9
|
||||
# (see https://ballistica.net/wiki/meta-tag-system)
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
#
|
||||
"""Defines Race mini-game."""
|
||||
|
||||
# ba_meta require api 8
|
||||
# ba_meta require api 9
|
||||
# (see https://ballistica.net/wiki/meta-tag-system)
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
# We wear the cone of shame.
|
||||
# pylint: disable=too-many-lines
|
||||
|
||||
# ba_meta require api 8
|
||||
# ba_meta require api 9
|
||||
# (see https://ballistica.net/wiki/meta-tag-system)
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
#
|
||||
"""Implements Target Practice game."""
|
||||
|
||||
# ba_meta require api 8
|
||||
# ba_meta require api 9
|
||||
# (see https://ballistica.net/wiki/meta-tag-system)
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
#
|
||||
"""Ballistica Template Feature Set - just an example."""
|
||||
|
||||
# ba_meta require api 8
|
||||
# ba_meta require api 9
|
||||
|
||||
# Package up various private bits (including stuff from our native
|
||||
# module) into a nice clean public API.
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
#
|
||||
"""Ballistica user interface api version 1"""
|
||||
|
||||
# ba_meta require api 8
|
||||
# ba_meta require api 9
|
||||
|
||||
# The stuff we expose here at the top level is our 'public' api.
|
||||
# It should only be imported by code outside of this package or
|
||||
@ -60,6 +60,7 @@ from babase import (
|
||||
getclass,
|
||||
have_permission,
|
||||
in_logic_thread,
|
||||
in_main_menu,
|
||||
increment_analytics_count,
|
||||
is_browser_likely_available,
|
||||
is_xcode_build,
|
||||
@ -188,6 +189,7 @@ __all__ = [
|
||||
'hscrollwidget',
|
||||
'imagewidget',
|
||||
'in_logic_thread',
|
||||
'in_main_menu',
|
||||
'increment_analytics_count',
|
||||
'is_browser_likely_available',
|
||||
'is_party_icon_visible',
|
||||
|
||||
@ -99,7 +99,7 @@ class MainWindow(Window):
|
||||
edit=self._root_widget, transition=self._main_window_transition_out
|
||||
)
|
||||
|
||||
def can_change_main_window(self) -> bool:
|
||||
def main_window_has_control(self) -> bool:
|
||||
"""Is this MainWindow allowed to change the global main window?
|
||||
|
||||
It is a good idea to make sure this is True before calling
|
||||
@ -118,12 +118,12 @@ class MainWindow(Window):
|
||||
def main_window_back(self) -> None:
|
||||
"""Move back in the main window stack."""
|
||||
|
||||
# Users should always check can_change_main_window() before
|
||||
# Users should always check main_window_has_control() before
|
||||
# calling us. Error if it seems they did not.
|
||||
if not self.can_change_main_window():
|
||||
if not self.main_window_has_control():
|
||||
raise RuntimeError(
|
||||
'main_window_back() should only be called'
|
||||
' if can_change_main_window() returns True'
|
||||
' if main_window_has_control() returns True'
|
||||
' (it currently is False).'
|
||||
)
|
||||
|
||||
@ -137,13 +137,13 @@ class MainWindow(Window):
|
||||
) -> None:
|
||||
"""Replace ourself with a new MainWindow."""
|
||||
|
||||
# Users should always check can_change_main_window() before
|
||||
# Users should always check main_window_has_control() before
|
||||
# creating new MainWindows and passing them in here. Error if it
|
||||
# seems they did not.
|
||||
if not self.can_change_main_window():
|
||||
if not self.main_window_has_control():
|
||||
raise RuntimeError(
|
||||
'main_window_replace() should only be called'
|
||||
' if can_change_main_window() returns True'
|
||||
' if main_window_has_control() returns True'
|
||||
' (it currently is False).'
|
||||
)
|
||||
|
||||
|
||||
@ -2,4 +2,4 @@
|
||||
#
|
||||
"""Library of stuff using the bauiv1 api: windows, custom controls, etc."""
|
||||
|
||||
# ba_meta require api 8
|
||||
# ba_meta require api 9
|
||||
|
||||
@ -12,6 +12,8 @@ import bauiv1 as bui
|
||||
if TYPE_CHECKING:
|
||||
from typing import Any, Sequence
|
||||
|
||||
REQUIRE_PRO = False
|
||||
|
||||
|
||||
class ColorPicker(PopupWindow):
|
||||
"""A popup UI to select from a set of colors.
|
||||
@ -105,9 +107,8 @@ class ColorPicker(PopupWindow):
|
||||
on_activate_call=bui.WeakCall(self._select_other),
|
||||
)
|
||||
|
||||
# Custom colors are limited to pro currently.
|
||||
assert bui.app.classic is not None
|
||||
if not bui.app.classic.accounts.have_pro():
|
||||
if REQUIRE_PRO and not bui.app.classic.accounts.have_pro():
|
||||
bui.imagewidget(
|
||||
parent=self.root_widget,
|
||||
position=(50, 12),
|
||||
@ -137,7 +138,7 @@ class ColorPicker(PopupWindow):
|
||||
|
||||
# Requires pro.
|
||||
assert bui.app.classic is not None
|
||||
if not bui.app.classic.accounts.have_pro():
|
||||
if REQUIRE_PRO and not bui.app.classic.accounts.have_pro():
|
||||
purchase.PurchaseWindow(items=['pro'])
|
||||
self._transition_out()
|
||||
return
|
||||
|
||||
@ -16,6 +16,8 @@ if TYPE_CHECKING:
|
||||
|
||||
from bauiv1lib.coop.tournamentbutton import TournamentButton
|
||||
|
||||
HARD_REQUIRES_PRO = False
|
||||
|
||||
|
||||
class CoopBrowserWindow(bui.MainWindow):
|
||||
"""Window for browsing co-op levels/games/etc."""
|
||||
@ -90,6 +92,7 @@ class CoopBrowserWindow(bui.MainWindow):
|
||||
|
||||
if (
|
||||
self._campaign_difficulty == 'hard'
|
||||
and HARD_REQUIRES_PRO
|
||||
and not classic.accounts.have_pro_options()
|
||||
):
|
||||
plus.add_v1_account_transaction(
|
||||
@ -430,7 +433,12 @@ class CoopBrowserWindow(bui.MainWindow):
|
||||
bui.imagewidget(
|
||||
edit=self._hard_button_lock_image,
|
||||
opacity=(
|
||||
0.0 if bui.app.classic.accounts.have_pro_options() else 1.0
|
||||
0.0
|
||||
if (
|
||||
(not HARD_REQUIRES_PRO)
|
||||
or bui.app.classic.accounts.have_pro_options()
|
||||
)
|
||||
else 1.0
|
||||
),
|
||||
)
|
||||
except Exception:
|
||||
@ -490,6 +498,7 @@ class CoopBrowserWindow(bui.MainWindow):
|
||||
if difficulty != self._campaign_difficulty:
|
||||
if (
|
||||
difficulty == 'hard'
|
||||
and HARD_REQUIRES_PRO
|
||||
and not bui.app.classic.accounts.have_pro_options()
|
||||
):
|
||||
PurchaseWindow(items=['pro'])
|
||||
|
||||
@ -49,7 +49,11 @@ class InGameMenuWindow(bui.MainWindow):
|
||||
@override
|
||||
def get_main_window_state(self) -> bui.MainWindowState:
|
||||
# Support recreating our window for back/refresh purposes.
|
||||
cls = type(self)
|
||||
return self.do_get_main_window_state()
|
||||
|
||||
@classmethod
|
||||
def do_get_main_window_state(cls) -> bui.MainWindowState:
|
||||
"""Classmethod to gen a windowstate for the main menu."""
|
||||
return bui.BasicMainWindowState(
|
||||
create_call=lambda transition, origin_widget: cls(
|
||||
transition=transition, origin_widget=origin_widget
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
#
|
||||
"""Defines a default keyboards."""
|
||||
|
||||
# ba_meta require api 8
|
||||
# ba_meta require api 9
|
||||
# (see https://ballistica.net/wiki/meta-tag-system)
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@ -153,6 +153,33 @@ class MainMenuWindow(bui.MainWindow):
|
||||
|
||||
app = bui.app
|
||||
assert app.classic is not None
|
||||
uiscale = app.ui_v1.uiscale
|
||||
|
||||
# Temp note about UI changes.
|
||||
bui.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(
|
||||
(-400, 400)
|
||||
if uiscale is bui.UIScale.LARGE
|
||||
else (
|
||||
(-270, 320)
|
||||
if uiscale is bui.UIScale.MEDIUM
|
||||
else (-280, 280)
|
||||
)
|
||||
),
|
||||
size=(0, 0),
|
||||
scale=0.4,
|
||||
flatness=1.0,
|
||||
text=(
|
||||
'WARNING: This build contains a revamped UI\n'
|
||||
'which is still a work-in-progress. A number\n'
|
||||
'of features are not currently functional or\n'
|
||||
'contain bugs. To go back to the stable legacy UI,\n'
|
||||
'grab version 1.7.36 from ballistica.net'
|
||||
),
|
||||
h_align='left',
|
||||
v_align='top',
|
||||
)
|
||||
|
||||
self._have_quit_button = app.classic.platform in (
|
||||
'windows',
|
||||
@ -183,7 +210,6 @@ class MainMenuWindow(bui.MainWindow):
|
||||
side_button_2_y_offs = 10.0
|
||||
side_button_2_scale = 0.5
|
||||
|
||||
uiscale = bui.app.ui_v1.uiscale
|
||||
if uiscale is bui.UIScale.SMALL:
|
||||
root_widget_scale = 1.3
|
||||
button_y_offs = -20.0
|
||||
@ -204,6 +230,24 @@ class MainMenuWindow(bui.MainWindow):
|
||||
scale=root_widget_scale,
|
||||
)
|
||||
|
||||
# Version/copyright info.
|
||||
bui.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(self._width * 0.5, button_y_offs - 10),
|
||||
size=(0, 0),
|
||||
scale=0.4,
|
||||
flatness=1.0,
|
||||
color=(1, 1, 1, 0.3),
|
||||
text=(
|
||||
f'{app.env.engine_version}'
|
||||
f' build {app.env.engine_build_number}.'
|
||||
f' Copyright 2024 Eric Froemling.'
|
||||
),
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
transition_delay=self._t_delay_play,
|
||||
)
|
||||
|
||||
# In kiosk mode, provide a button to get back to the kiosk menu.
|
||||
if bui.app.env.demo or bui.app.env.arcade:
|
||||
# h, v, scale = positions[self._p_index]
|
||||
@ -443,7 +487,7 @@ class MainMenuWindow(bui.MainWindow):
|
||||
from bauiv1lib.confirm import QuitWindow
|
||||
|
||||
# no-op if we're not currently in control.
|
||||
if not self.can_change_main_window():
|
||||
if not self.main_window_has_control():
|
||||
return
|
||||
|
||||
# Note: Normally we should go through bui.quit(confirm=True) but
|
||||
@ -456,7 +500,7 @@ class MainMenuWindow(bui.MainWindow):
|
||||
from bauiv1lib.credits import CreditsWindow
|
||||
|
||||
# no-op if we're not currently in control.
|
||||
if not self.can_change_main_window():
|
||||
if not self.main_window_has_control():
|
||||
return
|
||||
|
||||
self.main_window_replace(
|
||||
@ -469,7 +513,7 @@ class MainMenuWindow(bui.MainWindow):
|
||||
from bauiv1lib.helpui import HelpWindow
|
||||
|
||||
# no-op if we're not currently in control.
|
||||
if not self.can_change_main_window():
|
||||
if not self.main_window_has_control():
|
||||
return
|
||||
|
||||
self.main_window_replace(
|
||||
@ -536,7 +580,7 @@ class MainMenuWindow(bui.MainWindow):
|
||||
from bauiv1lib.gather import GatherWindow
|
||||
|
||||
# no-op if we're not currently in control.
|
||||
if not self.can_change_main_window():
|
||||
if not self.main_window_has_control():
|
||||
return
|
||||
|
||||
self.main_window_replace(
|
||||
@ -548,7 +592,7 @@ class MainMenuWindow(bui.MainWindow):
|
||||
from bauiv1lib.watch import WatchWindow
|
||||
|
||||
# no-op if we're not currently in control.
|
||||
if not self.can_change_main_window():
|
||||
if not self.main_window_has_control():
|
||||
return
|
||||
|
||||
self.main_window_replace(
|
||||
@ -560,7 +604,7 @@ class MainMenuWindow(bui.MainWindow):
|
||||
from bauiv1lib.play import PlayWindow
|
||||
|
||||
# no-op if we're not currently in control.
|
||||
if not self.can_change_main_window():
|
||||
if not self.main_window_has_control():
|
||||
return
|
||||
|
||||
classic = bui.app.classic
|
||||
|
||||
@ -541,7 +541,7 @@ class PlayWindow(bui.MainWindow):
|
||||
# pylint: disable=cyclic-import
|
||||
|
||||
# no-op if we're not currently in control.
|
||||
if not self.can_change_main_window():
|
||||
if not self.main_window_has_control():
|
||||
return
|
||||
|
||||
if self._is_main_menu:
|
||||
@ -567,7 +567,7 @@ class PlayWindow(bui.MainWindow):
|
||||
from bauiv1lib.coop.browser import CoopBrowserWindow
|
||||
|
||||
# no-op if we're not currently in control.
|
||||
if not self.can_change_main_window():
|
||||
if not self.main_window_has_control():
|
||||
return
|
||||
|
||||
plus = bui.app.plus
|
||||
@ -588,7 +588,7 @@ class PlayWindow(bui.MainWindow):
|
||||
from bauiv1lib.playlist.browser import PlaylistBrowserWindow
|
||||
|
||||
# no-op if we're not currently in control.
|
||||
if not self.can_change_main_window():
|
||||
if not self.main_window_has_control():
|
||||
return
|
||||
|
||||
self._save_state()
|
||||
@ -604,7 +604,7 @@ class PlayWindow(bui.MainWindow):
|
||||
from bauiv1lib.playlist.browser import PlaylistBrowserWindow
|
||||
|
||||
# no-op if we're not currently in control.
|
||||
if not self.can_change_main_window():
|
||||
if not self.main_window_has_control():
|
||||
return
|
||||
|
||||
self._save_state()
|
||||
|
||||
@ -1,569 +0,0 @@
|
||||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""UI for presenting sales/etc."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import logging
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import bauiv1 as bui
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Any
|
||||
|
||||
|
||||
class SpecialOfferWindow(bui.Window):
|
||||
"""Window for presenting sales/etc."""
|
||||
|
||||
def __init__(self, offer: dict[str, Any], transition: str = 'in_right'):
|
||||
# pylint: disable=too-many-statements
|
||||
# pylint: disable=too-many-branches
|
||||
# pylint: disable=too-many-locals
|
||||
from babase import SpecialChar
|
||||
from bauiv1lib.store import item as storeitemui
|
||||
|
||||
plus = bui.app.plus
|
||||
assert plus is not None
|
||||
|
||||
assert bui.app.classic is not None
|
||||
store = bui.app.classic.store
|
||||
|
||||
self._cancel_delay = offer.get('cancelDelay', 0)
|
||||
|
||||
# First thing: if we're offering pro or an IAP, see if we have a
|
||||
# price for it.
|
||||
# If not, abort and go into zombie mode (the user should never see
|
||||
# us that way).
|
||||
|
||||
real_price: str | None
|
||||
|
||||
# Misnomer: 'pro' actually means offer 'pro_sale'.
|
||||
if offer['item'] in ['pro', 'pro_fullprice']:
|
||||
real_price = plus.get_price(
|
||||
'pro' if offer['item'] == 'pro_fullprice' else 'pro_sale'
|
||||
)
|
||||
if real_price is None and bui.app.env.debug:
|
||||
print('NOTE: Faking prices for debug build.')
|
||||
real_price = '$1.23'
|
||||
zombie = real_price is None
|
||||
elif isinstance(offer['price'], str):
|
||||
# (a string price implies IAP id)
|
||||
real_price = plus.get_price(offer['price'])
|
||||
if real_price is None and bui.app.env.debug:
|
||||
print('NOTE: Faking price for debug build.')
|
||||
real_price = '$1.23'
|
||||
zombie = real_price is None
|
||||
else:
|
||||
real_price = None
|
||||
zombie = False
|
||||
if real_price is None:
|
||||
real_price = '?'
|
||||
|
||||
if offer['item'] in ['pro', 'pro_fullprice']:
|
||||
self._offer_item = 'pro'
|
||||
else:
|
||||
self._offer_item = offer['item']
|
||||
|
||||
# If we wanted a real price but didn't find one, go zombie.
|
||||
if zombie:
|
||||
return
|
||||
|
||||
# This can pop up suddenly, so lets block input for 1 second.
|
||||
bui.lock_all_input()
|
||||
bui.apptimer(1.0, bui.unlock_all_input)
|
||||
bui.getsound('ding').play()
|
||||
bui.apptimer(0.3, bui.getsound('ooh').play)
|
||||
self._offer = copy.deepcopy(offer)
|
||||
self._width = 580
|
||||
self._height = 590
|
||||
uiscale = bui.app.ui_v1.uiscale
|
||||
super().__init__(
|
||||
root_widget=bui.containerwidget(
|
||||
size=(self._width, self._height),
|
||||
transition=transition,
|
||||
scale=(
|
||||
1.2
|
||||
if uiscale is bui.UIScale.SMALL
|
||||
else 1.15 if uiscale is bui.UIScale.MEDIUM else 1.0
|
||||
),
|
||||
stack_offset=(
|
||||
(0, -15) if uiscale is bui.UIScale.SMALL else (0, 0)
|
||||
),
|
||||
)
|
||||
)
|
||||
self._is_bundle_sale = False
|
||||
try:
|
||||
if offer['item'] in ['pro', 'pro_fullprice']:
|
||||
original_price_str = plus.get_price('pro')
|
||||
if original_price_str is None:
|
||||
original_price_str = '?'
|
||||
new_price_str = plus.get_price('pro_sale')
|
||||
if new_price_str is None:
|
||||
new_price_str = '?'
|
||||
percent_off_text = ''
|
||||
else:
|
||||
# If the offer includes bonus tickets, it's a bundle-sale.
|
||||
if (
|
||||
'bonusTickets' in offer
|
||||
and offer['bonusTickets'] is not None
|
||||
):
|
||||
self._is_bundle_sale = True
|
||||
original_price = plus.get_v1_account_misc_read_val(
|
||||
'price.' + self._offer_item, 9999
|
||||
)
|
||||
|
||||
# For pure ticket prices we can show a percent-off.
|
||||
if isinstance(offer['price'], int):
|
||||
new_price = offer['price']
|
||||
tchar = bui.charstr(SpecialChar.TICKET)
|
||||
original_price_str = tchar + str(original_price)
|
||||
new_price_str = tchar + str(new_price)
|
||||
percent_off = int(
|
||||
round(
|
||||
100.0 - (float(new_price) / original_price) * 100.0
|
||||
)
|
||||
)
|
||||
percent_off_text = ' ' + bui.Lstr(
|
||||
resource='store.salePercentText'
|
||||
).evaluate().replace('${PERCENT}', str(percent_off))
|
||||
else:
|
||||
original_price_str = new_price_str = '?'
|
||||
percent_off_text = ''
|
||||
|
||||
except Exception:
|
||||
logging.exception('Error setting up special-offer: %s.', offer)
|
||||
original_price_str = new_price_str = '?'
|
||||
percent_off_text = ''
|
||||
|
||||
# If its a bundle sale, change the title.
|
||||
if self._is_bundle_sale:
|
||||
sale_text = bui.Lstr(
|
||||
resource='store.saleBundleText',
|
||||
fallback_resource='store.saleText',
|
||||
).evaluate()
|
||||
else:
|
||||
# For full pro we say 'Upgrade?' since its not really a sale.
|
||||
if offer['item'] == 'pro_fullprice':
|
||||
sale_text = bui.Lstr(
|
||||
resource='store.upgradeQuestionText',
|
||||
fallback_resource='store.saleExclaimText',
|
||||
).evaluate()
|
||||
else:
|
||||
sale_text = bui.Lstr(
|
||||
resource='store.saleExclaimText',
|
||||
fallback_resource='store.saleText',
|
||||
).evaluate()
|
||||
|
||||
self._title_text = bui.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(self._width * 0.5, self._height - 40),
|
||||
size=(0, 0),
|
||||
text=sale_text
|
||||
+ (
|
||||
(' ' + bui.Lstr(resource='store.oneTimeOnlyText').evaluate())
|
||||
if self._offer['oneTimeOnly']
|
||||
else ''
|
||||
)
|
||||
+ percent_off_text,
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
maxwidth=self._width * 0.9 - 220,
|
||||
scale=1.4,
|
||||
color=(0.3, 1, 0.3),
|
||||
)
|
||||
|
||||
self._flash_on = False
|
||||
self._flashing_timer: bui.AppTimer | None = bui.AppTimer(
|
||||
0.05, bui.WeakCall(self._flash_cycle), repeat=True
|
||||
)
|
||||
bui.apptimer(0.6, bui.WeakCall(self._stop_flashing))
|
||||
|
||||
size = store.get_store_item_display_size(self._offer_item)
|
||||
display: dict[str, Any] = {}
|
||||
storeitemui.instantiate_store_item_display(
|
||||
self._offer_item,
|
||||
display,
|
||||
parent_widget=self._root_widget,
|
||||
b_pos=(
|
||||
self._width * 0.5
|
||||
- size[0] * 0.5
|
||||
+ 10
|
||||
- ((size[0] * 0.5 + 30) if self._is_bundle_sale else 0),
|
||||
self._height * 0.5
|
||||
- size[1] * 0.5
|
||||
+ 20
|
||||
+ (20 if self._is_bundle_sale else 0),
|
||||
),
|
||||
b_width=size[0],
|
||||
b_height=size[1],
|
||||
button=not self._is_bundle_sale,
|
||||
)
|
||||
|
||||
# Wire up the parts we need.
|
||||
if self._is_bundle_sale:
|
||||
self._plus_text = bui.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(self._width * 0.5, self._height * 0.5 + 50),
|
||||
size=(0, 0),
|
||||
text='+',
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
maxwidth=self._width * 0.9,
|
||||
scale=1.4,
|
||||
color=(0.5, 0.5, 0.5),
|
||||
)
|
||||
self._plus_tickets = bui.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(self._width * 0.5 + 120, self._height * 0.5 + 50),
|
||||
size=(0, 0),
|
||||
text=bui.charstr(SpecialChar.TICKET_BACKING)
|
||||
+ str(offer['bonusTickets']),
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
maxwidth=self._width * 0.9,
|
||||
scale=2.5,
|
||||
color=(0.2, 1, 0.2),
|
||||
)
|
||||
self._price_text = bui.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(self._width * 0.5, 150),
|
||||
size=(0, 0),
|
||||
text=real_price,
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
maxwidth=self._width * 0.9,
|
||||
scale=1.4,
|
||||
color=(0.2, 1, 0.2),
|
||||
)
|
||||
# Total-value if they supplied it.
|
||||
total_worth_item = offer.get('valueItem', 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 None
|
||||
)
|
||||
if total_worth_price is not None:
|
||||
total_worth_text = bui.Lstr(
|
||||
resource='store.totalWorthText',
|
||||
subs=[('${TOTAL_WORTH}', total_worth_price)],
|
||||
)
|
||||
self._total_worth_text = bui.textwidget(
|
||||
parent=self._root_widget,
|
||||
text=total_worth_text,
|
||||
position=(self._width * 0.5, 210),
|
||||
scale=0.9,
|
||||
maxwidth=self._width * 0.7,
|
||||
size=(0, 0),
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
shadow=1.0,
|
||||
flatness=1.0,
|
||||
color=(0.3, 1, 1),
|
||||
)
|
||||
|
||||
elif offer['item'] == 'pro_fullprice':
|
||||
# for full-price pro we simply show full price
|
||||
bui.textwidget(edit=display['price_widget'], text=real_price)
|
||||
bui.buttonwidget(
|
||||
edit=display['button'], on_activate_call=self._purchase
|
||||
)
|
||||
else:
|
||||
# Show old/new prices otherwise (for pro sale).
|
||||
bui.buttonwidget(
|
||||
edit=display['button'], on_activate_call=self._purchase
|
||||
)
|
||||
bui.imagewidget(edit=display['price_slash_widget'], opacity=1.0)
|
||||
bui.textwidget(
|
||||
edit=display['price_widget_left'], text=original_price_str
|
||||
)
|
||||
bui.textwidget(
|
||||
edit=display['price_widget_right'], text=new_price_str
|
||||
)
|
||||
|
||||
# Add ticket button only if this is ticket-purchasable.
|
||||
# if isinstance(offer.get('price'), int):
|
||||
# self._get_tickets_button = bui.buttonwidget(
|
||||
# parent=self._root_widget,
|
||||
# position=(self._width - 125, self._height - 68),
|
||||
# size=(90, 55),
|
||||
# scale=1.0,
|
||||
# button_type='square',
|
||||
# color=(0.7, 0.5, 0.85),
|
||||
# textcolor=(0.2, 1, 0.2),
|
||||
# autoselect=True,
|
||||
# label=bui.Lstr(resource='getTicketsWindow.titleText'),
|
||||
# on_activate_call=self._on_get_more_tickets_press,
|
||||
# )
|
||||
|
||||
# self._ticket_text_update_timer = bui.AppTimer(
|
||||
# 1.0, bui.WeakCall(self._update_tickets_text), repeat=True
|
||||
# )
|
||||
# self._update_tickets_text()
|
||||
|
||||
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)
|
||||
if self._is_bundle_sale
|
||||
else (self._width * 0.5 - 75, 40)
|
||||
),
|
||||
size=(150, 60),
|
||||
scale=1.0,
|
||||
on_activate_call=self._cancel,
|
||||
autoselect=True,
|
||||
label=bui.Lstr(resource='noThanksText'),
|
||||
)
|
||||
self._cancel_countdown_text = bui.textwidget(
|
||||
parent=self._root_widget,
|
||||
text='',
|
||||
position=(
|
||||
(50 + 150 + 20, 40 + 27)
|
||||
if self._is_bundle_sale
|
||||
else (self._width * 0.5 - 75 + 150 + 20, 40 + 27)
|
||||
),
|
||||
scale=1.1,
|
||||
size=(0, 0),
|
||||
h_align='left',
|
||||
v_align='center',
|
||||
shadow=1.0,
|
||||
flatness=1.0,
|
||||
color=(0.6, 0.5, 0.5),
|
||||
)
|
||||
self._update_cancel_button_graphics()
|
||||
|
||||
if self._is_bundle_sale:
|
||||
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 if self._is_bundle_sale else None
|
||||
),
|
||||
selected_child=(
|
||||
self._purchase_button
|
||||
if self._is_bundle_sale
|
||||
else display['button']
|
||||
),
|
||||
)
|
||||
|
||||
def _stop_flashing(self) -> None:
|
||||
self._flashing_timer = None
|
||||
bui.textwidget(edit=self._title_text, color=(0.3, 1, 0.3))
|
||||
|
||||
def _flash_cycle(self) -> None:
|
||||
if not self._root_widget:
|
||||
return
|
||||
self._flash_on = not self._flash_on
|
||||
bui.textwidget(
|
||||
edit=self._title_text,
|
||||
color=(0.3, 1, 0.3) if self._flash_on else (1, 0.5, 0),
|
||||
)
|
||||
|
||||
def _update_cancel_button_graphics(self) -> None:
|
||||
bui.buttonwidget(
|
||||
edit=self._cancel_button,
|
||||
color=(
|
||||
(0.5, 0.5, 0.5) if self._cancel_delay > 0 else (0.7, 0.4, 0.34)
|
||||
),
|
||||
textcolor=(
|
||||
(0.5, 0.5, 0.5) if self._cancel_delay > 0 else (0.9, 0.9, 1.0)
|
||||
),
|
||||
)
|
||||
bui.textwidget(
|
||||
edit=self._cancel_countdown_text,
|
||||
text=str(self._cancel_delay) if self._cancel_delay > 0 else '',
|
||||
)
|
||||
|
||||
def _update(self) -> None:
|
||||
plus = bui.app.plus
|
||||
assert plus is not None
|
||||
|
||||
# If we've got seconds left on our countdown, update it.
|
||||
if self._cancel_delay > 0:
|
||||
self._cancel_delay = max(0, self._cancel_delay - 1)
|
||||
self._update_cancel_button_graphics()
|
||||
|
||||
can_die = False
|
||||
|
||||
# We go away if we see that our target item is owned.
|
||||
if self._offer_item == 'pro':
|
||||
assert bui.app.classic is not None
|
||||
if bui.app.classic.accounts.have_pro():
|
||||
can_die = True
|
||||
else:
|
||||
if plus.get_purchased(self._offer_item):
|
||||
can_die = True
|
||||
|
||||
if can_die:
|
||||
self._transition_out('out_left')
|
||||
|
||||
def _transition_out(self, transition: str = 'out_left') -> None:
|
||||
# Also clear any pending-special-offer we've stored at this point.
|
||||
cfg = bui.app.config
|
||||
if 'pendingSpecialOffer' in cfg:
|
||||
del cfg['pendingSpecialOffer']
|
||||
cfg.commit()
|
||||
|
||||
bui.containerwidget(edit=self._root_widget, transition=transition)
|
||||
|
||||
# def _update_tickets_text(self) -> None:
|
||||
# from babase import SpecialChar
|
||||
|
||||
# plus = bui.app.plus
|
||||
# assert plus is not None
|
||||
|
||||
# if not self._root_widget:
|
||||
# return
|
||||
# sval: str | bui.Lstr
|
||||
# if plus.get_v1_account_state() == 'signed_in':
|
||||
# sval = bui.charstr(SpecialChar.TICKET) + str(
|
||||
# plus.get_v1_account_ticket_count()
|
||||
# )
|
||||
# else:
|
||||
# sval = bui.Lstr(resource='getTicketsWindow.titleText')
|
||||
# bui.buttonwidget(edit=self._get_tickets_button, label=sval)
|
||||
|
||||
# def _on_get_more_tickets_press(self) -> None:
|
||||
# from bauiv1lib import account
|
||||
# from bauiv1lib import gettickets
|
||||
|
||||
# plus = bui.app.plus
|
||||
# assert plus is not None
|
||||
|
||||
# if plus.get_v1_account_state() != 'signed_in':
|
||||
# account.show_sign_in_prompt()
|
||||
# return
|
||||
# gettickets.GetTicketsWindow(modal=True).get_root_widget()
|
||||
|
||||
def _purchase(self) -> None:
|
||||
# from bauiv1lib import gettickets
|
||||
from bauiv1lib import confirm
|
||||
|
||||
plus = bui.app.plus
|
||||
assert plus is not None
|
||||
|
||||
assert bui.app.classic is not None
|
||||
store = bui.app.classic.store
|
||||
|
||||
if self._offer['item'] == 'pro':
|
||||
plus.purchase('pro_sale')
|
||||
elif self._offer['item'] == 'pro_fullprice':
|
||||
plus.purchase('pro')
|
||||
elif self._is_bundle_sale:
|
||||
# With bundle sales, the price is the name of the IAP.
|
||||
plus.purchase(self._offer['price'])
|
||||
else:
|
||||
ticket_count: int | None
|
||||
try:
|
||||
ticket_count = plus.get_v1_account_ticket_count()
|
||||
except Exception:
|
||||
ticket_count = None
|
||||
if ticket_count is not None and ticket_count < self._offer['price']:
|
||||
# gettickets.show_get_tickets_prompt()
|
||||
bui.getsound('error').play()
|
||||
return
|
||||
|
||||
def do_it() -> None:
|
||||
assert plus is not None
|
||||
|
||||
plus.in_game_purchase(
|
||||
'offer:' + str(self._offer['id']), self._offer['price']
|
||||
)
|
||||
|
||||
bui.getsound('swish').play()
|
||||
confirm.ConfirmWindow(
|
||||
bui.Lstr(
|
||||
resource='store.purchaseConfirmText',
|
||||
subs=[
|
||||
(
|
||||
'${ITEM}',
|
||||
store.get_store_item_name_translated(
|
||||
self._offer['item']
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
width=400,
|
||||
height=120,
|
||||
action=do_it,
|
||||
ok_text=bui.Lstr(
|
||||
resource='store.purchaseText', fallback_resource='okText'
|
||||
),
|
||||
)
|
||||
|
||||
def _cancel(self) -> None:
|
||||
if self._cancel_delay > 0:
|
||||
bui.getsound('error').play()
|
||||
return
|
||||
self._transition_out('out_right')
|
||||
|
||||
|
||||
def show_offer() -> bool:
|
||||
"""(internal)"""
|
||||
try:
|
||||
from bauiv1lib import feedback
|
||||
|
||||
plus = bui.app.plus
|
||||
assert plus is not None
|
||||
|
||||
app = bui.app
|
||||
if app.classic is None:
|
||||
raise RuntimeError(
|
||||
'Classic feature-set is required to show offers.'
|
||||
)
|
||||
|
||||
# Space things out a bit so we don't hit the poor user with an
|
||||
# ad and then an in-game offer.
|
||||
has_been_long_enough_since_ad = True
|
||||
if app.classic.ads.last_ad_completion_time is not None and (
|
||||
bui.apptime() - app.classic.ads.last_ad_completion_time < 30.0
|
||||
):
|
||||
has_been_long_enough_since_ad = False
|
||||
|
||||
if (
|
||||
app.classic.special_offer is not None
|
||||
and has_been_long_enough_since_ad
|
||||
):
|
||||
# Special case: for pro offers, store this in our prefs so
|
||||
# we can re-show it if the user kills us (set phasers to
|
||||
# 'NAG'!!!).
|
||||
if app.classic.special_offer.get('item') == 'pro_fullprice':
|
||||
cfg = app.config
|
||||
cfg['pendingSpecialOffer'] = {
|
||||
'a': plus.get_v1_account_public_login_id(),
|
||||
'o': app.classic.special_offer,
|
||||
}
|
||||
cfg.commit()
|
||||
|
||||
if app.classic.special_offer['item'] == 'rating':
|
||||
# Go with a native thing if we've got one.
|
||||
if bui.native_review_request_supported():
|
||||
bui.native_review_request()
|
||||
else:
|
||||
if app.ui_v1.available:
|
||||
feedback.ask_for_rating()
|
||||
else:
|
||||
if app.ui_v1.available:
|
||||
SpecialOfferWindow(app.classic.special_offer)
|
||||
|
||||
app.classic.special_offer = None
|
||||
return True
|
||||
except Exception:
|
||||
logging.exception('Error showing offer.')
|
||||
|
||||
return False
|
||||
@ -125,55 +125,11 @@ class StoreBrowserWindow(bui.MainWindow):
|
||||
bui.containerwidget(
|
||||
edit=self._root_widget, on_cancel_call=self._back
|
||||
)
|
||||
# backbutton = bui.get_special_widget('back_button')
|
||||
backbuttonspecial = True
|
||||
else:
|
||||
bui.containerwidget(edit=self._root_widget, cancel_button=btn)
|
||||
# backbutton = self._back_button
|
||||
backbuttonspecial = False
|
||||
|
||||
# self._ticket_count_text: bui.Widget | None = None
|
||||
# self._get_tickets_button: bui.Widget | None = None
|
||||
|
||||
# if bool(False):
|
||||
# if app.classic.allow_ticket_purchases:
|
||||
# self._get_tickets_button = bui.buttonwidget(
|
||||
# parent=self._root_widget,
|
||||
# size=(210, 65),
|
||||
# on_activate_call=self._on_get_more_tickets_press,
|
||||
# autoselect=True,
|
||||
# scale=0.9,
|
||||
# text_scale=1.4,
|
||||
# left_widget=backbutton,
|
||||
# color=(0.7, 0.5, 0.85),
|
||||
# textcolor=(0.2, 1.0, 0.2),
|
||||
# label=bui.Lstr(resource='getTicketsWindow.titleText'),
|
||||
# )
|
||||
# else:
|
||||
# self._ticket_count_text = bui.textwidget(
|
||||
# parent=self._root_widget,
|
||||
# size=(210, 64),
|
||||
# color=(0.2, 1.0, 0.2),
|
||||
# h_align='center',
|
||||
# v_align='center',
|
||||
# )
|
||||
|
||||
# Move this dynamically to keep it out of the way of the party icon.
|
||||
# self._update_get_tickets_button_pos()
|
||||
# self._get_ticket_pos_update_timer = bui.AppTimer(
|
||||
# 1.0,
|
||||
# bui.WeakCall(self._update_get_tickets_button_pos),
|
||||
# repeat=True,
|
||||
# )
|
||||
# if self._get_tickets_button and not backbuttonspecial:
|
||||
# bui.widget(
|
||||
# edit=self._back_button, right_widget=self._get_tickets_button
|
||||
# )
|
||||
# self._ticket_text_update_timer = bui.AppTimer(
|
||||
# 1.0, bui.WeakCall(self._update_tickets_text), repeat=True
|
||||
# )
|
||||
# self._update_tickets_text()
|
||||
|
||||
if (
|
||||
app.classic.platform in ['mac', 'ios']
|
||||
and app.classic.subplatform == 'appstore'
|
||||
@ -316,17 +272,6 @@ class StoreBrowserWindow(bui.MainWindow):
|
||||
)
|
||||
self._update_tabs()
|
||||
|
||||
# if self._get_tickets_button:
|
||||
# last_tab_button = self._tab_row.tabs[tabs_def[-1][0]].button
|
||||
# bui.widget(
|
||||
# edit=self._get_tickets_button, down_widget=last_tab_button
|
||||
# )
|
||||
# bui.widget(
|
||||
# edit=last_tab_button,
|
||||
# up_widget=self._get_tickets_button,
|
||||
# right_widget=self._get_tickets_button,
|
||||
# )
|
||||
|
||||
if uiscale is bui.UIScale.SMALL:
|
||||
first_tab_button = self._tab_row.tabs[tabs_def[0][0]].button
|
||||
last_tab_button = self._tab_row.tabs[tabs_def[-1][0]].button
|
||||
@ -346,28 +291,6 @@ class StoreBrowserWindow(bui.MainWindow):
|
||||
self._status_textwidget: bui.Widget | None = None
|
||||
self._restore_state()
|
||||
|
||||
# def _update_get_tickets_button_pos(self) -> None:
|
||||
# assert bui.app.classic is not None
|
||||
# uiscale = bui.app.ui_v1.uiscale
|
||||
# pos = (
|
||||
# self._width
|
||||
# - 252
|
||||
# - (
|
||||
# self._x_inset
|
||||
# + (
|
||||
# 47
|
||||
# if uiscale is bui.UIScale.SMALL
|
||||
# and bui.is_party_icon_visible()
|
||||
# else 0
|
||||
# )
|
||||
# ),
|
||||
# self._height - 70,
|
||||
# )
|
||||
# if self._get_tickets_button:
|
||||
# bui.buttonwidget(edit=self._get_tickets_button, position=pos)
|
||||
# if self._ticket_count_text:
|
||||
# bui.textwidget(edit=self._ticket_count_text, position=pos)
|
||||
|
||||
def _restore_purchases(self) -> None:
|
||||
from bauiv1lib import account
|
||||
|
||||
@ -411,25 +334,6 @@ class StoreBrowserWindow(bui.MainWindow):
|
||||
bui.textwidget(edit=tab_data['text'], text='')
|
||||
bui.imagewidget(edit=tab_data['img'], opacity=0.0)
|
||||
|
||||
# def _update_tickets_text(self) -> None:
|
||||
# from bauiv1 import SpecialChar
|
||||
|
||||
# if not self._root_widget:
|
||||
# return
|
||||
# plus = bui.app.plus
|
||||
# assert plus is not None
|
||||
# sval: str | bui.Lstr
|
||||
# if plus.get_v1_account_state() == 'signed_in':
|
||||
# sval = bui.charstr(SpecialChar.TICKET) + str(
|
||||
# plus.get_v1_account_ticket_count()
|
||||
# )
|
||||
# else:
|
||||
# sval = bui.Lstr(resource='getTicketsWindow.titleText')
|
||||
# if self._get_tickets_button:
|
||||
# bui.buttonwidget(edit=self._get_tickets_button, label=sval)
|
||||
# if self._ticket_count_text:
|
||||
# bui.textwidget(edit=self._ticket_count_text, text=sval)
|
||||
|
||||
def _set_tab(self, tab_id: TabID) -> None:
|
||||
if self._current_tab is tab_id:
|
||||
return
|
||||
@ -491,7 +395,6 @@ class StoreBrowserWindow(bui.MainWindow):
|
||||
window = self._window()
|
||||
if window is not None and (window.request is self):
|
||||
window.request = None
|
||||
# noinspection PyProtectedMember
|
||||
window._on_response(data)
|
||||
|
||||
# Kick off a server request.
|
||||
@ -601,8 +504,6 @@ class StoreBrowserWindow(bui.MainWindow):
|
||||
from bauiv1lib import account
|
||||
from bauiv1lib.confirm import ConfirmWindow
|
||||
|
||||
# from bauiv1lib import gettickets
|
||||
|
||||
assert bui.app.classic is not None
|
||||
store = bui.app.classic.store
|
||||
|
||||
@ -1292,8 +1193,6 @@ class StoreBrowserWindow(bui.MainWindow):
|
||||
for tab_id, tab in self._tab_row.tabs.items()
|
||||
if sel == tab.button
|
||||
]
|
||||
# if sel == self._get_tickets_button:
|
||||
# sel_name = 'GetTickets'
|
||||
if sel == self._scrollwidget:
|
||||
sel_name = 'Scroll'
|
||||
elif sel == self._back_button:
|
||||
@ -1327,8 +1226,6 @@ class StoreBrowserWindow(bui.MainWindow):
|
||||
|
||||
if self._show_tab is not None:
|
||||
current_tab = self._show_tab
|
||||
# if sel_name == 'GetTickets' and self._get_tickets_button:
|
||||
# sel = self._get_tickets_button
|
||||
if sel_name == 'Back':
|
||||
sel = self._back_button
|
||||
elif sel_name == 'Scroll':
|
||||
@ -1342,7 +1239,7 @@ class StoreBrowserWindow(bui.MainWindow):
|
||||
else:
|
||||
sel = self._tab_row.tabs[current_tab].button
|
||||
|
||||
# If we were requested to show a tab, select it too..
|
||||
# If we were requested to show a tab, select it too.
|
||||
if (
|
||||
self._show_tab is not None
|
||||
and self._show_tab in self._tab_row.tabs
|
||||
@ -1354,63 +1251,21 @@ class StoreBrowserWindow(bui.MainWindow):
|
||||
except Exception:
|
||||
logging.exception('Error restoring state for %s.', self)
|
||||
|
||||
# def _on_get_more_tickets_press(self) -> None:
|
||||
# # pylint: disable=cyclic-import
|
||||
# from bauiv1lib.account import show_sign_in_prompt
|
||||
# from bauiv1lib.gettickets import GetTicketsWindow
|
||||
|
||||
# # no-op if our underlying widget is dead or on its way out.
|
||||
# if not self._root_widget or self._root_widget.transitioning_out:
|
||||
# return
|
||||
|
||||
# plus = bui.app.plus
|
||||
# assert plus is not None
|
||||
|
||||
# if plus.get_v1_account_state() != 'signed_in':
|
||||
# show_sign_in_prompt()
|
||||
# return
|
||||
# self._save_state()
|
||||
# bui.containerwidget(edit=self._root_widget, transition='out_left')
|
||||
# window = GetTicketsWindow(
|
||||
# from_modal_store=self._modal,
|
||||
# store_back_location=self._back_location,
|
||||
# )
|
||||
# if not self._modal:
|
||||
# assert bui.app.classic is not None
|
||||
# bui.app.ui_v1.set_main_window(window, from_window=self)
|
||||
|
||||
def _back(self) -> None:
|
||||
# pylint: disable=cyclic-import
|
||||
# from bauiv1lib.coop.browser import CoopBrowserWindow
|
||||
# from bauiv1lib.mainmenu import MainMenuWindow
|
||||
|
||||
# no-op if our underlying widget is dead or on its way out.
|
||||
if not self._root_widget or self._root_widget.transitioning_out:
|
||||
return
|
||||
|
||||
self._save_state()
|
||||
|
||||
if self._modal:
|
||||
# no-op if our underlying widget is dead or on its way out.
|
||||
if not self._root_widget or self._root_widget.transitioning_out:
|
||||
return
|
||||
self._save_state()
|
||||
bui.containerwidget(
|
||||
edit=self._root_widget, transition=self._transition_out
|
||||
)
|
||||
else:
|
||||
# no-op if we're not currently in control.
|
||||
if not self.main_window_has_control():
|
||||
return
|
||||
self.main_window_back()
|
||||
# if not self._modal:
|
||||
# assert bui.app.classic is not None
|
||||
# if self._back_location == 'CoopBrowserWindow':
|
||||
# bui.app.ui_v1.set_main_window(
|
||||
# CoopBrowserWindow(transition='in_left'),
|
||||
# from_window=self,
|
||||
# is_back=True,
|
||||
# )
|
||||
# else:
|
||||
# bui.app.ui_v1.set_main_window(
|
||||
# MainMenuWindow(transition='in_left'),
|
||||
# from_window=self,
|
||||
# is_back=True,
|
||||
# is_top_level=True,
|
||||
# )
|
||||
if self._on_close_call is not None:
|
||||
self._on_close_call()
|
||||
|
||||
|
||||
@ -66,6 +66,6 @@ void AppMode::LanguageChanged() {}
|
||||
|
||||
auto AppMode::LastClientJoinTime() const -> millisecs_t { return -1; }
|
||||
|
||||
auto AppMode::InClassicMainMenuSession() const -> bool { return false; }
|
||||
auto AppMode::IsInMainMenu() const -> bool { return false; }
|
||||
|
||||
} // namespace ballistica::base
|
||||
|
||||
@ -68,8 +68,8 @@ class AppMode {
|
||||
/// Called when language changes.
|
||||
virtual void LanguageChanged();
|
||||
|
||||
/// Are we currently in a classic 'main menu' session?
|
||||
virtual auto InClassicMainMenuSession() const -> bool;
|
||||
/// Are we currently in a 'main menu' situation (as opposed to gameplay)?
|
||||
virtual auto IsInMainMenu() const -> bool;
|
||||
|
||||
/// Get current party size (for legacy parties).
|
||||
virtual auto GetPartySize() const -> int;
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
#include <unordered_map>
|
||||
|
||||
#include "ballistica/base/app_adapter/app_adapter.h"
|
||||
#include "ballistica/base/app_mode/app_mode.h"
|
||||
#include "ballistica/base/assets/sound_asset.h"
|
||||
#include "ballistica/base/input/input.h"
|
||||
#include "ballistica/base/platform/base_platform.h"
|
||||
@ -350,6 +351,37 @@ static PyMethodDef PyInLogicThreadDef = {
|
||||
"Returns whether or not the current thread is the logic thread.",
|
||||
};
|
||||
|
||||
// ------------------------------ in_main_menu ---------------------------------
|
||||
|
||||
static auto PyInMainMenu(PyObject* self, PyObject* args,
|
||||
PyObject* keywds) -> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
static const char* kwlist[] = {nullptr};
|
||||
if (!PyArg_ParseTupleAndKeywords(args, keywds, "",
|
||||
const_cast<char**>(kwlist))) {
|
||||
return nullptr;
|
||||
}
|
||||
BA_PRECONDITION(g_base->InLogicThread());
|
||||
if (g_base->app_mode()->IsInMainMenu()) {
|
||||
Py_RETURN_TRUE;
|
||||
}
|
||||
Py_RETURN_FALSE;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
static PyMethodDef PyInMainMenuDef = {
|
||||
"in_main_menu", // name
|
||||
(PyCFunction)PyInMainMenu, // method
|
||||
METH_VARARGS | METH_KEYWORDS, // flags
|
||||
|
||||
"in_main_menu() -> bool\n"
|
||||
"\n"
|
||||
"(internal)\n"
|
||||
"\n"
|
||||
"Returns whether or not the app-mode is currently in a main menu\n"
|
||||
"situation (as opposed to gameplay).",
|
||||
};
|
||||
|
||||
// ----------------------------- set_thread_name -------------------------------
|
||||
|
||||
static auto PySetThreadName(PyObject* self, PyObject* args,
|
||||
@ -1949,6 +1981,7 @@ auto PythonMoethodsBase3::GetMethods() -> std::vector<PyMethodDef> {
|
||||
PyGetThreadNameDef,
|
||||
PySetThreadNameDef,
|
||||
PyInLogicThreadDef,
|
||||
PyInMainMenuDef,
|
||||
PyRequestPermissionDef,
|
||||
PyHavePermissionDef,
|
||||
PyUnlockAllInputDef,
|
||||
|
||||
@ -67,7 +67,7 @@ base::InputDeviceDelegate* ClassicAppMode::CreateInputDeviceDelegate(
|
||||
// Go with 5 minute ban.
|
||||
const int kKickBanSeconds = 5 * 60;
|
||||
|
||||
bool ClassicAppMode::InClassicMainMenuSession() const {
|
||||
bool ClassicAppMode::IsInMainMenu() const {
|
||||
scene_v1::HostSession* hostsession =
|
||||
scene_v1::ContextRefSceneV1::FromAppForegroundContext().GetHostSession();
|
||||
return (hostsession && hostsession->is_main_menu());
|
||||
|
||||
@ -155,7 +155,7 @@ class ClassicAppMode : public base::AppMode {
|
||||
void OnAppStart() override;
|
||||
void OnAppSuspend() override;
|
||||
void OnAppUnsuspend() override;
|
||||
auto InClassicMainMenuSession() const -> bool override;
|
||||
auto IsInMainMenu() const -> bool override;
|
||||
auto CreateInputDeviceDelegate(base::InputDevice* device)
|
||||
-> base::InputDeviceDelegate* override;
|
||||
|
||||
|
||||
@ -39,9 +39,9 @@ auto main(int argc, char** argv) -> int {
|
||||
namespace ballistica {
|
||||
|
||||
// These are set automatically via script; don't modify them here.
|
||||
const int kEngineBuildNumber = 21968;
|
||||
const int kEngineBuildNumber = 21977;
|
||||
const char* kEngineVersion = "1.7.37";
|
||||
const int kEngineApiVersion = 8;
|
||||
const int kEngineApiVersion = 9;
|
||||
|
||||
#if BA_MONOLITHIC_BUILD
|
||||
|
||||
|
||||
@ -3,7 +3,9 @@
|
||||
#include "ballistica/ui_v1/widget/root_widget.h"
|
||||
|
||||
#include "ballistica/base/app_mode/app_mode.h"
|
||||
#include "ballistica/base/graphics/renderer/renderer.h"
|
||||
#include "ballistica/base/assets/assets.h"
|
||||
#include "ballistica/base/graphics/renderer/render_pass.h"
|
||||
#include "ballistica/base/graphics/support/frame_def.h"
|
||||
#include "ballistica/shared/foundation/inline.h"
|
||||
#include "ballistica/ui_v1/python/ui_v1_python.h"
|
||||
#include "ballistica/ui_v1/widget/button_widget.h"
|
||||
@ -43,6 +45,8 @@ struct RootWidget::ButtonDef {
|
||||
uint32_t visibility_mask{};
|
||||
bool selectable{true};
|
||||
bool enable_sound{true};
|
||||
bool allow_in_main_menu{true};
|
||||
bool allow_in_game{true};
|
||||
float h_align{};
|
||||
float x{};
|
||||
float y{};
|
||||
@ -81,6 +85,8 @@ struct RootWidget::Button {
|
||||
bool selectable{true};
|
||||
bool fully_offscreen{};
|
||||
bool enabled{};
|
||||
bool allow_in_main_menu{true};
|
||||
bool allow_in_game{true};
|
||||
uint32_t visibility_mask{};
|
||||
};
|
||||
|
||||
@ -222,6 +228,8 @@ void RootWidget::AddMeter_(MeterType type, float h_align, float r, float g,
|
||||
| static_cast<uint32_t>(Widget::ToolbarVisibility::kMenuFullNoBack)
|
||||
| static_cast<uint32_t>(Widget::ToolbarVisibility::kMenuFullRoot));
|
||||
|
||||
bd.allow_in_game = false;
|
||||
|
||||
// Show some in store mode.
|
||||
if (type == MeterType::kLevel || type == MeterType::kTickets) {
|
||||
bd.visibility_mask |=
|
||||
@ -491,6 +499,8 @@ void RootWidget::AddMeter_(MeterType type, float h_align, float r, float g,
|
||||
}
|
||||
|
||||
bd.pre_buffer = -10.0f;
|
||||
bd.allow_in_game = false;
|
||||
|
||||
Button* btn = AddButton_(bd);
|
||||
if (type == MeterType::kTokens) {
|
||||
get_tokens_button_ = btn;
|
||||
@ -662,11 +672,7 @@ void RootWidget::Setup() {
|
||||
| static_cast<uint32_t>(Widget::ToolbarVisibility::kMenuFullNoBack)
|
||||
| static_cast<uint32_t>(Widget::ToolbarVisibility::kMenuFullRoot));
|
||||
|
||||
// on desktop, stick this in the top left corner
|
||||
// if (g_ui->scale() == UIScale::kLarge) {
|
||||
// bd.h_align = 0.0f;
|
||||
// bd.x = 120.0f;
|
||||
// }
|
||||
bd.allow_in_game = false;
|
||||
|
||||
Button* b = account_button_ = AddButton_(bd);
|
||||
top_left_buttons_.push_back(b);
|
||||
@ -675,9 +681,9 @@ void RootWidget::Setup() {
|
||||
{
|
||||
TextDef td;
|
||||
td.button = b;
|
||||
td.y = 9.0f;
|
||||
td.width = bd.width * 0.9f;
|
||||
td.text = "Player Name";
|
||||
td.y = 0.0f;
|
||||
td.width = bd.width * 0.8f;
|
||||
td.text = "AccountName";
|
||||
td.scale = 1.2f;
|
||||
td.depth_min = 0.3f;
|
||||
td.color_r = 0.5f;
|
||||
@ -688,19 +694,19 @@ void RootWidget::Setup() {
|
||||
}
|
||||
|
||||
// Clan.
|
||||
{
|
||||
TextDef td;
|
||||
td.button = b;
|
||||
td.y = -12.0f;
|
||||
td.width = bd.width * 0.9f;
|
||||
td.depth_min = 0.3f;
|
||||
td.text = "Clan Name";
|
||||
td.color_a = 0.6f;
|
||||
td.scale = 0.6f;
|
||||
td.flatness = 1.0f;
|
||||
td.shadow = 0.0f;
|
||||
AddText_(td);
|
||||
}
|
||||
// {
|
||||
// TextDef td;
|
||||
// td.button = b;
|
||||
// td.y = -12.0f;
|
||||
// td.width = bd.width * 0.9f;
|
||||
// td.depth_min = 0.3f;
|
||||
// td.text = "Clan Name";
|
||||
// td.color_a = 0.6f;
|
||||
// td.scale = 0.6f;
|
||||
// td.flatness = 1.0f;
|
||||
// td.shadow = 0.0f;
|
||||
// AddText_(td);
|
||||
// }
|
||||
}
|
||||
|
||||
// float anchorx = 0.0f;
|
||||
@ -737,6 +743,7 @@ void RootWidget::Setup() {
|
||||
| static_cast<uint32_t>(Widget::ToolbarVisibility::kMenuFullRoot));
|
||||
b.pre_buffer = 5.0f;
|
||||
b.enable_sound = false;
|
||||
b.allow_in_main_menu = false;
|
||||
menu_button_ = AddButton_(b);
|
||||
top_right_buttons_.push_back(menu_button_);
|
||||
}
|
||||
@ -795,6 +802,7 @@ void RootWidget::Setup() {
|
||||
| static_cast<uint32_t>(Widget::ToolbarVisibility::kMenuFullNoBack)
|
||||
| static_cast<uint32_t>(Widget::ToolbarVisibility::kMenuFullRoot));
|
||||
b.pre_buffer = 20.0f;
|
||||
b.allow_in_game = false;
|
||||
inbox_button_ = AddButton_(b);
|
||||
bottom_left_buttons_.push_back(inbox_button_);
|
||||
}
|
||||
@ -819,6 +827,7 @@ void RootWidget::Setup() {
|
||||
| static_cast<uint32_t>(Widget::ToolbarVisibility::kMenuFullNoBack)
|
||||
| static_cast<uint32_t>(Widget::ToolbarVisibility::kMenuFullRoot));
|
||||
b.pre_buffer = 20.0f;
|
||||
b.allow_in_game = false;
|
||||
achievements_button_ = AddButton_(b);
|
||||
bottom_left_buttons_.push_back(achievements_button_);
|
||||
bx += 80.0f;
|
||||
@ -1007,7 +1016,7 @@ void RootWidget::Setup() {
|
||||
(static_cast<uint32_t>(Widget::ToolbarVisibility::kMenuFull)
|
||||
| static_cast<uint32_t>(Widget::ToolbarVisibility::kMenuFullNoBack)
|
||||
| static_cast<uint32_t>(Widget::ToolbarVisibility::kMenuFullRoot));
|
||||
|
||||
bd.allow_in_game = false;
|
||||
AddButton_(bd);
|
||||
}
|
||||
|
||||
@ -1026,6 +1035,7 @@ void RootWidget::Setup() {
|
||||
float spacing = 130.0f;
|
||||
b.x = -1.5f * spacing;
|
||||
b.call = UIV1Python::ObjID::kRootUIChestSlot1PressCall;
|
||||
b.allow_in_game = false;
|
||||
AddButton_(b);
|
||||
|
||||
b.x = -0.5f * spacing;
|
||||
@ -1090,6 +1100,7 @@ void RootWidget::Setup() {
|
||||
| static_cast<uint32_t>(Widget::ToolbarVisibility::kMenuFullRoot));
|
||||
b.disable_offset_scale = 1.5f;
|
||||
b.pre_buffer = 20.0f;
|
||||
b.allow_in_game = false;
|
||||
inventory_button_ = AddButton_(b);
|
||||
bottom_right_buttons_.push_back(inventory_button_);
|
||||
}
|
||||
@ -1109,6 +1120,8 @@ void RootWidget::Setup() {
|
||||
| static_cast<uint32_t>(Widget::ToolbarVisibility::kMenuFullNoBack)
|
||||
| static_cast<uint32_t>(Widget::ToolbarVisibility::kMenuFullRoot));
|
||||
b.pre_buffer = 20.0f;
|
||||
b.allow_in_game = false;
|
||||
|
||||
store_button_ = AddButton_(b);
|
||||
bottom_right_buttons_.push_back(store_button_);
|
||||
}
|
||||
@ -1147,6 +1160,8 @@ auto RootWidget::AddButton_(const ButtonDef& def) -> RootWidget::Button* {
|
||||
b.h_align = def.h_align;
|
||||
b.v_align = def.v_align;
|
||||
b.selectable = def.selectable;
|
||||
b.allow_in_game = def.allow_in_game;
|
||||
b.allow_in_main_menu = def.allow_in_main_menu;
|
||||
b.widget = Object::New<ButtonWidget>();
|
||||
b.widget->SetColor(def.color_r, def.color_g, def.color_b);
|
||||
b.widget->set_opacity(def.opacity);
|
||||
@ -1246,11 +1261,8 @@ void RootWidget::UpdateForFocusedWindow() {
|
||||
}
|
||||
|
||||
void RootWidget::UpdateForFocusedWindow_(Widget* widget) {
|
||||
// Take note if the current session is the main menu; we do a few things
|
||||
// differently there.
|
||||
//
|
||||
// FIXME - need a more generalized way to determine this.
|
||||
in_main_menu_ = g_base->app_mode()->InClassicMainMenuSession();
|
||||
// Take note whether we're currently in a main menu vs gameplay.
|
||||
in_main_menu_ = g_base->app_mode()->IsInMainMenu();
|
||||
|
||||
if (widget == nullptr) {
|
||||
toolbar_visibility_ = ToolbarVisibility::kInGame;
|
||||
@ -1278,9 +1290,23 @@ void RootWidget::StepPositions_(float dt) {
|
||||
// When we're in the main menu, always disable the menu button and
|
||||
// shift the party button a bit to the right
|
||||
if (in_main_menu_) {
|
||||
if (&b == menu_button_) {
|
||||
if (!b.allow_in_main_menu) {
|
||||
enable_button = false;
|
||||
}
|
||||
// Disallow menu button in the main menu.
|
||||
// if (&b == menu_button_) {
|
||||
// enable_button = false;
|
||||
// }
|
||||
} else {
|
||||
if (!b.allow_in_game) {
|
||||
enable_button = false;
|
||||
}
|
||||
// Disallow most buttons in-game.
|
||||
// if (&b == inbox_button_ || &b == achievements_button_
|
||||
// || &b == account_button_ || &b == level_meter_button_
|
||||
// || &b == trophy_meter_button_ || &b == store_button_ || &b == ) {
|
||||
// enable_button = false;
|
||||
// }
|
||||
}
|
||||
if (&b == back_button_) {
|
||||
// Back button is always disabled in medium/large UI.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user