better tourney results inbox messages

This commit is contained in:
Eric Froemling 2025-01-14 19:44:48 -08:00
parent 206e3d7f4b
commit 9de17896d1
No known key found for this signature in database
31 changed files with 739 additions and 219 deletions

94
.efrocachemap generated
View File

@ -432,43 +432,43 @@
"build/assets/ba_data/audio/zoeOw.ogg": "b2d705c31c9dcc1efdc71394764c3beb", "build/assets/ba_data/audio/zoeOw.ogg": "b2d705c31c9dcc1efdc71394764c3beb",
"build/assets/ba_data/audio/zoePickup01.ogg": "e9366dc2d2b8ab8b0c4e2c14c02d0789", "build/assets/ba_data/audio/zoePickup01.ogg": "e9366dc2d2b8ab8b0c4e2c14c02d0789",
"build/assets/ba_data/audio/zoeScream01.ogg": "903e0e45ee9b3373e9d9ce20c814374e", "build/assets/ba_data/audio/zoeScream01.ogg": "903e0e45ee9b3373e9d9ce20c814374e",
"build/assets/ba_data/data/langdata.json": "dbbd8f26d2f85c0b649d461e991b80cb", "build/assets/ba_data/data/langdata.json": "4c8242df36a1b589035beb4711cbf772",
"build/assets/ba_data/data/languages/arabic.json": "3c22e7b6d7b09a812a2e28b35c9e9241", "build/assets/ba_data/data/languages/arabic.json": "397dfd469ef7c744cbb472cd19b73f1f",
"build/assets/ba_data/data/languages/belarussian.json": "0b60a9d4496d1213c2d0b647d346ce30", "build/assets/ba_data/data/languages/belarussian.json": "0b60a9d4496d1213c2d0b647d346ce30",
"build/assets/ba_data/data/languages/chinese.json": "fc45d2838b834889c06920ae7c2102fa", "build/assets/ba_data/data/languages/chinese.json": "168b529f2d55d714886be57f162f6842",
"build/assets/ba_data/data/languages/chinesetraditional.json": "904b35b656c53f9830e406565edd5120", "build/assets/ba_data/data/languages/chinesetraditional.json": "32f53581b80ce723edbe8aa7956e6727",
"build/assets/ba_data/data/languages/croatian.json": "e131a87cf5783e0fbb3d211a927efe1a", "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/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/english.json": "527d106870b0690cc39a80b88e60ab7a",
"build/assets/ba_data/data/languages/esperanto.json": "0e397cfa5f3fb8cef5f4a64f21cda880", "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/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/gibberish.json": "2569fe1b2f686670f825e2faaa8c5dc3",
"build/assets/ba_data/data/languages/greek.json": "d28d1092fbb00ed857cbd53124c0dc78", "build/assets/ba_data/data/languages/greek.json": "d28d1092fbb00ed857cbd53124c0dc78",
"build/assets/ba_data/data/languages/hindi.json": "567e6976b3c72f891431ad7fcc62ab16", "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/indonesian.json": "607ba358179185f032096ea1978e4448",
"build/assets/ba_data/data/languages/italian.json": "eabad2faba952c426876bc07e1490d09", "build/assets/ba_data/data/languages/italian.json": "254d4d3962fda17fe127636fa6221851",
"build/assets/ba_data/data/languages/korean.json": "4e3524327a0174250aff5e1ef4c0c597", "build/assets/ba_data/data/languages/korean.json": "360760d72832863e1a3451b0a514cb08",
"build/assets/ba_data/data/languages/malay.json": "0212e18e54efa202c17505376e5b82fb", "build/assets/ba_data/data/languages/malay.json": "0212e18e54efa202c17505376e5b82fb",
"build/assets/ba_data/data/languages/persian.json": "859a60de6226fdf9fc24b68b7f6782b6", "build/assets/ba_data/data/languages/persian.json": "517217e679c768fff4ffec7f8000ab77",
"build/assets/ba_data/data/languages/piratespeak.json": "7c7e3b72b87c1bcd5b04c9f64d912f0c", "build/assets/ba_data/data/languages/piratespeak.json": "be23decfaf220b3aa3de3cf35e59b420",
"build/assets/ba_data/data/languages/polish.json": "941eb816c7db9e04d6a3b8f28a64e2e8", "build/assets/ba_data/data/languages/polish.json": "993b612c5854fc42a78726ed09c65251",
"build/assets/ba_data/data/languages/portuguese.json": "b4463a05d65515f6812e1177c60ac666", "build/assets/ba_data/data/languages/portuguese.json": "f034d8099298b56792d4e0e41c5c34a0",
"build/assets/ba_data/data/languages/romanian.json": "5ae206fe0b71c4015b02b86da8931c8f", "build/assets/ba_data/data/languages/romanian.json": "b04345d8c7631d657a69c73eb7be755a",
"build/assets/ba_data/data/languages/russian.json": "fc64ed6b6356ea11385ee5c20748425a", "build/assets/ba_data/data/languages/russian.json": "eca8fe1ef8343aee559e49c49805b850",
"build/assets/ba_data/data/languages/serbian.json": "623fa4129a1154c2f32ed7867e56ff6a", "build/assets/ba_data/data/languages/serbian.json": "623fa4129a1154c2f32ed7867e56ff6a",
"build/assets/ba_data/data/languages/slovak.json": "c11c29708b3742cdc2a92b4fa0d6d29f", "build/assets/ba_data/data/languages/slovak.json": "c11c29708b3742cdc2a92b4fa0d6d29f",
"build/assets/ba_data/data/languages/spanish.json": "f8ab976d219e579546bb98b6d7fd12ce", "build/assets/ba_data/data/languages/spanish.json": "f8ab976d219e579546bb98b6d7fd12ce",
"build/assets/ba_data/data/languages/swedish.json": "3b179e7333183c70adb0811246b09959", "build/assets/ba_data/data/languages/swedish.json": "3b179e7333183c70adb0811246b09959",
"build/assets/ba_data/data/languages/tamil.json": "ead39b864228696a9b0d19344bc4b5ec", "build/assets/ba_data/data/languages/tamil.json": "ead39b864228696a9b0d19344bc4b5ec",
"build/assets/ba_data/data/languages/thai.json": "383540a1e9c7c131ac579f51afc87471", "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/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/languages/vietnamese.json": "59f6686890ceac2b0ac92597751a18ca",
"build/assets/ba_data/data/maps/big_g.json": "1dd301d490643088a435ce75df971054", "build/assets/ba_data/data/maps/big_g.json": "1dd301d490643088a435ce75df971054",
"build/assets/ba_data/data/maps/bridgit.json": "6aea74805f4880cc11237c5734a24422", "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.dds": "48e117e4a57a1d99ebf0e38dd414a95b",
"build/assets/ba_data/textures/buttonSquare.ktx": "936f04a43f99f692071c7e9567cc2a3d", "build/assets/ba_data/textures/buttonSquare.ktx": "936f04a43f99f692071c7e9567cc2a3d",
"build/assets/ba_data/textures/buttonSquare.pvr": "b53fe9f1725e0d28bc974f6ef1623fb0", "build/assets/ba_data/textures/buttonSquare.pvr": "b53fe9f1725e0d28bc974f6ef1623fb0",
"build/assets/ba_data/textures/buttonSquareWide.dds": "5ab9ff791eae959d49f7ac1018f395a9", "build/assets/ba_data/textures/buttonSquareWide.dds": "879ecc705072338a427f25fdbf759792",
"build/assets/ba_data/textures/buttonSquareWide.ktx": "043bbadbe5adff0236d2841fcaeadb87", "build/assets/ba_data/textures/buttonSquareWide.ktx": "98e3b5d628823ffac2add60aa63c0274",
"build/assets/ba_data/textures/buttonSquareWide.pvr": "ca3773bfeacc8dcf42387a6da6ef0807", "build/assets/ba_data/textures/buttonSquareWide.pvr": "f5ea38658215153ef97bba12ce113923",
"build/assets/ba_data/textures/buttonSquareWide_preview.png": "a5a02ed8a9a0b4d0043fcf21fc0f5f8a", "build/assets/ba_data/textures/buttonSquareWide_preview.png": "543a1f28b538ada0f63c998f2c75e518",
"build/assets/ba_data/textures/buttonSquare_preview.png": "a9abe7bbf392caf8141adf81fc63f4f8", "build/assets/ba_data/textures/buttonSquare_preview.png": "a9abe7bbf392caf8141adf81fc63f4f8",
"build/assets/ba_data/textures/chTitleChar1.dds": "d8c615a51d900da15b8aba5ce35296bf", "build/assets/ba_data/textures/chTitleChar1.dds": "d8c615a51d900da15b8aba5ce35296bf",
"build/assets/ba_data/textures/chTitleChar1.ktx": "44384ea28c9fe01440deb1fc80c7224a", "build/assets/ba_data/textures/chTitleChar1.ktx": "44384ea28c9fe01440deb1fc80c7224a",
@ -4126,22 +4126,22 @@
"build/assets/windows/Win32/ucrtbased.dll": "bfd1180c269d3950b76f35a63655e9e1", "build/assets/windows/Win32/ucrtbased.dll": "bfd1180c269d3950b76f35a63655e9e1",
"build/assets/windows/Win32/vc_redist.x86.exe": "15a5f1f876503885adbdf5b3989b3718", "build/assets/windows/Win32/vc_redist.x86.exe": "15a5f1f876503885adbdf5b3989b3718",
"build/assets/windows/Win32/vcruntime140d.dll": "865b2af4d1e26a1a8073c89acb06e599", "build/assets/windows/Win32/vcruntime140d.dll": "865b2af4d1e26a1a8073c89acb06e599",
"build/prefab/full/linux_arm64_gui/debug/ballisticakit": "d0d676371cecc9fff81f2b23fa6e7275", "build/prefab/full/linux_arm64_gui/debug/ballisticakit": "1ffc1f5dcda65363be124baa30006ccd",
"build/prefab/full/linux_arm64_gui/release/ballisticakit": "1a8c30616d66c2ab31879d44c355a436", "build/prefab/full/linux_arm64_gui/release/ballisticakit": "4cc5de1993654275f1d5156af47581a1",
"build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "b7e5a81d1170d4e932d06c48e4500fe6", "build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "15fd8eb2457aa3a925f723ef657f384c",
"build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "e28fcdb70262585d0b3ae62f75d8e487", "build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "1c4bb442f9a71ee616a85ff404ee4a1d",
"build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "714db737373c55db0126046e3a5998cf", "build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "6c50ad81231ef51d23c09033babfef16",
"build/prefab/full/linux_x86_64_gui/release/ballisticakit": "eb601b6b70a673fa8b5f67e766bf2a5e", "build/prefab/full/linux_x86_64_gui/release/ballisticakit": "abbd6b5cb2708d1b45e748d4383794d4",
"build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "618d617773fce1dc2cc0a7dcc48722e2", "build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "35a616d3ef72a4867a36da69d0b997c7",
"build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "3a95038a0d6ec21e53585613d5736f64", "build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "c261940da039b1e32eb0137a6e2bdfa8",
"build/prefab/full/mac_arm64_gui/debug/ballisticakit": "5b3e194acd4d65030362258c19c5c378", "build/prefab/full/mac_arm64_gui/debug/ballisticakit": "951b3624f3ea80423f00cae72be2854c",
"build/prefab/full/mac_arm64_gui/release/ballisticakit": "5d8bcc750c247f14fe44bd0a8c432e36", "build/prefab/full/mac_arm64_gui/release/ballisticakit": "5079d46536257fb5d751461fde6eac50",
"build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "c31c365f0353760437328dc05badf43a", "build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "17113f9727ada85f564042267a861f4b",
"build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "9dbcec2283230c23e21e7a607153d322", "build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "822261f0c05dc5de3c68f9d76f8fd969",
"build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "25b1790251a2190bbb8b036cf764b11f", "build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "e63a924dde0304441f96a8304855754b",
"build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "4d08c824bff1dccf9b9fe5f5af011023", "build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "2bf917a51df7eb8f322d7d7cb5523458",
"build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "2b93477aa54b233ce0e65805eac1c160", "build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "95943c9107f7d3756bcf88a03a1673d1",
"build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "321b48eeb777a3955b3afd250e99c4c5", "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/debug/libballisticaplus.a": "1c375e8003442dd3d059bc0baa260e61",
"build/prefab/lib/linux_arm64_gui/release/libballisticaplus.a": "40daac4bbc8990d5140f97e792bc4fb1", "build/prefab/lib/linux_arm64_gui/release/libballisticaplus.a": "40daac4bbc8990d5140f97e792bc4fb1",
"build/prefab/lib/linux_arm64_server/debug/libballisticaplus.a": "1c375e8003442dd3d059bc0baa260e61", "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_gui/release/libballisticaplus.a": "36bb6f32ab12e2a46b82155a93b2e527",
"build/prefab/lib/mac_arm64_server/debug/libballisticaplus.a": "51884d81e2d7bdeb6b59a72f0247c8e1", "build/prefab/lib/mac_arm64_server/debug/libballisticaplus.a": "51884d81e2d7bdeb6b59a72f0247c8e1",
"build/prefab/lib/mac_arm64_server/release/libballisticaplus.a": "36bb6f32ab12e2a46b82155a93b2e527", "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.lib": "0cd1427bb99690616b493a59e7d567d9",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "ef9b111ceb3bb8566ae0c0d6579c06a3", "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "8b472a65a2407291dde5c54bb1c24df1",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "426c27616a8c706e27ea94b308cc4f98", "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "c97c08a508bd57b325cc8817d12af73f",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "a1c5a7fa4b717ec9edf2cd030ff189ef", "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "082957f5ee4f2e5d50d62384bda60895",
"build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "9a4debdbef814405208292bdeb7bee18", "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "4d8bf5c5937ed8c6e4f5e775366590cb",
"build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "dc38927a539aa85ce720e085ee28490d", "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "11909c454caaa9d50f3ed70869964743",
"build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "1256eedc2b351c1264e55a90f37efca9", "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "4a9492a2cfa1d944ea5618eb2ac9cb7b",
"build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "ae14008d451581c06702334f94b50d4d", "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/__init__.py": "f885fed7f2ed98ff2ba271f9dbe3391c",
"src/assets/ba_data/python/babase/_mgen/enums.py": "794d258d59fd17a61752843a9a0551ad", "src/assets/ba_data/python/babase/_mgen/enums.py": "794d258d59fd17a61752843a9a0551ad",
"src/ballistica/base/mgen/pyembed/binding_base.inc": "06042d31df0ff9af96b99477162e2a91", "src/ballistica/base/mgen/pyembed/binding_base.inc": "06042d31df0ff9af96b99477162e2a91",

View File

@ -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 - 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 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. touch UI stuff at all you can simply bump your api version and call it a day.

View File

@ -79,6 +79,7 @@
"ba_data/python/baclassic/__pycache__/_benchmark.cpython-312.opt-1.pyc", "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__/_chest.cpython-312.opt-1.pyc",
"ba_data/python/baclassic/__pycache__/_clienteffect.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__/_input.cpython-312.opt-1.pyc",
"ba_data/python/baclassic/__pycache__/_music.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", "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/_benchmark.py",
"ba_data/python/baclassic/_chest.py", "ba_data/python/baclassic/_chest.py",
"ba_data/python/baclassic/_clienteffect.py", "ba_data/python/baclassic/_clienteffect.py",
"ba_data/python/baclassic/_displayitem.py",
"ba_data/python/baclassic/_input.py", "ba_data/python/baclassic/_input.py",
"ba_data/python/baclassic/_music.py", "ba_data/python/baclassic/_music.py",
"ba_data/python/baclassic/_net.py", "ba_data/python/baclassic/_net.py",

View File

@ -208,6 +208,7 @@ SCRIPT_TARGETS_PY_PUBLIC = \
$(BUILD_DIR)/ba_data/python/baclassic/_benchmark.py \ $(BUILD_DIR)/ba_data/python/baclassic/_benchmark.py \
$(BUILD_DIR)/ba_data/python/baclassic/_chest.py \ $(BUILD_DIR)/ba_data/python/baclassic/_chest.py \
$(BUILD_DIR)/ba_data/python/baclassic/_clienteffect.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/_input.py \
$(BUILD_DIR)/ba_data/python/baclassic/_music.py \ $(BUILD_DIR)/ba_data/python/baclassic/_music.py \
$(BUILD_DIR)/ba_data/python/baclassic/_net.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__/_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__/_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__/_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__/_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__/_music.cpython-312.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/baclassic/__pycache__/_net.cpython-312.opt-1.pyc \ $(BUILD_DIR)/ba_data/python/baclassic/__pycache__/_net.cpython-312.opt-1.pyc \

View File

@ -41,8 +41,8 @@ def is_browser_likely_available() -> bool:
category: General Utility Functions category: General Utility Functions
If this returns False you may want to avoid calling babase.show_url() If this returns False you may want to avoid calling babase.open_url()
with any lengthy addresses. (ba.show_url() will display an address 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 as a string in a window if unable to bring up a browser, but that
is only useful for simple URLs.) is only useful for simple URLs.)
""" """

View File

@ -176,6 +176,7 @@ class _WeakCall:
'Warning: callable passed to babase.WeakCall() is not' 'Warning: callable passed to babase.WeakCall() is not'
' weak-referencable (%s); use functools.partial instead' ' weak-referencable (%s); use functools.partial instead'
' to avoid this warning.', ' to avoid this warning.',
args[0],
stack_info=True, stack_info=True,
) )
type(self)._did_invalid_call_warning = True type(self)._did_invalid_call_warning = True

View File

@ -27,6 +27,7 @@ from baclassic._chest import (
CHEST_APPEARANCE_DISPLAY_INFO_DEFAULT, CHEST_APPEARANCE_DISPLAY_INFO_DEFAULT,
CHEST_APPEARANCE_DISPLAY_INFOS, CHEST_APPEARANCE_DISPLAY_INFOS,
) )
from baclassic._displayitem import show_display_item
__all__ = [ __all__ = [
'ChestAppearanceDisplayInfo', 'ChestAppearanceDisplayInfo',
@ -36,6 +37,7 @@ __all__ = [
'ClassicAppSubsystem', 'ClassicAppSubsystem',
'Achievement', 'Achievement',
'AchievementSubsystem', 'AchievementSubsystem',
'show_display_item',
] ]
# We want stuff here to show up as packagename.Foo instead of # We want stuff here to show up as packagename.Foo instead of

View File

@ -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',
)

View File

@ -53,7 +53,7 @@ if TYPE_CHECKING:
# Build number and version of the ballistica binary we expect to be # Build number and version of the ballistica binary we expect to be
# using. # using.
TARGET_BALLISTICA_BUILD = 22184 TARGET_BALLISTICA_BUILD = 22189
TARGET_BALLISTICA_VERSION = '1.7.37' TARGET_BALLISTICA_VERSION = '1.7.37'

View File

@ -88,7 +88,7 @@ class AccountViewerWindow(PopupWindow):
scale=0.6, scale=0.6,
text=bui.Lstr(resource='playerInfoText'), text=bui.Lstr(resource='playerInfoText'),
maxwidth=200, maxwidth=200,
color=(0.7, 0.7, 0.7, 0.7), color=bui.app.ui_v1.title_color,
) )
self._scrollwidget = bui.scrollwidget( self._scrollwidget = bui.scrollwidget(
@ -97,6 +97,7 @@ class AccountViewerWindow(PopupWindow):
position=(30, 30), position=(30, 30),
capture_arrows=True, capture_arrows=True,
simple_culling_v=10, simple_culling_v=10,
border_opacity=0.4,
) )
bui.widget(edit=self._scrollwidget, autoselect=True) bui.widget(edit=self._scrollwidget, autoselect=True)

View File

@ -20,7 +20,7 @@ class AchievementsWindow(bui.MainWindow):
# pylint: disable=too-many-locals # pylint: disable=too-many-locals
assert bui.app.classic is not None assert bui.app.classic is not None
uiscale = bui.app.ui_v1.uiscale 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 = ( self._height = (
380 380
if uiscale is bui.UIScale.SMALL if uiscale is bui.UIScale.SMALL
@ -102,11 +102,11 @@ class AchievementsWindow(bui.MainWindow):
parent=self._root_widget, parent=self._root_widget,
size=( size=(
self._width - 60, 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=( position=(
30, 30,
(110 if uiscale is bui.UIScale.SMALL else 30) + yoffs, (110 if uiscale is bui.UIScale.SMALL else 35) + yoffs,
), ),
capture_arrows=True, capture_arrows=True,
simple_culling_v=10, simple_culling_v=10,

View File

@ -9,6 +9,7 @@ import math
import random import random
from typing import override, TYPE_CHECKING from typing import override, TYPE_CHECKING
from efro.util import strict_partial
import bacommon.bs import bacommon.bs
import bauiv1 as bui import bauiv1 as bui
@ -29,8 +30,6 @@ class ChestWindow(bui.MainWindow):
transition: str | None = 'in_right', transition: str | None = 'in_right',
origin_widget: bui.Widget | None = None, origin_widget: bui.Widget | None = None,
): ):
# print('ChestWindow()')
self._index = index self._index = index
assert bui.app.classic is not None assert bui.app.classic is not None
@ -514,7 +513,6 @@ class ChestWindow(bui.MainWindow):
) )
self._show_odds(initial_highlighted_row=-1) self._show_odds(initial_highlighted_row=-1)
# bui.textwidget(edit=self._infotext, text='')
def _highlight_odds_row(self, row: int, extra: bool = False) -> None: 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. # Convey that something is in progress.
if self._open_now_button: 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) bui.spinnerwidget(edit=self._open_now_spinner, visible=True)
for twidget in self._open_now_texts: for twidget in self._open_now_texts:
bui.textwidget(edit=twidget, color=(1, 1, 1, 0.2)) bui.textwidget(edit=twidget, color=(1, 1, 1, 0.2))
@ -788,7 +784,7 @@ class ChestWindow(bui.MainWindow):
self._reset() self._reset()
msg = ( msg = (
'This slot can hold a treasure chest.\n\n' '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' 'placing in tournaments, and completing\n'
'achievements.' 'achievements.'
) )
@ -799,12 +795,22 @@ class ChestWindow(bui.MainWindow):
) -> None: ) -> None:
# pylint: disable=too-many-locals # pylint: disable=too-many-locals
from baclassic import show_display_item
# No-op if our ui is dead. # No-op if our ui is dead.
if not self._root_widget: if not self._root_widget:
return return
assert response.contents is not None 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 tincr = 0.4
tendoffs = tincr * 4.0 tendoffs = tincr * 4.0
toffs = 0.0 toffs = 0.0
@ -861,7 +867,7 @@ class ChestWindow(bui.MainWindow):
), ),
) )
xspacing = 150 xspacing = 100
xoffs = -0.5 * (len(response.contents) - 1) * xspacing xoffs = -0.5 * (len(response.contents) - 1) * xspacing
bui.apptimer( bui.apptimer(
toffs - 0.2, lambda: bui.getsound('corkPop2').play(volume=4.0) toffs - 0.2, lambda: bui.getsound('corkPop2').play(volume=4.0)
@ -898,14 +904,25 @@ class ChestWindow(bui.MainWindow):
toffsopen = toffs toffsopen = toffs
bui.apptimer(toffs, bui.WeakCall(self._show_chest_opening)) bui.apptimer(toffs, bui.WeakCall(self._show_chest_opening))
toffs += tincr * 1.0 toffs += tincr * 1.0
width = xspacing * 0.75 width = xspacing * 0.95
for obj in response.contents:
for item in response.contents:
toffs += tincr toffs += tincr
bui.apptimer( bui.apptimer(
toffs - 0.1, lambda: bui.getsound('cashRegister').play() toffs - 0.1, lambda: bui.getsound('cashRegister').play()
) )
bui.apptimer( 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 xoffs += xspacing
toffs += tincr toffs += tincr
@ -1003,61 +1020,6 @@ class ChestWindow(bui.MainWindow):
initial_highlighted_extra=True, 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: def _show_done_button(self) -> None:
# No-op if our ui is dead. # No-op if our ui is dead.
if not self._root_widget: if not self._root_widget:

View File

@ -275,7 +275,7 @@ class CoopBrowserWindow(bui.MainWindow):
simple_culling_v=10.0, simple_culling_v=10.0,
claims_left_right=True, claims_left_right=True,
selection_loops_to_parent=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: if uiscale is bui.UIScale.SMALL:

View File

@ -627,6 +627,10 @@ class TournamentButton:
max_players = bui.app.classic.accounts.tournament_info[ max_players = bui.app.classic.accounts.tournament_info[
self.tournament_id self.tournament_id
]['maxPlayers'] ]['maxPlayers']
print('GOT GAME', game)
print('GOT ID', self.tournament_id)
print('GOT PLAYERS', max_players)
txt = bui.Lstr( txt = bui.Lstr(
value='${A} ${B}', value='${A} ${B}',
subs=[ subs=[

View File

@ -1,25 +1,35 @@
# Released under the MIT License. See LICENSE for details. # Released under the MIT License. See LICENSE for details.
# #
# pylint: disable=too-many-lines
"""Provides a popup window to view achievements.""" """Provides a popup window to view achievements."""
from __future__ import annotations from __future__ import annotations
import weakref import weakref
from functools import partial
from dataclasses import dataclass 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 from efro.error import CommunicationError
import bacommon.bs import bacommon.bs
import bauiv1 as bui import bauiv1 as bui
if TYPE_CHECKING:
from typing import Callable
class _Section: class _Section:
def get_height(self) -> float: def get_height(self) -> float:
"""Return section height.""" """Return section height."""
raise NotImplementedError() raise NotImplementedError()
def draw(self, subcontainer: bui.Widget, y: float) -> None: def get_button_row(self) -> list[bui.Widget]:
"""Draw the section.""" """Return rows of selectable controls."""
return []
def emit(self, subcontainer: bui.Widget, y: float) -> None:
"""Emit the section."""
class _TextSection(_Section): class _TextSection(_Section):
@ -27,9 +37,8 @@ class _TextSection(_Section):
def __init__( def __init__(
self, self,
sub_width: float, sub_width: float,
text: str, text: bui.Lstr | str,
*, *,
subs: list[str],
spacing_top: float = 0.0, spacing_top: float = 0.0,
spacing_bottom: float = 0.0, spacing_bottom: float = 0.0,
scale: float = 0.6, scale: float = 0.6,
@ -40,23 +49,22 @@ class _TextSection(_Section):
self.spacing_bottom = spacing_bottom self.spacing_bottom = spacing_bottom
self.color = color self.color = color
self.textfin = bui.Lstr(translate=('serverResponses', text)).evaluate() # We need to bake this down since we plug its final size into
assert len(subs) % 2 == 0 # Should always be even. # our math.
for j in range(0, len(subs) - 1, 2): self.textbaked = text.evaluate() if isinstance(text, bui.Lstr) else text
self.textfin = self.textfin.replace(subs[j], subs[j + 1])
# Calc scale to fit width and then see what height we need at # Calc scale to fit width and then see what height we need at
# that scale. # that scale.
t_width = max( t_width = max(
10.0, 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_scale = scale * min(1.0, (sub_width * 0.9) / t_width)
self.text_height = ( self.text_height = (
0.0 0.0
if not self.textfin if not self.textbaked
else bui.get_string_height(self.textfin, suppress_warning=True) else bui.get_string_height(self.textbaked, suppress_warning=True)
) * self.text_scale ) * self.text_scale
self.full_height = self.text_height + spacing_top + spacing_bottom self.full_height = self.text_height + spacing_top + spacing_bottom
@ -66,25 +74,137 @@ class _TextSection(_Section):
return self.full_height return self.full_height
@override @override
def draw(self, subcontainer: bui.Widget, y: float) -> None: def emit(self, subcontainer: bui.Widget, y: float) -> None:
bui.textwidget( bui.textwidget(
parent=subcontainer, parent=subcontainer,
position=( position=(
self.sub_width * 0.5, self.sub_width * 0.5,
y - self.spacing_top - self.text_height * 0.5, y - self.spacing_top - self.text_height * 0.5,
# y - self.height * 0.5 - 23.0,
), ),
color=self.color, color=self.color,
scale=self.text_scale, scale=self.text_scale,
flatness=1.0, flatness=1.0,
shadow=0.0, shadow=0.0,
text=self.textfin, text=self.textbaked,
size=(0, 0), size=(0, 0),
h_align='center', h_align='center',
v_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 @dataclass
class _EntryDisplay: class _EntryDisplay:
interaction_style: bacommon.bs.BasicClientUI.InteractionStyle interaction_style: bacommon.bs.BasicClientUI.InteractionStyle
@ -99,7 +219,6 @@ class _EntryDisplay:
button_spinner_positive: bui.Widget | None = None button_spinner_positive: bui.Widget | None = None
button_negative: bui.Widget | None = None button_negative: bui.Widget | None = None
button_spinner_negative: bui.Widget | None = None button_spinner_negative: bui.Widget | None = None
# message_text: bui.Widget | None = None
processing_complete: bool = False processing_complete: bool = False
@ -498,7 +617,7 @@ class InboxWindow(bui.MainWindow):
button_label_negative: bacommon.bs.BasicClientUI.ButtonLabel button_label_negative: bacommon.bs.BasicClientUI.ButtonLabel
sections: list[_Section] = [] sections: list[_Section] = []
total_height = 90.0 total_height = 80.0
# Display only entries where we recognize all style/label # Display only entries where we recognize all style/label
# values and ui component types. # values and ui component types.
@ -514,14 +633,18 @@ class InboxWindow(bui.MainWindow):
idcls = bacommon.bs.BasicClientUIComponentTypeID idcls = bacommon.bs.BasicClientUIComponentTypeID
for component in wrapper.ui.components: for component in wrapper.ui.components:
ctypeid = component.get_type_id() ctypeid = component.get_type_id()
section: _Section
if ctypeid is idcls.TEXT: if ctypeid is idcls.TEXT:
assert isinstance( assert isinstance(
component, bacommon.bs.BasicClientUIComponentText component, bacommon.bs.BasicClientUIComponentText
) )
section = _TextSection( section = _TextSection(
sub_width=sub_width, sub_width=sub_width,
text=component.text, text=bui.Lstr(
subs=component.subs, translate=('serverResponses', component.text),
subs=pairs_from_flat(component.subs),
),
color=component.color, color=component.color,
scale=component.scale, scale=component.scale,
spacing_top=component.spacing_top, spacing_top=component.spacing_top,
@ -530,8 +653,209 @@ class InboxWindow(bui.MainWindow):
total_height += section.get_height() total_height += section.get_height()
sections.append(section) 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: elif ctypeid is idcls.UNKNOWN:
raise RuntimeError('Should not get here.') raise RuntimeError('Should not get here.')
else: else:
# Make sure we handle all types. # Make sure we handle all types.
assert_never(ctypeid) assert_never(ctypeid)
@ -550,8 +874,9 @@ class InboxWindow(bui.MainWindow):
section = _TextSection( section = _TextSection(
sub_width=sub_width, sub_width=sub_width,
text='You must update the app to view this.', text=bui.Lstr(
subs=[], value='You must update the app to view this.'
),
) )
total_height += section.get_height() total_height += section.get_height()
sections.append(section) sections.append(section)
@ -611,7 +936,11 @@ class InboxWindow(bui.MainWindow):
# Section contents. # Section contents.
for sec in entry_display.sections: 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() ysection -= sec.get_height()
buttonrow: list[bui.Widget] = [] buttonrow: list[bui.Widget] = []
@ -633,6 +962,7 @@ class InboxWindow(bui.MainWindow):
entry_display.button_positive = btn = bui.buttonwidget( entry_display.button_positive = btn = bui.buttonwidget(
parent=subcontainer, parent=subcontainer,
position=bpos, position=bpos,
autoselect=True,
size=(bwidth, bheight), size=(bwidth, bheight),
label=bui.app.classic.basic_client_ui_button_label_str( label=bui.app.classic.basic_client_ui_button_label_str(
entry_display.button_label_positive entry_display.button_label_positive
@ -663,6 +993,7 @@ class InboxWindow(bui.MainWindow):
entry_display.button_negative = btn2 = bui.buttonwidget( entry_display.button_negative = btn2 = bui.buttonwidget(
parent=subcontainer, parent=subcontainer,
position=bpos, position=bpos,
autoselect=True,
size=(bwidth, bheight), size=(bwidth, bheight),
label=bui.app.classic.basic_client_ui_button_label_str( label=bui.app.classic.basic_client_ui_button_label_str(
entry_display.button_label_negative entry_display.button_label_negative
@ -712,11 +1043,16 @@ class InboxWindow(bui.MainWindow):
bui.widget( bui.widget(
edit=button, edit=button,
up_widget=above_widget, up_widget=above_widget,
down_widget=( down_widget=below_widget,
button if below_widget is None else below_widget # down_widget=(
), # button if below_widget is None else below_widget
# ),
right_widget=buttons[max(j - 1, 0)], right_widget=buttons[max(j - 1, 0)],
left_widget=buttons[min(j + 1, len(buttons) - 1)], left_widget=buttons[min(j + 1, len(buttons) - 1)],
) )
above_widget = buttons[0] above_widget = buttons[0]
def _get_bs_classic_tourney_results_sections() -> list[_Section]:
return []

View File

@ -126,6 +126,7 @@ class PlaylistAddGameWindow(bui.MainWindow):
position=(x_inset + 61, v - scroll_height), position=(x_inset + 61, v - scroll_height),
size=(self._scroll_width, scroll_height), size=(self._scroll_width, scroll_height),
highlight=False, highlight=False,
border_opacity=0.4,
) )
bui.widget( bui.widget(
edit=self._scrollwidget, edit=self._scrollwidget,

View File

@ -142,9 +142,9 @@ class PlaylistBrowserWindow(bui.MainWindow):
size=(self._scroll_width, self._scroll_height), size=(self._scroll_width, self._scroll_height),
position=( position=(
(self._width - self._scroll_width) * 0.5, (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) bui.containerwidget(edit=self._scrollwidget, claims_left_right=True)
self._subcontainer: bui.Widget | None = None self._subcontainer: bui.Widget | None = None

View File

@ -274,6 +274,7 @@ class PlaylistCustomizeBrowserWindow(bui.MainWindow):
position=(140 + x_inset, v - self._scroll_height), position=(140 + x_inset, v - self._scroll_height),
size=(self._width - (180 + 2 * x_inset), self._scroll_height + 10), size=(self._width - (180 + 2 * x_inset), self._scroll_height + 10),
highlight=False, highlight=False,
border_opacity=0.4,
) )
if self._back_button is not None: if self._back_button is not None:
bui.widget(edit=self._back_button, right_widget=scrollwidget) bui.widget(edit=self._back_button, right_widget=scrollwidget)

View File

@ -232,6 +232,7 @@ class PlaylistEditWindow(bui.MainWindow):
highlight=False, highlight=False,
on_select_call=bui.Call(self._set_ui_selection, 'gameList'), on_select_call=bui.Call(self._set_ui_selection, 'gameList'),
size=(self._scroll_width, (scroll_height - 15)), size=(self._scroll_width, (scroll_height - 15)),
border_opacity=0.4,
) )
bui.widget( bui.widget(
edit=scrollwidget, edit=scrollwidget,

View File

@ -206,6 +206,7 @@ class PlaylistEditGameWindow(bui.MainWindow):
highlight=False, highlight=False,
claims_left_right=True, claims_left_right=True,
selection_loops_to_parent=True, selection_loops_to_parent=True,
border_opacity=0.4,
) )
self._subcontainer = bui.containerwidget( self._subcontainer = bui.containerwidget(
parent=self._scrollwidget, parent=self._scrollwidget,

View File

@ -111,6 +111,7 @@ class PlaylistMapSelectWindow(bui.MainWindow):
parent=self._root_widget, parent=self._root_widget,
position=(40 + x_inset, v - self._scroll_height), position=(40 + x_inset, v - self._scroll_height),
size=(self._scroll_width, self._scroll_height), size=(self._scroll_width, self._scroll_height),
border_opacity=0.4,
) )
bui.containerwidget( bui.containerwidget(
edit=self._root_widget, selected_child=self._scrollwidget edit=self._root_widget, selected_child=self._scrollwidget

View File

@ -30,7 +30,7 @@ class ResourceTypeInfoWindow(PopupWindow):
) )
self._transitioning_out = False self._transitioning_out = False
self._width = 570 self._width = 570
self._height = 350 self._height = 400
bg_color = (0.5, 0.4, 0.6) bg_color = (0.5, 0.4, 0.6)
super().__init__( super().__init__(
size=(self._width, self._height), size=(self._width, self._height),
@ -56,6 +56,7 @@ class ResourceTypeInfoWindow(PopupWindow):
yoffs = self._height - 145 yoffs = self._height - 145
if resource_type == 'tickets': if resource_type == 'tickets':
yoffs -= 20
rdesc = ( rdesc = (
'Tickets can be used to unlock characters,\n' 'Tickets can be used to unlock characters,\n'
'maps, minigames, and more in the store.\n' 'maps, minigames, and more in the store.\n'
@ -70,8 +71,8 @@ class ResourceTypeInfoWindow(PopupWindow):
'and for other game and account features.\n' 'and for other game and account features.\n'
'\n' '\n'
'You can win tokens in the game or buy them\n' 'You can win tokens in the game or buy them\n'
'in packs. Or buy a Gold Pass to get infinite\n' 'in packs. Or buy a Gold Pass for infinite\n'
'tokens forever and never hear of them again.' 'tokens and never hear about them again.'
) )
texname = 'coin' texname = 'coin'
elif resource_type == 'trophies': elif resource_type == 'trophies':

View File

@ -93,7 +93,7 @@ class AllSettingsWindow(bui.MainWindow):
all_buttons_width = 4.0 * bwidth + 3.0 * margin all_buttons_width = 4.0 * bwidth + 3.0 * margin
x = width * 0.5 - all_buttons_width * 0.5 x = width * 0.5 - all_buttons_width * 0.5
y = height + yoffs - 320.0 y = height + yoffs - 335.0
def _button( def _button(
position: tuple[float, float], position: tuple[float, float],

View File

@ -146,7 +146,8 @@ class TournamentEntryWindow(PopupWindow):
scale=0.6, scale=0.6,
text=bui.Lstr(resource='tournamentEntryText'), text=bui.Lstr(resource='tournamentEntryText'),
maxwidth=180, 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( btn = self._pay_with_tickets_button = bui.buttonwidget(

View File

@ -22,33 +22,20 @@ class TournamentScoresWindow(PopupWindow):
self, self,
tournament_id: str, tournament_id: str,
*, *,
tournament_activity: bs.GameActivity | None = None,
position: tuple[float, float] = (0.0, 0.0), 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 plus = bui.app.plus
assert plus is not None 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._tournament_id = tournament_id
self._subcontainer: bui.Widget | None = None self._subcontainer: bui.Widget | None = None
self._on_close_call = on_close_call
assert bui.app.classic is not None assert bui.app.classic is not None
uiscale = bui.app.ui_v1.uiscale uiscale = bui.app.ui_v1.uiscale
if scale is None: scale = (
scale = ( 2.3
2.3 if uiscale is bui.UIScale.SMALL
if uiscale is bui.UIScale.SMALL else 1.65 if uiscale is bui.UIScale.MEDIUM else 1.23
else 1.65 if uiscale is bui.UIScale.MEDIUM else 1.23 )
)
self._transitioning_out = False self._transitioning_out = False
self._width = 400 self._width = 400
@ -60,13 +47,12 @@ class TournamentScoresWindow(PopupWindow):
bg_color = (0.5, 0.4, 0.6) bg_color = (0.5, 0.4, 0.6)
# creates our _root_widget # Creates our _root_widget.
super().__init__( super().__init__(
position=position, position=position,
size=(self._width, self._height), size=(self._width, self._height),
scale=scale, scale=scale,
bg_color=bg_color, bg_color=bg_color,
offset=offset,
) )
self._cancel_button = bui.buttonwidget( self._cancel_button = bui.buttonwidget(
@ -91,7 +77,7 @@ class TournamentScoresWindow(PopupWindow):
scale=0.6, scale=0.6,
text=bui.Lstr(resource='tournamentStandingsText'), text=bui.Lstr(resource='tournamentStandingsText'),
maxwidth=200, maxwidth=200,
color=(1, 1, 1, 0.4), color=bui.app.ui_v1.title_color,
) )
self._scrollwidget = bui.scrollwidget( self._scrollwidget = bui.scrollwidget(
@ -100,16 +86,18 @@ class TournamentScoresWindow(PopupWindow):
position=(30, 30), position=(30, 30),
highlight=False, highlight=False,
simple_culling_v=10, simple_culling_v=10,
border_opacity=0.4,
) )
bui.widget(edit=self._scrollwidget, autoselect=True) 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( self._loading_text = bui.textwidget(
parent=self._scrollwidget, parent=self._scrollwidget,
scale=0.5, scale=0.5,
text=bui.Lstr( text='',
value='${A}...',
subs=[('${A}', bui.Lstr(resource='loadingText'))],
),
size=(self._width - 60, 100), size=(self._width - 60, 100),
h_align='center', h_align='center',
v_align='center', v_align='center',
@ -132,10 +120,12 @@ class TournamentScoresWindow(PopupWindow):
self, data: dict[str, Any] | None self, data: dict[str, Any] | None
) -> None: ) -> None:
if data is not 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'] 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']: if data_t[0]['scores']:
self._loading_text.delete() self._loading_text.delete()
else: else:
@ -219,7 +209,8 @@ class TournamentScoresWindow(PopupWindow):
def _show_player_info(self, entry: Any, textwidget: bui.Widget) -> None: def _show_player_info(self, entry: Any, textwidget: bui.Widget) -> None:
from bauiv1lib.account.viewer import AccountViewerWindow 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: if len(entry[2]) != 1:
bui.getsound('error').play() bui.getsound('error').play()
return return
@ -238,8 +229,6 @@ class TournamentScoresWindow(PopupWindow):
if not self._transitioning_out: if not self._transitioning_out:
self._transitioning_out = True self._transitioning_out = True
bui.containerwidget(edit=self.root_widget, transition='out_scale') bui.containerwidget(edit=self.root_widget, transition='out_scale')
if self._on_close_call is not None:
self._on_close_call()
@override @override
def on_popup_cancel(self) -> None: def on_popup_cancel(self) -> None:

View File

@ -535,7 +535,7 @@ static auto PyGetStringHeight(PyObject* self, PyObject* args, PyObject* keywds)
#if BA_DEBUG_BUILD #if BA_DEBUG_BUILD
if (g_base->assets->CompileResourceString(s) != s) { if (g_base->assets->CompileResourceString(s) != s) {
BA_LOG_PYTHON_TRACE( 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 #endif
assert(g_base->graphics); assert(g_base->graphics);

View File

@ -39,7 +39,7 @@ auto main(int argc, char** argv) -> int {
namespace ballistica { namespace ballistica {
// These are set automatically via script; don't modify them here. // These are set automatically via script; don't modify them here.
const int kEngineBuildNumber = 22184; const int kEngineBuildNumber = 22189;
const char* kEngineVersion = "1.7.37"; const char* kEngineVersion = "1.7.37";
const int kEngineApiVersion = 9; const int kEngineApiVersion = 9;

View File

@ -778,9 +778,10 @@ void HScrollWidget::Draw(base::RenderPass* pass, bool draw_transparent) {
if (draw_transparent && IsHierarchySelected() if (draw_transparent && IsHierarchySelected()
&& g_base->ui->ShouldHighlightWidgets() && highlight_ && g_base->ui->ShouldHighlightWidgets() && highlight_
&& border_opacity_ > 0.0f) { && border_opacity_ > 0.0f) {
float m = 0.8f float m = (0.8f
+ std::abs(sinf(static_cast<float>(current_time_ms) * 0.006467f)) + std::abs(sinf(static_cast<float>(current_time_ms) * 0.006467f))
* 0.2f * border_opacity_; * 0.2f)
* border_opacity_;
if (glow_dirty_) { if (glow_dirty_) {
float r2 = l + width(); float r2 = l + width();

View File

@ -836,9 +836,10 @@ void ScrollWidget::Draw(base::RenderPass* pass, bool draw_transparent) {
// If selected, do glow at depth 0.9 - 1.0. // If selected, do glow at depth 0.9 - 1.0.
if (draw_transparent && IsHierarchySelected() if (draw_transparent && IsHierarchySelected()
&& g_base->ui->ShouldHighlightWidgets() && highlight_) { && g_base->ui->ShouldHighlightWidgets() && highlight_) {
float m = 0.8f float m =
+ std::abs(sinf(static_cast<float>(current_time) * 0.006467f)) (0.8f
* 0.2f * border_opacity_; + std::abs(sinf(static_cast<float>(current_time) * 0.006467f)) * 0.2f)
* border_opacity_;
if (glow_dirty_) { if (glow_dirty_) {
float r2 = l + width(); float r2 = l + width();
float l2 = l; float l2 = l;

View File

@ -9,6 +9,7 @@ from enum import Enum
from dataclasses import dataclass, field from dataclasses import dataclass, field
from typing import Annotated, override, assert_never from typing import Annotated, override, assert_never
from efro.util import pairs_to_flat
from efro.dataclassio import ioprepped, IOAttrs, IOMultiType from efro.dataclassio import ioprepped, IOAttrs, IOMultiType
from efro.message import Message, Response from efro.message import Message, Response
@ -102,6 +103,8 @@ class DisplayItemTypeID(Enum):
UNKNOWN = 'u' UNKNOWN = 'u'
TICKETS = 't' TICKETS = 't'
TOKENS = 'k' TOKENS = 'k'
TEST = 's'
CHEST = 'c'
class DisplayItem(IOMultiType[DisplayItemTypeID]): class DisplayItem(IOMultiType[DisplayItemTypeID]):
@ -123,19 +126,21 @@ class DisplayItem(IOMultiType[DisplayItemTypeID]):
def get_type(cls, type_id: DisplayItemTypeID) -> type[DisplayItem]: def get_type(cls, type_id: DisplayItemTypeID) -> type[DisplayItem]:
"""Return the subclass for each of our type-ids.""" """Return the subclass for each of our type-ids."""
# pylint: disable=cyclic-import # pylint: disable=cyclic-import
out: type[DisplayItem]
t = DisplayItemTypeID t = DisplayItemTypeID
if type_id is t.UNKNOWN: if type_id is t.UNKNOWN:
out = UnknownDisplayItem return UnknownDisplayItem
elif type_id is t.TICKETS: if type_id is t.TICKETS:
out = TicketsDisplayItem return TicketsDisplayItem
elif type_id is t.TOKENS: if type_id is t.TOKENS:
out = TokensDisplayItem return TokensDisplayItem
else: if type_id is t.TEST:
# Important to make sure we provide all types. return TestDisplayItem
assert_never(type_id) if type_id is t.CHEST:
return out return ChestDisplayItem
# Important to make sure we provide all types.
assert_never(type_id)
def get_description(self) -> tuple[str, list[tuple[str, str]]]: def get_description(self) -> tuple[str, list[tuple[str, str]]]:
"""Return a string description and subs for the item. """Return a string description and subs for the item.
@ -211,6 +216,38 @@ class TokensDisplayItem(DisplayItem):
return '${C} Tokens', [('${C}', str(self.count))] 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 @ioprepped
@dataclass @dataclass
class DisplayItemWrapper: class DisplayItemWrapper:
@ -224,9 +261,7 @@ class DisplayItemWrapper:
def for_display_item(cls, item: DisplayItem) -> DisplayItemWrapper: def for_display_item(cls, item: DisplayItem) -> DisplayItemWrapper:
"""Convenience method to wrap a DisplayItem.""" """Convenience method to wrap a DisplayItem."""
desc, subs = item.get_description() desc, subs = item.get_description()
# Flatten subs to single list. return DisplayItemWrapper(item, desc, pairs_to_flat(subs))
flat_subs = [item for pair in subs for item in pair]
return DisplayItemWrapper(item, desc, flat_subs)
@ioprepped @ioprepped
@ -263,10 +298,10 @@ class ChestInfoResponse(Response):
IOAttrs('a', enum_fallback=ClassicChestAppearance.UNKNOWN), IOAttrs('a', enum_fallback=ClassicChestAppearance.UNKNOWN),
] ]
# How much to unlock *now*. # How much it costs to unlock *now*.
unlock_tokens: Annotated[int, IOAttrs('tk')] unlock_tokens: Annotated[int, IOAttrs('tk')]
# When unlocks on its own. # When it unlocks on its own.
unlock_time: Annotated[datetime.datetime, IOAttrs('t')] unlock_time: Annotated[datetime.datetime, IOAttrs('t')]
# Possible prizes we contain. # Possible prizes we contain.
@ -396,6 +431,9 @@ class BasicClientUIComponentTypeID(Enum):
UNKNOWN = 'u' UNKNOWN = 'u'
TEXT = 't' TEXT = 't'
LINK = 'l'
BS_CLASSIC_TOURNEY_RESULT = 'ct'
DISPLAY_ITEMS = 'di'
class BasicClientUIComponent(IOMultiType[BasicClientUIComponentTypeID]): class BasicClientUIComponent(IOMultiType[BasicClientUIComponentTypeID]):
@ -422,8 +460,12 @@ class BasicClientUIComponent(IOMultiType[BasicClientUIComponentTypeID]):
return BasicClientUIComponentUnknown return BasicClientUIComponentUnknown
if type_id is t.TEXT: if type_id is t.TEXT:
return BasicClientUIComponentText return BasicClientUIComponentText
# if type_id is t.SCREEN_MESSAGE: if type_id is t.LINK:
# return BasicClientUIComponentScreenMessage 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. # Important to make sure we provide all types.
assert_never(type_id) assert_never(type_id)
@ -464,12 +506,7 @@ class BasicClientUIComponentText(BasicClientUIComponent):
scale: Annotated[float, IOAttrs('sc', store_default=False)] = 1.0 scale: Annotated[float, IOAttrs('sc', store_default=False)] = 1.0
color: Annotated[ color: Annotated[
tuple[float, float, float, float], IOAttrs('c', store_default=False) 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_top: Annotated[float, IOAttrs('st', store_default=False)] = 0.0
spacing_bottom: Annotated[float, IOAttrs('sb', 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 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 @ioprepped
@dataclass @dataclass
class BasicClientUI(ClientUI): class BasicClientUI(ClientUI):
@ -642,9 +732,6 @@ class ClientEffectScreenMessage(ClientEffect):
"""Display a screen-message.""" """Display a screen-message."""
message: Annotated[str, IOAttrs('m')] 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')] subs: Annotated[list[str], IOAttrs('s')]
color: Annotated[tuple[float, float, float], IOAttrs('c')] = (1.0, 1.0, 1.0) color: Annotated[tuple[float, float, float], IOAttrs('c')] = (1.0, 1.0, 1.0)

View File

@ -1,5 +1,6 @@
# Released under the MIT License. See LICENSE for details. # Released under the MIT License. See LICENSE for details.
# #
# pylint: disable=too-many-lines
"""Small handy bits of functionality.""" """Small handy bits of functionality."""
from __future__ import annotations from __future__ import annotations
@ -15,7 +16,7 @@ from typing import TYPE_CHECKING, cast, TypeVar, Generic, overload, ParamSpec
if TYPE_CHECKING: if TYPE_CHECKING:
import asyncio import asyncio
from typing import Any, Callable, Literal from typing import Any, Callable, Literal, Sequence
T = TypeVar('T') T = TypeVar('T')
ValT = TypeVar('ValT') ValT = TypeVar('ValT')
@ -983,6 +984,21 @@ def extract_arg(
return val 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: def weighted_choice(*args: tuple[T, float]) -> T:
"""Given object/weight pairs as args, returns a random object. """Given object/weight pairs as args, returns a random object.