diff --git a/.efrocachemap b/.efrocachemap index 2d23d073..5bc02571 100644 --- a/.efrocachemap +++ b/.efrocachemap @@ -432,43 +432,43 @@ "build/assets/ba_data/audio/zoeOw.ogg": "b2d705c31c9dcc1efdc71394764c3beb", "build/assets/ba_data/audio/zoePickup01.ogg": "e9366dc2d2b8ab8b0c4e2c14c02d0789", "build/assets/ba_data/audio/zoeScream01.ogg": "903e0e45ee9b3373e9d9ce20c814374e", - "build/assets/ba_data/data/langdata.json": "dbbd8f26d2f85c0b649d461e991b80cb", - "build/assets/ba_data/data/languages/arabic.json": "3c22e7b6d7b09a812a2e28b35c9e9241", + "build/assets/ba_data/data/langdata.json": "4c8242df36a1b589035beb4711cbf772", + "build/assets/ba_data/data/languages/arabic.json": "397dfd469ef7c744cbb472cd19b73f1f", "build/assets/ba_data/data/languages/belarussian.json": "0b60a9d4496d1213c2d0b647d346ce30", - "build/assets/ba_data/data/languages/chinese.json": "fc45d2838b834889c06920ae7c2102fa", - "build/assets/ba_data/data/languages/chinesetraditional.json": "904b35b656c53f9830e406565edd5120", + "build/assets/ba_data/data/languages/chinese.json": "168b529f2d55d714886be57f162f6842", + "build/assets/ba_data/data/languages/chinesetraditional.json": "32f53581b80ce723edbe8aa7956e6727", "build/assets/ba_data/data/languages/croatian.json": "e131a87cf5783e0fbb3d211a927efe1a", - "build/assets/ba_data/data/languages/czech.json": "d18b7d1c6bf51fc81af4084ef0e69e3e", + "build/assets/ba_data/data/languages/czech.json": "3418bee44e69be13b7f72996abe96921", "build/assets/ba_data/data/languages/danish.json": "8e57db30c5250df2abff14a822f83ea7", - "build/assets/ba_data/data/languages/dutch.json": "4085dec5af362cf068b494524ced3872", + "build/assets/ba_data/data/languages/dutch.json": "4ba5bbcc0fecddd0aac6ee2c165d1e40", "build/assets/ba_data/data/languages/english.json": "527d106870b0690cc39a80b88e60ab7a", "build/assets/ba_data/data/languages/esperanto.json": "0e397cfa5f3fb8cef5f4a64f21cda880", - "build/assets/ba_data/data/languages/filipino.json": "3d9269a90a2fee164d0a7513c4f130a3", + "build/assets/ba_data/data/languages/filipino.json": "1894fc331dcad7ce9cf4c180843f548f", "build/assets/ba_data/data/languages/french.json": "6d20655730b1017ef187fd828b91d43c", - "build/assets/ba_data/data/languages/german.json": "b92ec951b5a0ce4f73677051ca59a06b", + "build/assets/ba_data/data/languages/german.json": "bc656f1ada467161c23546f48d0dacc5", "build/assets/ba_data/data/languages/gibberish.json": "2569fe1b2f686670f825e2faaa8c5dc3", "build/assets/ba_data/data/languages/greek.json": "d28d1092fbb00ed857cbd53124c0dc78", "build/assets/ba_data/data/languages/hindi.json": "567e6976b3c72f891431ad7fcc62ab16", - "build/assets/ba_data/data/languages/hungarian.json": "9d88004a98f0fbe2ea72edd5e0b3002e", + "build/assets/ba_data/data/languages/hungarian.json": "af801baffb2c06460635dfb04c34bb3e", "build/assets/ba_data/data/languages/indonesian.json": "607ba358179185f032096ea1978e4448", - "build/assets/ba_data/data/languages/italian.json": "eabad2faba952c426876bc07e1490d09", - "build/assets/ba_data/data/languages/korean.json": "4e3524327a0174250aff5e1ef4c0c597", + "build/assets/ba_data/data/languages/italian.json": "254d4d3962fda17fe127636fa6221851", + "build/assets/ba_data/data/languages/korean.json": "360760d72832863e1a3451b0a514cb08", "build/assets/ba_data/data/languages/malay.json": "0212e18e54efa202c17505376e5b82fb", - "build/assets/ba_data/data/languages/persian.json": "859a60de6226fdf9fc24b68b7f6782b6", - "build/assets/ba_data/data/languages/piratespeak.json": "7c7e3b72b87c1bcd5b04c9f64d912f0c", - "build/assets/ba_data/data/languages/polish.json": "941eb816c7db9e04d6a3b8f28a64e2e8", - "build/assets/ba_data/data/languages/portuguese.json": "b4463a05d65515f6812e1177c60ac666", - "build/assets/ba_data/data/languages/romanian.json": "5ae206fe0b71c4015b02b86da8931c8f", - "build/assets/ba_data/data/languages/russian.json": "fc64ed6b6356ea11385ee5c20748425a", + "build/assets/ba_data/data/languages/persian.json": "517217e679c768fff4ffec7f8000ab77", + "build/assets/ba_data/data/languages/piratespeak.json": "be23decfaf220b3aa3de3cf35e59b420", + "build/assets/ba_data/data/languages/polish.json": "993b612c5854fc42a78726ed09c65251", + "build/assets/ba_data/data/languages/portuguese.json": "f034d8099298b56792d4e0e41c5c34a0", + "build/assets/ba_data/data/languages/romanian.json": "b04345d8c7631d657a69c73eb7be755a", + "build/assets/ba_data/data/languages/russian.json": "eca8fe1ef8343aee559e49c49805b850", "build/assets/ba_data/data/languages/serbian.json": "623fa4129a1154c2f32ed7867e56ff6a", "build/assets/ba_data/data/languages/slovak.json": "c11c29708b3742cdc2a92b4fa0d6d29f", "build/assets/ba_data/data/languages/spanish.json": "f8ab976d219e579546bb98b6d7fd12ce", "build/assets/ba_data/data/languages/swedish.json": "3b179e7333183c70adb0811246b09959", "build/assets/ba_data/data/languages/tamil.json": "ead39b864228696a9b0d19344bc4b5ec", "build/assets/ba_data/data/languages/thai.json": "383540a1e9c7c131ac579f51afc87471", - "build/assets/ba_data/data/languages/turkish.json": "ccec3224e41bee03f798d9c1a7d23342", + "build/assets/ba_data/data/languages/turkish.json": "1415bdb746551f0a24f0e675304dfe07", "build/assets/ba_data/data/languages/ukrainian.json": "6063d27c9d6ed013b2b64ff452433621", - "build/assets/ba_data/data/languages/venetian.json": "abebcc38ca2655578e65428cc0dd3c45", + "build/assets/ba_data/data/languages/venetian.json": "e0666c6a1db1792d895fcb250e59861b", "build/assets/ba_data/data/languages/vietnamese.json": "59f6686890ceac2b0ac92597751a18ca", "build/assets/ba_data/data/maps/big_g.json": "1dd301d490643088a435ce75df971054", "build/assets/ba_data/data/maps/bridgit.json": "6aea74805f4880cc11237c5734a24422", @@ -1333,10 +1333,10 @@ "build/assets/ba_data/textures/buttonSquare.dds": "48e117e4a57a1d99ebf0e38dd414a95b", "build/assets/ba_data/textures/buttonSquare.ktx": "936f04a43f99f692071c7e9567cc2a3d", "build/assets/ba_data/textures/buttonSquare.pvr": "b53fe9f1725e0d28bc974f6ef1623fb0", - "build/assets/ba_data/textures/buttonSquareWide.dds": "5ab9ff791eae959d49f7ac1018f395a9", - "build/assets/ba_data/textures/buttonSquareWide.ktx": "043bbadbe5adff0236d2841fcaeadb87", - "build/assets/ba_data/textures/buttonSquareWide.pvr": "ca3773bfeacc8dcf42387a6da6ef0807", - "build/assets/ba_data/textures/buttonSquareWide_preview.png": "a5a02ed8a9a0b4d0043fcf21fc0f5f8a", + "build/assets/ba_data/textures/buttonSquareWide.dds": "879ecc705072338a427f25fdbf759792", + "build/assets/ba_data/textures/buttonSquareWide.ktx": "98e3b5d628823ffac2add60aa63c0274", + "build/assets/ba_data/textures/buttonSquareWide.pvr": "f5ea38658215153ef97bba12ce113923", + "build/assets/ba_data/textures/buttonSquareWide_preview.png": "543a1f28b538ada0f63c998f2c75e518", "build/assets/ba_data/textures/buttonSquare_preview.png": "a9abe7bbf392caf8141adf81fc63f4f8", "build/assets/ba_data/textures/chTitleChar1.dds": "d8c615a51d900da15b8aba5ce35296bf", "build/assets/ba_data/textures/chTitleChar1.ktx": "44384ea28c9fe01440deb1fc80c7224a", @@ -4126,22 +4126,22 @@ "build/assets/windows/Win32/ucrtbased.dll": "bfd1180c269d3950b76f35a63655e9e1", "build/assets/windows/Win32/vc_redist.x86.exe": "15a5f1f876503885adbdf5b3989b3718", "build/assets/windows/Win32/vcruntime140d.dll": "865b2af4d1e26a1a8073c89acb06e599", - "build/prefab/full/linux_arm64_gui/debug/ballisticakit": "d0d676371cecc9fff81f2b23fa6e7275", - "build/prefab/full/linux_arm64_gui/release/ballisticakit": "1a8c30616d66c2ab31879d44c355a436", - "build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "b7e5a81d1170d4e932d06c48e4500fe6", - "build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "e28fcdb70262585d0b3ae62f75d8e487", - "build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "714db737373c55db0126046e3a5998cf", - "build/prefab/full/linux_x86_64_gui/release/ballisticakit": "eb601b6b70a673fa8b5f67e766bf2a5e", - "build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "618d617773fce1dc2cc0a7dcc48722e2", - "build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "3a95038a0d6ec21e53585613d5736f64", - "build/prefab/full/mac_arm64_gui/debug/ballisticakit": "5b3e194acd4d65030362258c19c5c378", - "build/prefab/full/mac_arm64_gui/release/ballisticakit": "5d8bcc750c247f14fe44bd0a8c432e36", - "build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "c31c365f0353760437328dc05badf43a", - "build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "9dbcec2283230c23e21e7a607153d322", - "build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "25b1790251a2190bbb8b036cf764b11f", - "build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "4d08c824bff1dccf9b9fe5f5af011023", - "build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "2b93477aa54b233ce0e65805eac1c160", - "build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "321b48eeb777a3955b3afd250e99c4c5", + "build/prefab/full/linux_arm64_gui/debug/ballisticakit": "1ffc1f5dcda65363be124baa30006ccd", + "build/prefab/full/linux_arm64_gui/release/ballisticakit": "4cc5de1993654275f1d5156af47581a1", + "build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "15fd8eb2457aa3a925f723ef657f384c", + "build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "1c4bb442f9a71ee616a85ff404ee4a1d", + "build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "6c50ad81231ef51d23c09033babfef16", + "build/prefab/full/linux_x86_64_gui/release/ballisticakit": "abbd6b5cb2708d1b45e748d4383794d4", + "build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "35a616d3ef72a4867a36da69d0b997c7", + "build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "c261940da039b1e32eb0137a6e2bdfa8", + "build/prefab/full/mac_arm64_gui/debug/ballisticakit": "951b3624f3ea80423f00cae72be2854c", + "build/prefab/full/mac_arm64_gui/release/ballisticakit": "5079d46536257fb5d751461fde6eac50", + "build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "17113f9727ada85f564042267a861f4b", + "build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "822261f0c05dc5de3c68f9d76f8fd969", + "build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "e63a924dde0304441f96a8304855754b", + "build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "2bf917a51df7eb8f322d7d7cb5523458", + "build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "95943c9107f7d3756bcf88a03a1673d1", + "build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "194d951ddd839028b2c6efabdf53019f", "build/prefab/lib/linux_arm64_gui/debug/libballisticaplus.a": "1c375e8003442dd3d059bc0baa260e61", "build/prefab/lib/linux_arm64_gui/release/libballisticaplus.a": "40daac4bbc8990d5140f97e792bc4fb1", "build/prefab/lib/linux_arm64_server/debug/libballisticaplus.a": "1c375e8003442dd3d059bc0baa260e61", @@ -4154,14 +4154,14 @@ "build/prefab/lib/mac_arm64_gui/release/libballisticaplus.a": "36bb6f32ab12e2a46b82155a93b2e527", "build/prefab/lib/mac_arm64_server/debug/libballisticaplus.a": "51884d81e2d7bdeb6b59a72f0247c8e1", "build/prefab/lib/mac_arm64_server/release/libballisticaplus.a": "36bb6f32ab12e2a46b82155a93b2e527", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "b73abcca68eebe3067b6fe13db2d6545", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "ef9b111ceb3bb8566ae0c0d6579c06a3", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "426c27616a8c706e27ea94b308cc4f98", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "a1c5a7fa4b717ec9edf2cd030ff189ef", - "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "9a4debdbef814405208292bdeb7bee18", - "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "dc38927a539aa85ce720e085ee28490d", - "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "1256eedc2b351c1264e55a90f37efca9", - "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "ae14008d451581c06702334f94b50d4d", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "0cd1427bb99690616b493a59e7d567d9", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "8b472a65a2407291dde5c54bb1c24df1", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "c97c08a508bd57b325cc8817d12af73f", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "082957f5ee4f2e5d50d62384bda60895", + "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "4d8bf5c5937ed8c6e4f5e775366590cb", + "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "11909c454caaa9d50f3ed70869964743", + "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "4a9492a2cfa1d944ea5618eb2ac9cb7b", + "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "e1aeaf29546c0a357214c68fb2f0399c", "src/assets/ba_data/python/babase/_mgen/__init__.py": "f885fed7f2ed98ff2ba271f9dbe3391c", "src/assets/ba_data/python/babase/_mgen/enums.py": "794d258d59fd17a61752843a9a0551ad", "src/ballistica/base/mgen/pyembed/binding_base.inc": "06042d31df0ff9af96b99477162e2a91", diff --git a/CHANGELOG.md b/CHANGELOG.md index b03a8c76..c53ce5c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -### 1.7.37 (build 22184, api 9, 2025-01-13) +### 1.7.37 (build 22189, api 9, 2025-01-14) - 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. diff --git a/src/assets/.asset_manifest_public.json b/src/assets/.asset_manifest_public.json index bd9cbca3..f5123788 100644 --- a/src/assets/.asset_manifest_public.json +++ b/src/assets/.asset_manifest_public.json @@ -79,6 +79,7 @@ "ba_data/python/baclassic/__pycache__/_benchmark.cpython-312.opt-1.pyc", "ba_data/python/baclassic/__pycache__/_chest.cpython-312.opt-1.pyc", "ba_data/python/baclassic/__pycache__/_clienteffect.cpython-312.opt-1.pyc", + "ba_data/python/baclassic/__pycache__/_displayitem.cpython-312.opt-1.pyc", "ba_data/python/baclassic/__pycache__/_input.cpython-312.opt-1.pyc", "ba_data/python/baclassic/__pycache__/_music.cpython-312.opt-1.pyc", "ba_data/python/baclassic/__pycache__/_net.cpython-312.opt-1.pyc", @@ -97,6 +98,7 @@ "ba_data/python/baclassic/_benchmark.py", "ba_data/python/baclassic/_chest.py", "ba_data/python/baclassic/_clienteffect.py", + "ba_data/python/baclassic/_displayitem.py", "ba_data/python/baclassic/_input.py", "ba_data/python/baclassic/_music.py", "ba_data/python/baclassic/_net.py", diff --git a/src/assets/Makefile b/src/assets/Makefile index 80815e96..18004034 100644 --- a/src/assets/Makefile +++ b/src/assets/Makefile @@ -208,6 +208,7 @@ SCRIPT_TARGETS_PY_PUBLIC = \ $(BUILD_DIR)/ba_data/python/baclassic/_benchmark.py \ $(BUILD_DIR)/ba_data/python/baclassic/_chest.py \ $(BUILD_DIR)/ba_data/python/baclassic/_clienteffect.py \ + $(BUILD_DIR)/ba_data/python/baclassic/_displayitem.py \ $(BUILD_DIR)/ba_data/python/baclassic/_input.py \ $(BUILD_DIR)/ba_data/python/baclassic/_music.py \ $(BUILD_DIR)/ba_data/python/baclassic/_net.py \ @@ -490,6 +491,7 @@ SCRIPT_TARGETS_PYC_PUBLIC = \ $(BUILD_DIR)/ba_data/python/baclassic/__pycache__/_benchmark.cpython-312.opt-1.pyc \ $(BUILD_DIR)/ba_data/python/baclassic/__pycache__/_chest.cpython-312.opt-1.pyc \ $(BUILD_DIR)/ba_data/python/baclassic/__pycache__/_clienteffect.cpython-312.opt-1.pyc \ + $(BUILD_DIR)/ba_data/python/baclassic/__pycache__/_displayitem.cpython-312.opt-1.pyc \ $(BUILD_DIR)/ba_data/python/baclassic/__pycache__/_input.cpython-312.opt-1.pyc \ $(BUILD_DIR)/ba_data/python/baclassic/__pycache__/_music.cpython-312.opt-1.pyc \ $(BUILD_DIR)/ba_data/python/baclassic/__pycache__/_net.cpython-312.opt-1.pyc \ diff --git a/src/assets/ba_data/python/babase/_apputils.py b/src/assets/ba_data/python/babase/_apputils.py index 9d58da48..0ff3960d 100644 --- a/src/assets/ba_data/python/babase/_apputils.py +++ b/src/assets/ba_data/python/babase/_apputils.py @@ -41,8 +41,8 @@ def is_browser_likely_available() -> bool: category: General Utility Functions - If this returns False you may want to avoid calling babase.show_url() - with any lengthy addresses. (ba.show_url() will display an address + If this returns False you may want to avoid calling babase.open_url() + with any lengthy addresses. (babase.open_url() will display an address as a string in a window if unable to bring up a browser, but that is only useful for simple URLs.) """ diff --git a/src/assets/ba_data/python/babase/_general.py b/src/assets/ba_data/python/babase/_general.py index 92f9707d..3469fcdf 100644 --- a/src/assets/ba_data/python/babase/_general.py +++ b/src/assets/ba_data/python/babase/_general.py @@ -176,6 +176,7 @@ class _WeakCall: 'Warning: callable passed to babase.WeakCall() is not' ' weak-referencable (%s); use functools.partial instead' ' to avoid this warning.', + args[0], stack_info=True, ) type(self)._did_invalid_call_warning = True diff --git a/src/assets/ba_data/python/baclassic/__init__.py b/src/assets/ba_data/python/baclassic/__init__.py index 1c428fb2..7b5a0496 100644 --- a/src/assets/ba_data/python/baclassic/__init__.py +++ b/src/assets/ba_data/python/baclassic/__init__.py @@ -27,6 +27,7 @@ from baclassic._chest import ( CHEST_APPEARANCE_DISPLAY_INFO_DEFAULT, CHEST_APPEARANCE_DISPLAY_INFOS, ) +from baclassic._displayitem import show_display_item __all__ = [ 'ChestAppearanceDisplayInfo', @@ -36,6 +37,7 @@ __all__ = [ 'ClassicAppSubsystem', 'Achievement', 'AchievementSubsystem', + 'show_display_item', ] # We want stuff here to show up as packagename.Foo instead of diff --git a/src/assets/ba_data/python/baclassic/_displayitem.py b/src/assets/ba_data/python/baclassic/_displayitem.py new file mode 100644 index 00000000..57b0e0d7 --- /dev/null +++ b/src/assets/ba_data/python/baclassic/_displayitem.py @@ -0,0 +1,109 @@ +# Released under the MIT License. See LICENSE for details. +# +"""Display-item related functionality.""" +from __future__ import annotations + +from typing import TYPE_CHECKING + +from efro.util import pairs_from_flat +import bacommon.bs +import bauiv1 + + +if TYPE_CHECKING: + pass + + +def show_display_item( + itemwrapper: bacommon.bs.DisplayItemWrapper, + parent: bauiv1.Widget, + pos: tuple[float, float], + width: float, +) -> None: + """Create ui to depict a display-item.""" + + height = width * 0.666 + + # Silent no-op if our parent ui is dead. + if not parent: + return + + img: str | None = None + img_y_offs = 0.0 + text_y_offs = 0.0 + show_text = True + + if isinstance(itemwrapper.item, bacommon.bs.TicketsDisplayItem): + img = 'tickets' + img_y_offs = width * 0.11 + text_y_offs = width * -0.15 + elif isinstance(itemwrapper.item, bacommon.bs.TokensDisplayItem): + img = 'coin' + img_y_offs = width * 0.11 + text_y_offs = width * -0.15 + elif isinstance(itemwrapper.item, bacommon.bs.ChestDisplayItem): + from baclassic._chest import ( + CHEST_APPEARANCE_DISPLAY_INFOS, + CHEST_APPEARANCE_DISPLAY_INFO_DEFAULT, + ) + + img = None + show_text = False + c_info = CHEST_APPEARANCE_DISPLAY_INFOS.get( + itemwrapper.item.appearance, CHEST_APPEARANCE_DISPLAY_INFO_DEFAULT + ) + c_size = width * 0.85 + bauiv1.imagewidget( + parent=parent, + position=(pos[0] - c_size * 0.5, pos[1] - c_size * 0.5), + color=c_info.color, + size=(c_size, c_size), + texture=bauiv1.gettexture(c_info.texclosed), + tint_texture=bauiv1.gettexture(c_info.texclosedtint), + tint_color=c_info.tint, + tint2_color=c_info.tint2, + ) + + # Enable this for testing spacing. + if bool(False): + bauiv1.imagewidget( + parent=parent, + position=( + pos[0] - width * 0.5, + pos[1] - height * 0.5, + ), + size=(width, height), + texture=bauiv1.gettexture('white'), + color=(0, 1, 0), + opacity=0.1, + ) + + imgsize = width * 0.33 + if img is not None: + bauiv1.imagewidget( + parent=parent, + position=( + pos[0] - imgsize * 0.5, + pos[1] + img_y_offs - imgsize * 0.5, + ), + size=(imgsize, imgsize), + texture=bauiv1.gettexture(img), + ) + if show_text: + subs = itemwrapper.description_subs + if subs is None: + subs = [] + bauiv1.textwidget( + parent=parent, + position=(pos[0], pos[1] + text_y_offs), + scale=width * 0.006, + size=(0, 0), + text=bauiv1.Lstr( + translate=('serverResponses', itemwrapper.description), + subs=pairs_from_flat(subs), + ), + maxwidth=width * 0.9, + color=(0.0, 1.0, 0.0), + h_align='center', + v_align='center', + ) diff --git a/src/assets/ba_data/python/baenv.py b/src/assets/ba_data/python/baenv.py index eb0b2297..4c1136b3 100644 --- a/src/assets/ba_data/python/baenv.py +++ b/src/assets/ba_data/python/baenv.py @@ -53,7 +53,7 @@ if TYPE_CHECKING: # Build number and version of the ballistica binary we expect to be # using. -TARGET_BALLISTICA_BUILD = 22184 +TARGET_BALLISTICA_BUILD = 22189 TARGET_BALLISTICA_VERSION = '1.7.37' diff --git a/src/assets/ba_data/python/bauiv1lib/account/viewer.py b/src/assets/ba_data/python/bauiv1lib/account/viewer.py index e38211f6..532972ec 100644 --- a/src/assets/ba_data/python/bauiv1lib/account/viewer.py +++ b/src/assets/ba_data/python/bauiv1lib/account/viewer.py @@ -88,7 +88,7 @@ class AccountViewerWindow(PopupWindow): scale=0.6, text=bui.Lstr(resource='playerInfoText'), maxwidth=200, - color=(0.7, 0.7, 0.7, 0.7), + color=bui.app.ui_v1.title_color, ) self._scrollwidget = bui.scrollwidget( @@ -97,6 +97,7 @@ class AccountViewerWindow(PopupWindow): position=(30, 30), capture_arrows=True, simple_culling_v=10, + border_opacity=0.4, ) bui.widget(edit=self._scrollwidget, autoselect=True) diff --git a/src/assets/ba_data/python/bauiv1lib/achievements.py b/src/assets/ba_data/python/bauiv1lib/achievements.py index c8c0813d..d2296d95 100644 --- a/src/assets/ba_data/python/bauiv1lib/achievements.py +++ b/src/assets/ba_data/python/bauiv1lib/achievements.py @@ -20,7 +20,7 @@ class AchievementsWindow(bui.MainWindow): # pylint: disable=too-many-locals assert bui.app.classic is not None uiscale = bui.app.ui_v1.uiscale - self._width = 600 if uiscale is bui.UIScale.SMALL else 450 + self._width = 600 if uiscale is bui.UIScale.SMALL else 500 self._height = ( 380 if uiscale is bui.UIScale.SMALL @@ -102,11 +102,11 @@ class AchievementsWindow(bui.MainWindow): parent=self._root_widget, size=( self._width - 60, - self._height - (150 if uiscale is bui.UIScale.SMALL else 70), + self._height - (150 if uiscale is bui.UIScale.SMALL else 80), ), position=( 30, - (110 if uiscale is bui.UIScale.SMALL else 30) + yoffs, + (110 if uiscale is bui.UIScale.SMALL else 35) + yoffs, ), capture_arrows=True, simple_culling_v=10, diff --git a/src/assets/ba_data/python/bauiv1lib/chest.py b/src/assets/ba_data/python/bauiv1lib/chest.py index 5172bee6..2b0226ad 100644 --- a/src/assets/ba_data/python/bauiv1lib/chest.py +++ b/src/assets/ba_data/python/bauiv1lib/chest.py @@ -9,6 +9,7 @@ import math import random from typing import override, TYPE_CHECKING +from efro.util import strict_partial import bacommon.bs import bauiv1 as bui @@ -29,8 +30,6 @@ class ChestWindow(bui.MainWindow): transition: str | None = 'in_right', origin_widget: bui.Widget | None = None, ): - # print('ChestWindow()') - self._index = index assert bui.app.classic is not None @@ -514,7 +513,6 @@ class ChestWindow(bui.MainWindow): ) self._show_odds(initial_highlighted_row=-1) - # bui.textwidget(edit=self._infotext, text='') def _highlight_odds_row(self, row: int, extra: bool = False) -> None: @@ -702,8 +700,6 @@ class ChestWindow(bui.MainWindow): # Convey that something is in progress. if self._open_now_button: - # bui.buttonwidget(edit=self._open_now_button, - # color=(0.4, 1.0, 0.4)) bui.spinnerwidget(edit=self._open_now_spinner, visible=True) for twidget in self._open_now_texts: bui.textwidget(edit=twidget, color=(1, 1, 1, 0.2)) @@ -788,7 +784,7 @@ class ChestWindow(bui.MainWindow): self._reset() msg = ( 'This slot can hold a treasure chest.\n\n' - 'Earn chests by beating campaing levels,\n' + 'Earn chests by playing campaign levels,\n' 'placing in tournaments, and completing\n' 'achievements.' ) @@ -799,12 +795,22 @@ class ChestWindow(bui.MainWindow): ) -> None: # pylint: disable=too-many-locals + from baclassic import show_display_item + # No-op if our ui is dead. if not self._root_widget: return assert response.contents is not None + # Insert test items for testing. + if bool(False): + response.contents += [ + bacommon.bs.DisplayItemWrapper.for_display_item( + bacommon.bs.TestDisplayItem() + ) + ] + tincr = 0.4 tendoffs = tincr * 4.0 toffs = 0.0 @@ -861,7 +867,7 @@ class ChestWindow(bui.MainWindow): ), ) - xspacing = 150 + xspacing = 100 xoffs = -0.5 * (len(response.contents) - 1) * xspacing bui.apptimer( toffs - 0.2, lambda: bui.getsound('corkPop2').play(volume=4.0) @@ -898,14 +904,25 @@ class ChestWindow(bui.MainWindow): toffsopen = toffs bui.apptimer(toffs, bui.WeakCall(self._show_chest_opening)) toffs += tincr * 1.0 - width = xspacing * 0.75 - for obj in response.contents: + width = xspacing * 0.95 + + for item in response.contents: toffs += tincr bui.apptimer( toffs - 0.1, lambda: bui.getsound('cashRegister').play() ) bui.apptimer( - toffs, bui.WeakCall(self._show_chest_item, obj, xoffs, width) + toffs, + strict_partial( + show_display_item, + item, + self._root_widget, + pos=( + self._width * 0.5 + xoffs, + self._height - 250.0 + self._yoffs, + ), + width=width, + ), ) xoffs += xspacing toffs += tincr @@ -1003,61 +1020,6 @@ class ChestWindow(bui.MainWindow): initial_highlighted_extra=True, ) - def _show_chest_item( - self, - itemwrapper: bacommon.bs.DisplayItemWrapper, - xoffs: float, - width: float, - ) -> None: - # No-op if our ui is dead. - if not self._root_widget: - return - - img: str | None = None - if isinstance(itemwrapper.item, bacommon.bs.TicketsDisplayItem): - img = 'tickets' - elif isinstance(itemwrapper.item, bacommon.bs.TokensDisplayItem): - img = 'coin' - - # Translate the wrapper description and apply any subs. - descfin = bui.Lstr( - translate=('serverResponses', itemwrapper.description) - ).evaluate() - subs = ( - [] - if itemwrapper.description_subs is None - else itemwrapper.description_subs - ) - assert len(subs) % 2 == 0 # Should always be even. - for j in range(0, len(subs) - 1, 2): - descfin = descfin.replace(subs[j], subs[j + 1]) - - imgsize = 34 - if img is not None: - bui.imagewidget( - parent=self._root_widget, - position=( - self._width * 0.5 + xoffs - imgsize * 0.5, - self._height - 252 + 14.0 + self._yoffs - imgsize * 0.5, - ), - size=(imgsize, imgsize), - texture=bui.gettexture(img), - ) - bui.textwidget( - parent=self._root_widget, - position=( - self._width * 0.5 + xoffs, - self._height - 252 - 14.0 + self._yoffs, - ), - scale=0.65, - size=(0, 0), - text=f'+ {descfin}', - maxwidth=width, - color=(0.0, 1.0, 0.0), - h_align='center', - v_align='center', - ) - def _show_done_button(self) -> None: # No-op if our ui is dead. if not self._root_widget: diff --git a/src/assets/ba_data/python/bauiv1lib/coop/browser.py b/src/assets/ba_data/python/bauiv1lib/coop/browser.py index 4ad85450..c623d21a 100644 --- a/src/assets/ba_data/python/bauiv1lib/coop/browser.py +++ b/src/assets/ba_data/python/bauiv1lib/coop/browser.py @@ -275,7 +275,7 @@ class CoopBrowserWindow(bui.MainWindow): simple_culling_v=10.0, claims_left_right=True, selection_loops_to_parent=True, - border_opacity=0.3 if uiscale is bui.UIScale.SMALL else 1.0, + border_opacity=0.4, ) if uiscale is bui.UIScale.SMALL: diff --git a/src/assets/ba_data/python/bauiv1lib/coop/tournamentbutton.py b/src/assets/ba_data/python/bauiv1lib/coop/tournamentbutton.py index a7584685..b630e3a8 100644 --- a/src/assets/ba_data/python/bauiv1lib/coop/tournamentbutton.py +++ b/src/assets/ba_data/python/bauiv1lib/coop/tournamentbutton.py @@ -627,6 +627,10 @@ class TournamentButton: max_players = bui.app.classic.accounts.tournament_info[ self.tournament_id ]['maxPlayers'] + + print('GOT GAME', game) + print('GOT ID', self.tournament_id) + print('GOT PLAYERS', max_players) txt = bui.Lstr( value='${A} ${B}', subs=[ diff --git a/src/assets/ba_data/python/bauiv1lib/inbox.py b/src/assets/ba_data/python/bauiv1lib/inbox.py index 7be76156..eb4b7f27 100644 --- a/src/assets/ba_data/python/bauiv1lib/inbox.py +++ b/src/assets/ba_data/python/bauiv1lib/inbox.py @@ -1,25 +1,35 @@ # Released under the MIT License. See LICENSE for details. # +# pylint: disable=too-many-lines """Provides a popup window to view achievements.""" from __future__ import annotations import weakref +from functools import partial from dataclasses import dataclass -from typing import override, assert_never +from typing import override, assert_never, TYPE_CHECKING +from efro.util import strict_partial, pairs_from_flat from efro.error import CommunicationError import bacommon.bs import bauiv1 as bui +if TYPE_CHECKING: + from typing import Callable + class _Section: def get_height(self) -> float: """Return section height.""" raise NotImplementedError() - def draw(self, subcontainer: bui.Widget, y: float) -> None: - """Draw the section.""" + def get_button_row(self) -> list[bui.Widget]: + """Return rows of selectable controls.""" + return [] + + def emit(self, subcontainer: bui.Widget, y: float) -> None: + """Emit the section.""" class _TextSection(_Section): @@ -27,9 +37,8 @@ class _TextSection(_Section): def __init__( self, sub_width: float, - text: str, + text: bui.Lstr | str, *, - subs: list[str], spacing_top: float = 0.0, spacing_bottom: float = 0.0, scale: float = 0.6, @@ -40,23 +49,22 @@ class _TextSection(_Section): self.spacing_bottom = spacing_bottom self.color = color - self.textfin = bui.Lstr(translate=('serverResponses', text)).evaluate() - assert len(subs) % 2 == 0 # Should always be even. - for j in range(0, len(subs) - 1, 2): - self.textfin = self.textfin.replace(subs[j], subs[j + 1]) + # We need to bake this down since we plug its final size into + # our math. + self.textbaked = text.evaluate() if isinstance(text, bui.Lstr) else text # Calc scale to fit width and then see what height we need at # that scale. t_width = max( 10.0, - bui.get_string_width(self.textfin, suppress_warning=True) * scale, + bui.get_string_width(self.textbaked, suppress_warning=True) * scale, ) self.text_scale = scale * min(1.0, (sub_width * 0.9) / t_width) self.text_height = ( 0.0 - if not self.textfin - else bui.get_string_height(self.textfin, suppress_warning=True) + if not self.textbaked + else bui.get_string_height(self.textbaked, suppress_warning=True) ) * self.text_scale self.full_height = self.text_height + spacing_top + spacing_bottom @@ -66,25 +74,137 @@ class _TextSection(_Section): return self.full_height @override - def draw(self, subcontainer: bui.Widget, y: float) -> None: + def emit(self, subcontainer: bui.Widget, y: float) -> None: bui.textwidget( parent=subcontainer, position=( self.sub_width * 0.5, y - self.spacing_top - self.text_height * 0.5, - # y - self.height * 0.5 - 23.0, ), color=self.color, scale=self.text_scale, flatness=1.0, shadow=0.0, - text=self.textfin, + text=self.textbaked, size=(0, 0), h_align='center', v_align='center', ) +class _ButtonSection(_Section): + + def __init__( + self, + sub_width: float, + label: bui.Lstr | str, + *, + color: tuple[float, float, float], + label_color: tuple[float, float, float], + call: Callable[[_ButtonSection], None], + spacing_top: float = 0.0, + spacing_bottom: float = 0.0, + ) -> None: + self.sub_width = sub_width + self.spacing_top = spacing_top + self.spacing_bottom = spacing_bottom + self.color = color + self.label_color = label_color + self.button: bui.Widget | None = None + self.call = call + self.labelfin = label + self.button_width = 130 + self.button_height = 30 + self.full_height = self.button_height + spacing_top + spacing_bottom + + @override + def get_height(self) -> float: + return self.full_height + + @staticmethod + def weak_call(section: weakref.ref[_ButtonSection]) -> None: + """Call button section call if section still exists.""" + section_strong = section() + if section_strong is None: + return + + section_strong.call(section_strong) + + @override + def emit(self, subcontainer: bui.Widget, y: float) -> None: + self.button = bui.buttonwidget( + parent=subcontainer, + position=( + self.sub_width * 0.5 - self.button_width * 0.5, + y - self.spacing_top - self.button_height, + ), + autoselect=True, + label=self.labelfin, + textcolor=self.label_color, + text_scale=0.55, + size=(self.button_width, self.button_height), + color=self.color, + on_activate_call=strict_partial(self.weak_call, weakref.ref(self)), + ) + bui.widget(edit=self.button, depth_range=(0.1, 1.0)) + + @override + def get_button_row(self) -> list[bui.Widget]: + """Return rows of selectable controls.""" + assert self.button is not None + return [self.button] + + +class _DisplayItemsSection(_Section): + + def __init__( + self, + sub_width: float, + items: list[bacommon.bs.DisplayItemWrapper], + width: float = 100.0, + *, + spacing_top: float = 0.0, + spacing_bottom: float = 0.0, + ) -> None: + self.display_item_width = width + + # FIXME - ask for this somewhere in case it changes. + self.display_item_height = self.display_item_width * 0.666 + self.items = items + self.sub_width = sub_width + self.spacing_top = spacing_top + self.spacing_bottom = spacing_bottom + self.full_height = ( + self.display_item_height + spacing_top + spacing_bottom + ) + + @override + def get_height(self) -> float: + return self.full_height + + @override + def emit(self, subcontainer: bui.Widget, y: float) -> None: + # pylint: disable=cyclic-import + from baclassic import show_display_item + + xspacing = 1.1 * self.display_item_width + total_width = ( + 0 if not self.items else ((len(self.items) - 1) * xspacing) + ) + x = -0.5 * total_width + for item in self.items: + show_display_item( + item, + subcontainer, + pos=( + self.sub_width * 0.5 + x, + y - self.spacing_top - self.display_item_height * 0.5, + ), + width=self.display_item_width, + ) + x += xspacing + + @dataclass class _EntryDisplay: interaction_style: bacommon.bs.BasicClientUI.InteractionStyle @@ -99,7 +219,6 @@ class _EntryDisplay: button_spinner_positive: bui.Widget | None = None button_negative: bui.Widget | None = None button_spinner_negative: bui.Widget | None = None - # message_text: bui.Widget | None = None processing_complete: bool = False @@ -498,7 +617,7 @@ class InboxWindow(bui.MainWindow): button_label_negative: bacommon.bs.BasicClientUI.ButtonLabel sections: list[_Section] = [] - total_height = 90.0 + total_height = 80.0 # Display only entries where we recognize all style/label # values and ui component types. @@ -514,14 +633,18 @@ class InboxWindow(bui.MainWindow): idcls = bacommon.bs.BasicClientUIComponentTypeID for component in wrapper.ui.components: ctypeid = component.get_type_id() + section: _Section + if ctypeid is idcls.TEXT: assert isinstance( component, bacommon.bs.BasicClientUIComponentText ) section = _TextSection( sub_width=sub_width, - text=component.text, - subs=component.subs, + text=bui.Lstr( + translate=('serverResponses', component.text), + subs=pairs_from_flat(component.subs), + ), color=component.color, scale=component.scale, spacing_top=component.spacing_top, @@ -530,8 +653,209 @@ class InboxWindow(bui.MainWindow): total_height += section.get_height() sections.append(section) + elif ctypeid is idcls.LINK: + assert isinstance( + component, bacommon.bs.BasicClientUIComponentLink + ) + + def _do_open_url(url: str, sec: _ButtonSection) -> None: + del sec # Unused. + bui.open_url(url) + + section = _ButtonSection( + sub_width=sub_width, + label=bui.Lstr( + translate=('serverResponses', component.label), + subs=pairs_from_flat(component.subs), + ), + color=color, + call=partial(_do_open_url, component.url), + label_color=(0.5, 0.7, 0.6), + spacing_top=component.spacing_top, + spacing_bottom=component.spacing_bottom, + ) + total_height += section.get_height() + sections.append(section) + + elif ctypeid is idcls.DISPLAY_ITEMS: + assert isinstance( + component, + bacommon.bs.BasicClientUIDisplayItems, + ) + section = _DisplayItemsSection( + sub_width=sub_width, + items=component.items, + width=component.width, + spacing_top=component.spacing_top, + spacing_bottom=component.spacing_bottom, + ) + total_height += section.get_height() + sections.append(section) + + elif ctypeid is idcls.BS_CLASSIC_TOURNEY_RESULT: + from bascenev1 import get_trophy_string + + assert isinstance( + component, + bacommon.bs.BasicClientUIBsClassicTourneyResult, + ) + campaignname, levelname = component.game.split(':') + assert bui.app.classic is not None + campaign = bui.app.classic.getcampaign(campaignname) + + tourney_name = bui.Lstr( + value='${A} ${B}', + subs=[ + ( + '${A}', + campaign.getlevel(levelname).displayname, + ), + ( + '${B}', + bui.Lstr( + resource='playerCountAbbreviatedText', + subs=[ + ('${COUNT}', str(component.players)) + ], + ), + ), + ], + ) + + if component.trophy is not None: + trophy_prefix = ( + get_trophy_string(component.trophy) + ' ' + ) + else: + trophy_prefix = '' + + section = _TextSection( + sub_width=sub_width, + # text=bui.Lstr( + # translate=( + # 'serverResponses', + # 'You placed #${RANK}' ' in a tournament!', + # # 'You placed in a tournament!', + # ), + # subs=[('${RANK}', str(component.rank))], + # ), + text=bui.Lstr( + value='${P}${V}', + subs=[ + ('${P}', trophy_prefix), + ( + '${V}', + bui.Lstr( + translate=( + 'serverResponses', + 'You placed #${RANK}' + ' in a tournament!', + # 'You placed in a tournament!', + ), + subs=[ + ('${RANK}', str(component.rank)) + ], + ), + ), + ], + ), + color=(1.0, 1.0, 1.0, 1.0), + scale=0.6, + ) + total_height += section.get_height() + sections.append(section) + + section = _TextSection( + sub_width=sub_width, + # text=bui.Lstr( + # value='${P}${V}', + # subs=[ + # ('${P}', trophy_prefix), + # ('${V}', tourney_name), + # ], + # ), + text=tourney_name, + spacing_top=5, + color=(0.7, 0.7, 1.0, 1.0), + scale=0.7, + ) + total_height += section.get_height() + sections.append(section) + + # rank_trophy_str = f'#{component.rank}' + # if component.trophy is not None: + # rank_trophy_str = get_trophy_string( + # component.trophy + # ) + # section = _TextSection( + # sub_width=sub_width, + # text=rank_trophy_str, + # spacing_top=10, + # scale=1.0, + # ) + # total_height += section.get_height() + # sections.append(section) + + def _do_tourney_scores( + tournament_id: str, sec: _ButtonSection + ) -> None: + from bauiv1lib.tournamentscores import ( + TournamentScoresWindow, + ) + + assert sec.button is not None + _ = ( + TournamentScoresWindow( + tournament_id=tournament_id, + position=( + sec.button + ).get_screen_space_center(), + ), + ) + + section = _ButtonSection( + sub_width=sub_width, + label=bui.Lstr( + translate=('serverResponses', 'Final Standings') + ), + color=color, + call=partial( + _do_tourney_scores, component.tournament_id + ), + label_color=(0.5, 0.7, 0.6), + spacing_top=7.0, + ) + total_height += section.get_height() + sections.append(section) + + section = _TextSection( + sub_width=sub_width, + text=bui.Lstr( + translate=( + 'serverResponses', + 'Your prize:', + ) + ), + spacing_top=6, + color=(1.0, 1.0, 1.0, 0.4), + scale=0.35, + ) + total_height += section.get_height() + sections.append(section) + + section = _DisplayItemsSection( + sub_width=sub_width, + items=component.prizes, + width=70.0, + spacing_top=0.0, + spacing_bottom=0.0, + ) + total_height += section.get_height() + sections.append(section) + elif ctypeid is idcls.UNKNOWN: raise RuntimeError('Should not get here.') + else: # Make sure we handle all types. assert_never(ctypeid) @@ -550,8 +874,9 @@ class InboxWindow(bui.MainWindow): section = _TextSection( sub_width=sub_width, - text='You must update the app to view this.', - subs=[], + text=bui.Lstr( + value='You must update the app to view this.' + ), ) total_height += section.get_height() sections.append(section) @@ -611,7 +936,11 @@ class InboxWindow(bui.MainWindow): # Section contents. for sec in entry_display.sections: - sec.draw(subcontainer, ysection) + sec.emit(subcontainer, ysection) + # Wire up any widgets created by this section. + sec_button_row = sec.get_button_row() + if sec_button_row: + buttonrows.append(sec_button_row) ysection -= sec.get_height() buttonrow: list[bui.Widget] = [] @@ -633,6 +962,7 @@ class InboxWindow(bui.MainWindow): entry_display.button_positive = btn = bui.buttonwidget( parent=subcontainer, position=bpos, + autoselect=True, size=(bwidth, bheight), label=bui.app.classic.basic_client_ui_button_label_str( entry_display.button_label_positive @@ -663,6 +993,7 @@ class InboxWindow(bui.MainWindow): entry_display.button_negative = btn2 = bui.buttonwidget( parent=subcontainer, position=bpos, + autoselect=True, size=(bwidth, bheight), label=bui.app.classic.basic_client_ui_button_label_str( entry_display.button_label_negative @@ -712,11 +1043,16 @@ class InboxWindow(bui.MainWindow): bui.widget( edit=button, up_widget=above_widget, - down_widget=( - button if below_widget is None else below_widget - ), + down_widget=below_widget, + # down_widget=( + # button if below_widget is None else below_widget + # ), right_widget=buttons[max(j - 1, 0)], left_widget=buttons[min(j + 1, len(buttons) - 1)], ) above_widget = buttons[0] + + +def _get_bs_classic_tourney_results_sections() -> list[_Section]: + return [] diff --git a/src/assets/ba_data/python/bauiv1lib/playlist/addgame.py b/src/assets/ba_data/python/bauiv1lib/playlist/addgame.py index bcffd6e3..993e359a 100644 --- a/src/assets/ba_data/python/bauiv1lib/playlist/addgame.py +++ b/src/assets/ba_data/python/bauiv1lib/playlist/addgame.py @@ -126,6 +126,7 @@ class PlaylistAddGameWindow(bui.MainWindow): position=(x_inset + 61, v - scroll_height), size=(self._scroll_width, scroll_height), highlight=False, + border_opacity=0.4, ) bui.widget( edit=self._scrollwidget, diff --git a/src/assets/ba_data/python/bauiv1lib/playlist/browser.py b/src/assets/ba_data/python/bauiv1lib/playlist/browser.py index 36a7b20b..0c599f9f 100644 --- a/src/assets/ba_data/python/bauiv1lib/playlist/browser.py +++ b/src/assets/ba_data/python/bauiv1lib/playlist/browser.py @@ -142,9 +142,9 @@ class PlaylistBrowserWindow(bui.MainWindow): size=(self._scroll_width, self._scroll_height), position=( (self._width - self._scroll_width) * 0.5, - 65 + scroll_offs, + 65 + scroll_offs + (0 if uiscale is bui.UIScale.SMALL else -5), ), - border_opacity=0.4 if uiscale is bui.UIScale.SMALL else 1.0, + border_opacity=0.4, ) bui.containerwidget(edit=self._scrollwidget, claims_left_right=True) self._subcontainer: bui.Widget | None = None diff --git a/src/assets/ba_data/python/bauiv1lib/playlist/customizebrowser.py b/src/assets/ba_data/python/bauiv1lib/playlist/customizebrowser.py index 559998cf..cf65bb0c 100644 --- a/src/assets/ba_data/python/bauiv1lib/playlist/customizebrowser.py +++ b/src/assets/ba_data/python/bauiv1lib/playlist/customizebrowser.py @@ -274,6 +274,7 @@ class PlaylistCustomizeBrowserWindow(bui.MainWindow): position=(140 + x_inset, v - self._scroll_height), size=(self._width - (180 + 2 * x_inset), self._scroll_height + 10), highlight=False, + border_opacity=0.4, ) if self._back_button is not None: bui.widget(edit=self._back_button, right_widget=scrollwidget) diff --git a/src/assets/ba_data/python/bauiv1lib/playlist/edit.py b/src/assets/ba_data/python/bauiv1lib/playlist/edit.py index eb7c1d35..b4626f9d 100644 --- a/src/assets/ba_data/python/bauiv1lib/playlist/edit.py +++ b/src/assets/ba_data/python/bauiv1lib/playlist/edit.py @@ -232,6 +232,7 @@ class PlaylistEditWindow(bui.MainWindow): highlight=False, on_select_call=bui.Call(self._set_ui_selection, 'gameList'), size=(self._scroll_width, (scroll_height - 15)), + border_opacity=0.4, ) bui.widget( edit=scrollwidget, diff --git a/src/assets/ba_data/python/bauiv1lib/playlist/editgame.py b/src/assets/ba_data/python/bauiv1lib/playlist/editgame.py index 6a7be089..52ffe8ce 100644 --- a/src/assets/ba_data/python/bauiv1lib/playlist/editgame.py +++ b/src/assets/ba_data/python/bauiv1lib/playlist/editgame.py @@ -206,6 +206,7 @@ class PlaylistEditGameWindow(bui.MainWindow): highlight=False, claims_left_right=True, selection_loops_to_parent=True, + border_opacity=0.4, ) self._subcontainer = bui.containerwidget( parent=self._scrollwidget, diff --git a/src/assets/ba_data/python/bauiv1lib/playlist/mapselect.py b/src/assets/ba_data/python/bauiv1lib/playlist/mapselect.py index ca8cdc9e..a5378595 100644 --- a/src/assets/ba_data/python/bauiv1lib/playlist/mapselect.py +++ b/src/assets/ba_data/python/bauiv1lib/playlist/mapselect.py @@ -111,6 +111,7 @@ class PlaylistMapSelectWindow(bui.MainWindow): parent=self._root_widget, position=(40 + x_inset, v - self._scroll_height), size=(self._scroll_width, self._scroll_height), + border_opacity=0.4, ) bui.containerwidget( edit=self._root_widget, selected_child=self._scrollwidget diff --git a/src/assets/ba_data/python/bauiv1lib/resourcetypeinfo.py b/src/assets/ba_data/python/bauiv1lib/resourcetypeinfo.py index b701805f..7d853c14 100644 --- a/src/assets/ba_data/python/bauiv1lib/resourcetypeinfo.py +++ b/src/assets/ba_data/python/bauiv1lib/resourcetypeinfo.py @@ -30,7 +30,7 @@ class ResourceTypeInfoWindow(PopupWindow): ) self._transitioning_out = False self._width = 570 - self._height = 350 + self._height = 400 bg_color = (0.5, 0.4, 0.6) super().__init__( size=(self._width, self._height), @@ -56,6 +56,7 @@ class ResourceTypeInfoWindow(PopupWindow): yoffs = self._height - 145 if resource_type == 'tickets': + yoffs -= 20 rdesc = ( 'Tickets can be used to unlock characters,\n' 'maps, minigames, and more in the store.\n' @@ -70,8 +71,8 @@ class ResourceTypeInfoWindow(PopupWindow): 'and for other game and account features.\n' '\n' 'You can win tokens in the game or buy them\n' - 'in packs. Or buy a Gold Pass to get infinite\n' - 'tokens forever and never hear of them again.' + 'in packs. Or buy a Gold Pass for infinite\n' + 'tokens and never hear about them again.' ) texname = 'coin' elif resource_type == 'trophies': diff --git a/src/assets/ba_data/python/bauiv1lib/settings/allsettings.py b/src/assets/ba_data/python/bauiv1lib/settings/allsettings.py index 37550242..99fc8d84 100644 --- a/src/assets/ba_data/python/bauiv1lib/settings/allsettings.py +++ b/src/assets/ba_data/python/bauiv1lib/settings/allsettings.py @@ -93,7 +93,7 @@ class AllSettingsWindow(bui.MainWindow): all_buttons_width = 4.0 * bwidth + 3.0 * margin x = width * 0.5 - all_buttons_width * 0.5 - y = height + yoffs - 320.0 + y = height + yoffs - 335.0 def _button( position: tuple[float, float], diff --git a/src/assets/ba_data/python/bauiv1lib/tournamententry.py b/src/assets/ba_data/python/bauiv1lib/tournamententry.py index b8bfa9c1..550beaff 100644 --- a/src/assets/ba_data/python/bauiv1lib/tournamententry.py +++ b/src/assets/ba_data/python/bauiv1lib/tournamententry.py @@ -146,7 +146,8 @@ class TournamentEntryWindow(PopupWindow): scale=0.6, text=bui.Lstr(resource='tournamentEntryText'), maxwidth=180, - color=(1, 1, 1, 0.4), + # color=(1, 1, 1, 0.4), + color=bui.app.ui_v1.title_color, ) btn = self._pay_with_tickets_button = bui.buttonwidget( diff --git a/src/assets/ba_data/python/bauiv1lib/tournamentscores.py b/src/assets/ba_data/python/bauiv1lib/tournamentscores.py index 7cff3db1..dabad891 100644 --- a/src/assets/ba_data/python/bauiv1lib/tournamentscores.py +++ b/src/assets/ba_data/python/bauiv1lib/tournamentscores.py @@ -22,33 +22,20 @@ class TournamentScoresWindow(PopupWindow): self, tournament_id: str, *, - tournament_activity: bs.GameActivity | None = None, position: tuple[float, float] = (0.0, 0.0), - scale: float | None = None, - offset: tuple[float, float] = (0.0, 0.0), - tint_color: Sequence[float] = (1.0, 1.0, 1.0), - tint2_color: Sequence[float] = (1.0, 1.0, 1.0), - selected_character: str | None = None, - on_close_call: Callable[[], Any] | None = None, ): plus = bui.app.plus assert plus is not None - del tournament_activity # unused arg - del tint_color # unused arg - del tint2_color # unused arg - del selected_character # unused arg self._tournament_id = tournament_id self._subcontainer: bui.Widget | None = None - self._on_close_call = on_close_call assert bui.app.classic is not None uiscale = bui.app.ui_v1.uiscale - if scale is None: - scale = ( - 2.3 - if uiscale is bui.UIScale.SMALL - else 1.65 if uiscale is bui.UIScale.MEDIUM else 1.23 - ) + scale = ( + 2.3 + if uiscale is bui.UIScale.SMALL + else 1.65 if uiscale is bui.UIScale.MEDIUM else 1.23 + ) self._transitioning_out = False self._width = 400 @@ -60,13 +47,12 @@ class TournamentScoresWindow(PopupWindow): bg_color = (0.5, 0.4, 0.6) - # creates our _root_widget + # Creates our _root_widget. super().__init__( position=position, size=(self._width, self._height), scale=scale, bg_color=bg_color, - offset=offset, ) self._cancel_button = bui.buttonwidget( @@ -91,7 +77,7 @@ class TournamentScoresWindow(PopupWindow): scale=0.6, text=bui.Lstr(resource='tournamentStandingsText'), maxwidth=200, - color=(1, 1, 1, 0.4), + color=bui.app.ui_v1.title_color, ) self._scrollwidget = bui.scrollwidget( @@ -100,16 +86,18 @@ class TournamentScoresWindow(PopupWindow): position=(30, 30), highlight=False, simple_culling_v=10, + border_opacity=0.4, ) bui.widget(edit=self._scrollwidget, autoselect=True) + self._loading_spinner = bui.spinnerwidget( + parent=self.root_widget, + position=(self._width * 0.5, self._height * 0.5), + ) self._loading_text = bui.textwidget( parent=self._scrollwidget, scale=0.5, - text=bui.Lstr( - value='${A}...', - subs=[('${A}', bui.Lstr(resource='loadingText'))], - ), + text='', size=(self._width - 60, 100), h_align='center', v_align='center', @@ -132,10 +120,12 @@ class TournamentScoresWindow(PopupWindow): self, data: dict[str, Any] | None ) -> None: if data is not None: - # this used to be the whole payload + # This used to be the whole payload. data_t: list[dict[str, Any]] = data['t'] - # kill our loading text if we've got scores.. otherwise just - # replace it with 'no scores yet' + + # Kill our loading text if we've got scores; otherwise just + # replace it with 'no scores yet'. + bui.spinnerwidget(edit=self._loading_spinner, visible=False) if data_t[0]['scores']: self._loading_text.delete() else: @@ -219,7 +209,8 @@ class TournamentScoresWindow(PopupWindow): def _show_player_info(self, entry: Any, textwidget: bui.Widget) -> None: from bauiv1lib.account.viewer import AccountViewerWindow - # for the moment we only work if a single player-info is present.. + # For the moment we only work if a single player-info is + # present. if len(entry[2]) != 1: bui.getsound('error').play() return @@ -238,8 +229,6 @@ class TournamentScoresWindow(PopupWindow): if not self._transitioning_out: self._transitioning_out = True bui.containerwidget(edit=self.root_widget, transition='out_scale') - if self._on_close_call is not None: - self._on_close_call() @override def on_popup_cancel(self) -> None: diff --git a/src/ballistica/base/python/methods/python_methods_base_2.cc b/src/ballistica/base/python/methods/python_methods_base_2.cc index 96737c06..5d9d03b9 100644 --- a/src/ballistica/base/python/methods/python_methods_base_2.cc +++ b/src/ballistica/base/python/methods/python_methods_base_2.cc @@ -535,7 +535,7 @@ static auto PyGetStringHeight(PyObject* self, PyObject* args, PyObject* keywds) #if BA_DEBUG_BUILD if (g_base->assets->CompileResourceString(s) != s) { BA_LOG_PYTHON_TRACE( - "resource-string passed to get_string_height; this should be avoided"); + "Resource-string passed to get_string_height; this should be avoided."); } #endif assert(g_base->graphics); diff --git a/src/ballistica/shared/ballistica.cc b/src/ballistica/shared/ballistica.cc index 2055ff32..7706a8b4 100644 --- a/src/ballistica/shared/ballistica.cc +++ b/src/ballistica/shared/ballistica.cc @@ -39,7 +39,7 @@ auto main(int argc, char** argv) -> int { namespace ballistica { // These are set automatically via script; don't modify them here. -const int kEngineBuildNumber = 22184; +const int kEngineBuildNumber = 22189; const char* kEngineVersion = "1.7.37"; const int kEngineApiVersion = 9; diff --git a/src/ballistica/ui_v1/widget/h_scroll_widget.cc b/src/ballistica/ui_v1/widget/h_scroll_widget.cc index 0234dd11..cd13c282 100644 --- a/src/ballistica/ui_v1/widget/h_scroll_widget.cc +++ b/src/ballistica/ui_v1/widget/h_scroll_widget.cc @@ -778,9 +778,10 @@ void HScrollWidget::Draw(base::RenderPass* pass, bool draw_transparent) { if (draw_transparent && IsHierarchySelected() && g_base->ui->ShouldHighlightWidgets() && highlight_ && border_opacity_ > 0.0f) { - float m = 0.8f - + std::abs(sinf(static_cast(current_time_ms) * 0.006467f)) - * 0.2f * border_opacity_; + float m = (0.8f + + std::abs(sinf(static_cast(current_time_ms) * 0.006467f)) + * 0.2f) + * border_opacity_; if (glow_dirty_) { float r2 = l + width(); diff --git a/src/ballistica/ui_v1/widget/scroll_widget.cc b/src/ballistica/ui_v1/widget/scroll_widget.cc index ef23e764..01521718 100644 --- a/src/ballistica/ui_v1/widget/scroll_widget.cc +++ b/src/ballistica/ui_v1/widget/scroll_widget.cc @@ -836,9 +836,10 @@ void ScrollWidget::Draw(base::RenderPass* pass, bool draw_transparent) { // If selected, do glow at depth 0.9 - 1.0. if (draw_transparent && IsHierarchySelected() && g_base->ui->ShouldHighlightWidgets() && highlight_) { - float m = 0.8f - + std::abs(sinf(static_cast(current_time) * 0.006467f)) - * 0.2f * border_opacity_; + float m = + (0.8f + + std::abs(sinf(static_cast(current_time) * 0.006467f)) * 0.2f) + * border_opacity_; if (glow_dirty_) { float r2 = l + width(); float l2 = l; diff --git a/tools/bacommon/bs.py b/tools/bacommon/bs.py index 7d673b80..d08d6a88 100644 --- a/tools/bacommon/bs.py +++ b/tools/bacommon/bs.py @@ -9,6 +9,7 @@ from enum import Enum from dataclasses import dataclass, field from typing import Annotated, override, assert_never +from efro.util import pairs_to_flat from efro.dataclassio import ioprepped, IOAttrs, IOMultiType from efro.message import Message, Response @@ -102,6 +103,8 @@ class DisplayItemTypeID(Enum): UNKNOWN = 'u' TICKETS = 't' TOKENS = 'k' + TEST = 's' + CHEST = 'c' class DisplayItem(IOMultiType[DisplayItemTypeID]): @@ -123,19 +126,21 @@ class DisplayItem(IOMultiType[DisplayItemTypeID]): def get_type(cls, type_id: DisplayItemTypeID) -> type[DisplayItem]: """Return the subclass for each of our type-ids.""" # pylint: disable=cyclic-import - out: type[DisplayItem] t = DisplayItemTypeID if type_id is t.UNKNOWN: - out = UnknownDisplayItem - elif type_id is t.TICKETS: - out = TicketsDisplayItem - elif type_id is t.TOKENS: - out = TokensDisplayItem - else: - # Important to make sure we provide all types. - assert_never(type_id) - return out + 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. @@ -211,6 +216,38 @@ class TokensDisplayItem(DisplayItem): 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 '${TYPE} Chest', [('${TYPE}', self.appearance.name.capitalize())] + + @ioprepped @dataclass class DisplayItemWrapper: @@ -224,9 +261,7 @@ class DisplayItemWrapper: def for_display_item(cls, item: DisplayItem) -> DisplayItemWrapper: """Convenience method to wrap a DisplayItem.""" desc, subs = item.get_description() - # Flatten subs to single list. - flat_subs = [item for pair in subs for item in pair] - return DisplayItemWrapper(item, desc, flat_subs) + return DisplayItemWrapper(item, desc, pairs_to_flat(subs)) @ioprepped @@ -263,10 +298,10 @@ class ChestInfoResponse(Response): IOAttrs('a', enum_fallback=ClassicChestAppearance.UNKNOWN), ] - # How much to unlock *now*. + # How much it costs to unlock *now*. unlock_tokens: Annotated[int, IOAttrs('tk')] - # When unlocks on its own. + # When it unlocks on its own. unlock_time: Annotated[datetime.datetime, IOAttrs('t')] # Possible prizes we contain. @@ -396,6 +431,9 @@ class BasicClientUIComponentTypeID(Enum): UNKNOWN = 'u' TEXT = 't' + LINK = 'l' + BS_CLASSIC_TOURNEY_RESULT = 'ct' + DISPLAY_ITEMS = 'di' class BasicClientUIComponent(IOMultiType[BasicClientUIComponentTypeID]): @@ -422,8 +460,12 @@ class BasicClientUIComponent(IOMultiType[BasicClientUIComponentTypeID]): return BasicClientUIComponentUnknown if type_id is t.TEXT: return BasicClientUIComponentText - # if type_id is t.SCREEN_MESSAGE: - # return BasicClientUIComponentScreenMessage + if type_id is t.LINK: + return BasicClientUIComponentLink + if type_id is t.BS_CLASSIC_TOURNEY_RESULT: + return BasicClientUIBsClassicTourneyResult + if type_id is t.DISPLAY_ITEMS: + return BasicClientUIDisplayItems # Important to make sure we provide all types. assert_never(type_id) @@ -464,12 +506,7 @@ class BasicClientUIComponentText(BasicClientUIComponent): 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, - ) + ] = (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 @@ -479,6 +516,59 @@ class BasicClientUIComponentText(BasicClientUIComponent): return BasicClientUIComponentTypeID.TEXT +@ioprepped +@dataclass +class BasicClientUIComponentLink(BasicClientUIComponent): + """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) -> BasicClientUIComponentTypeID: + return BasicClientUIComponentTypeID.LINK + + +@ioprepped +@dataclass +class BasicClientUIBsClassicTourneyResult(BasicClientUIComponent): + """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) -> BasicClientUIComponentTypeID: + return BasicClientUIComponentTypeID.BS_CLASSIC_TOURNEY_RESULT + + +@ioprepped +@dataclass +class BasicClientUIDisplayItems(BasicClientUIComponent): + """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) -> BasicClientUIComponentTypeID: + return BasicClientUIComponentTypeID.DISPLAY_ITEMS + + @ioprepped @dataclass class BasicClientUI(ClientUI): @@ -642,9 +732,6 @@ class ClientEffectScreenMessage(ClientEffect): """Display a screen-message.""" message: Annotated[str, IOAttrs('m')] - - # Note: Firestore can't store arrays of arrays so we flatten it to a - # single dimension. subs: Annotated[list[str], IOAttrs('s')] color: Annotated[tuple[float, float, float], IOAttrs('c')] = (1.0, 1.0, 1.0) diff --git a/tools/efro/util.py b/tools/efro/util.py index dfc85e35..85efc836 100644 --- a/tools/efro/util.py +++ b/tools/efro/util.py @@ -1,5 +1,6 @@ # Released under the MIT License. See LICENSE for details. # +# pylint: disable=too-many-lines """Small handy bits of functionality.""" from __future__ import annotations @@ -15,7 +16,7 @@ from typing import TYPE_CHECKING, cast, TypeVar, Generic, overload, ParamSpec if TYPE_CHECKING: import asyncio - from typing import Any, Callable, Literal + from typing import Any, Callable, Literal, Sequence T = TypeVar('T') ValT = TypeVar('ValT') @@ -983,6 +984,21 @@ def extract_arg( return val +def pairs_to_flat(pairs: Sequence[tuple[T, T]]) -> list[T]: + """Given a sequence of same-typed pairs, flattens to a list.""" + return [item for pair in pairs for item in pair] + + +def pairs_from_flat(flat: Sequence[T]) -> list[tuple[T, T]]: + """Given a flat even numbered sequence, returns pairs.""" + if len(flat) % 2 != 0: + raise ValueError('Provided sequence has an odd number of elements.') + out: list[tuple[T, T]] = [] + for i in range(0, len(flat) - 1, 2): + out.append((flat[i], flat[i + 1])) + return out + + def weighted_choice(*args: tuple[T, float]) -> T: """Given object/weight pairs as args, returns a random object.