Merge branch 'efroemling:master' into master

This commit is contained in:
Sasha 2023-12-24 11:16:08 +09:00 committed by GitHub
commit eac3418972
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
386 changed files with 12822 additions and 7644 deletions

216
.efrocachemap generated
View File

@ -1,5 +1,5 @@
{
"ballisticakit-windows/Generic/BallisticaKit.ico": "be1b956dcd7f7a261b1afe5bce2a0336",
"ballisticakit-windows/Generic/BallisticaKit.ico": "6f33e74cb282f070871413f092983fcd",
"build/assets/ba_data/audio/achievement.ogg": "079a366ce183b25a63550ef7072af605",
"build/assets/ba_data/audio/actionHero1.ogg": "f0f986f268f036a5ac2f940e07f2f27e",
"build/assets/ba_data/audio/actionHero2.ogg": "204a6735dc655f0975cf8308b585f2fd",
@ -421,42 +421,42 @@
"build/assets/ba_data/audio/zoeOw.ogg": "74befe45a8417e95b6a2233c51992a26",
"build/assets/ba_data/audio/zoePickup01.ogg": "48ab8cddfcde36a750856f3f81dd20c8",
"build/assets/ba_data/audio/zoeScream01.ogg": "2b468aedfa8741090247f04eb9e6df55",
"build/assets/ba_data/data/langdata.json": "992c5c5ce292132c4f011f39e0d13de8",
"build/assets/ba_data/data/languages/arabic.json": "d1f900ab5aa2433d402bd46ed1149cc7",
"build/assets/ba_data/data/languages/belarussian.json": "e151808b6b4f6dc159cf55ee62adad3c",
"build/assets/ba_data/data/languages/chinese.json": "8d889accdd49334591209bdaf6eaf02f",
"build/assets/ba_data/data/langdata.json": "fae88cbb2a5b9c24096f2e43452114a2",
"build/assets/ba_data/data/languages/arabic.json": "0db32e21b6d5337ccca478381744aa88",
"build/assets/ba_data/data/languages/belarussian.json": "a112dfca3e188387516788bd8229c5b0",
"build/assets/ba_data/data/languages/chinese.json": "ff9a595726f0aff42a39be576d0ff037",
"build/assets/ba_data/data/languages/chinesetraditional.json": "f858da49be0a5374157c627857751078",
"build/assets/ba_data/data/languages/croatian.json": "766532c67af5bd0144c2d63cab0516fa",
"build/assets/ba_data/data/languages/czech.json": "93c5fe0d884d95435da6c675f64e30e0",
"build/assets/ba_data/data/languages/czech.json": "c9d518a324870066b987b8f412881dd3",
"build/assets/ba_data/data/languages/danish.json": "3fd69080783d5c9dcc0af737f02b6f1e",
"build/assets/ba_data/data/languages/dutch.json": "22b44a33bf81142ba2befad14eb5746e",
"build/assets/ba_data/data/languages/english.json": "b38d54aecf3ac47b8d8ca97d8bab3006",
"build/assets/ba_data/data/languages/english.json": "2434e127b6788e3128d3523fcb1b8994",
"build/assets/ba_data/data/languages/esperanto.json": "0e397cfa5f3fb8cef5f4a64f21cda880",
"build/assets/ba_data/data/languages/filipino.json": "347f38524816691170d266708fe25894",
"build/assets/ba_data/data/languages/french.json": "d8527da977a563185de25ef02bacf826",
"build/assets/ba_data/data/languages/german.json": "549754d2a530d825200c6126be56df5c",
"build/assets/ba_data/data/languages/gibberish.json": "837423db378b3e7679683805826aa26e",
"build/assets/ba_data/data/languages/greek.json": "a65d78f912e9a89f98de004405167a6a",
"build/assets/ba_data/data/languages/hindi.json": "88ee0cda537bab9ac827def5e236fe1a",
"build/assets/ba_data/data/languages/filipino.json": "e750fb1a95e4c5611115f9ece9ecab53",
"build/assets/ba_data/data/languages/french.json": "163362f7b33866ef069cae62d0387551",
"build/assets/ba_data/data/languages/german.json": "450fa41ae264f29a5d1af22143d0d0ad",
"build/assets/ba_data/data/languages/gibberish.json": "e24d391c9fd12f9afa92f7ff65a06d23",
"build/assets/ba_data/data/languages/greek.json": "287c0ec437b38772284ef9d3e4fb2fc3",
"build/assets/ba_data/data/languages/hindi.json": "8848f6b0caec0fcf9d85bc6e683809ec",
"build/assets/ba_data/data/languages/hungarian.json": "796a290a8c44a1e7635208c2ff5fdc6e",
"build/assets/ba_data/data/languages/indonesian.json": "bff88ce57744a639810b93a1d1dd79f4",
"build/assets/ba_data/data/languages/italian.json": "338e7a03dff47f4eefc0ca3a995cd4f4",
"build/assets/ba_data/data/languages/korean.json": "ca1122a9ee551da3f75ae632012bd0e2",
"build/assets/ba_data/data/languages/indonesian.json": "408fb026e84c24a8dd7a43cb2b794541",
"build/assets/ba_data/data/languages/italian.json": "61c5308638bed44194f0ec24f19bf3cb",
"build/assets/ba_data/data/languages/korean.json": "03fd99d5e1155e81053fc028f69df982",
"build/assets/ba_data/data/languages/malay.json": "832562ce997fc70704b9234c95fb2e38",
"build/assets/ba_data/data/languages/persian.json": "71cc5b33abda0f285b970b8cc4a014a8",
"build/assets/ba_data/data/languages/polish.json": "e1a1a801851924748ad38fa68216439a",
"build/assets/ba_data/data/languages/portuguese.json": "9fcd6b4da9e5d0dc0e337ab00b5debe2",
"build/assets/ba_data/data/languages/persian.json": "4c3394f6662bb6dcf55728cfe213d750",
"build/assets/ba_data/data/languages/polish.json": "3a90b2d9e2c59305580c96f8098fc839",
"build/assets/ba_data/data/languages/portuguese.json": "0274cb9a4b7d2bd49c8eb8120144a1bf",
"build/assets/ba_data/data/languages/romanian.json": "aeebdd54f65939c2facc6ac50c117826",
"build/assets/ba_data/data/languages/russian.json": "910cf653497654a16d5c4f067d6def22",
"build/assets/ba_data/data/languages/russian.json": "9d0b40586301a82e532c4a250d5f6d58",
"build/assets/ba_data/data/languages/serbian.json": "d7452dd72ac0e51680cb39b5ebaa1c69",
"build/assets/ba_data/data/languages/slovak.json": "27962d53dc3f7dd4e877cd40faafeeef",
"build/assets/ba_data/data/languages/spanish.json": "0122b0b24aa111ab259af02bbae9b7b6",
"build/assets/ba_data/data/languages/swedish.json": "77d671f10613291ebf9c71da66f18a18",
"build/assets/ba_data/data/languages/tamil.json": "b9d4b4e107456ea6420ee0f9d9d7a03e",
"build/assets/ba_data/data/languages/thai.json": "33f63753c9af9a5b238d229a0bf23fbc",
"build/assets/ba_data/data/languages/turkish.json": "9d7e58c9062dc517c3779c255a9b3142",
"build/assets/ba_data/data/languages/ukrainian.json": "f72eb51abfbbb56e27866895d7e947d2",
"build/assets/ba_data/data/languages/venetian.json": "88595b7ee696b4094d7874c3c4188852",
"build/assets/ba_data/data/languages/spanish.json": "42f857c40dbd4b637e3866849489f7d1",
"build/assets/ba_data/data/languages/swedish.json": "5142a96597d17d8344be96a603da64ac",
"build/assets/ba_data/data/languages/tamil.json": "b4de1a2851afe4869c82e9acd94cd89c",
"build/assets/ba_data/data/languages/thai.json": "77755219bbf5fb7eea0d6b226684f403",
"build/assets/ba_data/data/languages/turkish.json": "ab149ebbd57cf4daa3cf8f310d91519a",
"build/assets/ba_data/data/languages/ukrainian.json": "e5c861187c4c6db37d1a033f4ef3dd5a",
"build/assets/ba_data/data/languages/venetian.json": "a559a5608d2e0b4708f7a4dee42ff354",
"build/assets/ba_data/data/languages/vietnamese.json": "921cd1e50f60fe3e101f246e172750ba",
"build/assets/ba_data/data/maps/big_g.json": "1dd301d490643088a435ce75df971054",
"build/assets/ba_data/data/maps/bridgit.json": "6aea74805f4880cc11237c5734a24422",
@ -946,11 +946,11 @@
"build/assets/ba_data/meshes/zoeUpperArm.bob": "a8a881010ac1ee9ec5ca872d5c5e853a",
"build/assets/ba_data/meshes/zoeUpperLeg.bob": "95b2502f74c70f934927f67cd505c3ad",
"build/assets/ba_data/python-site-packages/_yaml/__init__.py": "b09d1968d73a04d6cf20e4e79657a6e7",
"build/assets/ba_data/python-site-packages/certifi/__init__.py": "6337efa17f5b457b793332df33904162",
"build/assets/ba_data/python-site-packages/certifi/__init__.py": "b1fb6436db400125ecbb288262d00f0f",
"build/assets/ba_data/python-site-packages/certifi/__main__.py": "ef02e73f8581609df189a9f61aca365b",
"build/assets/ba_data/python-site-packages/certifi/cacert.pem": "6ac29a6bccca11cd2ed7e16e27dfccec",
"build/assets/ba_data/python-site-packages/certifi/cacert.pem": "4422aed09ab445f7290df7d72a301a47",
"build/assets/ba_data/python-site-packages/certifi/core.py": "1b505388f1475fabd1b60031f985271c",
"build/assets/ba_data/python-site-packages/typing_extensions.py": "084d93bb609d798a3930dfb5e25eba59",
"build/assets/ba_data/python-site-packages/typing_extensions.py": "2d974cad17a71505d86513d1322976a5",
"build/assets/ba_data/python-site-packages/yaml/__init__.py": "2b747e5772c203377222afc888ac6b71",
"build/assets/ba_data/python-site-packages/yaml/composer.py": "cef871e1f5f99ba2a7c44941b70afb06",
"build/assets/ba_data/python-site-packages/yaml/constructor.py": "8a15e361e34b79491c81553bb3534062",
@ -1444,6 +1444,10 @@
"build/assets/ba_data/textures/discordLogo.ktx": "d56ab6389e2770e0601d87f99375d7a5",
"build/assets/ba_data/textures/discordLogo.pvr": "bd39785cf2cbf9bc41fdde8b86ab3310",
"build/assets/ba_data/textures/discordLogo_preview.png": "90efd54d3abd371c0150a363f3f673e7",
"build/assets/ba_data/textures/discordServer.dds": "782f63ce8bcf0de2c338ee1fbfb5df2e",
"build/assets/ba_data/textures/discordServer.ktx": "09c969fd0a278b5c426344845ef07c43",
"build/assets/ba_data/textures/discordServer.pvr": "54216467f9527c3fc2d8c777811e97a7",
"build/assets/ba_data/textures/discordServer_preview.png": "99cf0a7f77b909e9079d423a687e399f",
"build/assets/ba_data/textures/doomShroomBGColor.dds": "628100f564789f57e22f2ce0dcf53b76",
"build/assets/ba_data/textures/doomShroomBGColor.ktx": "ffbb38af60ab59e6030b01cd129bfaf9",
"build/assets/ba_data/textures/doomShroomBGColor.pvr": "77d103392813e302cdaf631cef88ba36",
@ -1524,9 +1528,9 @@
"build/assets/ba_data/textures/fontBig.ktx": "94b56c2488d6c9ebabfbbb740eca07dd",
"build/assets/ba_data/textures/fontBig.pvr": "dff3f6c04a8c7b0bb937001640b42c8d",
"build/assets/ba_data/textures/fontBig_preview.png": "f8b15cb04f0deca7774def335a72f053",
"build/assets/ba_data/textures/fontExtras.dds": "7ab11df1b3a3daa651dfad34219b89f5",
"build/assets/ba_data/textures/fontExtras.ktx": "30c3c8ca2cdf1209ff177017bb10f0a8",
"build/assets/ba_data/textures/fontExtras.pvr": "fd3b0bd902c30e4b7aa5fe00e1eec4be",
"build/assets/ba_data/textures/fontExtras.dds": "0a5a39028853c443cd88bc2492cb6ad9",
"build/assets/ba_data/textures/fontExtras.ktx": "5b14075ce3d1d29c6d5635602e2176d8",
"build/assets/ba_data/textures/fontExtras.pvr": "8cc68ca85ba327c20c45bad73b000d8c",
"build/assets/ba_data/textures/fontExtras2.dds": "18063a12912dadc9528afd90d1cf2369",
"build/assets/ba_data/textures/fontExtras2.ktx": "36da7f6cfbfb8d32fb14371de0a8f660",
"build/assets/ba_data/textures/fontExtras2.pvr": "7a4e8e64ac05313b1782fb5b958150d0",
@ -1539,7 +1543,7 @@
"build/assets/ba_data/textures/fontExtras4.ktx": "6d872ac15e2e874c1252f63b4584722b",
"build/assets/ba_data/textures/fontExtras4.pvr": "6a0a0a1a8bbbc3ee9d6b8b914e7aa697",
"build/assets/ba_data/textures/fontExtras4_preview.png": "363e2647621917b3821c9068267d2516",
"build/assets/ba_data/textures/fontExtras_preview.png": "9c9c58aff612e7b6386f3522c0b4f1f6",
"build/assets/ba_data/textures/fontExtras_preview.png": "b6503267cc15e9e2524f41fabd94e773",
"build/assets/ba_data/textures/fontSmall0.dds": "b30bfe5f9e436be7be8b5eae6e8490c3",
"build/assets/ba_data/textures/fontSmall0.ktx": "7e6058f37e6c5a4ea628f35b5f92c227",
"build/assets/ba_data/textures/fontSmall0.pvr": "c66e3d6aa1f7def83aaacd8a6c9185e5",
@ -2586,21 +2590,21 @@
"build/assets/pylib-android/_pyio.py": "a6e88d66fbca88b13213cdd2177390b8",
"build/assets/pylib-android/_sitebuiltins.py": "8b5e3f6e73917962fa014ad2c4a55e61",
"build/assets/pylib-android/_strptime.py": "ff699c3f7647db7621bb88c43cc282d3",
"build/assets/pylib-android/_sysconfigdata__linux_aarch64-linux-android.py": "cb9a77b04173c8776365999b57186e36",
"build/assets/pylib-android/_sysconfigdata__linux_arm-linux-androideabi.py": "6d50596ec7f4858a0c6a5edefde21f7a",
"build/assets/pylib-android/_sysconfigdata__linux_i686-linux-android.py": "bf9358a2243aa7884b8e80d85c969fa5",
"build/assets/pylib-android/_sysconfigdata__linux_x86_64-linux-android.py": "45eee0efbc2441535b94a8ad5acf4d2e",
"build/assets/pylib-android/_sysconfigdata_d_linux_aarch64-linux-android.py": "f8ff271cf6df0b5b4d46d9c548abb84e",
"build/assets/pylib-android/_sysconfigdata_d_linux_arm-linux-androideabi.py": "da171b290c06a34d6a5cfbb296c22c34",
"build/assets/pylib-android/_sysconfigdata_d_linux_i686-linux-android.py": "dde2516b5ac29412dfbebaa7b3de0d0d",
"build/assets/pylib-android/_sysconfigdata_d_linux_x86_64-linux-android.py": "7df452144c6630afb96951487c1257a0",
"build/assets/pylib-android/_sysconfigdata__linux_aarch64-linux-android.py": "b1a9ca985ff6a159aa5ef94abd287f46",
"build/assets/pylib-android/_sysconfigdata__linux_arm-linux-androideabi.py": "21a5842f39c86fccaaa0a30e0e4ab347",
"build/assets/pylib-android/_sysconfigdata__linux_i686-linux-android.py": "9349023049d7599da61456b3f9a9687b",
"build/assets/pylib-android/_sysconfigdata__linux_x86_64-linux-android.py": "4151fa62c11c32cddf538e5cc7647160",
"build/assets/pylib-android/_sysconfigdata_d_linux_aarch64-linux-android.py": "d9f7f1d3f5b89b08150dfa00cf243901",
"build/assets/pylib-android/_sysconfigdata_d_linux_arm-linux-androideabi.py": "f4b99d4501a1cf1eb20fbc8973fa0040",
"build/assets/pylib-android/_sysconfigdata_d_linux_i686-linux-android.py": "454094da5fe52a969b53bb46d360da84",
"build/assets/pylib-android/_sysconfigdata_d_linux_x86_64-linux-android.py": "1e23f45f4243c1aacc83f23ad5852390",
"build/assets/pylib-android/_threading_local.py": "4a9688e3987d7d692db46feb9214945e",
"build/assets/pylib-android/_weakrefset.py": "e4fa8532ace46dfbc35149c41ea497f7",
"build/assets/pylib-android/abc.py": "a0daa1ed187eee8690c1e8438b97da90",
"build/assets/pylib-android/aifc.py": "1b9134c72b1e542417bee5bf345a1d0a",
"build/assets/pylib-android/antigravity.py": "6d56bedf73be574cb6d7117caf5d334c",
"build/assets/pylib-android/argparse.py": "e22cac9b12c09592929d57eb982fc554",
"build/assets/pylib-android/ast.py": "3aaa1b0e56b21b28155707c54bc225a8",
"build/assets/pylib-android/ast.py": "f287ccaa1cd7cb0ea256e3984fd4ce4d",
"build/assets/pylib-android/asynchat.py": "2ef3a0ce322332fabbf8fad4e133c6a3",
"build/assets/pylib-android/asyncio/__init__.py": "edf0e79e2b8b85c08f09fd14668e4822",
"build/assets/pylib-android/asyncio/__main__.py": "8e391b47f448ad922dc2614dbd93011e",
@ -2624,10 +2628,10 @@
"build/assets/pylib-android/asyncio/selector_events.py": "a108fbd3a49f967da245f39cebf7694e",
"build/assets/pylib-android/asyncio/sslproto.py": "2ec1b21e523055147d94c8c634154aab",
"build/assets/pylib-android/asyncio/staggered.py": "f5056f0a56b73b477a9fa65e71145366",
"build/assets/pylib-android/asyncio/streams.py": "f00ddd2b2fd74554ae1d3088bd9d2bfd",
"build/assets/pylib-android/asyncio/subprocess.py": "edb8d98278300b6c99f36cd08643c743",
"build/assets/pylib-android/asyncio/streams.py": "8cc026c067fc9245568199ea659167df",
"build/assets/pylib-android/asyncio/subprocess.py": "46e8b0ba32b4ac7bb5f840c49c89c85a",
"build/assets/pylib-android/asyncio/taskgroups.py": "5162e5b1806d9b647383d34ba1b21b56",
"build/assets/pylib-android/asyncio/tasks.py": "234550593cd4928e6ee2c9591b6928ca",
"build/assets/pylib-android/asyncio/tasks.py": "c1bc59c01792bac43b79b425bb61e10e",
"build/assets/pylib-android/asyncio/threads.py": "7bbf81d424901524510e07b5d20e4a50",
"build/assets/pylib-android/asyncio/timeouts.py": "c7cb81c7ee938bc47ff75342befc872a",
"build/assets/pylib-android/asyncio/transports.py": "04598090d813bb363cea9bf714b97c3f",
@ -2641,13 +2645,13 @@
"build/assets/pylib-android/bisect.py": "9b70437e327d5176da41192567ad0064",
"build/assets/pylib-android/bz2.py": "cd6a5f2491bc52afd8fc180097371473",
"build/assets/pylib-android/cProfile.py": "9e9c07ac3b9e4195a62b74e4f2b9489f",
"build/assets/pylib-android/calendar.py": "4ef3d6d85d44e36212e5d784051c80b6",
"build/assets/pylib-android/calendar.py": "18df862e8e3c3fcbe4ab8a0b0348e339",
"build/assets/pylib-android/cgi.py": "090c5cfc8b4b92a730beec975159bd2a",
"build/assets/pylib-android/cgitb.py": "2bcff1cec7f3a3a9c96de7a55ebb4ea3",
"build/assets/pylib-android/chunk.py": "13d7633b1ff28f5aed4eb043c65c99c5",
"build/assets/pylib-android/cmd.py": "8befee2654b0954af7886e24e2e7871f",
"build/assets/pylib-android/code.py": "5d47099984013b933c96b02ef16981b8",
"build/assets/pylib-android/codecs.py": "6fac5e2969e98ceaba92d3b8e42cb2ec",
"build/assets/pylib-android/codecs.py": "e11eabe4824899dea4b26a89a568a361",
"build/assets/pylib-android/codeop.py": "d375467fb29fccd43ab94d15a2e63085",
"build/assets/pylib-android/collections/__init__.py": "dcffbb6ee2cadd0c05ad22f2ef41f89b",
"build/assets/pylib-android/collections/abc.py": "15f410d3821352033a90a04539c99060",
@ -2656,7 +2660,7 @@
"build/assets/pylib-android/concurrent/__init__.py": "aa990702e8f3a7af205efb5ae23a7c85",
"build/assets/pylib-android/concurrent/futures/__init__.py": "3e46fadb9de9c995c37dca4311641d6a",
"build/assets/pylib-android/concurrent/futures/_base.py": "a1cd37aea6fe0efff1bc00a39543609e",
"build/assets/pylib-android/concurrent/futures/process.py": "a44e8618e158f8f351dafcb566a02544",
"build/assets/pylib-android/concurrent/futures/process.py": "1d1bb7b14e3999b383ba8bd11aa8951c",
"build/assets/pylib-android/concurrent/futures/thread.py": "e63753b8201f1392dbebc84a15054a13",
"build/assets/pylib-android/configparser.py": "914afd2b2cec90bbca0b94fd176b5176",
"build/assets/pylib-android/contextlib.py": "6f52eac914e438ef54407760def8305f",
@ -2680,7 +2684,7 @@
"build/assets/pylib-android/curses/panel.py": "8f36fdade9588f8a4362d2cc057a6eff",
"build/assets/pylib-android/curses/textpad.py": "94aa9ebc47a6068d4461652346646dbb",
"build/assets/pylib-android/dataclasses.py": "febeea138bff21dbed88762be772514e",
"build/assets/pylib-android/datetime.py": "5dcfd7f3b1a4db8214c1442164ac999c",
"build/assets/pylib-android/datetime.py": "521d6767afcfef887ac4c3719386b8fd",
"build/assets/pylib-android/decimal.py": "f57d255d45b5d1d7d8e13c41a283c3e4",
"build/assets/pylib-android/difflib.py": "6b3c8fd541b2b8d0320727025cd25275",
"build/assets/pylib-android/dis.py": "cecdc0c02aa3d70a7f550e60ebc9b3ba",
@ -2836,7 +2840,7 @@
"build/assets/pylib-android/encodings/utf_8_sig.py": "8f3542863ef311d8b970a37c0d66b0de",
"build/assets/pylib-android/encodings/uu_codec.py": "4ef8a65413574c017a96b97fc1638ba6",
"build/assets/pylib-android/encodings/zlib_codec.py": "1388fb103fdf395451bfc8a2d60933a9",
"build/assets/pylib-android/enum.py": "73b214a43ceef88aff7098b83623ed09",
"build/assets/pylib-android/enum.py": "e2a5734675e418870d7b379b5dba1ed3",
"build/assets/pylib-android/filecmp.py": "7648fdc6d0fc8bae7429d5e4081cf353",
"build/assets/pylib-android/fileinput.py": "c3def1041e6b12dd5f1906c9dbbd1101",
"build/assets/pylib-android/fnmatch.py": "a1bc67633695d4defd4c0886428c5363",
@ -2916,7 +2920,7 @@
"build/assets/pylib-android/optparse.py": "5f65f891612b68c71a2846da86254285",
"build/assets/pylib-android/os.py": "36f9692131ffb9ba4db510de31afc651",
"build/assets/pylib-android/pathlib.py": "095ec821fec243124d0a286b4de3848a",
"build/assets/pylib-android/pdb.py": "117b0d24ccb89edc5f183c94f6722f70",
"build/assets/pylib-android/pdb.py": "c44527d9e905ca3b1b45d3c158df730a",
"build/assets/pylib-android/pickle.py": "e6f9f53d29988454690ccde3279c7c38",
"build/assets/pylib-android/pickletools.py": "85b30fba86d32dfc4a588300dedf5f01",
"build/assets/pylib-android/pipes.py": "2dd796bdbb87982034234fec50d4526c",
@ -2945,10 +2949,10 @@
"build/assets/pylib-android/runpy.py": "3a2dd98314791c7e36b6bd3585f6ad82",
"build/assets/pylib-android/sched.py": "f5579c8c711dd3e89da70ec9e1788c9c",
"build/assets/pylib-android/secrets.py": "bbf9ed672044ef3ab4b83ca2aea1644e",
"build/assets/pylib-android/selectors.py": "98e0d83849452cbc2cc1381555bd5024",
"build/assets/pylib-android/selectors.py": "3c94b3b678c473543cdc7f1d2b20a6f6",
"build/assets/pylib-android/shelve.py": "3e569c07c863ecbd7f35a6c382d1785a",
"build/assets/pylib-android/shlex.py": "0873fac90a491702950816ead0e59dd0",
"build/assets/pylib-android/shutil.py": "a5d0ee9f28244b42a06e682312d0e3fa",
"build/assets/pylib-android/shutil.py": "aa636d67785c2e92d34c7c5c81f9e8c5",
"build/assets/pylib-android/signal.py": "114ef47b1798fca6f56ac8a250974b3e",
"build/assets/pylib-android/site.py": "2a99f7de2702aa8411d35acbb91fe926",
"build/assets/pylib-android/smtpd.py": "0602b6a39c4e37133303bee16c3e28a4",
@ -2958,7 +2962,7 @@
"build/assets/pylib-android/socketserver.py": "98e33643181a54765e6d0b9e01b03d53",
"build/assets/pylib-android/sqlite3/__init__.py": "8838d75ad0e465e25bb0c8dfeab7a9ab",
"build/assets/pylib-android/sqlite3/dbapi2.py": "c85f3ff9ddbd56683a8c801885dc5e53",
"build/assets/pylib-android/sqlite3/dump.py": "8364bd18be01acf7e56e168db98c0e6f",
"build/assets/pylib-android/sqlite3/dump.py": "8d2085ec40031d544694759608e53178",
"build/assets/pylib-android/sre_compile.py": "a1784e9ccbea7d9963cab75b536b40c8",
"build/assets/pylib-android/sre_constants.py": "5c5be32a5334d9b0a848dad520746a63",
"build/assets/pylib-android/sre_parse.py": "cca15b9ab31509e6642f9d2fd4fb9d91",
@ -2978,8 +2982,8 @@
"build/assets/pylib-android/tempfile.py": "436007fbe6821c864a53861bd73b4d43",
"build/assets/pylib-android/textwrap.py": "3eb16a40553205dc96be5cb9039f3c8c",
"build/assets/pylib-android/this.py": "8b0a9a1fa0a45a37e6c656eca1922277",
"build/assets/pylib-android/threading.py": "dda98a9e1169adb496655300454ecc09",
"build/assets/pylib-android/timeit.py": "8dc6f4245abf1d44814745e22a2f78b1",
"build/assets/pylib-android/threading.py": "3354bf0cad72286a0532b0754de78704",
"build/assets/pylib-android/timeit.py": "c918c7dee7538ff6e5a92288f55b4327",
"build/assets/pylib-android/token.py": "d8ff4e6c8eb59896891d01148f481e27",
"build/assets/pylib-android/tokenize.py": "3056f048c07e6c5a6442a5ef4f38e54c",
"build/assets/pylib-android/tomllib/__init__.py": "253ecf9dd67cb81a3e19911a4a39f930",
@ -2987,7 +2991,7 @@
"build/assets/pylib-android/tomllib/_re.py": "0e509117e16c41c491615e06bb98861d",
"build/assets/pylib-android/tomllib/_types.py": "07be9616d6f5e401fd31fbeea619fc97",
"build/assets/pylib-android/trace.py": "3d8698a2c3ec03dc0f394a2f48c2ffbc",
"build/assets/pylib-android/traceback.py": "91f67818e621e3b2f5bf583ed6863ef8",
"build/assets/pylib-android/traceback.py": "668bd36fc103a89554d2f9202a07f56d",
"build/assets/pylib-android/tracemalloc.py": "e4d10d2bee7773566e46797a939e5cbf",
"build/assets/pylib-android/tty.py": "271c7d61005a0a3c2c0952efc60dcb6d",
"build/assets/pylib-android/types.py": "78f8942c08dbfc9c582f1bb8d5206639",
@ -4056,59 +4060,59 @@
"build/assets/windows/Win32/ucrtbased.dll": "2def5335207d41b21b9823f6805997f1",
"build/assets/windows/Win32/vc_redist.x86.exe": "b08a55e2e77623fe657bea24f223a3ae",
"build/assets/windows/Win32/vcruntime140d.dll": "865b2af4d1e26a1a8073c89acb06e599",
"build/prefab/full/linux_arm64_gui/debug/ballisticakit": "43a2b924e39a8ec1c1c4dc29c4dd82a1",
"build/prefab/full/linux_arm64_gui/release/ballisticakit": "870c3973e3f84ca977496f79706ceab1",
"build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "49251d9bf7583a51e7e28cf6b62ff357",
"build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "23068b129ac54e4f24c96aa154f0bb4d",
"build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "045814b1c6e365ca5ac73883bbd9f22d",
"build/prefab/full/linux_x86_64_gui/release/ballisticakit": "52d8664a3ed40b0715fa5097005f58da",
"build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "4f364467c2f636a83a04f4f2930b34ee",
"build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "9b2afa754ae00d687750b243ee977312",
"build/prefab/full/mac_arm64_gui/debug/ballisticakit": "182c54fa13364049339aedfa67cbfcba",
"build/prefab/full/mac_arm64_gui/release/ballisticakit": "341976d8d665a94bf20a685aace37be5",
"build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "b60fe7cb522e9aa3969c7e92af06d14e",
"build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "e03943051877ae0105c7526b5094c67f",
"build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "bfc7e631e7319b0749912e40b4bdbe88",
"build/prefab/full/mac_x86_64_gui/release/ballisticakit": "d30e8266191cfd80f108129086f83b87",
"build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "bcf27351436cb58df186612b82be983b",
"build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "39f8ce569f1faed09bd4189e291a91bd",
"build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "71b1c542f48d5abae5fb790b521a156a",
"build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "553f71787d119cb01046f247711424b5",
"build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "12cd08021ae0ed1ce4238c1adead61dc",
"build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "b7e08e6b9f76def9b61e4839b2682497",
"build/prefab/lib/linux_arm64_gui/debug/libballisticaplus.a": "58946f3534363d88f713c54d3d643d6d",
"build/prefab/lib/linux_arm64_gui/release/libballisticaplus.a": "be356d05ecccd68043258d87b1892805",
"build/prefab/lib/linux_arm64_server/debug/libballisticaplus.a": "58946f3534363d88f713c54d3d643d6d",
"build/prefab/lib/linux_arm64_server/release/libballisticaplus.a": "be356d05ecccd68043258d87b1892805",
"build/prefab/lib/linux_x86_64_gui/debug/libballisticaplus.a": "bf7d793d62416db7273590a796001cb6",
"build/prefab/lib/linux_x86_64_gui/release/libballisticaplus.a": "b309e0cc3ec04024712c4ca938efdb92",
"build/prefab/lib/linux_x86_64_server/debug/libballisticaplus.a": "bf7d793d62416db7273590a796001cb6",
"build/prefab/lib/linux_x86_64_server/release/libballisticaplus.a": "b309e0cc3ec04024712c4ca938efdb92",
"build/prefab/lib/mac_arm64_gui/debug/libballisticaplus.a": "a86b09c31abf0b5ec934ef28c8bd9fa3",
"build/prefab/lib/mac_arm64_gui/release/libballisticaplus.a": "87be7a2f6e83c495f99024bb68660e17",
"build/prefab/lib/mac_arm64_server/debug/libballisticaplus.a": "a86b09c31abf0b5ec934ef28c8bd9fa3",
"build/prefab/lib/mac_arm64_server/release/libballisticaplus.a": "87be7a2f6e83c495f99024bb68660e17",
"build/prefab/lib/mac_x86_64_gui/debug/libballisticaplus.a": "9fb5d3cb36dd53bd18c7ca831e7c73ee",
"build/prefab/lib/mac_x86_64_gui/release/libballisticaplus.a": "88332859e6e9ee70848f5252e5ee6ce0",
"build/prefab/lib/mac_x86_64_server/debug/libballisticaplus.a": "55b6db8700acfc573cc3db31c6b210f7",
"build/prefab/lib/mac_x86_64_server/release/libballisticaplus.a": "88332859e6e9ee70848f5252e5ee6ce0",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "cf40ba3bce2391e82978b08785405a5e",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "a8a74156e04932a2a5cc6d2d4b202acf",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "a135f9210a1c3be6b5d5d8228c8f6184",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "e412e20e4a0ac33b9f83c7750cde7109",
"build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "cfcae11dab1c6752f821f0816706fa47",
"build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "16daa37287a6d9d3404461da8565aadb",
"build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "b3faf8b8925145f121b09e67d6114fb8",
"build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "ed7ec02978df94f92168c5990cb6c78c",
"build/prefab/full/linux_arm64_gui/debug/ballisticakit": "5f7e668a6a904ba8c0fe391654a8211b",
"build/prefab/full/linux_arm64_gui/release/ballisticakit": "77cd63dbc1760a3d1bb55753794701b0",
"build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "9f674a5b767f798b350ef30d84968d44",
"build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "84546f0452561d3f6519d296a9e9b2ca",
"build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "3122dbce63fb248c9ebe66d545be8480",
"build/prefab/full/linux_x86_64_gui/release/ballisticakit": "4004c01a3ea1a7b62544f08f14e76f9e",
"build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "cd9b5b9ae925cef7417d2b86fd0f0489",
"build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "35c2b307e5d228b105315b8d022d31d2",
"build/prefab/full/mac_arm64_gui/debug/ballisticakit": "90993c873a2d1ca7b593992e52b1865a",
"build/prefab/full/mac_arm64_gui/release/ballisticakit": "46119cb9ff99629ca16471f51032bac7",
"build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "8cac6eff8eb4f9e453a669b6f239b8a5",
"build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "3e20c91bf8948354a0e167faa5c184e9",
"build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "52ff216e1b0394a07bf178b8eea8c269",
"build/prefab/full/mac_x86_64_gui/release/ballisticakit": "32d85cc58ade8e7a63dfb604ef175553",
"build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "fdf3da5251bc0d3b8f4e5a5b2e0d4b94",
"build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "c03afc18b7aa72ab065381be985f48ae",
"build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "67a58d483ac028f9fc59112b59463a99",
"build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "0800d71a41320664bacba49822e8b442",
"build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "9019d9b48cefa45a7a16bae6bd696896",
"build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "90eca6cc81b0a2c39fd1d41d0d029f04",
"build/prefab/lib/linux_arm64_gui/debug/libballisticaplus.a": "db535f0ca1e01af825f75f204fbc8928",
"build/prefab/lib/linux_arm64_gui/release/libballisticaplus.a": "97d51afca996ae15b61fd9f409a00459",
"build/prefab/lib/linux_arm64_server/debug/libballisticaplus.a": "db535f0ca1e01af825f75f204fbc8928",
"build/prefab/lib/linux_arm64_server/release/libballisticaplus.a": "97d51afca996ae15b61fd9f409a00459",
"build/prefab/lib/linux_x86_64_gui/debug/libballisticaplus.a": "43794f4973b09588367261faf46b652a",
"build/prefab/lib/linux_x86_64_gui/release/libballisticaplus.a": "9bc71c9874596dd708841d84dff69b55",
"build/prefab/lib/linux_x86_64_server/debug/libballisticaplus.a": "43794f4973b09588367261faf46b652a",
"build/prefab/lib/linux_x86_64_server/release/libballisticaplus.a": "9bc71c9874596dd708841d84dff69b55",
"build/prefab/lib/mac_arm64_gui/debug/libballisticaplus.a": "87f6bf3ea1196d91cd6486785702a5b2",
"build/prefab/lib/mac_arm64_gui/release/libballisticaplus.a": "60336f9d664825ca08236dda311276ca",
"build/prefab/lib/mac_arm64_server/debug/libballisticaplus.a": "87f6bf3ea1196d91cd6486785702a5b2",
"build/prefab/lib/mac_arm64_server/release/libballisticaplus.a": "60336f9d664825ca08236dda311276ca",
"build/prefab/lib/mac_x86_64_gui/debug/libballisticaplus.a": "4399c87b58fbe58fa67096cfa878f86a",
"build/prefab/lib/mac_x86_64_gui/release/libballisticaplus.a": "452623f0495dd4375e5b5d9b80d643d5",
"build/prefab/lib/mac_x86_64_server/debug/libballisticaplus.a": "ca49b32ed573feea11613d62cd89840c",
"build/prefab/lib/mac_x86_64_server/release/libballisticaplus.a": "452623f0495dd4375e5b5d9b80d643d5",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "ff81e6eeea861f59e71db628bc64918b",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "ba89e5949d1cdf2b857089feb901285c",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "5e063f8acc0e0e9f35f82480d7dbf143",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "1f0d14fcc16dd0d4896d91c75e32be25",
"build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "251d3dd0bc9a6418eb1cb5176bb5509c",
"build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "96c003edb87b3f506d1b15af461487c3",
"build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "274ebd634f05b23653719ef973119cf5",
"build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "796edace73f874ebf46054b2a1ff0ba1",
"src/assets/ba_data/python/babase/_mgen/__init__.py": "f885fed7f2ed98ff2ba271f9dbe3391c",
"src/assets/ba_data/python/babase/_mgen/enums.py": "28323912b56ec07701eda3d41a6a4101",
"src/ballistica/base/mgen/pyembed/binding_base.inc": "ba8ce3ca3858b4c2d20db68f99b788b2",
"src/ballistica/base/mgen/pyembed/binding_base_app.inc": "00f81f9bd92386ec12a6e60170678a98",
"src/ballistica/base/mgen/pyembed/binding_base.inc": "72bfed2cce8ff19741989dec28302f3f",
"src/ballistica/base/mgen/pyembed/binding_base_app.inc": "97efb93f4bfd8e8b09f2db24398e29fc",
"src/ballistica/classic/mgen/pyembed/binding_classic.inc": "3ceb412513963f0818ab39c58bf292e3",
"src/ballistica/core/mgen/pyembed/binding_core.inc": "9d0a3c9636138e35284923e0c8311c69",
"src/ballistica/core/mgen/pyembed/env.inc": "8be46e5818f360d10b7b0224a9e91d07",
"src/ballistica/core/mgen/python_modules_monolithic.h": "fb967ed1c7db0c77d8deb4f00a7103c5",
"src/ballistica/scene_v1/mgen/pyembed/binding_scene_v1.inc": "d80f970053099b3044204bfe29ddefce",
"src/ballistica/scene_v1/mgen/pyembed/binding_scene_v1.inc": "c25b263f2a31fb5ebe057db07d144879",
"src/ballistica/template_fs/mgen/pyembed/binding_template_fs.inc": "44a45492db057bf7f7158c3b0fa11f0f",
"src/ballistica/ui_v1/mgen/pyembed/binding_ui_v1.inc": "8f4c2070174bdc2fbf735180394d7b3a"
"src/ballistica/ui_v1/mgen/pyembed/binding_ui_v1.inc": "f5f054050d2b2fcd3763a4833fb32269"
}

178
.github/workflows/cd.yml vendored Normal file
View File

@ -0,0 +1,178 @@
name: CD
on:
# Run on pushes and pull-requests
push:
pull_request:
jobs:
make_linux_x86_64_gui_debug_build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install pip requirements
run: tools/pcommand install_pip_reqs
- name: Make the build
run: make prefab-gui-debug-build
- name: Upload the build
uses: actions/upload-artifact@v3
with:
name: linux_x86_64_gui_(debug)
path: build/prefab/full/linux_x86_64_gui
make_linux_x86_64_server_debug_build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install pip requirements
run: tools/pcommand install_pip_reqs
- name: Make the build
run: make prefab-server-debug-build
- name: Upload the build
uses: actions/upload-artifact@v3
with:
name: linux_x86_64_server_(debug)
path: build/prefab/full/linux_x86_64_server
make_linux_arm64_gui_debug_build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install pip requirements
run: tools/pcommand install_pip_reqs
- name: Make the build
run: make prefab-linux-arm64-gui-debug-build
- name: Upload the build
uses: actions/upload-artifact@v3
with:
name: linux_arm64_gui_(debug)
path: build/prefab/full/linux_arm64_gui
make_linux_arm64_server_debug_build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install pip requirements
run: tools/pcommand install_pip_reqs
- name: Make the build
run: make prefab-linux-arm64-server-debug-build
- name: Upload the build
uses: actions/upload-artifact@v3
with:
name: linux_arm64_server_(debug)
path: build/prefab/full/linux_arm64_server
make_mac_x86_64_gui_debug_build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install pip requirements
run: tools/pcommand install_pip_reqs
- name: Make the build
run: make prefab-mac-x86-64-gui-debug-build
- name: Upload the build
uses: actions/upload-artifact@v3
with:
name: mac_x86_64_gui_(debug)
path: build/prefab/full/mac_x86_64_gui
make_mac_x86_64_server_debug_build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install pip requirements
run: tools/pcommand install_pip_reqs
- name: Make the build
run: make prefab-mac-x86-64-server-debug-build
- name: Upload the build
uses: actions/upload-artifact@v3
with:
name: mac_x86_64_server_(debug)
path: build/prefab/full/mac_x86_64_server
make_mac_arm64_gui_debug_build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install pip requirements
run: tools/pcommand install_pip_reqs
- name: Make the build
run: make prefab-mac-arm64-gui-debug-build
- name: Upload the build
uses: actions/upload-artifact@v3
with:
name: mac_arm64_gui_(debug)
path: build/prefab/full/mac_arm64_gui
make_mac_arm64_server_debug_build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install pip requirements
run: tools/pcommand install_pip_reqs
- name: Make the build
run: make prefab-mac-arm64-server-debug-build
- name: Upload the build
uses: actions/upload-artifact@v3
with:
name: mac_arm64_server_(debug)
path: build/prefab/full/mac_arm64_server
make_windows_x86_gui_debug_build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install pip requirements
run: tools/pcommand install_pip_reqs
- name: Make the build
run: make prefab-windows-x86-gui-debug-build
- name: Upload the build
uses: actions/upload-artifact@v3
with:
name: windows_x86_gui_(debug)
path: build/prefab/full/windows_x86_gui
make_windows_x86_server_debug_build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install pip requirements
run: tools/pcommand install_pip_reqs
- name: Make the build
run: make prefab-windows-x86-server-debug-build
- name: Upload the build
uses: actions/upload-artifact@v3
with:
name: windows_x86_server_(debug)
path: build/prefab/full/windows_x86_server

2
.gitignore vendored
View File

@ -120,10 +120,10 @@ xcuserdata/
/ballisticakit-android/BallisticaKit/src/main/res/mipmap-*/ic_launcher*.png
/ballisticakit-android/BallisticaKit/src/cardboard/res/mipmap-*/ic_launcher*.png
BallisticaKit.ico
/ballisticakit-xcode/BallisticaKit Shared/Assets.xcassets/Cursor macOS.appiconset/cursor_*.png
/ballisticakit-xcode/BallisticaKit Shared/Assets.xcassets/AppIcon iOS.appiconset/icon_*.png
/ballisticakit-xcode/BallisticaKit Shared/Assets.xcassets/AppIcon macOS.appiconset/icon_*.png
/ballisticakit-xcode/BallisticaKit Shared/Assets.xcassets/tvOS App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Layer*.imagestacklayer/Content.imageset/*.png
/ballisticakit-xcode/BallisticaKit Shared/Assets.xcassets/tvOS App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Layer*.imagestacklayer/Content.imageset/*.png
/ballisticakit-xcode/BallisticaKit Shared/Assets.xcassets/tvOS App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/*.png
/ballisticakit-xcode/BallisticaKit Shared/Assets.xcassets/tvOS App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/*.png
/ballisticakit-xcode/BallisticaKit Shared/Assets.xcassets/Cursor macOS.imageset/cursor_*.png

View File

@ -21,7 +21,6 @@
<excludeFolder url="file://$MODULE_DIR$/ballisticakit-android" />
<excludeFolder url="file://$MODULE_DIR$/ballisticakit-cmake" />
<excludeFolder url="file://$MODULE_DIR$/ballisticakit-ios.xcodeproj" />
<excludeFolder url="file://$MODULE_DIR$/ballisticakit-mac.xcodeproj" />
<excludeFolder url="file://$MODULE_DIR$/ballisticakit-windows" />
<excludeFolder url="file://$MODULE_DIR$/ballisticakit-windows-oculus" />
<excludeFolder url="file://$MODULE_DIR$/ballisticakit-xcode" />
@ -74,4 +73,4 @@
<component name="PyDocumentationSettings">
<option name="analyzeDoctest" value="false" />
</component>
</module>
</module>

4
.idea/misc.xml generated
View File

@ -4,10 +4,10 @@
<option name="cmdArguments" value="--line-length 80 --skip-string-normalization" />
<option name="enabledOnReformat" value="true" />
<option name="pathToExecutable" value="/opt/homebrew/bin/black" />
<option name="sdkUUID" value="1b270adb-5261-4492-85e8-d79b3894255d" />
<option name="sdkName" value="Python 3.11" />
</component>
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.11" project-jdk-type="Python SDK" />
<component name="PythonCompatibilityInspectionAdvertiser">
<option name="version" value="3" />
</component>
</project>
</project>

View File

@ -1,19 +1,128 @@
### 1.7.28 (build 21445, api 8, 2023-10-11)
### 1.7.33 (build 21743, api 8, 2023-12-21)
### 1.7.32 (build 21741, api 8, 2023-12-20)
- Fixed a screen message that no one will ever see (Thanks vishal332008?...)
- Plugins window now displays 'No Plugins Installed' when no plugins are present (Thanks vishal332008!)
- Old messages are now displayed as soon as you press 'Unmute Chat' (Thanks vishal332008!)
- Added an 'Add to Favorites' entry to the party menu (Thanks vishal332008!)
- Now displays 'No Parties Added' in favorites tab if no favorites are present (Thanks vishal332008!)
- Now shows character icons in the profiles list window (Thanks vishal332008!)
- Added a Random button for names in the Player Profiles window (Thanks vishal332008!)
- Fixed a bug where no server is selected by default in the favorites tab (Thanks vishal332008!)
- Fixed a bug where no replay is selected by default in the watch tab (Thanks vishal332008!)
- Fixed a bug where no profile is selected by default in the profile tab (Thanks vishal332008!)
- Fixed a number of UI screens so that ugly window edges are no longer visible
in corners on modern ultra wide phone displays.
- Added a `player_rejoin_cooldown` server config option. This defaults to 10
seconds for servers but 0 for normal gui clients. This mechanism had been
introduced recently to combat multiplayer fast-rejoin exploits and was set to
10 seconds everywhere, but it could tend to be annoying for local single
player play, dev testing, etc. Hopefully this strikes a good balance now.
- Removed the player-rejoin-cooldown mechanism from the C++ layer since it was
redundant with the Python level one and didn't cover as many cases.
- Restored the behavior from before 1.7.28 where backgrounding the app would
bring up the main menu and pause the action. Now it is implemented more
cleanly however (an `on_app_active_changed()` call in the `AppMode` class).
This means that it also applies to other platforms when the app reaches the
'inactive' state; for instance when minimizing the window on the SDL build.
### 1.7.31 (build 21727, api 8, 2023-12-17)
- Added `bascenev1.get_connection_to_host_info_2()` which is an improved
type-safe version of `bascenev1.get_connection_to_host_info()`.
- There is now a link to the official Discord server in the About section
(thanks EraOSBeta!).
- Native stack traces now work on Android; woohoo! Should be very helpful for
debugging.
- Added the concept of 'ui-operations' in the native layer to hopefully clear
out the remaining double-window bugs. Basically, widgets used to schedule
their payload commands to a future cycle of the event loop, meaning it was
possible for commands that switched the main window to get scheduled twice
before the first one ran (due to 2 key presses, etc), which could lead to all
sorts of weirdness happening such as multiple windows popping up when one was
intended. Now, however, such commands get scheduled to a current
'ui-operation' and then run *almost* immediately, which should prevent such
situations. Please holler if you run into any UI weirdness at this point.
### 1.7.30 (build 21697, api 8, 2023-12-08)
- Continued work on the big 1.7.28 update.
- Got the Android version back up and running. There's been lots of cleanup and
simplification on the Android layer, cleaning out years of cruft. This should
put things in a better more maintainable place, but there will probably be
some bugs to iron out, so please holler if you run into any.
- Minimum supported Android version has been bumped from 5.0 to 6.0. Some
upcoming tech such as ASTC textures will likely not be well supported on such
old devices, so I think it is better to leave them running an older version
that performs decently instead of a newer version that performs poorly. And
letting go of old Android versions lets us better support new ones.
- Android version now uses the 'Oboe' library as an audio back-end instead of
OpenSL. This should result in better behaving audio in general. Please holler
if you experience otherwise.
- Bundled Android Python has been bumped to version 3.11.6.
- Android app suspend behavior has been revamped. The app should stay running
more often and be quicker to respond when dialogs or other activities
temporarily pop up in front of it. This also allows it to continue playing
music over other activities such as Google Play Games
Achievements/Leaderboards screens. Please holler if you run into strange side
effects such as the app continuing to play audio when it should not be.
- Modernized the Android fullscreen setup code when running in Android 11 or
newer. The game should now use the whole screen area, including the area
around notches or camera cutouts. Please holler if you are seeing any problems
related to this.
- (build 21626) Fixed a bug where click/tap locations were incorrect on some
builds when tv-border was on (Thanks for the heads-up Loup(Dliwk's fan)!).
- (build 21631) Fixes an issue where '^^^^^^^^^^^^^' lines in stack traces could
get chopped into tiny bits each on their own line in the dev console.
- Hopefully finally fixed a longstanding issue where obscure cases such as
multiple key presses simultaneously could cause multiple main menu windows to
pop up. Please holler if you still see this problem happening anywhere. Also
added a few related safety checks and warnings to help ensure UI code is free
from such problems going forward. To make sure your custom UIs are behaving
well in this system, do the following two things: 1) any time you call
`set_main_menu_window()`, pass your existing main menu window root widget as
`from_window`. 2) In any call that can lead to you switching the main menu
window, check if your root widget is dead or transitioning out first and abort
if it is. See any window in `ui_v1_lib` for examples.
- (build 21691) Fixed a bug causing touches to not register in some cases on
newer Android devices. (Huge thanks to JESWIN A J for helping me track that
down!).
- Temporarily removed the pause-the-game-when-backgrounded behavior for locally
hosted games, mainly due to the code being hacky. Will try to restore this
functionality in a cleaner way soon.
### 1.7.29 (build 21619, api 8, 2023-11-21)
- Simply continued work on the big 1.7.28 update. I was able to finally start
updating the Mac App Store version of the game again (it had been stuck at
1.4!), and it turns out that Apple AppStore submissions require the version
number to increase each time and not just the build number, so we may start
seeing more minor version number bumps for that reason.
- Windows builds should now die with a clear error when the OpenGL version is
too old (OpenGL 3.0 or newer is required). Previously they could die with more
cryptic error messages such as "OpenGL function 'glActiveTexture2D' not
found".
### 1.7.28 (build 21599, api 8, 2023-11-16)
- Turning off ticket continues on all platforms. I'll be moving the game towards
a new monetization scheme mostly based on cosmetics and this has always felt a
bit ugly pay-to-win to me, so it's time for it to go. Note that the
functionality is still in there if anyone wants to support it in mods.
- Massively cleaned up code related to rendering and window systems (OpenGL,
SDL, etc). This code had been growing into a nasty tangle for 15 years
attempting to support various old/hacked versions of SDL, etc. I ripped out
huge chunks of it and put back still-relevant pieces in a much more cleanly
designed way. This should put us in a much better place for supporting various
platforms and making graphical improvements going forward. See
`ballistica/base/app_adapter/app_adapter_sdl.cc` for an example of the now
platforms and making graphical improvements going forward.
`ballistica/base/app_adapter/app_adapter_sdl.cc` is an example of the now
nicely implemented system.
- The engine now requires OpenGL 3.0 or newer on desktop and OpenGL ES 3.0 or
newer on mobile. This means we're cutting off a few percent of old devices on
Android that only support ES 2, but ES 3 has been out for 10 years now so I
feel it is time. As mentioned above, this allows massively cleaning up the
graphics code which means we can start to improve it.
- Removed gamma controls. These were only active on the old Mac version anyway
graphics code which means we can start to improve it. Ideally now the GL
renderer can be abstracted a bit more which will make the process of writing
other renderers easier.
- Removed gamma controls. These were only active on the old Mac builds anyway
and are being removed from the upcoming SDL3, so if we want this sort of thing
we should do it through shading in the renderer now.
- Implemented both vsync and max-fps for the SDL build of the game. This means
@ -129,7 +238,87 @@
before. It also takes a `confirm` bool arg which allows it to be used to bring
up a confirm dialog.
- Clicking on a window close button to quit no longer brings up a confirm dialog
and instead quits immediately (though with a proper graceful shutdown).
and instead quits immediately (though with a proper graceful shutdown and a
lovely little fade).
- Camera shake is now supported in network games and replays. Somehow I didn't
notice that was missing for years. The downside is this requires a server to
be hosting protocol 35, which cuts off support for 1.4 clients. So for now I
am keeping the default at 33. Once there a fewer 1.4 clients around we can
consider changing this (if everything hasn't moved to SceneV2 by then).
- Added a server option to set the hosting protocol for servers who might want
to allow camera shake (or other minor features/fixes) that don't work in the
default protocol 33. See `protocol_version` in `config.yaml`. Just remember
that you will be cutting off support for older clients if you use 35.
- Fixed a bug with screen-messages animating off screen too fast when frame
rates are high.
- Added a proper graceful shutdown process for the audio server. This should
result in fewer ugly pops and warning messages when the app is quit.
- Tidied up some keyboard shortcuts to be more platform-appropriate. For
example, toggling fullscreen on Windows is now Alt+Enter or F11.
- Fancy rebuilt Mac build should now automatically sync its frame rate to the
display its running on (using CVDisplayLinks, not VSync).
- Mac build is now relying solely on Apple's Game Controller Framework, which
seems pretty awesome these days. It should support most stuff SDL does and
with less configuring involved. Please holler if you come across something
that doesn't work.
- Mac build is also now using the Game Controller Framework to handle keyboard
events. This should better handle things like modifier keys and also will
allow us to use that exact same code on the iPad/iPhone version.
- OS key repeat events are no longer passed through the engine. This means that
any time we want repeating behavior, such as holding an arrow key to move
through UI elements, we will need to wire it up ourselves. We already do this
for things like game controllers however, so this is more consistent in a way.
- Dev console no longer claims key events unless the Python tab is showing and
there is a hardware keyboard attached. This allows showing dev console tabs
above gameplay without interfering with it.
- Added clipboard paste support to the dev console python terminal.
- Added various text editing functionality to the dev console python terminal
(cursor movement, deleting chars and words, etc.)
- Internal on-screen-keyboard now has a cancel button (thanks vishal332008!)
- Public servers list now shows 'No servers found' if there are no servers to
show instead of just remaining mysteriously blank (thanks vishal332008!)
- Players are now prevented from rejoining a session for 10 seconds after they
leave to prevent game exploits. Note this is different than the existing
system that prevents joining a *party* for 10 seconds; this covers people
who never leave the party (Thanks EraOSBeta!).
- Fixes an issue where servers could be crashed by flooding them with join
requests (Thanks for the heads-up Era!).
- The engine will now ignore empty device config dicts and fall back to
defaults; these could theoretically happen if device config code fails
somewhere and it previously would leave the device mysteriously inoperable.
- The game will now show <unset> for controls with no bindings in the in-game
guide and controller/keyboard config screens.
- Fixed a crash that could occur if SDL couldn't find a name for connected
joystick.
- Simplified the app's handling of broken config files. Previously it would do
various complex things such as offering to edit the broken config on desktop
builds, avoiding overwriting broken configs, and automatically loading
previous configs. Now, if it finds a broken config, it will simply back it up
to a .broken file, log an error message, and then start up normally with a
default config. This way, things are more consistent across platforms, and
technical users can still fix and restore their old configs. Note that the app
still also writes .prev configs for extra security, though it no longer uses
them for anything itself.
- Converted more internal engine time values from milliseconds to microseconds,
including things like the internal EventLoop timeline. Please holler if you
notice anything running 1000x too fast or slow. In general my strategy going
forward is to use microseconds for exact internal time values but to mostly
expose float seconds to the user, especially on the Python layer. There were
starting to be a few cases were integer milliseconds was not enough precision
for internal values. For instance, if we run with unclamped framerates and hit
several hundred FPS, milliseconds per frame would drop to 0 which caused some
problems. Note that scenev1 will be remaining on milliseconds internally for
compatibility reasons. Scenev2 should move to microseconds though.
- The V2 account id for the signed in account is now available at
`ba*.app.plus.accounts.primary.accountid` (alongside some other existing
account info).
- (build 21585) Fixed an issue where some navigation key presses were getting
incorrectly absorbed by text widgets. (Thanks for the heads-up Temp!)
- (build 21585) Fixed an issue where texture quality changes would not take
effect until next launch.
- Added a 'glow_type' arg to `bauiv1.textwidget()` to adjust the glow used when
the text is selected. The default is 'gradient' but there is now a 'uniform'
option which may look better in some circumstances.
### 1.7.27 (build 21282, api 8, 2023-08-30)

View File

@ -41,8 +41,12 @@
- Modder
### Era0S
- Community Suggestions Implementer
- Bug Fixer
- Modder
### VinniTR
- Fixes
### Rikko
- Created the original "reject_recently_left_players" plugin

View File

@ -779,13 +779,12 @@ check-full: py_check_prereqs
# Same as 'check' plus optional/slow extra checks.
check2: py_check_prereqs
@$(DMAKE) -j$(CPUS) update-check cpplint pylint mypy pycharm
@$(DMAKE) -j$(CPUS) update-check cpplint pylint mypy
@$(PCOMMANDBATCH) echo SGRN BLD ALL CHECKS PASSED!
# Same as check2 but no caching (all files are checked).
check2-full: py_check_prereqs
@$(DMAKE) -j$(CPUS) update-check cpplint-full pylint-full mypy-full \
pycharm-full
@$(DMAKE) -j$(CPUS) update-check cpplint-full pylint-full mypy-full
@$(PCOMMANDBATCH) echo SGRN BLD ALL CHECKS PASSED!
# Run Cpplint checks on all C/C++ code.
@ -924,14 +923,14 @@ preflight-full:
preflight2:
@$(MAKE) format
@$(MAKE) update
@$(MAKE) -j$(CPUS) cpplint pylint mypy pycharm test
@$(MAKE) -j$(CPUS) cpplint pylint mypy test
@$(PCOMMANDBATCH) echo SGRN BLD PREFLIGHT SUCCESSFUL!
# Same as 'preflight2' but without caching (all files visited).
preflight2-full:
@$(MAKE) format-full
@$(MAKE) update
@$(MAKE) -j$(CPUS) cpplint-full pylint-full mypy-full pycharm-full test-full
@$(MAKE) -j$(CPUS) cpplint-full pylint-full mypy-full test-full
@$(PCOMMANDBATCH) echo SGRN BLD PREFLIGHT SUCCESSFUL!
# Tell make which of these targets don't represent files.

View File

@ -6,7 +6,7 @@ height="50" alt="logo">
***-ica***: collection of things relating to a specific theme.
[![](https://github.com/efroemling/ballistica/actions/workflows/ci.yml/badge.svg)](https://github.com/efroemling/ballistica/actions/workflows/ci.yml)
[![](https://github.com/efroemling/ballistica/actions/workflows/ci.yml/badge.svg)](https://github.com/efroemling/ballistica/actions/workflows/ci.yml) [![](https://github.com/efroemling/ballistica/actions/workflows/cd.yml/badge.svg)](https://github.com/efroemling/ballistica/actions/workflows/cd.yml)
The Ballistica project is the foundation for
[BombSquad](https://www.froemling.net/apps/bombsquad) and potentially other
@ -52,7 +52,7 @@ want to keep that spirit alive as the Ballistica project moves forward. Whether
this means making it easier to share mods, organize tournaments, join up with
friends, teach each other some Python, or whatever else. Life is short; let's
play some games. Or make them. Maybe both.
### Frequently Asked Questions
* **Q: What's with this name? Is it BombSquad or Ballistica?**
@ -86,4 +86,4 @@ Playstation / My Toaster??**
for more details or the [Ballistica
Downloads](https://ballistica.net/downloads) page for early test builds on
some platforms.

View File

@ -14,7 +14,6 @@
<file path="$PROJECT_DIR$/../ballisticakit-android" />
<file path="$PROJECT_DIR$/.idea" />
<file path="$PROJECT_DIR$/../ballisticakit-ios.xcodeproj" />
<file path="$PROJECT_DIR$/../ballisticakit-mac.xcodeproj" />
<file path="$PROJECT_DIR$/../ballisticakit-windows" />
<file path="$PROJECT_DIR$/../ballisticakit-xcode" />
<file path="$PROJECT_DIR$/../build" />

View File

@ -353,9 +353,15 @@ set(BALLISTICA_SOURCES
${BA_SRC_ROOT}/ballistica/base/graphics/support/camera.h
${BA_SRC_ROOT}/ballistica/base/graphics/support/frame_def.cc
${BA_SRC_ROOT}/ballistica/base/graphics/support/frame_def.h
${BA_SRC_ROOT}/ballistica/base/graphics/support/graphics_client_context.cc
${BA_SRC_ROOT}/ballistica/base/graphics/support/graphics_client_context.h
${BA_SRC_ROOT}/ballistica/base/graphics/support/graphics_settings.cc
${BA_SRC_ROOT}/ballistica/base/graphics/support/graphics_settings.h
${BA_SRC_ROOT}/ballistica/base/graphics/support/net_graph.cc
${BA_SRC_ROOT}/ballistica/base/graphics/support/net_graph.h
${BA_SRC_ROOT}/ballistica/base/graphics/support/render_command_buffer.h
${BA_SRC_ROOT}/ballistica/base/graphics/support/screen_messages.cc
${BA_SRC_ROOT}/ballistica/base/graphics/support/screen_messages.h
${BA_SRC_ROOT}/ballistica/base/graphics/text/font_page_map_data.h
${BA_SRC_ROOT}/ballistica/base/graphics/text/text_graphics.cc
${BA_SRC_ROOT}/ballistica/base/graphics/text/text_graphics.h
@ -432,16 +438,19 @@ set(BALLISTICA_SOURCES
${BA_SRC_ROOT}/ballistica/base/support/app_config.cc
${BA_SRC_ROOT}/ballistica/base/support/app_config.h
${BA_SRC_ROOT}/ballistica/base/support/app_timer.h
${BA_SRC_ROOT}/ballistica/base/support/base_build_switches.cc
${BA_SRC_ROOT}/ballistica/base/support/base_build_switches.h
${BA_SRC_ROOT}/ballistica/base/support/classic_soft.h
${BA_SRC_ROOT}/ballistica/base/support/context.cc
${BA_SRC_ROOT}/ballistica/base/support/context.h
${BA_SRC_ROOT}/ballistica/base/support/display_timer.h
${BA_SRC_ROOT}/ballistica/base/support/huffman.cc
${BA_SRC_ROOT}/ballistica/base/support/huffman.h
${BA_SRC_ROOT}/ballistica/base/support/plus_soft.h
${BA_SRC_ROOT}/ballistica/base/support/repeater.cc
${BA_SRC_ROOT}/ballistica/base/support/repeater.h
${BA_SRC_ROOT}/ballistica/base/support/stdio_console.cc
${BA_SRC_ROOT}/ballistica/base/support/stdio_console.h
${BA_SRC_ROOT}/ballistica/base/support/stress_test.cc
${BA_SRC_ROOT}/ballistica/base/support/stress_test.h
${BA_SRC_ROOT}/ballistica/base/ui/dev_console.cc
${BA_SRC_ROOT}/ballistica/base/ui/dev_console.h
${BA_SRC_ROOT}/ballistica/base/ui/ui.cc
@ -454,6 +463,8 @@ set(BALLISTICA_SOURCES
${BA_SRC_ROOT}/ballistica/classic/python/classic_python.h
${BA_SRC_ROOT}/ballistica/classic/python/methods/python_methods_classic.cc
${BA_SRC_ROOT}/ballistica/classic/python/methods/python_methods_classic.h
${BA_SRC_ROOT}/ballistica/classic/support/stress_test.cc
${BA_SRC_ROOT}/ballistica/classic/support/stress_test.h
${BA_SRC_ROOT}/ballistica/classic/support/v1_account.cc
${BA_SRC_ROOT}/ballistica/classic/support/v1_account.h
${BA_SRC_ROOT}/ballistica/core/core.cc
@ -686,8 +697,10 @@ set(BALLISTICA_SOURCES
${BA_SRC_ROOT}/ballistica/shared/generic/json.cc
${BA_SRC_ROOT}/ballistica/shared/generic/json.h
${BA_SRC_ROOT}/ballistica/shared/generic/lambda_runnable.h
${BA_SRC_ROOT}/ballistica/shared/generic/native_stack_trace.h
${BA_SRC_ROOT}/ballistica/shared/generic/runnable.cc
${BA_SRC_ROOT}/ballistica/shared/generic/runnable.h
${BA_SRC_ROOT}/ballistica/shared/generic/snapshot.h
${BA_SRC_ROOT}/ballistica/shared/generic/timer_list.cc
${BA_SRC_ROOT}/ballistica/shared/generic/timer_list.h
${BA_SRC_ROOT}/ballistica/shared/generic/utf8.cc

View File

@ -345,9 +345,15 @@
<ClInclude Include="..\..\src\ballistica\base\graphics\support\camera.h" />
<ClCompile Include="..\..\src\ballistica\base\graphics\support\frame_def.cc" />
<ClInclude Include="..\..\src\ballistica\base\graphics\support\frame_def.h" />
<ClCompile Include="..\..\src\ballistica\base\graphics\support\graphics_client_context.cc" />
<ClInclude Include="..\..\src\ballistica\base\graphics\support\graphics_client_context.h" />
<ClCompile Include="..\..\src\ballistica\base\graphics\support\graphics_settings.cc" />
<ClInclude Include="..\..\src\ballistica\base\graphics\support\graphics_settings.h" />
<ClCompile Include="..\..\src\ballistica\base\graphics\support\net_graph.cc" />
<ClInclude Include="..\..\src\ballistica\base\graphics\support\net_graph.h" />
<ClInclude Include="..\..\src\ballistica\base\graphics\support\render_command_buffer.h" />
<ClCompile Include="..\..\src\ballistica\base\graphics\support\screen_messages.cc" />
<ClInclude Include="..\..\src\ballistica\base\graphics\support\screen_messages.h" />
<ClInclude Include="..\..\src\ballistica\base\graphics\text\font_page_map_data.h" />
<ClCompile Include="..\..\src\ballistica\base\graphics\text\text_graphics.cc" />
<ClInclude Include="..\..\src\ballistica\base\graphics\text\text_graphics.h" />
@ -424,16 +430,19 @@
<ClCompile Include="..\..\src\ballistica\base\support\app_config.cc" />
<ClInclude Include="..\..\src\ballistica\base\support\app_config.h" />
<ClInclude Include="..\..\src\ballistica\base\support\app_timer.h" />
<ClCompile Include="..\..\src\ballistica\base\support\base_build_switches.cc" />
<ClInclude Include="..\..\src\ballistica\base\support\base_build_switches.h" />
<ClInclude Include="..\..\src\ballistica\base\support\classic_soft.h" />
<ClCompile Include="..\..\src\ballistica\base\support\context.cc" />
<ClInclude Include="..\..\src\ballistica\base\support\context.h" />
<ClInclude Include="..\..\src\ballistica\base\support\display_timer.h" />
<ClCompile Include="..\..\src\ballistica\base\support\huffman.cc" />
<ClInclude Include="..\..\src\ballistica\base\support\huffman.h" />
<ClInclude Include="..\..\src\ballistica\base\support\plus_soft.h" />
<ClCompile Include="..\..\src\ballistica\base\support\repeater.cc" />
<ClInclude Include="..\..\src\ballistica\base\support\repeater.h" />
<ClCompile Include="..\..\src\ballistica\base\support\stdio_console.cc" />
<ClInclude Include="..\..\src\ballistica\base\support\stdio_console.h" />
<ClCompile Include="..\..\src\ballistica\base\support\stress_test.cc" />
<ClInclude Include="..\..\src\ballistica\base\support\stress_test.h" />
<ClCompile Include="..\..\src\ballistica\base\ui\dev_console.cc" />
<ClInclude Include="..\..\src\ballistica\base\ui\dev_console.h" />
<ClCompile Include="..\..\src\ballistica\base\ui\ui.cc" />
@ -446,6 +455,8 @@
<ClInclude Include="..\..\src\ballistica\classic\python\classic_python.h" />
<ClCompile Include="..\..\src\ballistica\classic\python\methods\python_methods_classic.cc" />
<ClInclude Include="..\..\src\ballistica\classic\python\methods\python_methods_classic.h" />
<ClCompile Include="..\..\src\ballistica\classic\support\stress_test.cc" />
<ClInclude Include="..\..\src\ballistica\classic\support\stress_test.h" />
<ClCompile Include="..\..\src\ballistica\classic\support\v1_account.cc" />
<ClInclude Include="..\..\src\ballistica\classic\support\v1_account.h" />
<ClCompile Include="..\..\src\ballistica\core\core.cc" />
@ -678,8 +689,10 @@
<ClCompile Include="..\..\src\ballistica\shared\generic\json.cc" />
<ClInclude Include="..\..\src\ballistica\shared\generic\json.h" />
<ClInclude Include="..\..\src\ballistica\shared\generic\lambda_runnable.h" />
<ClInclude Include="..\..\src\ballistica\shared\generic\native_stack_trace.h" />
<ClCompile Include="..\..\src\ballistica\shared\generic\runnable.cc" />
<ClInclude Include="..\..\src\ballistica\shared\generic\runnable.h" />
<ClInclude Include="..\..\src\ballistica\shared\generic\snapshot.h" />
<ClCompile Include="..\..\src\ballistica\shared\generic\timer_list.cc" />
<ClInclude Include="..\..\src\ballistica\shared\generic\timer_list.h" />
<ClCompile Include="..\..\src\ballistica\shared\generic\utf8.cc" />

View File

@ -469,6 +469,18 @@
<ClInclude Include="..\..\src\ballistica\base\graphics\support\frame_def.h">
<Filter>ballistica\base\graphics\support</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ballistica\base\graphics\support\graphics_client_context.cc">
<Filter>ballistica\base\graphics\support</Filter>
</ClCompile>
<ClInclude Include="..\..\src\ballistica\base\graphics\support\graphics_client_context.h">
<Filter>ballistica\base\graphics\support</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ballistica\base\graphics\support\graphics_settings.cc">
<Filter>ballistica\base\graphics\support</Filter>
</ClCompile>
<ClInclude Include="..\..\src\ballistica\base\graphics\support\graphics_settings.h">
<Filter>ballistica\base\graphics\support</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ballistica\base\graphics\support\net_graph.cc">
<Filter>ballistica\base\graphics\support</Filter>
</ClCompile>
@ -478,6 +490,12 @@
<ClInclude Include="..\..\src\ballistica\base\graphics\support\render_command_buffer.h">
<Filter>ballistica\base\graphics\support</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ballistica\base\graphics\support\screen_messages.cc">
<Filter>ballistica\base\graphics\support</Filter>
</ClCompile>
<ClInclude Include="..\..\src\ballistica\base\graphics\support\screen_messages.h">
<Filter>ballistica\base\graphics\support</Filter>
</ClInclude>
<ClInclude Include="..\..\src\ballistica\base\graphics\text\font_page_map_data.h">
<Filter>ballistica\base\graphics\text</Filter>
</ClInclude>
@ -706,6 +724,12 @@
<ClInclude Include="..\..\src\ballistica\base\support\app_timer.h">
<Filter>ballistica\base\support</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ballistica\base\support\base_build_switches.cc">
<Filter>ballistica\base\support</Filter>
</ClCompile>
<ClInclude Include="..\..\src\ballistica\base\support\base_build_switches.h">
<Filter>ballistica\base\support</Filter>
</ClInclude>
<ClInclude Include="..\..\src\ballistica\base\support\classic_soft.h">
<Filter>ballistica\base\support</Filter>
</ClInclude>
@ -715,6 +739,9 @@
<ClInclude Include="..\..\src\ballistica\base\support\context.h">
<Filter>ballistica\base\support</Filter>
</ClInclude>
<ClInclude Include="..\..\src\ballistica\base\support\display_timer.h">
<Filter>ballistica\base\support</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ballistica\base\support\huffman.cc">
<Filter>ballistica\base\support</Filter>
</ClCompile>
@ -724,18 +751,18 @@
<ClInclude Include="..\..\src\ballistica\base\support\plus_soft.h">
<Filter>ballistica\base\support</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ballistica\base\support\repeater.cc">
<Filter>ballistica\base\support</Filter>
</ClCompile>
<ClInclude Include="..\..\src\ballistica\base\support\repeater.h">
<Filter>ballistica\base\support</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ballistica\base\support\stdio_console.cc">
<Filter>ballistica\base\support</Filter>
</ClCompile>
<ClInclude Include="..\..\src\ballistica\base\support\stdio_console.h">
<Filter>ballistica\base\support</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ballistica\base\support\stress_test.cc">
<Filter>ballistica\base\support</Filter>
</ClCompile>
<ClInclude Include="..\..\src\ballistica\base\support\stress_test.h">
<Filter>ballistica\base\support</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ballistica\base\ui\dev_console.cc">
<Filter>ballistica\base\ui</Filter>
</ClCompile>
@ -772,6 +799,12 @@
<ClInclude Include="..\..\src\ballistica\classic\python\methods\python_methods_classic.h">
<Filter>ballistica\classic\python\methods</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ballistica\classic\support\stress_test.cc">
<Filter>ballistica\classic\support</Filter>
</ClCompile>
<ClInclude Include="..\..\src\ballistica\classic\support\stress_test.h">
<Filter>ballistica\classic\support</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ballistica\classic\support\v1_account.cc">
<Filter>ballistica\classic\support</Filter>
</ClCompile>
@ -1468,12 +1501,18 @@
<ClInclude Include="..\..\src\ballistica\shared\generic\lambda_runnable.h">
<Filter>ballistica\shared\generic</Filter>
</ClInclude>
<ClInclude Include="..\..\src\ballistica\shared\generic\native_stack_trace.h">
<Filter>ballistica\shared\generic</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ballistica\shared\generic\runnable.cc">
<Filter>ballistica\shared\generic</Filter>
</ClCompile>
<ClInclude Include="..\..\src\ballistica\shared\generic\runnable.h">
<Filter>ballistica\shared\generic</Filter>
</ClInclude>
<ClInclude Include="..\..\src\ballistica\shared\generic\snapshot.h">
<Filter>ballistica\shared\generic</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ballistica\shared\generic\timer_list.cc">
<Filter>ballistica\shared\generic</Filter>
</ClCompile>

View File

@ -340,9 +340,15 @@
<ClInclude Include="..\..\src\ballistica\base\graphics\support\camera.h" />
<ClCompile Include="..\..\src\ballistica\base\graphics\support\frame_def.cc" />
<ClInclude Include="..\..\src\ballistica\base\graphics\support\frame_def.h" />
<ClCompile Include="..\..\src\ballistica\base\graphics\support\graphics_client_context.cc" />
<ClInclude Include="..\..\src\ballistica\base\graphics\support\graphics_client_context.h" />
<ClCompile Include="..\..\src\ballistica\base\graphics\support\graphics_settings.cc" />
<ClInclude Include="..\..\src\ballistica\base\graphics\support\graphics_settings.h" />
<ClCompile Include="..\..\src\ballistica\base\graphics\support\net_graph.cc" />
<ClInclude Include="..\..\src\ballistica\base\graphics\support\net_graph.h" />
<ClInclude Include="..\..\src\ballistica\base\graphics\support\render_command_buffer.h" />
<ClCompile Include="..\..\src\ballistica\base\graphics\support\screen_messages.cc" />
<ClInclude Include="..\..\src\ballistica\base\graphics\support\screen_messages.h" />
<ClInclude Include="..\..\src\ballistica\base\graphics\text\font_page_map_data.h" />
<ClCompile Include="..\..\src\ballistica\base\graphics\text\text_graphics.cc" />
<ClInclude Include="..\..\src\ballistica\base\graphics\text\text_graphics.h" />
@ -419,16 +425,19 @@
<ClCompile Include="..\..\src\ballistica\base\support\app_config.cc" />
<ClInclude Include="..\..\src\ballistica\base\support\app_config.h" />
<ClInclude Include="..\..\src\ballistica\base\support\app_timer.h" />
<ClCompile Include="..\..\src\ballistica\base\support\base_build_switches.cc" />
<ClInclude Include="..\..\src\ballistica\base\support\base_build_switches.h" />
<ClInclude Include="..\..\src\ballistica\base\support\classic_soft.h" />
<ClCompile Include="..\..\src\ballistica\base\support\context.cc" />
<ClInclude Include="..\..\src\ballistica\base\support\context.h" />
<ClInclude Include="..\..\src\ballistica\base\support\display_timer.h" />
<ClCompile Include="..\..\src\ballistica\base\support\huffman.cc" />
<ClInclude Include="..\..\src\ballistica\base\support\huffman.h" />
<ClInclude Include="..\..\src\ballistica\base\support\plus_soft.h" />
<ClCompile Include="..\..\src\ballistica\base\support\repeater.cc" />
<ClInclude Include="..\..\src\ballistica\base\support\repeater.h" />
<ClCompile Include="..\..\src\ballistica\base\support\stdio_console.cc" />
<ClInclude Include="..\..\src\ballistica\base\support\stdio_console.h" />
<ClCompile Include="..\..\src\ballistica\base\support\stress_test.cc" />
<ClInclude Include="..\..\src\ballistica\base\support\stress_test.h" />
<ClCompile Include="..\..\src\ballistica\base\ui\dev_console.cc" />
<ClInclude Include="..\..\src\ballistica\base\ui\dev_console.h" />
<ClCompile Include="..\..\src\ballistica\base\ui\ui.cc" />
@ -441,6 +450,8 @@
<ClInclude Include="..\..\src\ballistica\classic\python\classic_python.h" />
<ClCompile Include="..\..\src\ballistica\classic\python\methods\python_methods_classic.cc" />
<ClInclude Include="..\..\src\ballistica\classic\python\methods\python_methods_classic.h" />
<ClCompile Include="..\..\src\ballistica\classic\support\stress_test.cc" />
<ClInclude Include="..\..\src\ballistica\classic\support\stress_test.h" />
<ClCompile Include="..\..\src\ballistica\classic\support\v1_account.cc" />
<ClInclude Include="..\..\src\ballistica\classic\support\v1_account.h" />
<ClCompile Include="..\..\src\ballistica\core\core.cc" />
@ -673,8 +684,10 @@
<ClCompile Include="..\..\src\ballistica\shared\generic\json.cc" />
<ClInclude Include="..\..\src\ballistica\shared\generic\json.h" />
<ClInclude Include="..\..\src\ballistica\shared\generic\lambda_runnable.h" />
<ClInclude Include="..\..\src\ballistica\shared\generic\native_stack_trace.h" />
<ClCompile Include="..\..\src\ballistica\shared\generic\runnable.cc" />
<ClInclude Include="..\..\src\ballistica\shared\generic\runnable.h" />
<ClInclude Include="..\..\src\ballistica\shared\generic\snapshot.h" />
<ClCompile Include="..\..\src\ballistica\shared\generic\timer_list.cc" />
<ClInclude Include="..\..\src\ballistica\shared\generic\timer_list.h" />
<ClCompile Include="..\..\src\ballistica\shared\generic\utf8.cc" />

View File

@ -469,6 +469,18 @@
<ClInclude Include="..\..\src\ballistica\base\graphics\support\frame_def.h">
<Filter>ballistica\base\graphics\support</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ballistica\base\graphics\support\graphics_client_context.cc">
<Filter>ballistica\base\graphics\support</Filter>
</ClCompile>
<ClInclude Include="..\..\src\ballistica\base\graphics\support\graphics_client_context.h">
<Filter>ballistica\base\graphics\support</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ballistica\base\graphics\support\graphics_settings.cc">
<Filter>ballistica\base\graphics\support</Filter>
</ClCompile>
<ClInclude Include="..\..\src\ballistica\base\graphics\support\graphics_settings.h">
<Filter>ballistica\base\graphics\support</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ballistica\base\graphics\support\net_graph.cc">
<Filter>ballistica\base\graphics\support</Filter>
</ClCompile>
@ -478,6 +490,12 @@
<ClInclude Include="..\..\src\ballistica\base\graphics\support\render_command_buffer.h">
<Filter>ballistica\base\graphics\support</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ballistica\base\graphics\support\screen_messages.cc">
<Filter>ballistica\base\graphics\support</Filter>
</ClCompile>
<ClInclude Include="..\..\src\ballistica\base\graphics\support\screen_messages.h">
<Filter>ballistica\base\graphics\support</Filter>
</ClInclude>
<ClInclude Include="..\..\src\ballistica\base\graphics\text\font_page_map_data.h">
<Filter>ballistica\base\graphics\text</Filter>
</ClInclude>
@ -706,6 +724,12 @@
<ClInclude Include="..\..\src\ballistica\base\support\app_timer.h">
<Filter>ballistica\base\support</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ballistica\base\support\base_build_switches.cc">
<Filter>ballistica\base\support</Filter>
</ClCompile>
<ClInclude Include="..\..\src\ballistica\base\support\base_build_switches.h">
<Filter>ballistica\base\support</Filter>
</ClInclude>
<ClInclude Include="..\..\src\ballistica\base\support\classic_soft.h">
<Filter>ballistica\base\support</Filter>
</ClInclude>
@ -715,6 +739,9 @@
<ClInclude Include="..\..\src\ballistica\base\support\context.h">
<Filter>ballistica\base\support</Filter>
</ClInclude>
<ClInclude Include="..\..\src\ballistica\base\support\display_timer.h">
<Filter>ballistica\base\support</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ballistica\base\support\huffman.cc">
<Filter>ballistica\base\support</Filter>
</ClCompile>
@ -724,18 +751,18 @@
<ClInclude Include="..\..\src\ballistica\base\support\plus_soft.h">
<Filter>ballistica\base\support</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ballistica\base\support\repeater.cc">
<Filter>ballistica\base\support</Filter>
</ClCompile>
<ClInclude Include="..\..\src\ballistica\base\support\repeater.h">
<Filter>ballistica\base\support</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ballistica\base\support\stdio_console.cc">
<Filter>ballistica\base\support</Filter>
</ClCompile>
<ClInclude Include="..\..\src\ballistica\base\support\stdio_console.h">
<Filter>ballistica\base\support</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ballistica\base\support\stress_test.cc">
<Filter>ballistica\base\support</Filter>
</ClCompile>
<ClInclude Include="..\..\src\ballistica\base\support\stress_test.h">
<Filter>ballistica\base\support</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ballistica\base\ui\dev_console.cc">
<Filter>ballistica\base\ui</Filter>
</ClCompile>
@ -772,6 +799,12 @@
<ClInclude Include="..\..\src\ballistica\classic\python\methods\python_methods_classic.h">
<Filter>ballistica\classic\python\methods</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ballistica\classic\support\stress_test.cc">
<Filter>ballistica\classic\support</Filter>
</ClCompile>
<ClInclude Include="..\..\src\ballistica\classic\support\stress_test.h">
<Filter>ballistica\classic\support</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ballistica\classic\support\v1_account.cc">
<Filter>ballistica\classic\support</Filter>
</ClCompile>
@ -1468,12 +1501,18 @@
<ClInclude Include="..\..\src\ballistica\shared\generic\lambda_runnable.h">
<Filter>ballistica\shared\generic</Filter>
</ClInclude>
<ClInclude Include="..\..\src\ballistica\shared\generic\native_stack_trace.h">
<Filter>ballistica\shared\generic</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ballistica\shared\generic\runnable.cc">
<Filter>ballistica\shared\generic</Filter>
</ClCompile>
<ClInclude Include="..\..\src\ballistica\shared\generic\runnable.h">
<Filter>ballistica\shared\generic</Filter>
</ClInclude>
<ClInclude Include="..\..\src\ballistica\shared\generic\snapshot.h">
<Filter>ballistica\shared\generic</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ballistica\shared\generic\timer_list.cc">
<Filter>ballistica\shared\generic</Filter>
</ClCompile>

View File

@ -10,11 +10,11 @@
"src/ballistica/base/graphics/texture/ktx.cc",
"src/ballistica/core/platform/android/android_gl3.h",
"src/ballistica/base/platform/apple/app_delegate.h",
"src/ballistica/base/platform/apple/scripting_bridge_music.h",
"src/ballistica/base/platform/apple/MacMusicApp.h",
"src/ballistica/base/platform/apple/MacMusicAppScriptingBridge.h",
"src/ballistica/core/platform/android/utf8/checked.h",
"src/ballistica/core/platform/android/utf8/unchecked.h",
"src/ballistica/core/platform/android/utf8/core.h",
"src/ballistica/base/platform/apple/sdl_main_mac.h",
"src/ballistica/base/platform/oculus/main_rift.cc",
"src/ballistica/core/platform/android/android_gl3.c"
],

View File

@ -53,47 +53,60 @@ ctx.src_omit_paths = {
'src/assets/workspace',
}
# Use this to 'carve out' directories or exact file paths which will be
# git-managed on dst. By default, spinoff will consider dirs containing
# the files it syncs from src as 'spinoff-managed'; it will set them as
# git-ignored and will complain if any files appear in them that it does
# not manage itself (to prevent accidentally doing work in such places).
# Note that adding a dir to src_write_paths does not prevent files
# within it from being synced by spinoff; it just means that each of
# those individual spinoff-managed files will have their own gitignore
# entry since there is no longer one covering the whole dir. So to keep
# things tidy, carve out the minimal set of exact file/dir paths that
# you need.
# Use this to 'carve out' files or directories which will be git-managed
# on dst.
#
# By default, spinoff will consider dirs containing the files it syncs
# from src as 'spinoff-managed'; it will set them as git-ignored and
# will complain if any files appear in them that it does not manage
# itself (to prevent accidentally doing work in such places). Note that
# adding a dir to src_write_paths does not prevent files within it from
# being synced by spinoff; it just means that each of those individual
# spinoff-managed files will have their own gitignore entry since there
# can't be a single one covering the whole dir. So to keep things tidy,
# carve out the minimal set of exact file/dir paths that you need.
ctx.src_write_paths = {
'tools/spinoff',
'config/spinoffconfig.py',
}
# Normally spinoff errors if it finds any files in its managed dirs that
# it did not put there. This is to prevent accidentally working in these
# parts of a dst project; since these sections are git-ignored, git
# itself won't raise any warnings in such cases and it would be easy to
# accidentally lose work otherwise.
# Use this to 'carve out' files or directories under spinoff managed
# dirs which will be completely ignored by spinoff (but *not* placed
# under git control).
#
# This list can be used to suppress spinoff's errors for specific
# locations. This is generally used to allow build output or other
# dynamically generated files to exist within spinoff-managed
# directories. It is possible to use src_write_paths for such purposes,
# but this has the side-effect of greatly complicating the dst project's
# gitignore list; selectively marking a few dirs as unchecked makes for
# a cleaner setup. Just be careful to not set excessively broad regions
# as unchecked; you don't want to mask actual useful error messages.
# Normally spinoff will error if it finds any files under its managed
# dirs that it did not put there. This is to prevent accidentally
# working in these parts of a dst project; since spinoff-controlled
# stuff is git-ignored, git itself won't raise any warnings in such
# cases and it would be easy to accidentally blow away changes if
# spinoff didn't raise a stink.
#
# This list is used to suppress raising of said stink for specific
# locations. This allows build output or other dynamically generated
# files to exist under spinoff-managed directories. It is also possible
# to use src_write_paths for such carve-outs, but that can have the
# negative side-effect of greatly complicating the dst project's
# .gitignore file. Selectively marking a few specific files or dirs as
# unchecked instead can keep things tidier and more understandable.
#
# Note that files and paths marked as unchecked cannot be the
# destination for synced files, as that would be ambiguous (We can
# either sync the file ourself or expect someone else to write it, but
# not both).
ctx.src_unchecked_paths = {
'src/ballistica/mgen',
'src/ballistica/*/mgen',
'src/assets/ba_data/python/*/_mgen',
'src/meta/*/mgen',
'ballisticakit-cmake/.clang-format',
'ballisticakit-android/BallisticaKit/src/cardboard/res',
'ballisticakit-windows/*/BallisticaKit.ico',
'ballisticakit-xcode/BallisticaKit Shared/Assets.xcassets',
'ballisticakit-android/BallisticaKit/src/*/res',
'ballisticakit-android/BallisticaKit/src/*/assets',
'ballisticakit-xcode/BallisticaKit Shared/Assets.xcassets/*/*.png',
'ballisticakit-xcode/BallisticaKit Shared/Assets.xcassets/*/*/*.png',
'ballisticakit-xcode/BallisticaKit Shared/Assets.xcassets/*/*/*/*/*.png',
'ballisticakit-xcode/BallisticaKit.xcodeproj/'
'project.xcworkspace/xcuserdata',
'ballisticakit-android/BallisticaKit/src/*/res/*/*.png',
'ballisticakit-android/BallisticaKit/src/*/assets/ballistica_files',
'ballisticakit-android/local.properties',
'ballisticakit-android/.gradle',
'ballisticakit-android/build',
@ -139,7 +152,6 @@ ctx.filter_dirs = {
'ballisticakit-cmake',
'ballisticakit-xcode/BallisticaKit.xcodeproj',
'ballisticakit-ios.xcodeproj',
'ballisticakit-mac.xcodeproj',
'config',
'src/assets/pdoc',
}
@ -182,6 +194,7 @@ ctx.filter_file_names = {
'.projectile',
'.editorconfig',
'ci.yml',
'cd.yml',
'LICENSE',
'cloudtool',
'bacloud',
@ -263,6 +276,7 @@ ctx.filter_file_extensions = {
'.frag',
'.vert',
'.xcsettings',
'.xcstrings',
'.filters',
}

View File

@ -1465,6 +1465,10 @@
"ba_data/textures/discordLogo.ktx",
"ba_data/textures/discordLogo.pvr",
"ba_data/textures/discordLogo_preview.png",
"ba_data/textures/discordServer.dds",
"ba_data/textures/discordServer.ktx",
"ba_data/textures/discordServer.pvr",
"ba_data/textures/discordServer_preview.png",
"ba_data/textures/doomShroomBGColor.dds",
"ba_data/textures/doomShroomBGColor.ktx",
"ba_data/textures/doomShroomBGColor.pvr",

View File

@ -20,7 +20,6 @@
"ba_data/python/babase/__pycache__/_error.cpython-311.opt-1.pyc",
"ba_data/python/babase/__pycache__/_general.cpython-311.opt-1.pyc",
"ba_data/python/babase/__pycache__/_hooks.cpython-311.opt-1.pyc",
"ba_data/python/babase/__pycache__/_keyboard.cpython-311.opt-1.pyc",
"ba_data/python/babase/__pycache__/_language.cpython-311.opt-1.pyc",
"ba_data/python/babase/__pycache__/_login.cpython-311.opt-1.pyc",
"ba_data/python/babase/__pycache__/_math.cpython-311.opt-1.pyc",
@ -50,7 +49,6 @@
"ba_data/python/babase/_error.py",
"ba_data/python/babase/_general.py",
"ba_data/python/babase/_hooks.py",
"ba_data/python/babase/_keyboard.py",
"ba_data/python/babase/_language.py",
"ba_data/python/babase/_login.py",
"ba_data/python/babase/_math.py",
@ -152,6 +150,7 @@
"ba_data/python/bascenev1/__pycache__/_messages.cpython-311.opt-1.pyc",
"ba_data/python/bascenev1/__pycache__/_multiteamsession.cpython-311.opt-1.pyc",
"ba_data/python/bascenev1/__pycache__/_music.cpython-311.opt-1.pyc",
"ba_data/python/bascenev1/__pycache__/_net.cpython-311.opt-1.pyc",
"ba_data/python/bascenev1/__pycache__/_nodeactor.cpython-311.opt-1.pyc",
"ba_data/python/bascenev1/__pycache__/_player.cpython-311.opt-1.pyc",
"ba_data/python/bascenev1/__pycache__/_playlist.cpython-311.opt-1.pyc",
@ -186,6 +185,7 @@
"ba_data/python/bascenev1/_messages.py",
"ba_data/python/bascenev1/_multiteamsession.py",
"ba_data/python/bascenev1/_music.py",
"ba_data/python/bascenev1/_net.py",
"ba_data/python/bascenev1/_nodeactor.py",
"ba_data/python/bascenev1/_player.py",
"ba_data/python/bascenev1/_playlist.py",
@ -352,10 +352,12 @@
"ba_data/python/bauiv1/__init__.py",
"ba_data/python/bauiv1/__pycache__/__init__.cpython-311.opt-1.pyc",
"ba_data/python/bauiv1/__pycache__/_hooks.cpython-311.opt-1.pyc",
"ba_data/python/bauiv1/__pycache__/_keyboard.cpython-311.opt-1.pyc",
"ba_data/python/bauiv1/__pycache__/_subsystem.cpython-311.opt-1.pyc",
"ba_data/python/bauiv1/__pycache__/_uitypes.cpython-311.opt-1.pyc",
"ba_data/python/bauiv1/__pycache__/onscreenkeyboard.cpython-311.opt-1.pyc",
"ba_data/python/bauiv1/_hooks.py",
"ba_data/python/bauiv1/_keyboard.py",
"ba_data/python/bauiv1/_subsystem.py",
"ba_data/python/bauiv1/_uitypes.py",
"ba_data/python/bauiv1/onscreenkeyboard.py",
@ -366,11 +368,11 @@
"ba_data/python/bauiv1lib/__pycache__/characterpicker.cpython-311.opt-1.pyc",
"ba_data/python/bauiv1lib/__pycache__/colorpicker.cpython-311.opt-1.pyc",
"ba_data/python/bauiv1lib/__pycache__/config.cpython-311.opt-1.pyc",
"ba_data/python/bauiv1lib/__pycache__/configerror.cpython-311.opt-1.pyc",
"ba_data/python/bauiv1lib/__pycache__/confirm.cpython-311.opt-1.pyc",
"ba_data/python/bauiv1lib/__pycache__/continues.cpython-311.opt-1.pyc",
"ba_data/python/bauiv1lib/__pycache__/creditslist.cpython-311.opt-1.pyc",
"ba_data/python/bauiv1lib/__pycache__/debug.cpython-311.opt-1.pyc",
"ba_data/python/bauiv1lib/__pycache__/discord.cpython-311.opt-1.pyc",
"ba_data/python/bauiv1lib/__pycache__/feedback.cpython-311.opt-1.pyc",
"ba_data/python/bauiv1lib/__pycache__/fileselector.cpython-311.opt-1.pyc",
"ba_data/python/bauiv1lib/__pycache__/getcurrency.cpython-311.opt-1.pyc",
@ -417,7 +419,6 @@
"ba_data/python/bauiv1lib/characterpicker.py",
"ba_data/python/bauiv1lib/colorpicker.py",
"ba_data/python/bauiv1lib/config.py",
"ba_data/python/bauiv1lib/configerror.py",
"ba_data/python/bauiv1lib/confirm.py",
"ba_data/python/bauiv1lib/continues.py",
"ba_data/python/bauiv1lib/coop/__init__.py",
@ -432,6 +433,7 @@
"ba_data/python/bauiv1lib/coop/tournamentbutton.py",
"ba_data/python/bauiv1lib/creditslist.py",
"ba_data/python/bauiv1lib/debug.py",
"ba_data/python/bauiv1lib/discord.py",
"ba_data/python/bauiv1lib/feedback.py",
"ba_data/python/bauiv1lib/fileselector.py",
"ba_data/python/bauiv1lib/gather/__init__.py",

View File

@ -178,7 +178,6 @@ SCRIPT_TARGETS_PY_PUBLIC = \
$(BUILD_DIR)/ba_data/python/babase/_error.py \
$(BUILD_DIR)/ba_data/python/babase/_general.py \
$(BUILD_DIR)/ba_data/python/babase/_hooks.py \
$(BUILD_DIR)/ba_data/python/babase/_keyboard.py \
$(BUILD_DIR)/ba_data/python/babase/_language.py \
$(BUILD_DIR)/ba_data/python/babase/_login.py \
$(BUILD_DIR)/ba_data/python/babase/_math.py \
@ -237,6 +236,7 @@ SCRIPT_TARGETS_PY_PUBLIC = \
$(BUILD_DIR)/ba_data/python/bascenev1/_messages.py \
$(BUILD_DIR)/ba_data/python/bascenev1/_multiteamsession.py \
$(BUILD_DIR)/ba_data/python/bascenev1/_music.py \
$(BUILD_DIR)/ba_data/python/bascenev1/_net.py \
$(BUILD_DIR)/ba_data/python/bascenev1/_nodeactor.py \
$(BUILD_DIR)/ba_data/python/bascenev1/_player.py \
$(BUILD_DIR)/ba_data/python/bascenev1/_playlist.py \
@ -326,6 +326,7 @@ SCRIPT_TARGETS_PY_PUBLIC = \
$(BUILD_DIR)/ba_data/python/batemplatefs/_subsystem.py \
$(BUILD_DIR)/ba_data/python/bauiv1/__init__.py \
$(BUILD_DIR)/ba_data/python/bauiv1/_hooks.py \
$(BUILD_DIR)/ba_data/python/bauiv1/_keyboard.py \
$(BUILD_DIR)/ba_data/python/bauiv1/_subsystem.py \
$(BUILD_DIR)/ba_data/python/bauiv1/_uitypes.py \
$(BUILD_DIR)/ba_data/python/bauiv1/onscreenkeyboard.py \
@ -341,7 +342,6 @@ SCRIPT_TARGETS_PY_PUBLIC = \
$(BUILD_DIR)/ba_data/python/bauiv1lib/characterpicker.py \
$(BUILD_DIR)/ba_data/python/bauiv1lib/colorpicker.py \
$(BUILD_DIR)/ba_data/python/bauiv1lib/config.py \
$(BUILD_DIR)/ba_data/python/bauiv1lib/configerror.py \
$(BUILD_DIR)/ba_data/python/bauiv1lib/confirm.py \
$(BUILD_DIR)/ba_data/python/bauiv1lib/continues.py \
$(BUILD_DIR)/ba_data/python/bauiv1lib/coop/__init__.py \
@ -351,6 +351,7 @@ SCRIPT_TARGETS_PY_PUBLIC = \
$(BUILD_DIR)/ba_data/python/bauiv1lib/coop/tournamentbutton.py \
$(BUILD_DIR)/ba_data/python/bauiv1lib/creditslist.py \
$(BUILD_DIR)/ba_data/python/bauiv1lib/debug.py \
$(BUILD_DIR)/ba_data/python/bauiv1lib/discord.py \
$(BUILD_DIR)/ba_data/python/bauiv1lib/feedback.py \
$(BUILD_DIR)/ba_data/python/bauiv1lib/fileselector.py \
$(BUILD_DIR)/ba_data/python/bauiv1lib/gather/__init__.py \
@ -452,7 +453,6 @@ SCRIPT_TARGETS_PYC_PUBLIC = \
$(BUILD_DIR)/ba_data/python/babase/__pycache__/_error.cpython-311.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/babase/__pycache__/_general.cpython-311.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/babase/__pycache__/_hooks.cpython-311.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/babase/__pycache__/_keyboard.cpython-311.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/babase/__pycache__/_language.cpython-311.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/babase/__pycache__/_login.cpython-311.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/babase/__pycache__/_math.cpython-311.opt-1.pyc \
@ -511,6 +511,7 @@ SCRIPT_TARGETS_PYC_PUBLIC = \
$(BUILD_DIR)/ba_data/python/bascenev1/__pycache__/_messages.cpython-311.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/bascenev1/__pycache__/_multiteamsession.cpython-311.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/bascenev1/__pycache__/_music.cpython-311.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/bascenev1/__pycache__/_net.cpython-311.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/bascenev1/__pycache__/_nodeactor.cpython-311.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/bascenev1/__pycache__/_player.cpython-311.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/bascenev1/__pycache__/_playlist.cpython-311.opt-1.pyc \
@ -600,6 +601,7 @@ SCRIPT_TARGETS_PYC_PUBLIC = \
$(BUILD_DIR)/ba_data/python/batemplatefs/__pycache__/_subsystem.cpython-311.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/bauiv1/__pycache__/__init__.cpython-311.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/bauiv1/__pycache__/_hooks.cpython-311.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/bauiv1/__pycache__/_keyboard.cpython-311.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/bauiv1/__pycache__/_subsystem.cpython-311.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/bauiv1/__pycache__/_uitypes.cpython-311.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/bauiv1/__pycache__/onscreenkeyboard.cpython-311.opt-1.pyc \
@ -615,7 +617,6 @@ SCRIPT_TARGETS_PYC_PUBLIC = \
$(BUILD_DIR)/ba_data/python/bauiv1lib/__pycache__/characterpicker.cpython-311.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/bauiv1lib/__pycache__/colorpicker.cpython-311.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/bauiv1lib/__pycache__/config.cpython-311.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/bauiv1lib/__pycache__/configerror.cpython-311.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/bauiv1lib/__pycache__/confirm.cpython-311.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/bauiv1lib/__pycache__/continues.cpython-311.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/bauiv1lib/coop/__pycache__/__init__.cpython-311.opt-1.pyc \
@ -625,6 +626,7 @@ SCRIPT_TARGETS_PYC_PUBLIC = \
$(BUILD_DIR)/ba_data/python/bauiv1lib/coop/__pycache__/tournamentbutton.cpython-311.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/bauiv1lib/__pycache__/creditslist.cpython-311.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/bauiv1lib/__pycache__/debug.cpython-311.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/bauiv1lib/__pycache__/discord.cpython-311.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/bauiv1lib/__pycache__/feedback.cpython-311.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/bauiv1lib/__pycache__/fileselector.cpython-311.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/bauiv1lib/gather/__pycache__/__init__.cpython-311.opt-1.pyc \
@ -5710,6 +5712,7 @@ TEX2D_DDS_TARGETS = \
$(BUILD_DIR)/ba_data/textures/cyborgIcon.dds \
$(BUILD_DIR)/ba_data/textures/cyborgIconColorMask.dds \
$(BUILD_DIR)/ba_data/textures/discordLogo.dds \
$(BUILD_DIR)/ba_data/textures/discordServer.dds \
$(BUILD_DIR)/ba_data/textures/doomShroomBGColor.dds \
$(BUILD_DIR)/ba_data/textures/doomShroomLevelColor.dds \
$(BUILD_DIR)/ba_data/textures/doomShroomPreview.dds \
@ -6113,6 +6116,7 @@ TEX2D_PVR_TARGETS = \
$(BUILD_DIR)/ba_data/textures/cyborgIcon.pvr \
$(BUILD_DIR)/ba_data/textures/cyborgIconColorMask.pvr \
$(BUILD_DIR)/ba_data/textures/discordLogo.pvr \
$(BUILD_DIR)/ba_data/textures/discordServer.pvr \
$(BUILD_DIR)/ba_data/textures/doomShroomBGColor.pvr \
$(BUILD_DIR)/ba_data/textures/doomShroomLevelColor.pvr \
$(BUILD_DIR)/ba_data/textures/doomShroomPreview.pvr \
@ -6516,6 +6520,7 @@ TEX2D_KTX_TARGETS = \
$(BUILD_DIR)/ba_data/textures/cyborgIcon.ktx \
$(BUILD_DIR)/ba_data/textures/cyborgIconColorMask.ktx \
$(BUILD_DIR)/ba_data/textures/discordLogo.ktx \
$(BUILD_DIR)/ba_data/textures/discordServer.ktx \
$(BUILD_DIR)/ba_data/textures/doomShroomBGColor.ktx \
$(BUILD_DIR)/ba_data/textures/doomShroomLevelColor.ktx \
$(BUILD_DIR)/ba_data/textures/doomShroomPreview.ktx \
@ -6919,6 +6924,7 @@ TEX2D_PREVIEW_PNG_TARGETS = \
$(BUILD_DIR)/ba_data/textures/cyborgIconColorMask_preview.png \
$(BUILD_DIR)/ba_data/textures/cyborgIcon_preview.png \
$(BUILD_DIR)/ba_data/textures/discordLogo_preview.png \
$(BUILD_DIR)/ba_data/textures/discordServer_preview.png \
$(BUILD_DIR)/ba_data/textures/doomShroomBGColor_preview.png \
$(BUILD_DIR)/ba_data/textures/doomShroomLevelColor_preview.png \
$(BUILD_DIR)/ba_data/textures/doomShroomPreview_preview.png \

View File

@ -27,7 +27,10 @@ from _babase import (
apptime,
apptimer,
AppTimer,
can_toggle_fullscreen,
fullscreen_control_available,
fullscreen_control_get,
fullscreen_control_key_shortcut,
fullscreen_control_set,
charstr,
clipboard_get_text,
clipboard_has_text,
@ -57,11 +60,10 @@ from _babase import (
have_permission,
in_logic_thread,
increment_analytics_count,
invoke_main_menu,
is_os_playing_music,
is_running_on_fire_tv,
is_xcode_build,
lock_all_input,
mac_music_app_get_library_source,
mac_music_app_get_playlists,
mac_music_app_get_volume,
mac_music_app_init,
@ -72,7 +74,10 @@ from _babase import (
music_player_set_volume,
music_player_shutdown,
music_player_stop,
native_review_request,
native_review_request_supported,
native_stack_trace,
open_file_externally,
print_load_info,
pushcall,
quit,
@ -82,7 +87,6 @@ from _babase import (
screenmessage,
set_analytics_screen,
set_low_level_config_value,
set_stress_testing,
set_thread_name,
set_ui_input_device,
show_progress_bar,
@ -151,9 +155,8 @@ from babase._general import (
getclass,
get_type_name,
)
from babase._keyboard import Keyboard
from babase._language import Lstr, LanguageSubsystem
from babase._login import LoginAdapter
from babase._login import LoginAdapter, LoginInfo
# noinspection PyProtectedMember
# (PyCharm inspection bug?)
@ -200,7 +203,10 @@ __all__ = [
'apptimer',
'AppTimer',
'Call',
'can_toggle_fullscreen',
'fullscreen_control_available',
'fullscreen_control_get',
'fullscreen_control_key_shortcut',
'fullscreen_control_set',
'charstr',
'clipboard_get_text',
'clipboard_has_text',
@ -249,18 +255,17 @@ __all__ = [
'increment_analytics_count',
'InputDeviceNotFoundError',
'InputType',
'invoke_main_menu',
'is_browser_likely_available',
'is_browser_likely_available',
'is_os_playing_music',
'is_point_in_box',
'is_running_on_fire_tv',
'is_xcode_build',
'Keyboard',
'LanguageSubsystem',
'lock_all_input',
'LoginAdapter',
'LoginInfo',
'Lstr',
'mac_music_app_get_library_source',
'mac_music_app_get_playlists',
'mac_music_app_get_volume',
'mac_music_app_init',
@ -273,10 +278,13 @@ __all__ = [
'music_player_set_volume',
'music_player_shutdown',
'music_player_stop',
'native_review_request',
'native_review_request_supported',
'native_stack_trace',
'NodeNotFoundError',
'normalized_color',
'NotFoundError',
'open_file_externally',
'Permission',
'PlayerNotFoundError',
'Plugin',
@ -297,7 +305,6 @@ __all__ = [
'SessionTeamNotFoundError',
'set_analytics_screen',
'set_low_level_config_value',
'set_stress_testing',
'set_thread_name',
'set_ui_input_device',
'show_progress_bar',

View File

@ -6,7 +6,7 @@ from __future__ import annotations
import hashlib
import logging
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, assert_never
from efro.call import tpartial
from efro.error import CommunicationError
@ -16,7 +16,7 @@ import _babase
if TYPE_CHECKING:
from typing import Any
from babase._login import LoginAdapter
from babase._login import LoginAdapter, LoginInfo
DEBUG_LOG = False
@ -27,10 +27,12 @@ class AccountV2Subsystem:
Category: **App Classes**
Access the single shared instance of this class at 'ba.app.accounts'.
Access the single shared instance of this class at 'ba.app.plus.accounts'.
"""
def __init__(self) -> None:
from babase._login import LoginAdapterGPGS, LoginAdapterGameCenter
# Whether or not everything related to an initial login
# (or lack thereof) has completed. This includes things like
# workspace syncing. Completion of this is what flips the app
@ -45,16 +47,13 @@ class AccountV2Subsystem:
self._implicit_state_changed = False
self._can_do_auto_sign_in = True
if _babase.app.classic is None:
raise RuntimeError('Needs updating for no-classic case.')
if (
_babase.app.classic.platform == 'android'
and _babase.app.classic.subplatform == 'google'
):
from babase._login import LoginAdapterGPGS
self.login_adapters[LoginType.GPGS] = LoginAdapterGPGS()
adapter: LoginAdapter
if _babase.using_google_play_game_services():
adapter = LoginAdapterGPGS()
self.login_adapters[adapter.login_type] = adapter
if _babase.using_game_center():
adapter = LoginAdapterGameCenter()
self.login_adapters[adapter.login_type] = adapter
def on_app_loading(self) -> None:
"""Should be called at standard on_app_loading time."""
@ -62,10 +61,6 @@ class AccountV2Subsystem:
for adapter in self.login_adapters.values():
adapter.on_app_loading()
def set_primary_credentials(self, credentials: str | None) -> None:
"""Set credentials for the primary app account."""
raise NotImplementedError('This should be overridden.')
def have_primary_credentials(self) -> bool:
"""Are credentials currently set for the primary app account?
@ -80,10 +75,6 @@ class AccountV2Subsystem:
"""The primary account for the app, or None if not logged in."""
return self.do_get_primary()
def do_get_primary(self) -> AccountV2Handle | None:
"""Internal - should be overridden by subclass."""
return None
def on_primary_account_changed(
self, account: AccountV2Handle | None
) -> None:
@ -142,6 +133,8 @@ class AccountV2Subsystem:
"""An implicit sign-in happened (called by native layer)."""
from babase._login import LoginAdapter
assert _babase.in_logic_thread()
with _babase.ContextRef.empty():
self.login_adapters[login_type].set_implicit_login_state(
LoginAdapter.ImplicitLoginState(
@ -151,6 +144,7 @@ class AccountV2Subsystem:
def on_implicit_sign_out(self, login_type: LoginType) -> None:
"""An implicit sign-out happened (called by native layer)."""
assert _babase.in_logic_thread()
with _babase.ContextRef.empty():
self.login_adapters[login_type].set_implicit_login_state(None)
@ -192,9 +186,10 @@ class AccountV2Subsystem:
cfgkey = 'ImplicitLoginStates'
cfgdict = _babase.app.config.setdefault(cfgkey, {})
# Store which (if any) adapter is currently implicitly signed in.
# Making the assumption there will only ever be one implicit
# adapter at a time; may need to update this if that changes.
# Store which (if any) adapter is currently implicitly signed
# in. Making the assumption there will only ever be one implicit
# adapter at a time; may need to revisit this logic if that
# changes.
prev_state = cfgdict.get(login_type.value)
if state is None:
self._implicit_signed_in_adapter = None
@ -205,18 +200,26 @@ class AccountV2Subsystem:
state.login_id
)
# Special case: if the user is already signed in but not with
# this implicit login, we may want to let them know that the
# 'Welcome back FOO' they likely just saw is not actually
# accurate.
# Special case: if the user is already signed in but not
# with this implicit login, let them know that the 'Welcome
# back FOO' they likely just saw is not actually accurate.
if (
self.primary is not None
and not self.login_adapters[login_type].is_back_end_active()
):
service_str: Lstr | None
if login_type is LoginType.GPGS:
service_str = Lstr(resource='googlePlayText')
else:
elif login_type is LoginType.GAME_CENTER:
# Note: Apparently Game Center is just called 'Game
# Center' in all languages. Can revisit if not true.
# https://developer.apple.com/forums/thread/725779
service_str = Lstr(value='Game Center')
elif login_type is LoginType.EMAIL:
# Not possible; just here for exhaustive coverage.
service_str = None
else:
assert_never(login_type)
if service_str is not None:
_babase.apptimer(
2.0,
@ -259,6 +262,14 @@ class AccountV2Subsystem:
# We may want to auto-sign-in based on this new state.
self._update_auto_sign_in()
def do_get_primary(self) -> AccountV2Handle | None:
"""Internal - should be overridden by subclass."""
raise NotImplementedError('This should be overridden.')
def set_primary_credentials(self, credentials: str | None) -> None:
"""Set credentials for the primary app account."""
raise NotImplementedError('This should be overridden.')
def _update_auto_sign_in(self) -> None:
plus = _babase.app.plus
assert plus is not None
@ -266,7 +277,7 @@ class AccountV2Subsystem:
# If implicit state has changed, try to respond.
if self._implicit_state_changed:
if self._implicit_signed_in_adapter is None:
# If implicit back-end is signed out, follow suit
# If implicit back-end has signed out, we follow suit
# immediately; no need to wait for network connectivity.
if DEBUG_LOG:
logging.debug(
@ -286,9 +297,8 @@ class AccountV2Subsystem:
# Consider this an 'explicit' sign in because the
# implicit-login state change presumably was triggered
# by some user action (signing in, signing out, or
# switching accounts via the back-end).
# NOTE: should test case where we don't have
# connectivity here.
# switching accounts via the back-end). NOTE: should
# test case where we don't have connectivity here.
if plus.cloud.is_connected():
if DEBUG_LOG:
logging.debug(
@ -419,14 +429,11 @@ class AccountV2Handle:
used with some operations such as cloud messaging.
"""
def __init__(self) -> None:
self.tag = '?'
self.workspacename: str | None = None
self.workspaceid: str | None = None
# Login types and their display-names associated with this account.
self.logins: dict[LoginType, str] = {}
accountid: str
tag: str
workspacename: str | None
workspaceid: str | None
logins: dict[LoginType, LoginInfo]
def __enter__(self) -> None:
"""Support for "with" statement.

View File

@ -56,6 +56,8 @@ class App:
# pylint: disable=too-many-public-methods
# A few things defined as non-optional values but not actually
# available until the app starts.
plugins: PluginSubsystem
lang: LanguageSubsystem
health_monitor: AppHealthMonitor
@ -92,7 +94,7 @@ class App:
# Used on platforms such as mobile where the app basically needs
# to shut down while backgrounded. In this state, all event
# loops are suspended and all graphics and audio should cease
# loops are suspended and all graphics and audio must cease
# completely. Be aware that the suspended state can be entered
# from any other state including NATIVE_BOOTSTRAPPING and
# SHUTTING_DOWN.
@ -149,9 +151,9 @@ class App:
def __init__(self) -> None:
"""(internal)
Do not instantiate this class; access the single shared instance
of it as 'app' which is available in various Ballistica
feature-set modules such as babase.
Do not instantiate this class. You can access the single shared
instance of it through various high level packages: 'babase.app',
'bascenev1.app', 'bauiv1.app', etc.
"""
# Hack for docs-generation: we can be imported with dummy modules
@ -182,7 +184,6 @@ class App:
# foregrounded; can be a simple way to determine if network data
# should be refreshed/etc.
self.fg_state = 0
self.config_file_healthy: bool = False
self._subsystems: list[AppSubsystem] = []
self._native_bootstrapping_completed = False
@ -208,7 +209,8 @@ class App:
self._shutdown_task: asyncio.Task[None] | None = None
self._shutdown_tasks: list[Coroutine[None, None, None]] = [
self._wait_for_shutdown_suppressions(),
self._fade_for_shutdown(),
self._fade_and_shutdown_graphics(),
self._fade_and_shutdown_audio(),
]
self._pool_thread_count = 0
@ -227,6 +229,15 @@ class App:
self.lang = LanguageSubsystem()
self.plugins = PluginSubsystem()
@property
def active(self) -> bool:
"""Whether the app is currently front and center.
This will be False when the app is hidden, other activities
are covering it, etc. (depending on the platform).
"""
return _babase.app_is_active()
@property
def aioloop(self) -> asyncio.AbstractEventLoop:
"""The logic thread's asyncio event loop.
@ -426,11 +437,17 @@ class App:
self._native_shutdown_complete_called = True
self._update_state()
def on_native_active_changed(self) -> None:
"""Called by the native layer when the app active state changes."""
assert _babase.in_logic_thread()
if self._mode is not None:
self._mode.on_app_active_changed()
def read_config(self) -> None:
"""(internal)"""
from babase._appconfig import read_app_config
self._config, self.config_file_healthy = read_app_config()
self._config = read_app_config()
def handle_deep_link(self, url: str) -> None:
"""Handle a deep link URL."""
@ -508,7 +525,7 @@ class App:
except Exception:
logging.exception('Error setting app intent to %s.', intent)
_babase.pushcall(
tpartial(self._apply_intent_error, intent),
tpartial(self._display_set_intent_error, intent),
from_other_thread=True,
)
@ -553,10 +570,11 @@ class App:
'Error handling intent %s in app-mode %s.', intent, mode
)
def _apply_intent_error(self, intent: AppIntent) -> None:
def _display_set_intent_error(self, intent: AppIntent) -> None:
"""Show the *user* something went wrong setting an intent."""
from babase._language import Lstr
del intent # Unused.
del intent
_babase.screenmessage(Lstr(resource='errorText'), color=(1, 0, 0))
_babase.getsimplesound('error').play()
@ -579,19 +597,6 @@ class App:
self._aioloop = _asyncio.setup_asyncio()
self.health_monitor = AppHealthMonitor()
# Only proceed if our config file is healthy so we don't
# overwrite a broken one or whatnot and wipe out data.
if not self.config_file_healthy:
if self.classic is not None:
handled = self.classic.show_config_error_window()
if handled:
return
# For now on other systems we just overwrite the bum config.
# At this point settings are already set; lets just commit
# them to disk.
_appconfig.commit_app_config(force=True)
# __FEATURESET_APP_SUBSYSTEM_CREATE_BEGIN__
# This section generated by batools.appmodule; do not edit.
@ -795,6 +800,7 @@ class App:
async def _shutdown(self) -> None:
import asyncio
_babase.lock_all_input()
try:
async with asyncio.TaskGroup() as task_group:
for task_coro in self._shutdown_tasks:
@ -890,23 +896,45 @@ class App:
import asyncio
# Spin and wait for anything blocking shutdown to complete.
starttime = _babase.apptime()
_babase.lifecyclelog('shutdown-suppress wait begin')
while _babase.shutdown_suppress_count() > 0:
await asyncio.sleep(0.001)
_babase.lifecyclelog('shutdown-suppress wait end')
duration = _babase.apptime() - starttime
if duration > 1.0:
logging.warning(
'Shutdown-suppressions lasted longer than ideal '
'(%.2f seconds).',
duration,
)
async def _fade_for_shutdown(self) -> None:
async def _fade_and_shutdown_graphics(self) -> None:
import asyncio
# Kick off a fade, block input, and wait for a short bit.
# Ideally most shutdown activity completes during the fade so
# there's no tangible wait.
_babase.lifecyclelog('fade-for-shutdown begin')
# Kick off a short fade and give it time to complete.
_babase.lifecyclelog('fade-and-shutdown-graphics begin')
_babase.fade_screen(False, time=0.15)
_babase.lock_all_input()
# _babase.getsimplesound('swish2').play()
await asyncio.sleep(0.15)
_babase.lifecyclelog('fade-for-shutdown end')
# Now tell the graphics system to go down and wait until
# it has done so.
_babase.graphics_shutdown_begin()
while not _babase.graphics_shutdown_is_complete():
await asyncio.sleep(0.01)
_babase.lifecyclelog('fade-and-shutdown-graphics end')
async def _fade_and_shutdown_audio(self) -> None:
import asyncio
# Tell the audio system to go down and give it a bit of
# time to do so gracefully.
_babase.lifecyclelog('fade-and-shutdown-audio begin')
_babase.audio_shutdown_begin()
await asyncio.sleep(0.15)
while not _babase.audio_shutdown_is_complete():
await asyncio.sleep(0.01)
_babase.lifecyclelog('fade-and-shutdown-audio end')
def _threadpool_no_wait_done(self, fut: Future) -> None:
try:

View File

@ -101,15 +101,13 @@ class AppConfig(dict):
self.commit()
def read_app_config() -> tuple[AppConfig, bool]:
def read_app_config() -> AppConfig:
"""Read the app config."""
import os
import json
config_file_healthy = False
# NOTE: it is assumed that this only gets called once and the
# config object will not change from here on out
# NOTE: it is assumed that this only gets called once and the config
# object will not change from here on out
config_file_path = _babase.app.env.config_file_path
config_contents = ''
try:
@ -119,20 +117,16 @@ def read_app_config() -> tuple[AppConfig, bool]:
config = AppConfig(json.loads(config_contents))
else:
config = AppConfig()
config_file_healthy = True
except Exception:
logging.exception(
"Error reading config file at time %.3f: '%s'.",
"Error reading config file '%s' at time %.3f.\n"
"Backing up broken config to'%s.broken'.",
config_file_path,
_babase.apptime(),
config_file_path,
)
# Whenever this happens lets back up the broken one just in case it
# gets overwritten accidentally.
logging.info(
"Backing up current config file to '%s.broken'", config_file_path
)
try:
import shutil
@ -141,23 +135,10 @@ def read_app_config() -> tuple[AppConfig, bool]:
logging.exception('Error copying broken config.')
config = AppConfig()
# Now attempt to read one of our 'prev' backup copies.
prev_path = config_file_path + '.prev'
try:
if os.path.exists(prev_path):
with open(prev_path, encoding='utf-8') as infile:
config_contents = infile.read()
config = AppConfig(json.loads(config_contents))
else:
config = AppConfig()
config_file_healthy = True
logging.info('Successfully read backup config.')
except Exception:
logging.exception('Error reading prev backup config.')
return config, config_file_healthy
return config
def commit_app_config(force: bool = False) -> None:
def commit_app_config() -> None:
"""Commit the config to persistent storage.
Category: **General Utility Functions**
@ -167,10 +148,4 @@ def commit_app_config(force: bool = False) -> None:
plus = _babase.app.plus
assert plus is not None
if not _babase.app.config_file_healthy and not force:
logging.warning(
'Current config file is broken; '
'skipping write to avoid losing settings.'
)
return
plus.mark_config_dirty()

View File

@ -31,6 +31,7 @@ class AppMode:
AppExperience associated with the AppMode must be supported by
the current app and runtime environment.
"""
# FIXME: check AppExperience.
return cls._supports_intent(intent)
@classmethod
@ -51,3 +52,10 @@ class AppMode:
def on_deactivate(self) -> None:
"""Called when the mode is being deactivated."""
def on_app_active_changed(self) -> None:
"""Called when babase.app.active changes.
The app-mode may want to take action such as pausing a running
game in such cases.
"""

View File

@ -325,7 +325,7 @@ def dump_app_state(
)
def log_dumped_app_state() -> None:
def log_dumped_app_state(from_previous_run: bool = False) -> None:
"""If an app-state dump exists, log it and clear it. No-op otherwise."""
try:
@ -352,8 +352,13 @@ def log_dumped_app_state() -> None:
metadata = dataclass_from_json(DumpedAppStateMetadata, appstatedata)
header = (
'Found app state dump from previous app run'
if from_previous_run
else 'App state dump'
)
out += (
f'App state dump:\nReason: {metadata.reason}\n'
f'{header}:\nReason: {metadata.reason}\n'
f'Time: {metadata.app_time:.2f}'
)
tbpath = os.path.join(
@ -383,7 +388,7 @@ class AppHealthMonitor(AppSubsystem):
def on_app_loading(self) -> None:
# If any traceback dumps happened last run, log and clear them.
log_dumped_app_state()
log_dumped_app_state(from_previous_run=True)
def _app_monitor_thread_main(self) -> None:
_babase.set_thread_name('ballistica app-monitor')

View File

@ -26,6 +26,11 @@ DEBUG_LOG = False
class CloudSubsystem(AppSubsystem):
"""Manages communication with cloud components."""
@property
def connected(self) -> bool:
"""Property equivalent of CloudSubsystem.is_connected()."""
return self.is_connected()
def is_connected(self) -> bool:
"""Return whether a connection to the cloud is present.

View File

@ -3,6 +3,7 @@
"""Dev-Console functionality."""
from __future__ import annotations
import os
from typing import TYPE_CHECKING
from dataclasses import dataclass
import logging
@ -154,9 +155,10 @@ class DevConsoleSubsystem:
# All tabs in the dev-console. Add your own stuff here via
# plugins or whatnot.
self.tabs: list[DevConsoleTabEntry] = [
DevConsoleTabEntry('Python', DevConsoleTabPython),
DevConsoleTabEntry('Test', DevConsoleTabTest),
DevConsoleTabEntry('Python', DevConsoleTabPython)
]
if os.environ.get('BA_DEV_CONSOLE_TEST_TAB', '0') == '1':
self.tabs.append(DevConsoleTabEntry('Test', DevConsoleTabTest))
self.is_refreshing = False
def do_refresh_tab(self, tabname: str) -> None:

View File

@ -185,10 +185,8 @@ def _feed_logs_to_babase(log_handler: LogHandler) -> None:
def _on_log(entry: LogEntry) -> None:
# Forward this along to the engine to display in the in-app
# console, in the Android log, etc.
_babase.display_log(
name=entry.name,
level=entry.level.name,
message=entry.message,
_babase.emit_log(
name=entry.name, level=entry.level.name, message=entry.message
)
# We also want to feed some logs to the old v1-cloud-log system.

View File

@ -33,18 +33,47 @@ def reset_to_main_menu() -> None:
logging.warning('reset_to_main_menu: no-op due to classic not present.')
def set_config_fullscreen_on() -> None:
def get_v2_account_id() -> str | None:
"""Return the current V2 account id if signed in, or None if not."""
try:
plus = _babase.app.plus
if plus is not None:
account = plus.accounts.primary
if account is not None:
accountid = account.accountid
# (Avoids mypy complaints when plus is not present)
assert isinstance(accountid, (str, type(None)))
return accountid
return None
except Exception:
logging.exception('Error fetching v2 account id.')
return None
def store_config_fullscreen_on() -> None:
"""The OS has changed our fullscreen state and we should take note."""
_babase.app.config['Fullscreen'] = True
_babase.app.config.commit()
def set_config_fullscreen_off() -> None:
def store_config_fullscreen_off() -> None:
"""The OS has changed our fullscreen state and we should take note."""
_babase.app.config['Fullscreen'] = False
_babase.app.config.commit()
def set_config_fullscreen_on() -> None:
"""Set and store fullscreen state"""
_babase.app.config['Fullscreen'] = True
_babase.app.config.apply_and_commit()
def set_config_fullscreen_off() -> None:
"""The OS has changed our fullscreen state and we should take note."""
_babase.app.config['Fullscreen'] = False
_babase.app.config.apply_and_commit()
def not_signed_in_screen_message() -> None:
from babase._language import Lstr
@ -111,6 +140,14 @@ def error_message() -> None:
_babase.screenmessage(Lstr(resource='errorText'), color=(1, 0, 0))
def success_message() -> None:
from babase._language import Lstr
if _babase.app.env.gui:
_babase.getsimplesound('dingSmall').play()
_babase.screenmessage(Lstr(resource='successText'), color=(0, 1, 0))
def purchase_not_valid_error() -> None:
from babase._language import Lstr
@ -300,6 +337,7 @@ def implicit_sign_in(
from bacommon.login import LoginType
assert _babase.app.plus is not None
_babase.app.plus.accounts.on_implicit_sign_in(
login_type=LoginType(login_type_str),
login_id=login_id,
@ -377,3 +415,17 @@ def string_edit_adapter_can_be_replaced(adapter: StringEditAdapter) -> bool:
def get_dev_console_tab_names() -> list[str]:
"""Return the current set of dev-console tab names."""
return [t.name for t in _babase.app.devconsole.tabs]
def unsupported_controller_message(name: str) -> None:
"""Print a message when an unsupported controller is connected."""
from babase._language import Lstr
# Ick; this can get called early in the bootstrapping process
# before we're allowed to load assets. Guard against that.
if _babase.asset_loads_allowed():
_babase.getsimplesound('error').play()
_babase.screenmessage(
Lstr(resource='unsupportedControllerText', subs=[('${NAME}', name)]),
color=(1, 0, 0),
)

View File

@ -20,6 +20,13 @@ if TYPE_CHECKING:
DEBUG_LOG = False
@dataclass
class LoginInfo:
"""Basic info about a login available in the app.plus.accounts section."""
name: str
class LoginAdapter:
"""Allows using implicit login types in an explicit way.
@ -138,7 +145,7 @@ class LoginAdapter:
is actually being used by the app. It should therefore register
unlocked achievements, leaderboard scores, allow viewing native
UIs, etc. When not active it should ignore everything and behave
as if logged out, even if it technically is still logged in.
as if signed out, even if it technically is still signed in.
"""
assert _babase.in_logic_thread()
del active # Unused.
@ -149,7 +156,7 @@ class LoginAdapter:
result_cb: Callable[[LoginAdapter, SignInResult | Exception], None],
description: str,
) -> None:
"""Attempt an explicit sign in via this adapter.
"""Attempt to sign in via this adapter.
This can be called even if the back-end is not implicitly signed in;
the adapter will attempt to sign in if possible. An exception will
@ -161,7 +168,7 @@ class LoginAdapter:
# Have been seeing multiple sign-in attempts come through
# nearly simultaneously which can be problematic server-side.
# Let's error if a sign-in attempt is made within a few seconds
# of the last one to address this.
# of the last one to try and address this.
now = time.monotonic()
appnow = _babase.apptime()
if self._last_sign_in_time is not None:
@ -229,6 +236,7 @@ class LoginAdapter:
def _got_sign_in_response(
response: bacommon.cloud.SignInResponse | Exception,
) -> None:
# This likely means we couldn't communicate with the server.
if isinstance(response, Exception):
if DEBUG_LOG:
logging.debug(
@ -239,20 +247,18 @@ class LoginAdapter:
)
_babase.pushcall(Call(result_cb, self, response))
else:
if DEBUG_LOG:
logging.debug(
'LoginAdapter: %s adapter got successful'
' sign-in response',
self.login_type.name,
)
# This means our credentials were explicitly rejected.
if response.credentials is None:
result2: LoginAdapter.SignInResult | Exception = (
RuntimeError(
'No credentials returned after'
' submitting sign-in-token.'
)
RuntimeError('Sign-in-token was rejected.')
)
else:
if DEBUG_LOG:
logging.debug(
'LoginAdapter: %s adapter got successful'
' sign-in response',
self.login_type.name,
)
result2 = self.SignInResult(
credentials=response.credentials
)
@ -269,7 +275,7 @@ class LoginAdapter:
on_response=_got_sign_in_response,
)
# Kick off the process by fetching a sign-in token.
# Kick off the sign-in process by fetching a sign-in token.
self.get_sign_in_token(completion_cb=_got_sign_in_token_result)
def is_back_end_active(self) -> bool:
@ -282,11 +288,10 @@ class LoginAdapter:
"""Get a sign-in token from the adapter back end.
This token is then passed to the master-server to complete the
login process.
The adapter can use this opportunity to bring up account creation
UI, call its internal sign_in function, etc. as needed.
The provided completion_cb should then be called with either a token
or None if sign in failed or was cancelled.
sign-in process. The adapter can use this opportunity to bring
up account creation UI, call its internal sign_in function, etc.
as needed. The provided completion_cb should then be called with
either a token or None if sign in failed or was cancelled.
"""
from babase._general import Call
@ -295,7 +300,7 @@ class LoginAdapter:
def _update_implicit_login_state(self) -> None:
# If we've received an implicit login state, schedule it to be
# sent along to the app. We wait until on-app-launch has been
# sent along to the app. We wait until on-app-loading has been
# called so that account-client-v2 has had a chance to load
# any existing state so it can properly respond to this.
if self._implicit_login_state_dirty and self._on_app_loading_called:
@ -340,8 +345,8 @@ class LoginAdapter:
class LoginAdapterNative(LoginAdapter):
"""A login adapter that does its work in the native layer."""
def __init__(self) -> None:
super().__init__(LoginType.GPGS)
def __init__(self, login_type: LoginType) -> None:
super().__init__(login_type)
# Store int ids for in-flight attempts since they may go through
# various platform layers and back.
@ -375,3 +380,13 @@ class LoginAdapterNative(LoginAdapter):
class LoginAdapterGPGS(LoginAdapterNative):
"""Google Play Game Services adapter."""
def __init__(self) -> None:
super().__init__(LoginType.GPGS)
class LoginAdapterGameCenter(LoginAdapterNative):
"""Apple Game Center adapter."""
def __init__(self) -> None:
super().__init__(LoginType.GAME_CENTER)

View File

@ -24,6 +24,8 @@ if TYPE_CHECKING:
# instead of these or to make the meta system aware of arbitrary classes.
EXPORT_CLASS_NAME_SHORTCUTS: dict[str, str] = {
'plugin': 'babase.Plugin',
# DEPRECATED as of 12/2023. Currently am warning if finding these
# but should take this out eventually.
'keyboard': 'babase.Keyboard',
}
@ -414,30 +416,27 @@ class DirectoryScan:
if export_class_name is not None:
classname = modulename + '.' + export_class_name
# Since we'll soon have multiple versions of 'game'
# classes we need to migrate people to using base
# class names for them.
if exporttypestr == 'game':
# Migrating away from the 'keyboard' name shortcut
# since it's specific to bauiv1; warn if we find it.
if exporttypestr == 'keyboard':
logging.warning(
"metascan: %s:%d: '# ba_meta export"
" game' tag should be replaced by '# ba_meta"
" export bascenev1.GameActivity'.",
" keyboard' tag should be replaced by '# ba_meta"
" export bauiv1.Keyboard'.",
subpath,
lindex + 1,
)
self.results.announce_errors_occurred = True
else:
# If export type is one of our shortcuts, sub in the
# actual class path. Otherwise assume its a classpath
# itself.
exporttype = EXPORT_CLASS_NAME_SHORTCUTS.get(
exporttypestr
)
if exporttype is None:
exporttype = exporttypestr
self.results.exports.setdefault(exporttype, []).append(
classname
)
# If export type is one of our shortcuts, sub in the
# actual class path. Otherwise assume its a classpath
# itself.
exporttype = EXPORT_CLASS_NAME_SHORTCUTS.get(exporttypestr)
if exporttype is None:
exporttype = exporttypestr
self.results.exports.setdefault(exporttype, []).append(
classname
)
def _get_export_class_name(
self, subpath: Path, lines: list[str], lindex: int

View File

@ -104,8 +104,8 @@ def show_user_scripts() -> None:
_error.print_exception('error writing about_this_folder stuff')
# On a few platforms we try to open the dir in the UI.
if app.classic is not None and app.classic.platform in ['mac', 'windows']:
# On platforms that support it, open the dir in the UI.
if _babase.supports_open_dir_externally():
_babase.open_dir_externally(env.python_directory_user)
# Otherwise we just print a pretty version of it.

View File

@ -302,6 +302,11 @@ class AccountV1Subsystem:
"""(internal)"""
plus = babase.app.plus
if plus is None:
import logging
logging.warning(
'Error adding pending promo code; plus not present.'
)
babase.screenmessage(
babase.Lstr(resource='errorText'), color=(1, 0, 0)
)

View File

@ -4,10 +4,11 @@
from __future__ import annotations
import time
import asyncio
import logging
from typing import TYPE_CHECKING
import babase
import bauiv1
import bascenev1
if TYPE_CHECKING:
@ -31,6 +32,7 @@ class AdsSubsystem:
self.last_in_game_ad_remove_message_show_time: float | None = None
self.last_ad_completion_time: float | None = None
self.last_ad_was_short = False
self._fallback_task: asyncio.Task | None = None
def do_remove_in_game_ads_message(self) -> None:
"""(internal)"""
@ -69,7 +71,8 @@ class AdsSubsystem:
) -> None:
"""(internal)"""
self.last_ad_purpose = purpose
bauiv1.show_ad(purpose, on_completion_call)
assert babase.app.plus is not None
babase.app.plus.show_ad(purpose, on_completion_call)
def show_ad_2(
self,
@ -78,7 +81,8 @@ class AdsSubsystem:
) -> None:
"""(internal)"""
self.last_ad_purpose = purpose
bauiv1.show_ad_2(purpose, on_completion_call)
assert babase.app.plus is not None
babase.app.plus.show_ad_2(purpose, on_completion_call)
def call_after_ad(self, call: Callable[[], Any]) -> None:
"""Run a call after potentially showing an ad."""
@ -94,7 +98,7 @@ class AdsSubsystem:
show = True
# No ads without net-connections, etc.
if not bauiv1.can_show_ad():
if not plus.can_show_ad():
show = False
if classic.accounts.have_pro():
show = False # Pro disables interstitials.
@ -132,7 +136,7 @@ class AdsSubsystem:
# ad-show-threshold and see if we should *actually* show
# (we reach our threshold faster the longer we've been
# playing).
base = 'ads' if bauiv1.has_video_ads() else 'ads2'
base = 'ads' if plus.has_video_ads() else 'ads2'
min_lc = plus.get_v1_account_misc_read_val(base + '.minLC', 0.0)
max_lc = plus.get_v1_account_misc_read_val(base + '.maxLC', 5.0)
min_lc_scale = plus.get_v1_account_misc_read_val(
@ -181,36 +185,53 @@ class AdsSubsystem:
# If we're *still* cleared to show, actually tell the system to show.
if show:
# As a safety-check, set up an object that will run
# the completion callback if we've returned and sat for 10 seconds
# (in case some random ad network doesn't properly deliver its
# completion callback).
# As a safety-check, we set up an object that will run the
# completion callback if we've returned and sat for several
# seconds (in case some random ad network doesn't properly
# deliver its completion callback).
class _Payload:
def __init__(self, pcall: Callable[[], Any]):
self._call = pcall
self._ran = False
def run(self, fallback: bool = False) -> None:
"""Run fallback call (and issue a warning about it)."""
"""Run the payload."""
assert app.classic is not None
if not self._ran:
if fallback:
lanst = app.classic.ads.last_ad_network_set_time
print(
'ERROR: relying on fallback ad-callback! '
'last network: '
+ app.classic.ads.last_ad_network
+ ' (set '
+ str(int(time.time() - lanst))
+ 's ago); purpose='
+ app.classic.ads.last_ad_purpose
logging.error(
'Relying on fallback ad-callback! '
'last network: %s (set %s seconds ago);'
' purpose=%s.',
app.classic.ads.last_ad_network,
time.time() - lanst,
app.classic.ads.last_ad_purpose,
)
babase.pushcall(self._call)
self._ran = True
payload = _Payload(call)
# Set up our backup.
with babase.ContextRef.empty():
babase.apptimer(5.0, lambda: payload.run(fallback=True))
# Note to self: Previously this was a simple 5 second
# timer because the app got totally suspended while ads
# were showing (which delayed the timer), but these days
# the app may continue to run, so we need to be more
# careful and only fire the fallback after we see that
# the app has been front-and-center for several seconds.
async def add_fallback_task() -> None:
activesecs = 5
while activesecs > 0:
if babase.app.active:
activesecs -= 1
await asyncio.sleep(1.0)
payload.run(fallback=True)
_fallback_task = babase.app.aioloop.create_task(
add_fallback_task()
)
self.show_ad('between_game', on_completion_call=payload.run)
else:
babase.pushcall(call) # Just run the callback without the ad.

View File

@ -41,5 +41,6 @@ class AppDelegate:
sessiontype,
settings,
completion_call=completion_call,
).get_root_widget()
).get_root_widget(),
from_window=False, # Disable check since we don't know.
)

View File

@ -8,6 +8,7 @@ from typing import TYPE_CHECKING
import babase
import bascenev1
import _baclassic
if TYPE_CHECKING:
from typing import Any, Sequence
@ -54,7 +55,6 @@ def run_stress_test(
round_duration: int = 30,
) -> None:
"""Run a stress test."""
from babase import modutils
babase.screenmessage(
"Beginning stress test.. use 'End Test' to stop testing.",
@ -69,22 +69,12 @@ def run_stress_test(
'round_duration': round_duration,
}
)
babase.apptimer(
7.0,
babase.Call(
babase.screenmessage,
(
'stats will be written to '
+ modutils.get_human_readable_user_scripts_path()
+ '/stress_test_stats.csv'
),
),
)
def stop_stress_test() -> None:
"""End a running stress test."""
babase.set_stress_testing(False, 0)
_baclassic.set_stress_testing(False, 0)
assert babase.app.classic is not None
try:
if babase.app.classic.stress_test_reset_timer is not None:
@ -134,14 +124,14 @@ def start_stress_test(args: dict[str, Any]) -> None:
babase.Call(bascenev1.new_host_session, FreeForAllSession),
),
)
babase.set_stress_testing(True, args['player_count'])
_baclassic.set_stress_testing(True, args['player_count'])
babase.app.classic.stress_test_reset_timer = babase.AppTimer(
args['round_duration'], babase.Call(_reset_stress_test, args)
)
def _reset_stress_test(args: dict[str, Any]) -> None:
babase.set_stress_testing(False, args['player_count'])
_baclassic.set_stress_testing(False, args['player_count'])
babase.screenmessage('Resetting stress test...')
session = bascenev1.get_foreground_host_session()
assert session is not None

View File

@ -20,7 +20,6 @@ def get_input_device_mapped_value(
This checks the user config and falls back to default values
where available.
"""
# pylint: disable=too-many-statements
# pylint: disable=too-many-return-statements
# pylint: disable=too-many-branches
@ -40,7 +39,14 @@ def get_input_device_mapped_value(
mapping = ccfgs[devicename][unique_id]
elif 'default' in ccfgs[devicename]:
mapping = ccfgs[devicename]['default']
if mapping is not None:
# We now use the config mapping *only* if it is not empty.
# There have been cases of config writing code messing up
# and leaving empty dicts in the app config, which currently
# leaves the device unusable. Alternatively, we'd perhaps
# want to fall back to defaults for individual missing
# values, but that is a bigger change we can make later.
if isinstance(mapping, dict) and mapping:
return mapping.get(name, -1)
if platform == 'windows':
@ -76,91 +82,6 @@ def get_input_device_mapped_value(
'triggerRun1': 5,
}.get(name, -1)
# Look for some exact types.
if babase.is_running_on_fire_tv():
if devicename in ['Thunder', 'Amazon Fire Game Controller']:
return {
'triggerRun2': 23,
'unassignedButtonsRun': False,
'buttonPickUp': 101,
'buttonBomb': 98,
'buttonJump': 97,
'analogStickDeadZone': 0.0,
'startButtonActivatesDefaultWidget': False,
'buttonStart': 83,
'buttonPunch': 100,
'buttonRun2': 103,
'buttonRun1': 104,
'triggerRun1': 24,
}.get(name, -1)
if devicename == 'NYKO PLAYPAD PRO':
return {
'triggerRun2': 23,
'triggerRun1': 24,
'buttonPickUp': 101,
'buttonBomb': 98,
'buttonJump': 97,
'buttonUp': 20,
'buttonLeft': 22,
'buttonRight': 23,
'buttonStart': 83,
'buttonPunch': 100,
'buttonDown': 21,
}.get(name, -1)
if devicename == 'Logitech Dual Action':
return {
'triggerRun2': 23,
'triggerRun1': 24,
'buttonPickUp': 98,
'buttonBomb': 101,
'buttonJump': 100,
'buttonStart': 109,
'buttonPunch': 97,
}.get(name, -1)
if devicename == 'Xbox 360 Wireless Receiver':
return {
'triggerRun2': 23,
'triggerRun1': 24,
'buttonPickUp': 101,
'buttonBomb': 98,
'buttonJump': 97,
'buttonUp': 20,
'buttonLeft': 22,
'buttonRight': 23,
'buttonStart': 83,
'buttonPunch': 100,
'buttonDown': 21,
}.get(name, -1)
if devicename == 'Microsoft X-Box 360 pad':
return {
'triggerRun2': 23,
'triggerRun1': 24,
'buttonPickUp': 101,
'buttonBomb': 98,
'buttonJump': 97,
'buttonStart': 83,
'buttonPunch': 100,
}.get(name, -1)
if devicename in [
'Amazon Remote',
'Amazon Bluetooth Dev',
'Amazon Fire TV Remote',
]:
return {
'triggerRun2': 23,
'triggerRun1': 24,
'buttonPickUp': 24,
'buttonBomb': 91,
'buttonJump': 86,
'buttonUp': 20,
'buttonLeft': 22,
'startButtonActivatesDefaultWidget': False,
'buttonRight': 23,
'buttonStart': 83,
'buttonPunch': 90,
'buttonDown': 21,
}.get(name, -1)
elif 'NVIDIA SHIELD;' in useragentstring:
if 'NVIDIA Controller' in devicename:
return {
@ -175,112 +96,6 @@ def get_input_device_mapped_value(
'buttonIgnored': 184,
'buttonIgnored2': 86,
}.get(name, -1)
elif platform == 'mac':
if devicename == 'PLAYSTATION(R)3 Controller':
return {
'buttonLeft': 8,
'buttonUp': 5,
'buttonRight': 6,
'buttonDown': 7,
'buttonJump': 15,
'buttonPunch': 16,
'buttonBomb': 14,
'buttonPickUp': 13,
'buttonStart': 4,
'buttonIgnored': 17,
}.get(name, -1)
if devicename in ['Wireless 360 Controller', 'Controller']:
# Xbox360 gamepads
return {
'analogStickDeadZone': 1.2,
'buttonBomb': 13,
'buttonDown': 2,
'buttonJump': 12,
'buttonLeft': 3,
'buttonPickUp': 15,
'buttonPunch': 14,
'buttonRight': 4,
'buttonStart': 5,
'buttonUp': 1,
'triggerRun1': 5,
'triggerRun2': 6,
'buttonIgnored': 11,
}.get(name, -1)
if devicename in [
'Logitech Dual Action',
'Logitech Cordless RumblePad 2',
]:
return {
'buttonJump': 2,
'buttonPunch': 1,
'buttonBomb': 3,
'buttonPickUp': 4,
'buttonStart': 10,
}.get(name, -1)
# Old gravis gamepad.
if devicename == 'GamePad Pro USB ':
return {
'buttonJump': 2,
'buttonPunch': 1,
'buttonBomb': 3,
'buttonPickUp': 4,
'buttonStart': 10,
}.get(name, -1)
if devicename == 'Microsoft SideWinder Plug & Play Game Pad':
return {
'buttonJump': 1,
'buttonPunch': 3,
'buttonBomb': 2,
'buttonPickUp': 4,
'buttonStart': 6,
}.get(name, -1)
# Saitek P2500 Rumble Force Pad.. (hopefully works for others too?..)
if devicename == 'Saitek P2500 Rumble Force Pad':
return {
'buttonJump': 3,
'buttonPunch': 1,
'buttonBomb': 4,
'buttonPickUp': 2,
'buttonStart': 11,
}.get(name, -1)
# Some crazy 'Senze' dual gamepad.
if devicename == 'Twin USB Joystick':
return {
'analogStickLR': 3,
'analogStickLR_B': 7,
'analogStickUD': 4,
'analogStickUD_B': 8,
'buttonBomb': 2,
'buttonBomb_B': 14,
'buttonJump': 3,
'buttonJump_B': 15,
'buttonPickUp': 1,
'buttonPickUp_B': 13,
'buttonPunch': 4,
'buttonPunch_B': 16,
'buttonRun1': 7,
'buttonRun1_B': 19,
'buttonRun2': 8,
'buttonRun2_B': 20,
'buttonStart': 10,
'buttonStart_B': 22,
'enableSecondary': 1,
'unassignedButtonsRun': False,
}.get(name, -1)
if devicename == 'USB Gamepad ': # some weird 'JITE' gamepad
return {
'analogStickLR': 4,
'analogStickUD': 5,
'buttonJump': 3,
'buttonPunch': 4,
'buttonBomb': 2,
'buttonPickUp': 1,
'buttonStart': 10,
}.get(name, -1)
default_android_mapping = {
'triggerRun2': 19,
@ -303,6 +118,41 @@ def get_input_device_mapped_value(
# Generic android...
if platform == 'android':
if devicename in ['Amazon Fire Game Controller']:
return {
'triggerRun2': 23,
'unassignedButtonsRun': False,
'buttonPickUp': 101,
'buttonBomb': 98,
'buttonJump': 97,
'analogStickDeadZone': 0.0,
'startButtonActivatesDefaultWidget': False,
'buttonStart': 83,
'buttonPunch': 100,
'buttonRun2': 103,
'buttonRun1': 104,
'triggerRun1': 24,
}.get(name, -1)
if devicename in [
'Amazon Remote',
'Amazon Bluetooth Dev',
'Amazon Fire TV Remote',
]:
return {
'triggerRun2': 23,
'triggerRun1': 24,
'buttonPickUp': 24,
'buttonBomb': 91,
'buttonJump': 86,
'buttonUp': 20,
'buttonLeft': 22,
'startButtonActivatesDefaultWidget': False,
'buttonRight': 23,
'buttonStart': 83,
'buttonPunch': 90,
'buttonDown': 21,
}.get(name, -1)
# Steelseries stratus xl.
if devicename == 'SteelSeries Stratus XL':
return {
@ -380,14 +230,6 @@ def get_input_device_mapped_value(
'uiOnly': True,
}.get(name, -1)
# flag particular gamepads to use exact android defaults..
# (so they don't even ask to configure themselves)
if devicename in [
'Samsung Game Pad EI-GP20',
'ASUS Gamepad',
] or devicename.startswith('Freefly VR Glide'):
return default_android_mapping.get(name, -1)
# Nvidia controller is default, but gets some strange
# keypresses we want to ignore.. touching the touchpad,
# so lets ignore those.
@ -445,76 +287,11 @@ def get_input_device_mapped_value(
'buttonRight': 100,
}.get(name, -1)
# Ok, this gamepad's not in our specific preset list;
# fall back to some (hopefully) reasonable defaults.
# Leaving these in here for now but not gonna add any more now that we have
# fancy-pants config sharing across the internet.
if platform == 'mac':
if 'PLAYSTATION' in devicename: # ps3 gamepad?..
return {
'buttonLeft': 8,
'buttonUp': 5,
'buttonRight': 6,
'buttonDown': 7,
'buttonJump': 15,
'buttonPunch': 16,
'buttonBomb': 14,
'buttonPickUp': 13,
'buttonStart': 4,
}.get(name, -1)
# Dual Action Config - hopefully applies to more...
if 'Logitech' in devicename:
return {
'buttonJump': 2,
'buttonPunch': 1,
'buttonBomb': 3,
'buttonPickUp': 4,
'buttonStart': 10,
}.get(name, -1)
# Saitek P2500 Rumble Force Pad.. (hopefully works for others too?..)
if 'Saitek' in devicename:
return {
'buttonJump': 3,
'buttonPunch': 1,
'buttonBomb': 4,
'buttonPickUp': 2,
'buttonStart': 11,
}.get(name, -1)
# Gravis stuff?...
if 'GamePad' in devicename:
return {
'buttonJump': 2,
'buttonPunch': 1,
'buttonBomb': 3,
'buttonPickUp': 4,
'buttonStart': 10,
}.get(name, -1)
# Ok, this gamepad's not in our specific preset list; fall back to
# some (hopefully) reasonable defaults.
# Reasonable defaults.
if platform == 'android':
if babase.is_running_on_fire_tv():
# Mostly same as default firetv controller.
return {
'triggerRun2': 23,
'triggerRun1': 24,
'buttonPickUp': 101,
'buttonBomb': 98,
'buttonJump': 97,
'buttonStart': 83,
'buttonPunch': 100,
'buttonDown': 21,
'buttonUp': 20,
'buttonLeft': 22,
'buttonRight': 23,
'startButtonActivatesDefaultWidget': False,
}.get(name, -1)
# Mostly same as 'Gamepad' except with 'menu' for default start
# button instead of 'mode'.
return default_android_mapping.get(name, -1)
# Is there a point to any sort of fallbacks here?.. should check.
@ -533,9 +310,9 @@ def _gen_android_input_hash() -> str:
md5 = hashlib.md5()
# Currently we just do a single hash of *all* inputs on android
# and that's it.. good enough.
# (grabbing mappings for a specific device looks to be non-trivial)
# Currently we just do a single hash of *all* inputs on android and
# that's it. Good enough. (grabbing mappings for a specific device
# looks to be non-trivial)
for dirname in [
'/system/usr/keylayout',
'/data/usr/keylayout',
@ -544,9 +321,9 @@ def _gen_android_input_hash() -> str:
try:
if os.path.isdir(dirname):
for f_name in os.listdir(dirname):
# This is usually volume keys and stuff;
# assume we can skip it?..
# (since it'll vary a lot across devices)
# This is usually volume keys and stuff; assume we
# can skip it?.. (since it'll vary a lot across
# devices)
if f_name == 'gpio-keys.kl':
continue
try:
@ -569,8 +346,8 @@ def get_input_device_map_hash() -> str:
"""
app = babase.app
# Currently only using this when classic is present.
# Need to replace with a modern equivalent.
# Currently only using this when classic is present. Need to replace
# with a modern equivalent.
if app.classic is not None:
try:
if app.classic.input_map_hash is None:

View File

@ -165,15 +165,16 @@ class MusicSubsystem:
def supports_soundtrack_entry_type(self, entry_type: str) -> bool:
"""Return whether provided soundtrack entry type is supported here."""
uas = babase.env()['legacy_user_agent_string']
assert isinstance(uas, str)
# FIXME: Generalize this.
# Note to self; can't access babase.app.classic here because
# we are called during its construction.
env = babase.env()
platform = env.get('platform')
assert isinstance(platform, str)
if entry_type == 'iTunesPlaylist':
return 'Mac' in uas
return platform == 'mac' and babase.is_xcode_build()
if entry_type in ('musicFile', 'musicFolder'):
return (
'android' in uas
platform == 'android'
and babase.android_get_external_files_dir() is not None
)
if entry_type == 'default':

View File

@ -423,6 +423,10 @@ class ServerController:
bascenev1.set_public_party_stats_url(self._config.stats_url)
bascenev1.set_public_party_enabled(self._config.party_is_public)
bascenev1.set_player_rejoin_cooldown(
self._config.player_rejoin_cooldown
)
# And here.. we.. go.
if self._config.stress_test_players is not None:
# Special case: run a stress test.

View File

@ -451,15 +451,6 @@ class ClassicSubsystem(babase.AppSubsystem):
if playtype in val.get_play_types()
)
def show_online_score_ui(
self,
show: str = 'general',
game: str | None = None,
game_version: str | None = None,
) -> None:
"""(internal)"""
bauiv1.show_online_score_ui(show, game, game_version)
def game_begin_analytics(self) -> None:
"""(internal)"""
from baclassic import _analytics
@ -627,15 +618,6 @@ class ClassicSubsystem(babase.AppSubsystem):
"""(internal)"""
return bascenev1.get_foreground_host_activity()
def show_config_error_window(self) -> bool:
"""(internal)"""
if self.platform in ('mac', 'linux', 'windows'):
from bauiv1lib.configerror import ConfigErrorWindow
babase.pushcall(ConfigErrorWindow)
return True
return False
def value_test(
self,
arg: str,
@ -809,5 +791,6 @@ class ClassicSubsystem(babase.AppSubsystem):
bauiv1.getsound('swish').play()
babase.app.ui_v1.set_main_menu_window(
MainMenuWindow().get_root_widget()
MainMenuWindow().get_root_widget(),
from_window=False, # Disable check here.
)

View File

@ -80,14 +80,13 @@ class _MacMusicAppThread(threading.Thread):
def run(self) -> None:
"""Run the Music.app thread."""
babase.set_thread_name('BA_MacMusicAppThread')
babase.mac_music_app_init()
# Let's mention to the user we're launching Music.app in case
# it causes any funny business (this used to background the app
# sometimes, though I think that is fixed now)
def do_print() -> None:
babase.apptimer(
1.0,
0.5,
babase.Call(
babase.screenmessage,
babase.Lstr(resource='usingItunesText'),
@ -97,9 +96,8 @@ class _MacMusicAppThread(threading.Thread):
babase.pushcall(do_print, from_other_thread=True)
# Here we grab this to force the actual launch.
babase.mac_music_app_get_volume()
babase.mac_music_app_get_library_source()
babase.mac_music_app_init()
done = False
while not done:
self._commands_available.wait()

View File

@ -40,7 +40,7 @@ if TYPE_CHECKING:
# the last load. Either way, however, multiple execs will happen in some
# form.
#
# So we need to do a few things to handle that situation gracefully.
# To handle that situation gracefully, we need to do a few things:
#
# - First, we need to store any mutable global state in the __main__
# module; not in ourself. This way, alternate versions of ourself will
@ -48,12 +48,12 @@ if TYPE_CHECKING:
#
# - Second, we should avoid the use of isinstance and similar calls for
# our types. An EnvConfig we create would technically be a different
# type than that created by an alternate baenv.
# type than an EnvConfig created by an alternate baenv.
# Build number and version of the ballistica binary we expect to be
# using.
TARGET_BALLISTICA_BUILD = 21445
TARGET_BALLISTICA_VERSION = '1.7.28'
TARGET_BALLISTICA_BUILD = 21743
TARGET_BALLISTICA_VERSION = '1.7.33'
@dataclass

View File

@ -1,6 +1,6 @@
# Released under the MIT License. See LICENSE for details.
#
"""Provides classic app subsystem."""
"""Provides plus app subsystem."""
from __future__ import annotations
from typing import TYPE_CHECKING
@ -249,3 +249,41 @@ class PlusSubsystem(AppSubsystem):
) -> None:
"""(internal)"""
return _baplus.tournament_query(callback, args)
@staticmethod
def have_incentivized_ad() -> bool:
"""Is an incentivized ad available?"""
return _baplus.have_incentivized_ad()
@staticmethod
def has_video_ads() -> bool:
"""Are video ads available?"""
return _baplus.has_video_ads()
@staticmethod
def can_show_ad() -> bool:
"""Can we show an ad?"""
return _baplus.can_show_ad()
@staticmethod
def show_ad(
purpose: str, on_completion_call: Callable[[], None] | None = None
) -> None:
"""Show an ad."""
_baplus.show_ad(purpose, on_completion_call)
@staticmethod
def show_ad_2(
purpose: str, on_completion_call: Callable[[bool], None] | None = None
) -> None:
"""Show an ad."""
_baplus.show_ad_2(purpose, on_completion_call)
@staticmethod
def show_game_service_ui(
show: str = 'general',
game: str | None = None,
game_version: str | None = None,
) -> None:
"""Show game-service provided UI."""
_baplus.show_game_service_ui(show, game, game_version)

View File

@ -78,6 +78,7 @@ from _bascenev1 import (
end_host_scanning,
get_chat_messages,
get_connection_to_host_info,
get_connection_to_host_info_2,
get_foreground_host_activity,
get_foreground_host_session,
get_game_port,
@ -202,6 +203,7 @@ from bascenev1._multiteamsession import (
DEFAULT_TEAM_NAMES,
)
from bascenev1._music import MusicType, setmusic
from bascenev1._net import HostInfo
from bascenev1._nodeactor import NodeActor
from bascenev1._powerup import get_default_powerup_distribution
from bascenev1._profile import (
@ -226,7 +228,7 @@ from bascenev1._settings import (
IntSetting,
Setting,
)
from bascenev1._session import Session
from bascenev1._session import Session, set_player_rejoin_cooldown
from bascenev1._stats import PlayerScoredMessage, PlayerRecord, Stats
from bascenev1._team import SessionTeam, Team, EmptyTeam
from bascenev1._teamgame import TeamGameActivity
@ -303,6 +305,7 @@ __all__ = [
'GameTip',
'get_chat_messages',
'get_connection_to_host_info',
'get_connection_to_host_info_2',
'get_default_free_for_all_playlist',
'get_default_teams_playlist',
'get_default_powerup_distribution',
@ -338,6 +341,7 @@ __all__ = [
'have_connected_clients',
'have_touchscreen_input',
'HitMessage',
'HostInfo',
'host_scan_cycle',
'ImpactDamageMessage',
'increment_analytics_count',
@ -415,6 +419,7 @@ __all__ = [
'set_public_party_name',
'set_public_party_queue_enabled',
'set_public_party_stats_url',
'set_player_rejoin_cooldown',
'set_replay_speed_exponent',
'set_touchscreen_editing',
'setmusic',

View File

@ -6,7 +6,13 @@ from __future__ import annotations
from typing import TYPE_CHECKING
from bacommon.app import AppExperience
from babase import AppMode, AppIntentExec, AppIntentDefault
from babase import (
app,
AppMode,
AppIntentExec,
AppIntentDefault,
invoke_main_menu,
)
import _bascenev1
@ -40,3 +46,9 @@ class SceneV1AppMode(AppMode):
def on_deactivate(self) -> None:
# Let the native layer do its thing.
_bascenev1.on_app_mode_deactivate()
def on_app_active_changed(self) -> None:
# If we've gone inactive, bring up the main menu, which has the
# side effect of pausing the action (when possible).
if not app.active:
invoke_main_menu()

View File

@ -438,10 +438,16 @@ class GameActivity(Activity[PlayerT, TeamT]):
assert classic is not None
continues_window = classic.continues_window
# Turning these off. I want to migrate towards monetization that
# feels less pay-to-win-ish.
allow_continues = False
plus = babase.app.plus
try:
if plus is not None and plus.get_v1_account_misc_read_val(
'enableContinues', False
if (
plus is not None
and plus.get_v1_account_misc_read_val('enableContinues', False)
and allow_continues
):
session = self.session

View File

@ -0,0 +1,24 @@
# Released under the MIT License. See LICENSE for details.
#
"""Functionality related to net play."""
from __future__ import annotations
from typing import TYPE_CHECKING
from dataclasses import dataclass
if TYPE_CHECKING:
pass
@dataclass
class HostInfo:
"""Info about a host."""
name: str
build_number: int
# Note this can be None for non-ip hosts such as bluetooth.
address: str | None
# Note this can be None for non-ip hosts such as bluetooth.
port: int | None

View File

@ -3,6 +3,7 @@
"""Defines base session class."""
from __future__ import annotations
import math
import weakref
import logging
from typing import TYPE_CHECKING
@ -17,6 +18,17 @@ if TYPE_CHECKING:
import bascenev1
# How many seconds someone who left the session (but not the party) must
# wait to rejoin the session again. Intended to prevent game exploits
# such as skipping respawn waits.
_g_player_rejoin_cooldown: float = 0.0
def set_player_rejoin_cooldown(cooldown: float) -> None:
"""Set the cooldown for individual players rejoining after leaving."""
global _g_player_rejoin_cooldown # pylint: disable=global-statement
_g_player_rejoin_cooldown = max(0.0, cooldown)
class Session:
"""Defines a high level series of bascenev1.Activity-es.
@ -203,6 +215,11 @@ class Session:
# Instantiate our session globals node which will apply its settings.
self._sessionglobalsnode = _bascenev1.newnode('sessionglobals')
# Rejoin cooldown stuff.
self._players_on_wait: dict = {}
self._player_requested_identifiers: dict = {}
self._waitlist_timers: dict = {}
@property
def context(self) -> bascenev1.ContextRef:
"""A context-ref pointing at this activity."""
@ -253,6 +270,33 @@ class Session:
)
return False
# Rejoin cooldown.
identifier = player.get_v1_account_id()
if identifier:
leave_time = self._players_on_wait.get(identifier)
if leave_time:
diff = str(
math.ceil(
_g_player_rejoin_cooldown
- babase.apptime()
+ leave_time
)
)
_bascenev1.broadcastmessage(
babase.Lstr(
translate=(
'serverResponses',
'You can join in ${COUNT} seconds.',
),
subs=[('${COUNT}', diff)],
),
color=(1, 1, 0),
clients=[player.inputdevice.client_id],
transient=True,
)
return False
self._player_requested_identifiers[player.id] = identifier
_bascenev1.getsound('dripity').play()
return True
@ -270,6 +314,16 @@ class Session:
activity = self._activity_weak()
# Rejoin cooldown.
identifier = self._player_requested_identifiers.get(sessionplayer.id)
if identifier:
self._players_on_wait[identifier] = babase.apptime()
with babase.ContextRef.empty():
self._waitlist_timers[identifier] = babase.AppTimer(
_g_player_rejoin_cooldown,
babase.Call(self._remove_player_from_waitlist, identifier),
)
if not sessionplayer.in_game:
# Ok, the player is still in the lobby; simply remove them.
with self.context:
@ -770,3 +824,9 @@ class Session:
if pass_to_activity:
activity.add_player(sessionplayer)
return sessionplayer
def _remove_player_from_waitlist(self, identifier: str) -> None:
try:
self._players_on_wait.pop(identifier)
except KeyError:
pass

View File

@ -9,6 +9,7 @@ import random
import logging
from typing import TYPE_CHECKING
from bacommon.login import LoginType
import bascenev1 as bs
import bauiv1 as bui
@ -59,29 +60,25 @@ class CoopScoreScreen(bs.Activity[bs.Player, bs.Team]):
)
)
self._account_type = (
plus.get_v1_account_type()
if plus.get_v1_account_state() == 'signed_in'
else None
)
self._game_service_icon_color: Sequence[float] | None
self._game_service_achievements_texture: bui.Texture | None
self._game_service_leaderboards_texture: bui.Texture | None
if self._account_type == 'Game Center':
# Tie in to specific game services if they are active.
adapter = plus.accounts.login_adapters.get(LoginType.GPGS)
gpgs_active = adapter is not None and adapter.is_back_end_active()
adapter = plus.accounts.login_adapters.get(LoginType.GAME_CENTER)
game_center_active = (
adapter is not None and adapter.is_back_end_active()
)
if game_center_active:
self._game_service_icon_color = (1.0, 1.0, 1.0)
icon = bui.gettexture('gameCenterIcon')
self._game_service_achievements_texture = icon
self._game_service_leaderboards_texture = icon
self._account_has_achievements = True
elif self._account_type == 'Game Circle':
icon = bui.gettexture('gameCircleIcon')
self._game_service_icon_color = (1, 1, 1)
self._game_service_achievements_texture = icon
self._game_service_leaderboards_texture = icon
self._account_has_achievements = True
elif self._account_type == 'Google Play':
elif gpgs_active:
self._game_service_icon_color = (0.8, 1.0, 0.6)
self._game_service_achievements_texture = bui.gettexture(
'googlePlayAchievementsIcon'
@ -193,7 +190,7 @@ class CoopScoreScreen(bs.Activity[bs.Player, bs.Team]):
super().__del__()
# If our UI is still up, kill it.
if self._root_ui:
if self._root_ui and not self._root_ui.transitioning_out:
with bui.ContextRef.empty():
bui.containerwidget(edit=self._root_ui, transition='out_left')
@ -287,20 +284,20 @@ class CoopScoreScreen(bs.Activity[bs.Player, bs.Team]):
self.end({'outcome': 'next_level'})
def _ui_gc(self) -> None:
if bs.app.classic is not None:
bs.app.classic.show_online_score_ui(
if bs.app.plus is not None:
bs.app.plus.show_game_service_ui(
'leaderboard',
game=self._game_name_str,
game_version=self._game_config_str,
)
else:
logging.warning('show_online_score_ui requires classic')
logging.warning('show_game_service_ui requires plus feature-set')
def _ui_show_achievements(self) -> None:
if bs.app.classic is not None:
bs.app.classic.show_online_score_ui('achievements')
if bs.app.plus is not None:
bs.app.plus.show_game_service_ui('achievements')
else:
logging.warning('show_online_score_ui requires classic')
logging.warning('show_game_service_ui requires plus feature-set')
def _ui_worlds_best(self) -> None:
if self._score_link is None:

View File

@ -35,7 +35,7 @@ class ControlsGuide(bs.Actor):
delay: is the time in seconds before the overlay fades in.
lifespan: if not None, the overlay will fade back out and die after
that long (in milliseconds).
that long (in seconds).
bright: if True, brighter colors will be used; handy when showing
over gameplay but may be too bright for join-screens, etc.
@ -50,6 +50,7 @@ class ControlsGuide(bs.Actor):
offs5 = 43.0 * scale
ouya = False
maxw = 50
xtweak = -2.8 * scale
self._lifespan = lifespan
self._dead = False
self._bright = bright
@ -117,7 +118,7 @@ class ControlsGuide(bs.Actor):
'host_only': True,
'shadow': 1.0,
'maxwidth': maxw,
'position': (pos[0], pos[1] - offs5),
'position': (pos[0] + xtweak, pos[1] - offs5),
'color': clr,
},
)
@ -145,7 +146,7 @@ class ControlsGuide(bs.Actor):
'host_only': True,
'shadow': 1.0,
'maxwidth': maxw,
'position': (pos[0], pos[1] - offs5),
'position': (pos[0] + xtweak, pos[1] - offs5),
'color': clr,
},
)
@ -173,7 +174,7 @@ class ControlsGuide(bs.Actor):
'host_only': True,
'shadow': 1.0,
'maxwidth': maxw,
'position': (pos[0], pos[1] - offs5),
'position': (pos[0] + xtweak, pos[1] - offs5),
'color': clr,
},
)
@ -201,7 +202,7 @@ class ControlsGuide(bs.Actor):
'host_only': True,
'shadow': 1.0,
'maxwidth': maxw,
'position': (pos[0], pos[1] - offs5),
'position': (pos[0] + xtweak, pos[1] - offs5),
'color': clr,
},
)
@ -264,10 +265,19 @@ class ControlsGuide(bs.Actor):
bs.timer(delay, bs.WeakCall(self._start_updating))
@staticmethod
def _meaningful_button_name(device: bs.InputDevice, button: int) -> str:
def _meaningful_button_name(
device: bs.InputDevice, button_name: str
) -> str:
"""Return a flattened string button name; empty for non-meaningful."""
if not device.has_meaningful_button_names:
return ''
assert bs.app.classic is not None
button = bs.app.classic.get_input_device_mapped_value(
device, button_name
)
# -1 means unset; let's show that.
if button == -1:
return bs.Lstr(resource='configGamepadWindow.unsetText').evaluate()
return device.get_button_name(button).evaluate()
def _start_updating(self) -> None:
@ -289,10 +299,10 @@ class ControlsGuide(bs.Actor):
def _check_fade_in(self) -> None:
assert bs.app.classic is not None
# If we have a touchscreen, we only fade in if we have a player with
# an input device that is *not* the touchscreen.
# (otherwise it is confusing to see the touchscreen buttons right
# next to our display buttons)
# If we have a touchscreen, we only fade in if we have a player
# with an input device that is *not* the touchscreen. Otherwise
# it is confusing to see the touchscreen buttons right next to
# our display buttons.
touchscreen: bs.InputDevice | None = bs.getinputdevice(
'TouchScreen', '#1', doraise=False
)
@ -318,15 +328,7 @@ class ControlsGuide(bs.Actor):
'buttonBomb',
'buttonPickUp',
):
if (
self._meaningful_button_name(
device,
bs.app.classic.get_input_device_mapped_value(
device, name
),
)
!= ''
):
if self._meaningful_button_name(device, name) != '':
fade_in = True
break
if fade_in:
@ -401,58 +403,30 @@ class ControlsGuide(bs.Actor):
# We only care about movement buttons in the case of keyboards.
if all_keyboards:
right_button_names.add(
device.get_button_name(
classic.get_input_device_mapped_value(
device, 'buttonRight'
)
)
self._meaningful_button_name(device, 'buttonRight')
)
left_button_names.add(
device.get_button_name(
classic.get_input_device_mapped_value(
device, 'buttonLeft'
)
)
self._meaningful_button_name(device, 'buttonLeft')
)
down_button_names.add(
device.get_button_name(
classic.get_input_device_mapped_value(
device, 'buttonDown'
)
)
self._meaningful_button_name(device, 'buttonDown')
)
up_button_names.add(
device.get_button_name(
classic.get_input_device_mapped_value(
device, 'buttonUp'
)
)
self._meaningful_button_name(device, 'buttonUp')
)
# Ignore empty values; things like the remote app or
# wiimotes can return these.
bname = self._meaningful_button_name(
device,
classic.get_input_device_mapped_value(device, 'buttonPunch'),
)
bname = self._meaningful_button_name(device, 'buttonPunch')
if bname != '':
punch_button_names.add(bname)
bname = self._meaningful_button_name(
device,
classic.get_input_device_mapped_value(device, 'buttonJump'),
)
bname = self._meaningful_button_name(device, 'buttonJump')
if bname != '':
jump_button_names.add(bname)
bname = self._meaningful_button_name(
device,
classic.get_input_device_mapped_value(device, 'buttonBomb'),
)
bname = self._meaningful_button_name(device, 'buttonBomb')
if bname != '':
bomb_button_names.add(bname)
bname = self._meaningful_button_name(
device,
classic.get_input_device_mapped_value(device, 'buttonPickUp'),
)
bname = self._meaningful_button_name(device, 'buttonPickUp')
if bname != '':
pickup_button_names.add(bname)
@ -582,8 +556,8 @@ class ControlsGuide(bs.Actor):
if msg.immediate:
self._die()
else:
# If they don't need immediate,
# fade out our nodes and die later.
# If they don't need immediate, fade out our nodes and
# die later.
for node in self._nodes:
bs.animate(node, 'opacity', {0: node.opacity, 3.0: 0.0})
bs.timer(3.1, bs.WeakCall(self._die))

View File

@ -624,7 +624,7 @@ class Spaz(bs.Actor):
1000.0 * (tval + self.curse_time)
)
self._curse_timer = bs.Timer(
5.0, bs.WeakCall(self.curse_explode)
5.0, bs.WeakCall(self.handlemessage, CurseExplodeMessage())
)
def equip_boxing_gloves(self) -> None:

View File

@ -317,7 +317,8 @@ class MainMenuActivity(bs.Activity[bs.Player, bs.Team]):
from bauiv1lib.kiosk import KioskWindow
bs.app.ui_v1.set_main_menu_window(
KioskWindow().get_root_widget()
KioskWindow().get_root_widget(),
from_window=False, # Disable check here.
)
# ..or in normal cases go back to the main menu
else:
@ -326,14 +327,16 @@ class MainMenuActivity(bs.Activity[bs.Player, bs.Team]):
from bauiv1lib.gather import GatherWindow
bs.app.ui_v1.set_main_menu_window(
GatherWindow(transition=None).get_root_widget()
GatherWindow(transition=None).get_root_widget(),
from_window=False, # Disable check here.
)
elif main_menu_location == 'Watch':
# pylint: disable=cyclic-import
from bauiv1lib.watch import WatchWindow
bs.app.ui_v1.set_main_menu_window(
WatchWindow(transition=None).get_root_widget()
WatchWindow(transition=None).get_root_widget(),
from_window=False, # Disable check here.
)
elif main_menu_location == 'Team Game Select':
# pylint: disable=cyclic-import
@ -344,7 +347,8 @@ class MainMenuActivity(bs.Activity[bs.Player, bs.Team]):
bs.app.ui_v1.set_main_menu_window(
PlaylistBrowserWindow(
sessiontype=bs.DualTeamSession, transition=None
).get_root_widget()
).get_root_widget(),
from_window=False, # Disable check here.
)
elif main_menu_location == 'Free-for-All Game Select':
# pylint: disable=cyclic-import
@ -356,28 +360,34 @@ class MainMenuActivity(bs.Activity[bs.Player, bs.Team]):
PlaylistBrowserWindow(
sessiontype=bs.FreeForAllSession,
transition=None,
).get_root_widget()
).get_root_widget(),
from_window=False, # Disable check here.
)
elif main_menu_location == 'Coop Select':
# pylint: disable=cyclic-import
from bauiv1lib.coop.browser import CoopBrowserWindow
bs.app.ui_v1.set_main_menu_window(
CoopBrowserWindow(transition=None).get_root_widget()
CoopBrowserWindow(
transition=None
).get_root_widget(),
from_window=False, # Disable check here.
)
elif main_menu_location == 'Benchmarks & Stress Tests':
# pylint: disable=cyclic-import
from bauiv1lib.debug import DebugWindow
bs.app.ui_v1.set_main_menu_window(
DebugWindow(transition=None).get_root_widget()
DebugWindow(transition=None).get_root_widget(),
from_window=False, # Disable check here.
)
else:
# pylint: disable=cyclic-import
from bauiv1lib.mainmenu import MainMenuWindow
bs.app.ui_v1.set_main_menu_window(
MainMenuWindow(transition=None).get_root_widget()
MainMenuWindow(transition=None).get_root_widget(),
from_window=None,
)
# attempt to show any pending offers immediately.

View File

@ -31,7 +31,10 @@ from babase import (
apptimer,
AppTimer,
Call,
can_toggle_fullscreen,
fullscreen_control_available,
fullscreen_control_get,
fullscreen_control_key_shortcut,
fullscreen_control_set,
charstr,
clipboard_is_supported,
clipboard_set_text,
@ -57,13 +60,15 @@ from babase import (
in_logic_thread,
increment_analytics_count,
is_browser_likely_available,
is_running_on_fire_tv,
is_xcode_build,
Keyboard,
lock_all_input,
LoginAdapter,
LoginInfo,
Lstr,
native_review_request,
native_review_request_supported,
NotFoundError,
open_file_externally,
Permission,
Plugin,
PluginSpec,
@ -88,7 +93,6 @@ from babase import (
from _bauiv1 import (
buttonwidget,
can_show_ad,
checkboxwidget,
columnwidget,
containerwidget,
@ -97,21 +101,15 @@ from _bauiv1 import (
getmesh,
getsound,
gettexture,
has_video_ads,
have_incentivized_ad,
hscrollwidget,
imagewidget,
is_party_icon_visible,
Mesh,
open_file_externally,
open_url,
rowwidget,
scrollwidget,
set_party_icon_always_visible,
set_party_window_open,
show_ad,
show_ad_2,
show_online_score_ui,
Sound,
Texture,
textwidget,
@ -119,6 +117,7 @@ from _bauiv1 import (
Widget,
widget,
)
from bauiv1._keyboard import Keyboard
from bauiv1._uitypes import Window, uicleanupcheck
from bauiv1._subsystem import UIV1Subsystem
@ -138,8 +137,10 @@ __all__ = [
'AppTimer',
'buttonwidget',
'Call',
'can_show_ad',
'can_toggle_fullscreen',
'fullscreen_control_available',
'fullscreen_control_get',
'fullscreen_control_key_shortcut',
'fullscreen_control_set',
'charstr',
'checkboxwidget',
'clipboard_is_supported',
@ -169,8 +170,6 @@ __all__ = [
'getmesh',
'getsound',
'gettexture',
'has_video_ads',
'have_incentivized_ad',
'have_permission',
'hscrollwidget',
'imagewidget',
@ -178,13 +177,15 @@ __all__ = [
'increment_analytics_count',
'is_browser_likely_available',
'is_party_icon_visible',
'is_running_on_fire_tv',
'is_xcode_build',
'Keyboard',
'lock_all_input',
'LoginAdapter',
'LoginInfo',
'Lstr',
'Mesh',
'native_review_request',
'native_review_request_supported',
'NotFoundError',
'open_file_externally',
'open_url',
@ -204,9 +205,6 @@ __all__ = [
'set_party_icon_always_visible',
'set_party_window_open',
'set_ui_input_device',
'show_ad',
'show_ad_2',
'show_online_score_ui',
'Sound',
'SpecialChar',
'supports_max_fps',

View File

@ -6,6 +6,7 @@
from __future__ import annotations
import logging
import inspect
from typing import TYPE_CHECKING
import _bauiv1
@ -87,3 +88,19 @@ def show_url_window(address: str) -> None:
return
app.classic.show_url_window(address)
def double_transition_out_warning() -> None:
"""Called if a widget is set to transition out twice."""
caller_frame = inspect.stack()[1]
caller_filename = caller_frame.filename
caller_line_number = caller_frame.lineno
logging.warning(
'ContainerWidget was set to transition out twice;'
' this often implies buggy code (%s line %s).\n'
' Generally you should check the value of'
' _root_widget.transitioning_out and perform no actions if that'
' is True.',
caller_filename,
caller_line_number,
)

View File

@ -5,6 +5,7 @@
from __future__ import annotations
import logging
import inspect
from typing import TYPE_CHECKING
import babase
@ -66,6 +67,16 @@ class UIV1Subsystem(babase.AppSubsystem):
# a more elegant way once we revamp high level UI stuff a bit.
self.selecting_private_party_playlist: bool = False
@property
def available(self) -> bool:
"""Can uiv1 currently be used?
Code that may run in headless mode, before the UI has been spun up,
while other ui systems are active, etc. can check this to avoid
likely erroring.
"""
return _bauiv1.is_available()
@property
def uiscale(self) -> babase.UIScale:
"""Current ui scale for the app."""
@ -106,21 +117,69 @@ class UIV1Subsystem(babase.AppSubsystem):
# FIXME: Can probably kill this if we do immediate UI death checks.
self.upkeeptimer = babase.AppTimer(2.6543, ui_upkeep, repeat=True)
def set_main_menu_window(self, window: bauiv1.Widget) -> None:
"""Set the current 'main' window, replacing any existing."""
def set_main_menu_window(
self,
window: bauiv1.Widget,
from_window: bauiv1.Widget | None | bool = True,
) -> None:
"""Set the current 'main' window, replacing any existing.
If 'from_window' is passed as a bauiv1.Widget or None, a warning
will be issued if it that value does not match the current main
window. This can help clean up flawed code that can lead to bad
UI states. A value of False will disable the check.
"""
existing = self._main_menu_window
from inspect import currentframe, getframeinfo
try:
if isinstance(from_window, bool):
# For default val True we warn that the arg wasn't
# passed. False can be explicitly passed to disable this
# check.
if from_window is True:
caller_frame = inspect.stack()[1]
caller_filename = caller_frame.filename
caller_line_number = caller_frame.lineno
logging.warning(
'set_main_menu_window() should be passed a'
" 'from_window' value to help ensure proper UI behavior"
' (%s line %i).',
caller_filename,
caller_line_number,
)
else:
# For everything else, warn if what they passed wasn't
# the previous main menu widget.
if from_window is not existing:
caller_frame = inspect.stack()[1]
caller_filename = caller_frame.filename
caller_line_number = caller_frame.lineno
logging.warning(
"set_main_menu_window() was passed 'from_window' %s"
' but existing main-menu-window is %s. (%s line %i).',
from_window,
existing,
caller_filename,
caller_line_number,
)
except Exception:
# Prevent any bugs in these checks from causing problems.
logging.exception('Error checking from_window')
# Once the above code leads to us fixing all leftover window bugs
# at the source, we can kill the code below.
# Let's grab the location where we were called from to report
# if we have to force-kill the existing window (which normally
# should not happen).
frameline = None
try:
frame = currentframe()
frame = inspect.currentframe()
if frame is not None:
frame = frame.f_back
if frame is not None:
frameinfo = getframeinfo(frame)
frameinfo = inspect.getframeinfo(frame)
frameline = f'{frameinfo.filename} {frameinfo.lineno}'
except Exception:
logging.exception('Error calcing line for set_main_menu_window')
@ -150,13 +209,18 @@ class UIV1Subsystem(babase.AppSubsystem):
def clear_main_menu_window(self, transition: str | None = None) -> None:
"""Clear any existing 'main' window with the provided transition."""
assert transition is None or not transition.endswith('_in')
if self._main_menu_window:
if transition is not None:
if (
transition is not None
and not self._main_menu_window.transitioning_out
):
_bauiv1.containerwidget(
edit=self._main_menu_window, transition=transition
)
else:
self._main_menu_window.delete()
self._main_menu_window = None
def add_main_menu_close_callback(self, call: Callable[[], Any]) -> None:
"""(internal)"""

View File

@ -12,6 +12,7 @@ from typing import TYPE_CHECKING
import babase
import _bauiv1
from bauiv1._keyboard import Keyboard
from bauiv1._uitypes import Window
if TYPE_CHECKING:
@ -51,6 +52,19 @@ class OnScreenKeyboardWindow(Window):
else (0, 0),
)
)
self._cancel_button = _bauiv1.buttonwidget(
parent=self._root_widget,
scale=0.5,
position=(30, self._height - 55),
size=(60, 60),
label='',
enable_sound=False,
on_activate_call=self._cancel,
autoselect=True,
color=(0.55, 0.5, 0.6),
icon=_bauiv1.gettexture('crossOut'),
iconscale=1.2,
)
self._done_button = _bauiv1.buttonwidget(
parent=self._root_widget,
position=(self._width - 200, 44),
@ -240,9 +254,7 @@ class OnScreenKeyboardWindow(Window):
# Show change instructions only if we have more than one
# keyboard option.
keyboards = (
babase.app.meta.scanresults.exports_of_class(
babase.Keyboard
)
babase.app.meta.scanresults.exports_of_class(Keyboard)
if babase.app.meta.scanresults is not None
else []
)
@ -274,10 +286,10 @@ class OnScreenKeyboardWindow(Window):
def _get_keyboard(self) -> bui.Keyboard:
assert babase.app.meta.scanresults is not None
classname = babase.app.meta.scanresults.exports_of_class(
babase.Keyboard
)[self._keyboard_index]
kbclass = babase.getclass(classname, babase.Keyboard)
classname = babase.app.meta.scanresults.exports_of_class(Keyboard)[
self._keyboard_index
]
kbclass = babase.getclass(classname, Keyboard)
return kbclass()
def _refresh(self) -> None:
@ -372,9 +384,7 @@ class OnScreenKeyboardWindow(Window):
def _next_keyboard(self) -> None:
assert babase.app.meta.scanresults is not None
kbexports = babase.app.meta.scanresults.exports_of_class(
babase.Keyboard
)
kbexports = babase.app.meta.scanresults.exports_of_class(Keyboard)
self._keyboard_index = (self._keyboard_index + 1) % len(kbexports)
self._load_keyboard()

View File

@ -63,20 +63,14 @@ class AccountSettingsWindow(bui.Window):
1.0, bui.WeakCall(self._update), repeat=True
)
# Currently we can only reset achievements on game-center.
v1_account_type: str | None
if self._v1_signed_in:
v1_account_type = plus.get_v1_account_type()
else:
v1_account_type = None
self._can_reset_achievements = v1_account_type == 'Game Center'
self._can_reset_achievements = False
app = bui.app
assert app.classic is not None
uiscale = app.ui_v1.uiscale
self._width = 760 if uiscale is bui.UIScale.SMALL else 660
x_offs = 50 if uiscale is bui.UIScale.SMALL else 0
self._width = 860 if uiscale is bui.UIScale.SMALL else 660
x_offs = 100 if uiscale is bui.UIScale.SMALL else 0
self._height = (
390
if uiscale is bui.UIScale.SMALL
@ -98,6 +92,9 @@ class AccountSettingsWindow(bui.Window):
if LoginType.GPGS in plus.accounts.login_adapters:
self._show_sign_in_buttons.append('Google Play')
if LoginType.GAME_CENTER in plus.accounts.login_adapters:
self._show_sign_in_buttons.append('Game Center')
# Always want to show our web-based v2 login option.
self._show_sign_in_buttons.append('V2Proxy')
@ -227,6 +224,8 @@ class AccountSettingsWindow(bui.Window):
plus = bui.app.plus
assert plus is not None
via_lines: list[str] = []
primary_v2_account = plus.accounts.primary
v1_state = plus.get_v1_account_state()
@ -237,14 +236,55 @@ class AccountSettingsWindow(bui.Window):
# We expose GPGS-specific functionality only if it is 'active'
# (meaning the current GPGS player matches one of our account's
# logins).
gpgs_adapter = plus.accounts.login_adapters.get(LoginType.GPGS)
is_gpgs = (
False if gpgs_adapter is None else gpgs_adapter.is_back_end_active()
adapter = plus.accounts.login_adapters.get(LoginType.GPGS)
gpgs_active = adapter is not None and adapter.is_back_end_active()
# Ditto for Game Center.
adapter = plus.accounts.login_adapters.get(LoginType.GAME_CENTER)
game_center_active = (
adapter is not None and adapter.is_back_end_active()
)
show_signed_in_as = self._v1_signed_in
signed_in_as_space = 95.0
# To reduce confusion about the whole V2 account situation for
# people used to seeing their Google Play Games or Game Center
# account name and icon and whatnot, let's show those underneath
# the V2 tag to help communicate that they are in fact logged in
# through that account.
via_space = 25.0
if show_signed_in_as and bui.app.plus is not None:
accounts = bui.app.plus.accounts
if accounts.primary is not None:
# For these login types, we show 'via' IF there is a
# login of that type attached to our account AND it is
# currently active (We don't want to show 'via Game
# Center' if we're signed out of Game Center or
# currently running on Steam, even if there is a Game
# Center login attached to our account).
for ltype, lchar in [
(LoginType.GPGS, bui.SpecialChar.GOOGLE_PLAY_GAMES_LOGO),
(LoginType.GAME_CENTER, bui.SpecialChar.GAME_CENTER_LOGO),
]:
linfo = accounts.primary.logins.get(ltype)
ladapter = accounts.login_adapters.get(ltype)
if (
linfo is not None
and ladapter is not None
and ladapter.is_back_end_active()
):
via_lines.append(f'{bui.charstr(lchar)}{linfo.name}')
# TEMP TESTING
if bool(False):
icontxt = bui.charstr(bui.SpecialChar.GAME_CENTER_LOGO)
via_lines.append(f'{icontxt}FloofDibble')
icontxt = bui.charstr(
bui.SpecialChar.GOOGLE_PLAY_GAMES_LOGO
)
via_lines.append(f'{icontxt}StinkBobble')
show_sign_in_benefits = not self._v1_signed_in
sign_in_benefits_space = 80.0
@ -258,6 +298,11 @@ class AccountSettingsWindow(bui.Window):
and self._signing_in_adapter is None
and 'Google Play' in self._show_sign_in_buttons
)
show_game_center_sign_in_button = (
v1_state == 'signed_out'
and self._signing_in_adapter is None
and 'Game Center' in self._show_sign_in_buttons
)
show_v2_proxy_sign_in_button = (
v1_state == 'signed_out'
and self._signing_in_adapter is None
@ -271,9 +316,8 @@ class AccountSettingsWindow(bui.Window):
sign_in_button_space = 70.0
deprecated_space = 60
show_game_service_button = self._v1_signed_in and v1_account_type in [
'Game Center'
]
# Game Center currently has a single UI for everything.
show_game_service_button = game_center_active
game_service_button_space = 60.0
show_what_is_v2 = self._v1_signed_in and v1_account_type == 'V2'
@ -281,11 +325,9 @@ class AccountSettingsWindow(bui.Window):
show_linked_accounts_text = self._v1_signed_in
linked_accounts_text_space = 60.0
show_achievements_button = self._v1_signed_in and v1_account_type in (
'Google Play',
'Local',
'V2',
)
# Always show achievements except in the game-center case where
# its unified UI covers them.
show_achievements_button = self._v1_signed_in and not game_center_active
achievements_button_space = 60.0
show_achievements_text = (
@ -293,7 +335,7 @@ class AccountSettingsWindow(bui.Window):
)
achievements_text_space = 27.0
show_leaderboards_button = self._v1_signed_in and is_gpgs
show_leaderboards_button = self._v1_signed_in and gpgs_active
leaderboards_button_space = 60.0
show_campaign_progress = self._v1_signed_in
@ -330,7 +372,6 @@ class AccountSettingsWindow(bui.Window):
show_sign_out_button = self._v1_signed_in and v1_account_type in [
'Local',
'Google Play',
'V2',
]
sign_out_button_space = 70.0
@ -349,10 +390,13 @@ class AccountSettingsWindow(bui.Window):
self._sub_height = 60.0
if show_signed_in_as:
self._sub_height += signed_in_as_space
self._sub_height += via_space * len(via_lines)
if show_signing_in_text:
self._sub_height += signing_in_text_space
if show_google_play_sign_in_button:
self._sub_height += sign_in_button_space
if show_game_center_sign_in_button:
self._sub_height += sign_in_button_space
if show_v2_proxy_sign_in_button:
self._sub_height += sign_in_button_space
if show_device_sign_in_button:
@ -442,20 +486,21 @@ class AccountSettingsWindow(bui.Window):
self._account_name_what_is_text = bui.textwidget(
parent=self._subcontainer,
position=(0.0, self._account_name_what_is_y),
size=(200.0, 60),
size=(220.0, 60),
text=bui.Lstr(
value='${WHAT} -->',
subs=[('${WHAT}', bui.Lstr(resource='whatIsThisText'))],
),
scale=0.6,
color=(0.3, 0.7, 0.05),
maxwidth=200.0,
maxwidth=130.0,
h_align='right',
v_align='center',
autoselect=True,
selectable=True,
on_activate_call=show_what_is_v2_page,
click_activate=True,
glow_type='uniform',
)
if first_selectable is None:
first_selectable = self._account_name_what_is_text
@ -466,6 +511,54 @@ class AccountSettingsWindow(bui.Window):
v -= signed_in_as_space * 0.4
for via in via_lines:
v -= via_space * 0.1
sscale = 0.7
swidth = (
bui.get_string_width(via, suppress_warning=True) * sscale
)
bui.textwidget(
parent=self._subcontainer,
position=(self._sub_width * 0.5, v),
size=(0, 0),
text=via,
scale=sscale,
color=(0.6, 0.6, 0.6),
flatness=1.0,
shadow=0.0,
h_align='center',
v_align='center',
)
bui.textwidget(
parent=self._subcontainer,
position=(self._sub_width * 0.5 - swidth * 0.5 - 5, v),
size=(0, 0),
text=bui.Lstr(
value='(${VIA}',
subs=[('${VIA}', bui.Lstr(resource='viaText'))],
),
scale=0.5,
color=(0.4, 0.6, 0.4, 0.5),
flatness=1.0,
shadow=0.0,
h_align='right',
v_align='center',
)
bui.textwidget(
parent=self._subcontainer,
position=(self._sub_width * 0.5 + swidth * 0.5 + 10, v),
size=(0, 0),
text=')',
scale=0.5,
color=(0.4, 0.6, 0.4, 0.5),
flatness=1.0,
shadow=0.0,
h_align='right',
v_align='center',
)
v -= via_space * 0.9
else:
self._account_name_text = None
self._account_name_what_is_text = None
@ -477,22 +570,6 @@ class AccountSettingsWindow(bui.Window):
if show_sign_in_benefits:
v -= sign_in_benefits_space
app = bui.app
assert app.classic is not None
extra: str | bui.Lstr | None
if (
app.classic.platform in ['mac', 'ios']
and app.classic.subplatform == 'appstore'
):
extra = bui.Lstr(
value='\n${S}',
subs=[
('${S}', bui.Lstr(resource='signInWithGameCenterText'))
],
)
else:
extra = ''
bui.textwidget(
parent=self._subcontainer,
position=(
@ -500,16 +577,7 @@ class AccountSettingsWindow(bui.Window):
v + sign_in_benefits_space * 0.4,
),
size=(0, 0),
text=bui.Lstr(
value='${A}${B}',
subs=[
(
'${A}',
bui.Lstr(resource=self._r + '.signInInfoText'),
),
('${B}', extra),
],
),
text=bui.Lstr(resource=self._r + '.signInInfoText'),
max_height=sign_in_benefits_space * 0.9,
scale=0.9,
color=(0.75, 0.7, 0.8),
@ -554,7 +622,13 @@ class AccountSettingsWindow(bui.Window):
(
'${B}',
bui.Lstr(
resource=self._r + '.signInWithGooglePlayText'
resource=self._r + '.signInWithText',
subs=[
(
'${SERVICE}',
bui.Lstr(resource='googlePlayText'),
)
],
),
),
],
@ -572,6 +646,48 @@ class AccountSettingsWindow(bui.Window):
bui.widget(edit=btn, show_buffer_bottom=40, show_buffer_top=100)
self._sign_in_text = None
if show_game_center_sign_in_button:
button_width = 350
v -= sign_in_button_space
self._sign_in_google_play_button = btn = bui.buttonwidget(
parent=self._subcontainer,
position=((self._sub_width - button_width) * 0.5, v - 20),
autoselect=True,
size=(button_width, 60),
# Note: Apparently Game Center is just called 'Game Center'
# in all languages. Can revisit if not true.
# https://developer.apple.com/forums/thread/725779
label=bui.Lstr(
value='${A}${B}',
subs=[
(
'${A}',
bui.charstr(bui.SpecialChar.GAME_CENTER_LOGO),
),
(
'${B}',
bui.Lstr(
resource=self._r + '.signInWithText',
subs=[('${SERVICE}', 'Game Center')],
),
),
],
),
on_activate_call=lambda: self._sign_in_press(
LoginType.GAME_CENTER
),
)
if first_selectable is None:
first_selectable = btn
if bui.app.ui_v1.use_toolbars:
bui.widget(
edit=btn,
right_widget=bui.get_special_widget('party_button'),
)
bui.widget(edit=btn, left_widget=bbtn)
bui.widget(edit=btn, show_buffer_bottom=40, show_buffer_top=100)
self._sign_in_text = None
if show_v2_proxy_sign_in_button:
button_width = 350
v -= sign_in_button_space
@ -704,7 +820,7 @@ class AccountSettingsWindow(bui.Window):
position=((self._sub_width - button_width) * 0.5, v + 30),
autoselect=True,
size=(button_width, 60),
label=bui.Lstr(resource=self._r + '.manageAccountText'),
label=bui.Lstr(resource=f'{self._r}.manageAccountText'),
color=(0.55, 0.5, 0.6),
icon=bui.gettexture('settingsIcon'),
textcolor=(0.75, 0.7, 0.8),
@ -745,10 +861,15 @@ class AccountSettingsWindow(bui.Window):
# the button to go to OS-Specific leaderboards/high-score-lists/etc.
if show_game_service_button:
button_width = 300
v -= game_service_button_space * 0.85
v1_account_type = plus.get_v1_account_type()
if v1_account_type == 'Game Center':
v1_account_type_name = bui.Lstr(resource='gameCenterText')
v -= game_service_button_space * 0.6
if game_center_active:
# Note: Apparently Game Center is just called 'Game Center'
# in all languages. Can revisit if not true.
# https://developer.apple.com/forums/thread/725779
game_service_button_label = bui.Lstr(
value=bui.charstr(bui.SpecialChar.GAME_CENTER_LOGO)
+ 'Game Center'
)
else:
raise ValueError(
"unknown account type: '" + str(v1_account_type) + "'"
@ -761,7 +882,7 @@ class AccountSettingsWindow(bui.Window):
autoselect=True,
on_activate_call=self._on_game_service_button_press,
size=(button_width, 50),
label=v1_account_type_name,
label=game_service_button_label,
)
if first_selectable is None:
first_selectable = btn
@ -771,7 +892,7 @@ class AccountSettingsWindow(bui.Window):
right_widget=bui.get_special_widget('party_button'),
)
bui.widget(edit=btn, left_widget=bbtn)
v -= game_service_button_space * 0.15
v -= game_service_button_space * 0.4
else:
self.game_service_button = None
@ -804,13 +925,15 @@ class AccountSettingsWindow(bui.Window):
autoselect=True,
icon=bui.gettexture(
'googlePlayAchievementsIcon'
if is_gpgs
if gpgs_active
else 'achievementsIcon'
),
icon_color=(0.8, 0.95, 0.7) if is_gpgs else (0.85, 0.8, 0.9),
icon_color=(0.8, 0.95, 0.7)
if gpgs_active
else (0.85, 0.8, 0.9),
on_activate_call=(
self._on_custom_achievements_press
if is_gpgs
if gpgs_active
else self._on_achievements_press
),
size=(button_width, 50),
@ -1135,19 +1258,21 @@ class AccountSettingsWindow(bui.Window):
self._needs_refresh = False
def _on_game_service_button_press(self) -> None:
if bui.app.classic is not None:
bui.app.classic.show_online_score_ui()
if bui.app.plus is not None:
bui.app.plus.show_game_service_ui()
else:
logging.warning('game service ui not available without classic.')
logging.warning(
'game-service-ui not available without plus feature-set.'
)
def _on_custom_achievements_press(self) -> None:
if bui.app.classic is not None:
if bui.app.plus is not None:
bui.apptimer(
0.15,
bui.Call(bui.app.classic.show_online_score_ui, 'achievements'),
bui.Call(bui.app.plus.show_game_service_ui, 'achievements'),
)
else:
logging.warning('show_online_score_ui requires classic')
logging.warning('show_game_service_ui requires plus feature-set.')
def _on_achievements_press(self) -> None:
# pylint: disable=cyclic-import
@ -1162,11 +1287,21 @@ class AccountSettingsWindow(bui.Window):
show_what_is_v2_page()
def _on_manage_account_press(self) -> None:
bui.screenmessage(bui.Lstr(resource='oneMomentText'))
plus = bui.app.plus
assert plus is not None
# Preemptively fail if it looks like we won't be able to talk to
# the server anyway.
if not plus.cloud.connected:
bui.screenmessage(
bui.Lstr(resource='internal.unavailableNoConnectionText'),
color=(1, 0, 0),
)
bui.getsound('error').play()
return
bui.screenmessage(bui.Lstr(resource='oneMomentText'))
# We expect to have a v2 account signed in if we get here.
if plus.accounts.primary is None:
logging.exception(
@ -1184,6 +1319,9 @@ class AccountSettingsWindow(bui.Window):
self, response: bacommon.cloud.ManageAccountResponse | Exception
) -> None:
if isinstance(response, Exception) or response.url is None:
logging.warning(
'Got error in manage-account-response: %s.', response
)
bui.screenmessage(bui.Lstr(resource='errorText'), color=(1, 0, 0))
bui.getsound('error').play()
return
@ -1191,13 +1329,13 @@ class AccountSettingsWindow(bui.Window):
bui.open_url(response.url)
def _on_leaderboards_press(self) -> None:
if bui.app.classic is not None:
if bui.app.plus is not None:
bui.apptimer(
0.15,
bui.Call(bui.app.classic.show_online_score_ui, 'leaderboards'),
bui.Call(bui.app.plus.show_game_service_ui, 'leaderboards'),
)
else:
logging.warning('show_online_score_ui requires classic')
logging.warning('show_game_service_ui requires classic')
def _have_unlinkable_v1_accounts(self) -> bool:
plus = bui.app.plus
@ -1323,7 +1461,7 @@ class AccountSettingsWindow(bui.Window):
swidth = bui.get_string_width(name_str, suppress_warning=True)
# Eww; number-fudging. Need to recalibrate this if
# account name scaling changes.
x = self._sub_width * 0.5 - swidth * 0.75 - 170
x = self._sub_width * 0.5 - swidth * 0.75 - 190
bui.textwidget(
edit=self._account_name_what_is_text,
@ -1371,9 +1509,18 @@ class AccountSettingsWindow(bui.Window):
# pylint: disable=cyclic-import
from bauiv1lib.profile.browser import ProfileBrowserWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
self._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left')
ProfileBrowserWindow(origin_widget=self._player_profiles_button)
bui.app.ui_v1.set_main_menu_window(
ProfileBrowserWindow(
origin_widget=self._player_profiles_button
).get_root_widget(),
from_window=self._root_widget,
)
def _cancel_sign_in_press(self) -> None:
# If we're waiting on an adapter to give us credentials, abort.
@ -1466,7 +1613,11 @@ class AccountSettingsWindow(bui.Window):
if isinstance(result, Exception):
# For now just make a bit of noise if anything went wrong;
# can get more specific as needed later.
bui.screenmessage(bui.Lstr(resource='errorText'), color=(1, 0, 0))
logging.warning('Got error in v2 sign-in result: %s', result)
bui.screenmessage(
bui.Lstr(resource='internal.signInNoConnectionText'),
color=(1, 0, 0),
)
bui.getsound('error').play()
else:
# Success! Plug in these credentials which will begin
@ -1530,6 +1681,10 @@ class AccountSettingsWindow(bui.Window):
# pylint: disable=cyclic-import
from bauiv1lib.mainmenu import MainMenuWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
self._save_state()
bui.containerwidget(
edit=self._root_widget, transition=self._transition_out
@ -1538,7 +1693,8 @@ class AccountSettingsWindow(bui.Window):
if not self._modal:
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
MainMenuWindow(transition='in_left').get_root_widget()
MainMenuWindow(transition='in_left').get_root_widget(),
from_window=self._root_widget,
)
def _save_state(self) -> None:

View File

@ -62,14 +62,11 @@ class V2ProxySignInWindow(bui.Window):
label=bui.Lstr(resource='cancelText'),
on_activate_call=self._done,
autoselect=True,
color=(0.55, 0.5, 0.6),
textcolor=(0.75, 0.7, 0.8),
)
if bool(False):
bui.containerwidget(
edit=self._root_widget, cancel_button=self._cancel_button
)
bui.containerwidget(
edit=self._root_widget, cancel_button=self._cancel_button
)
self._update_timer: bui.AppTimer | None = None
@ -242,4 +239,7 @@ class V2ProxySignInWindow(bui.Window):
)
def _done(self) -> None:
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
bui.containerwidget(edit=self._root_widget, transition='out_scale')

View File

@ -93,6 +93,7 @@ class ConfigNumberEdit:
displayname: str | bui.Lstr | None = None,
changesound: bool = True,
textscale: float = 1.0,
as_percent: bool = False,
):
if displayname is None:
displayname = configkey
@ -103,6 +104,7 @@ class ConfigNumberEdit:
self._increment = increment
self._callback = callback
self._value = bui.app.config.resolve(configkey)
self._as_percent = as_percent
self.nametext = bui.textwidget(
parent=parent,
@ -166,4 +168,8 @@ class ConfigNumberEdit:
bui.app.config.apply_and_commit()
def _update_display(self) -> None:
bui.textwidget(edit=self.valuetext, text=f'{self._value:.1f}')
if self._as_percent:
val = f'{round(self._value*100.0)}%'
else:
val = f'{self._value:.1f}'
bui.textwidget(edit=self.valuetext, text=val)

View File

@ -1,80 +0,0 @@
# Released under the MIT License. See LICENSE for details.
#
"""UI for dealing with broken config files."""
from __future__ import annotations
import bauiv1 as bui
class ConfigErrorWindow(bui.Window):
"""Window for dealing with a broken config."""
def __init__(self) -> None:
self._config_file_path = bui.app.env.config_file_path
width = 800
super().__init__(
bui.containerwidget(size=(width, 400), transition='in_right')
)
padding = 20
bui.textwidget(
parent=self._root_widget,
position=(padding, 220 + 60),
size=(width - 2 * padding, 100 - 2 * padding),
h_align='center',
v_align='top',
scale=0.73,
text=(
f'Error reading {bui.appnameupper()} config file'
':\n\n\nCheck the console'
' (press ~ twice) for details.\n\nWould you like to quit and'
' try to fix it by hand\nor overwrite it with defaults?\n\n'
'(high scores, player profiles, etc will be lost if you'
' overwrite)'
),
)
bui.textwidget(
parent=self._root_widget,
position=(padding, 198 + 60),
size=(width - 2 * padding, 100 - 2 * padding),
h_align='center',
v_align='top',
scale=0.5,
text=self._config_file_path,
)
quit_button = bui.buttonwidget(
parent=self._root_widget,
position=(35, 30),
size=(240, 54),
label='Quit and Edit',
on_activate_call=self._quit,
)
bui.buttonwidget(
parent=self._root_widget,
position=(width - 370, 30),
size=(330, 54),
label='Overwrite with Defaults',
on_activate_call=self._defaults,
)
bui.containerwidget(
edit=self._root_widget,
cancel_button=quit_button,
selected_child=quit_button,
)
def _quit(self) -> None:
bui.apptimer(0.001, self._edit_and_quit)
bui.lock_all_input()
def _edit_and_quit(self) -> None:
bui.open_file_externally(self._config_file_path)
bui.apptimer(0.1, bui.quit)
def _defaults(self) -> None:
bui.containerwidget(edit=self._root_widget, transition='out_left')
bui.getsound('gunCocking').play()
bui.screenmessage('settings reset.', color=(1, 1, 0))
# At this point settings are already set; lets just commit them
# to disk.
bui.commit_app_config(force=True)

View File

@ -85,8 +85,8 @@ class CoopBrowserWindow(bui.Window):
assert bui.app.classic is not None
uiscale = bui.app.ui_v1.uiscale
self._width = 1320 if uiscale is bui.UIScale.SMALL else 1120
self._x_inset = x_inset = 100 if uiscale is bui.UIScale.SMALL else 0
self._width = 1520 if uiscale is bui.UIScale.SMALL else 1120
self._x_inset = x_inset = 200 if uiscale is bui.UIScale.SMALL else 0
self._height = (
657
if uiscale is bui.UIScale.SMALL
@ -415,7 +415,7 @@ class CoopBrowserWindow(bui.Window):
)
# Decrement time on our tournament buttons.
ads_enabled = bui.have_incentivized_ad()
ads_enabled = plus.have_incentivized_ad()
for tbtn in self._tournament_buttons:
tbtn.time_remaining = max(0, tbtn.time_remaining - 1)
if tbtn.time_remaining_value_text is not None:
@ -430,7 +430,7 @@ class CoopBrowserWindow(bui.Window):
)
# Also adjust the ad icon visibility.
if tbtn.allow_ads and bui.has_video_ads():
if tbtn.allow_ads and plus.has_video_ads():
bui.imagewidget(
edit=tbtn.entry_fee_ad_image,
opacity=1.0 if ads_enabled else 0.25,
@ -1019,6 +1019,10 @@ class CoopBrowserWindow(bui.Window):
from bauiv1lib.account import show_sign_in_prompt
from bauiv1lib.league.rankwindow import LeagueRankWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
plus = bui.app.plus
assert plus is not None
@ -1032,7 +1036,8 @@ class CoopBrowserWindow(bui.Window):
bui.app.ui_v1.set_main_menu_window(
LeagueRankWindow(
origin_widget=self._league_rank_button.get_button()
).get_root_widget()
).get_root_widget(),
from_window=self._root_widget,
)
def _switch_to_score(
@ -1043,6 +1048,10 @@ class CoopBrowserWindow(bui.Window):
# pylint: disable=cyclic-import
from bauiv1lib.account import show_sign_in_prompt
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
plus = bui.app.plus
assert plus is not None
@ -1058,7 +1067,8 @@ class CoopBrowserWindow(bui.Window):
origin_widget=self._store_button.get_button(),
show_tab=show_tab,
back_location='CoopBrowserWindow',
).get_root_widget()
).get_root_widget(),
from_window=self._root_widget,
)
def is_tourney_data_up_to_date(self) -> bool:
@ -1218,6 +1228,10 @@ class CoopBrowserWindow(bui.Window):
# pylint: disable=cyclic-import
from bauiv1lib.play import PlayWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
# If something is selected, store it.
self._save_state()
bui.containerwidget(
@ -1225,7 +1239,8 @@ class CoopBrowserWindow(bui.Window):
)
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
PlayWindow(transition='in_left').get_root_widget()
PlayWindow(transition='in_left').get_root_widget(),
from_window=self._root_widget,
)
def _save_state(self) -> None:

View File

@ -638,8 +638,8 @@ class TournamentButton:
# Now, if this fee allows ads and we support video ads, show
# the 'or ad' version.
if allow_ads and bui.has_video_ads():
ads_enabled = bui.have_incentivized_ad()
if allow_ads and plus.has_video_ads():
ads_enabled = plus.have_incentivized_ad()
bui.imagewidget(
edit=self.entry_fee_ad_image,
opacity=1.0 if ads_enabled else 0.25,

View File

@ -359,10 +359,15 @@ class CreditsListWindow(bui.Window):
def _back(self) -> None:
from bauiv1lib.mainmenu import MainMenuWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
bui.containerwidget(
edit=self._root_widget, transition=self._transition_out
)
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
MainMenuWindow(transition='in_left').get_root_widget()
MainMenuWindow(transition='in_left').get_root_widget(),
from_window=self._root_widget,
)

View File

@ -379,8 +379,13 @@ class DebugWindow(bui.Window):
# pylint: disable=cyclic-import
from bauiv1lib.settings.advanced import AdvancedSettingsWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
bui.containerwidget(edit=self._root_widget, transition='out_right')
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
AdvancedSettingsWindow(transition='in_left').get_root_widget()
AdvancedSettingsWindow(transition='in_left').get_root_widget(),
from_window=self._root_widget,
)

View File

@ -0,0 +1,133 @@
# Released under the MIT License. See LICENSE for details.
#
"""UI functionality for the Discord window."""
from __future__ import annotations
import bauiv1 as bui
class DiscordWindow(bui.Window):
"""Window for joining the Discord."""
def __init__(
self,
transition: str = 'in_right',
origin_widget: bui.Widget | None = None,
):
if bui.app.classic is None:
raise RuntimeError('This requires classic support.')
app = bui.app
assert app.classic is not None
# If they provided an origin-widget, scale up from that.
scale_origin: tuple[float, float] | None
if origin_widget is not None:
self._transition_out = 'out_scale'
scale_origin = origin_widget.get_screen_space_center()
transition = 'in_scale'
else:
self._transition_out = 'out_right'
scale_origin = None
uiscale = bui.app.ui_v1.uiscale
self._width = 800
x_inset = 100 if uiscale is bui.UIScale.SMALL else 0
self._height = 320
top_extra = 10 if uiscale is bui.UIScale.SMALL else 0
super().__init__(
root_widget=bui.containerwidget(
size=(self._width, self._height + top_extra),
transition=transition,
toolbar_visibility='menu_minimal',
scale_origin_stack_offset=scale_origin,
scale=(
1.6
if uiscale is bui.UIScale.SMALL
else 1.3
if uiscale is bui.UIScale.MEDIUM
else 1.0
),
stack_offset=(0, 5) if uiscale is bui.UIScale.SMALL else (0, 0),
)
)
if app.ui_v1.use_toolbars and uiscale is bui.UIScale.SMALL:
bui.containerwidget(
edit=self._root_widget, on_cancel_call=self._do_back
)
self._back_button = None
else:
self._back_button = bui.buttonwidget(
parent=self._root_widget,
position=(53 + x_inset, self._height - 60),
size=(140, 60),
scale=0.8,
autoselect=True,
label=bui.Lstr(resource='backText'),
button_type='back',
on_activate_call=self._do_back,
)
bui.containerwidget(
edit=self._root_widget, cancel_button=self._back_button
)
# Do we need to translate 'Discord'? Or is that always the name?
self._title_text = bui.textwidget(
parent=self._root_widget,
position=(0, self._height - 52),
size=(self._width, 25),
text='Discord',
color=app.ui_v1.title_color,
h_align='center',
v_align='top',
)
min_size = min(self._width - 25, self._height - 25)
bui.imagewidget(
parent=self._root_widget,
position=(40, -15),
size=(min_size, min_size),
texture=bui.gettexture('discordServer'),
)
# Hmm should we translate this? The discord server is mostly
# English so being able to read this might be a good screening
# process?..
bui.textwidget(
parent=self._root_widget,
position=(self._width / 2 - 60, self._height - 100),
text='We have our own Discord server where you can:\n- Find new'
' friends and people to play with\n- Participate in Office'
' Hours/Coffee with Eric\n- Share mods, plugins, art, and'
' memes\n- Report bugs and make feature suggestions\n'
'- Troubleshoot issues',
maxwidth=(self._width - 10) / 2,
color=(1, 1, 1, 1),
h_align='left',
v_align='top',
)
bui.buttonwidget(
parent=self._root_widget,
position=(self._width / 2 - 30, 20),
size=(self._width / 2 - 60, 60),
autoselect=True,
label=bui.Lstr(resource='discordJoinText'),
text_scale=1.0,
on_activate_call=bui.Call(
bui.open_url, 'https://ballistica.net/discord'
),
)
if self._back_button is not None:
bui.buttonwidget(
edit=self._back_button,
button_type='backSmall',
size=(60, 60),
label=bui.charstr(bui.SpecialChar.BACK),
)
def _do_back(self) -> None:
bui.containerwidget(edit=self._root_widget, transition='out_scale')

View File

@ -94,8 +94,8 @@ class GatherWindow(bui.Window):
bui.app.ui_v1.set_main_menu_location('Gather')
bui.set_party_icon_always_visible(True)
uiscale = bui.app.ui_v1.uiscale
self._width = 1240 if uiscale is bui.UIScale.SMALL else 1040
x_offs = 100 if uiscale is bui.UIScale.SMALL else 0
self._width = 1440 if uiscale is bui.UIScale.SMALL else 1040
x_offs = 200 if uiscale is bui.UIScale.SMALL else 0
self._height = (
582
if uiscale is bui.UIScale.SMALL
@ -270,12 +270,17 @@ class GatherWindow(bui.Window):
"""Called by the private-hosting tab to select a playlist."""
from bauiv1lib.play import PlayWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
self._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None
bui.app.ui_v1.selecting_private_party_playlist = True
bui.app.ui_v1.set_main_menu_window(
PlayWindow(origin_widget=origin_widget).get_root_widget()
PlayWindow(origin_widget=origin_widget).get_root_widget(),
from_window=self._root_widget,
)
def _set_tab(self, tab_id: TabID) -> None:
@ -383,11 +388,16 @@ class GatherWindow(bui.Window):
def _back(self) -> None:
from bauiv1lib.mainmenu import MainMenuWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
self._save_state()
bui.containerwidget(
edit=self._root_widget, transition=self._transition_out
)
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
MainMenuWindow(transition='in_left').get_root_widget()
MainMenuWindow(transition='in_left').get_root_widget(),
from_window=self._root_widget,
)

View File

@ -16,10 +16,6 @@ if TYPE_CHECKING:
class AboutGatherTab(GatherTab):
"""The about tab in the gather UI"""
def __init__(self, window: GatherWindow) -> None:
super().__init__(window)
self._container: bui.Widget | None = None
def on_activate(
self,
parent_widget: bui.Widget,
@ -29,9 +25,45 @@ class AboutGatherTab(GatherTab):
region_left: float,
region_bottom: float,
) -> bui.Widget:
# pylint: disable=too-many-locals
plus = bui.app.plus
assert plus is not None
try_tickets = plus.get_v1_account_misc_read_val(
'friendTryTickets', None
)
show_message = True
# Squish message as needed to get things to fit nicely at
# various scales.
uiscale = bui.app.ui_v1.uiscale
message_height = (
210
if uiscale is bui.UIScale.SMALL
else 305
if uiscale is bui.UIScale.MEDIUM
else 370
)
# Let's not talk about sharing in vr-mode; its tricky to fit more
# than one head in a VR-headset.
show_message_extra = not bui.app.env.vr
message_extra_height = 60
show_invite = try_tickets is not None
invite_height = 80
show_discord = True
discord_height = 80
c_height = 0
if show_message:
c_height += message_height
if show_message_extra:
c_height += message_extra_height
if show_invite:
c_height += invite_height
if show_discord:
c_height += discord_height
party_button_label = bui.charstr(bui.SpecialChar.TOP_BUTTON)
message = bui.Lstr(
resource='gatherWindow.aboutDescriptionText',
@ -41,9 +73,7 @@ class AboutGatherTab(GatherTab):
],
)
# Let's not talk about sharing in vr-mode; its tricky to fit more
# than one head in a VR-headset ;-)
if not bui.app.env.vr:
if show_message_extra:
message = bui.Lstr(
value='${A}\n\n${B}',
subs=[
@ -57,47 +87,52 @@ class AboutGatherTab(GatherTab):
),
],
)
string_height = 400
include_invite = True
msc_scale = 1.1
c_height_2 = min(region_height, string_height * msc_scale + 100)
try_tickets = plus.get_v1_account_misc_read_val(
'friendTryTickets', None
)
if try_tickets is None:
include_invite = False
self._container = bui.containerwidget(
scroll_widget = bui.scrollwidget(
parent=parent_widget,
position=(region_left, region_bottom),
size=(region_width, region_height),
highlight=False,
border_opacity=0,
)
msc_scale = 1.1
container = bui.containerwidget(
parent=scroll_widget,
position=(
region_left,
region_bottom + (region_height - c_height_2) * 0.5,
region_bottom + (region_height - c_height) * 0.5,
),
size=(region_width, c_height_2),
size=(region_width, c_height),
background=False,
selectable=include_invite,
selectable=show_invite or show_discord,
)
bui.widget(edit=self._container, up_widget=tab_button)
# Allows escaping if we select the container somehow (though
# shouldn't be possible when buttons are present).
bui.widget(edit=container, up_widget=tab_button)
bui.textwidget(
parent=self._container,
position=(
region_width * 0.5,
c_height_2 * (0.58 if include_invite else 0.5),
),
color=(0.6, 1.0, 0.6),
scale=msc_scale,
size=(0, 0),
maxwidth=region_width * 0.9,
max_height=c_height_2 * (0.7 if include_invite else 0.9),
h_align='center',
v_align='center',
text=message,
)
if include_invite:
y = c_height - 30
if show_message:
bui.textwidget(
parent=self._container,
position=(region_width * 0.57, 35),
parent=container,
position=(region_width * 0.5, y),
color=(0.6, 1.0, 0.6),
scale=msc_scale,
size=(0, 0),
maxwidth=region_width * 0.9,
max_height=message_height,
h_align='center',
v_align='top',
text=message,
)
y -= message_height
if show_message_extra:
y -= message_extra_height
if show_invite:
bui.textwidget(
parent=container,
position=(region_width * 0.57, y),
color=(0, 1, 0),
scale=0.6,
size=(0, 0),
@ -110,9 +145,9 @@ class AboutGatherTab(GatherTab):
subs=[('${COUNT}', str(try_tickets))],
),
)
bui.buttonwidget(
parent=self._container,
position=(region_width * 0.59, 10),
invite_button = bui.buttonwidget(
parent=container,
position=(region_width * 0.59, y - 25),
size=(230, 50),
color=(0.54, 0.42, 0.56),
textcolor=(0, 1, 0),
@ -124,7 +159,44 @@ class AboutGatherTab(GatherTab):
on_activate_call=bui.WeakCall(self._invite_to_try_press),
up_widget=tab_button,
)
return self._container
y -= invite_height
else:
invite_button = None
if show_discord:
bui.textwidget(
parent=container,
position=(region_width * 0.57, y),
color=(0.6, 0.6, 1),
scale=0.6,
size=(0, 0),
maxwidth=region_width * 0.5,
h_align='right',
v_align='center',
flatness=1.0,
text=bui.Lstr(resource='discordFriendsText'),
)
discord_button = bui.buttonwidget(
parent=container,
position=(region_width * 0.59, y - 25),
size=(230, 50),
color=(0.54, 0.42, 0.56),
textcolor=(0.6, 0.6, 1),
label=bui.Lstr(resource='discordJoinText'),
autoselect=True,
on_activate_call=bui.WeakCall(self._join_the_discord_press),
up_widget=(
invite_button if invite_button is not None else tab_button
),
)
y -= discord_height
else:
discord_button = None
if discord_button is not None:
pass
return scroll_widget
def _invite_to_try_press(self) -> None:
from bauiv1lib.account import show_sign_in_prompt
@ -137,3 +209,10 @@ class AboutGatherTab(GatherTab):
show_sign_in_prompt()
return
handle_app_invites_press()
def _join_the_discord_press(self) -> None:
# pylint: disable=cyclic-import
from bauiv1lib.discord import DiscordWindow
assert bui.app.classic is not None
DiscordWindow().get_root_widget()

View File

@ -99,6 +99,7 @@ class ManualGatherTab(GatherTab):
self._party_edit_name_text: bui.Widget | None = None
self._party_edit_addr_text: bui.Widget | None = None
self._party_edit_port_text: bui.Widget | None = None
self._no_parties_added_text: bui.Widget | None = None
def on_activate(
self,
@ -142,6 +143,7 @@ class ManualGatherTab(GatherTab):
playsound=True,
),
text=bui.Lstr(resource='gatherWindow.manualJoinSectionText'),
glow_type='uniform',
)
self._favorites_text = bui.textwidget(
parent=self._container,
@ -162,6 +164,7 @@ class ManualGatherTab(GatherTab):
playsound=True,
),
text=bui.Lstr(resource='gatherWindow.favoritesText'),
glow_type='uniform',
)
bui.widget(edit=self._join_by_address_text, up_widget=tab_button)
bui.widget(
@ -316,7 +319,7 @@ class ManualGatherTab(GatherTab):
self._check_button = bui.textwidget(
parent=self._container,
size=(250, 60),
text=bui.Lstr(resource='gatherWindow.' 'showMyAddressText'),
text=bui.Lstr(resource='gatherWindow.showMyAddressText'),
v_align='center',
h_align='center',
click_activate=True,
@ -331,6 +334,7 @@ class ManualGatherTab(GatherTab):
self._container,
c_width,
),
glow_type='uniform',
)
bui.widget(edit=self._check_button, up_widget=btn)
@ -453,6 +457,24 @@ class ManualGatherTab(GatherTab):
claims_left_right=True,
)
self._no_parties_added_text = bui.textwidget(
parent=self._container,
size=(0, 0),
h_align='center',
v_align='center',
text='',
color=(0.6, 0.6, 0.6),
scale=1.2,
position=(
(
(190 if uiscale is bui.UIScale.SMALL else 225)
+ sub_scroll_width * 0.5
),
v + sub_scroll_height * 0.5,
),
glow_type='uniform',
)
self._favorite_selected = None
self._refresh_favorites()
@ -695,6 +717,12 @@ class ManualGatherTab(GatherTab):
assert self._favorites_scroll_width is not None
assert self._favorites_connect_button is not None
bui.textwidget(
edit=self._no_parties_added_text,
text='',
)
num_of_fav = 0
for i, server in enumerate(servers):
txt = bui.textwidget(
parent=self._columnwidget,
@ -718,11 +746,13 @@ class ManualGatherTab(GatherTab):
)
if i == 0:
bui.widget(edit=txt, up_widget=self._favorites_text)
self._favorite_selected = server
bui.widget(
edit=txt,
left_widget=self._favorites_connect_button,
right_widget=txt,
)
num_of_fav = num_of_fav + 1
# If there's no servers, allow selecting out of the scroll area
bui.containerwidget(
@ -735,6 +765,11 @@ class ManualGatherTab(GatherTab):
up_widget=self._favorites_text,
left_widget=self._favorites_connect_button,
)
if num_of_fav == 0:
bui.textwidget(
edit=self._no_parties_added_text,
text=bui.Lstr(resource='gatherWindow.noPartiesAddedText'),
)
def on_deactivate(self) -> None:
self._access_check_timer = None
@ -800,8 +835,17 @@ class ManualGatherTab(GatherTab):
}
config.commit()
bui.getsound('gunCocking').play()
bui.screenmessage(
bui.Lstr(
resource='addedToFavoritesText', subs=[('${NAME}', addr)]
),
color=(0, 1, 0),
)
else:
bui.screenmessage('Invalid Address', color=(1, 0, 0))
bui.screenmessage(
bui.Lstr(resource='internal.invalidAddressErrorText'),
color=(1, 0, 0),
)
bui.getsound('error').play()
def _host_lookup_result(

View File

@ -120,6 +120,7 @@ class PrivateGatherTab(GatherTab):
playsound=True,
),
text=bui.Lstr(resource='gatherWindow.privatePartyJoinText'),
glow_type='uniform',
)
self._host_sub_tab_text = bui.textwidget(
parent=self._container,
@ -138,6 +139,7 @@ class PrivateGatherTab(GatherTab):
playsound=True,
),
text=bui.Lstr(resource='gatherWindow.privatePartyHostText'),
glow_type='uniform',
)
bui.widget(edit=self._join_sub_tab_text, up_widget=tab_button)
bui.widget(
@ -458,9 +460,9 @@ class PrivateGatherTab(GatherTab):
scale=1.5,
size=(300, 50),
editable=True,
max_chars=20,
description=bui.Lstr(resource='gatherWindow.partyCodeText'),
autoselect=True,
maxwidth=250,
h_align='left',
v_align='center',
text='',
@ -962,7 +964,7 @@ class PrivateGatherTab(GatherTab):
code = cast(str, bui.textwidget(query=self._join_party_code_text))
if not code:
bui.screenmessage(
bui.Lstr(resource='internal.invalidAddressErrorText'),
bui.Lstr(translate=('serverResponses', 'Invalid code.')),
color=(1, 0, 0),
)
bui.getsound('error').play()

View File

@ -114,7 +114,7 @@ class UIRow:
self._name_widget = bui.textwidget(
text=bui.Lstr(value=party.name),
parent=columnwidget,
size=(sub_scroll_width * 0.63, 20),
size=(sub_scroll_width * 0.46, 20),
position=(0 + hpos, 4 + vpos),
selectable=True,
on_select_call=bui.WeakCall(
@ -248,6 +248,7 @@ class AddrFetchThread(Thread):
self._call = call
def run(self) -> None:
sock: socket.socket | None = None
try:
# FIXME: Update this to work with IPv6 at some point.
import socket
@ -255,7 +256,6 @@ class AddrFetchThread(Thread):
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.connect(('8.8.8.8', 80))
val = sock.getsockname()[0]
sock.close()
bui.pushcall(bui.Call(self._call, val), from_other_thread=True)
except Exception as exc:
from efro.error import is_udp_communication_error
@ -265,6 +265,9 @@ class AddrFetchThread(Thread):
pass
else:
logging.exception('Error in addr-fetch-thread')
finally:
if sock is not None:
sock.close()
class PingThread(Thread):
@ -361,6 +364,7 @@ class PublicGatherTab(GatherTab):
self._last_server_list_query_time: float | None = None
self._join_list_column: bui.Widget | None = None
self._join_status_text: bui.Widget | None = None
self._no_servers_found_text: bui.Widget | None = None
self._host_max_party_size_value: bui.Widget | None = None
self._host_max_party_size_minus_button: (bui.Widget | None) = None
self._host_max_party_size_plus_button: (bui.Widget | None) = None
@ -431,6 +435,7 @@ class PublicGatherTab(GatherTab):
text=bui.Lstr(
resource='gatherWindow.' 'joinPublicPartyDescriptionText'
),
glow_type='uniform',
)
self._host_text = bui.textwidget(
parent=self._container,
@ -453,6 +458,7 @@ class PublicGatherTab(GatherTab):
text=bui.Lstr(
resource='gatherWindow.' 'hostPublicPartyDescriptionText'
),
glow_type='uniform',
)
bui.widget(edit=self._join_text, up_widget=tab_button)
bui.widget(
@ -658,6 +664,18 @@ class PublicGatherTab(GatherTab):
color=(0.6, 0.6, 0.6),
position=(c_width * 0.5, c_height * 0.5),
)
self._no_servers_found_text = bui.textwidget(
parent=self._container,
text='',
size=(0, 0),
scale=0.9,
flatness=1.0,
shadow=0.0,
h_align='center',
v_align='top',
color=(0.6, 0.6, 0.6),
position=(c_width * 0.5, c_height * 0.5),
)
def _build_host_tab(
self, region_width: float, region_height: float
@ -950,6 +968,9 @@ class PublicGatherTab(GatherTab):
self._update_party_rows()
def _update_party_rows(self) -> None:
plus = bui.app.plus
assert plus is not None
columnwidget = self._join_list_column
if not columnwidget:
return
@ -963,6 +984,7 @@ class PublicGatherTab(GatherTab):
edit=self._host_scrollwidget,
claims_up_down=(len(self._parties_displayed) > 0),
)
bui.textwidget(edit=self._no_servers_found_text, text='')
# Clip if we have more UI rows than parties to show.
clipcount = len(self._ui_rows) - len(self._parties_displayed)
@ -972,6 +994,15 @@ class PublicGatherTab(GatherTab):
# If we have no parties to show, we're done.
if not self._parties_displayed:
text = self._join_status_text
if (
plus.get_v1_account_state() == 'signed_in'
and cast(str, bui.textwidget(query=text)) == ''
):
bui.textwidget(
edit=self._no_servers_found_text,
text=bui.Lstr(resource='noServersFoundText'),
)
return
sub_scroll_width = 830

View File

@ -334,7 +334,7 @@ class GetCurrencyWindow(bui.Window):
tex_scale=1.2,
) # 19.99-ish
self._enable_ad_button = bui.has_video_ads()
self._enable_ad_button = plus.has_video_ads()
h = self._width * 0.5 + 110.0
v = self._height - b_size[1] - 115.0
@ -561,7 +561,7 @@ class GetCurrencyWindow(bui.Window):
next_reward_ad_time
)
now = datetime.datetime.utcnow()
if bui.have_incentivized_ad() and (
if plus.have_incentivized_ad() and (
next_reward_ad_time is None or next_reward_ad_time <= now
):
self._ad_button_greyed = False
@ -732,8 +732,13 @@ class GetCurrencyWindow(bui.Window):
def _back(self) -> None:
from bauiv1lib.store import browser
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
if self._transitioning_out:
return
bui.containerwidget(
edit=self._root_widget, transition=self._transition_out
)
@ -745,7 +750,9 @@ class GetCurrencyWindow(bui.Window):
).get_root_widget()
if not self._from_modal_store:
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(window)
bui.app.ui_v1.set_main_menu_window(
window, from_window=self._root_widget
)
self._transitioning_out = True

View File

@ -36,8 +36,8 @@ class HelpWindow(bui.Window):
self._main_menu = main_menu
assert bui.app.classic is not None
uiscale = bui.app.ui_v1.uiscale
width = 950 if uiscale is bui.UIScale.SMALL else 750
x_offs = 100 if uiscale is bui.UIScale.SMALL else 0
width = 1050 if uiscale is bui.UIScale.SMALL else 750
x_offs = 150 if uiscale is bui.UIScale.SMALL else 0
height = (
460
if uiscale is bui.UIScale.SMALL
@ -645,11 +645,16 @@ class HelpWindow(bui.Window):
# pylint: disable=cyclic-import
from bauiv1lib.mainmenu import MainMenuWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
bui.containerwidget(
edit=self._root_widget, transition=self._transition_out
)
if self._main_menu:
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
MainMenuWindow(transition='in_left').get_root_widget()
MainMenuWindow(transition='in_left').get_root_widget(),
from_window=self._root_widget,
)

View File

@ -9,7 +9,7 @@ from __future__ import annotations
from typing import TYPE_CHECKING
import babase
import bauiv1 as bui
if TYPE_CHECKING:
from typing import Iterable
@ -33,15 +33,15 @@ def split(chars: Iterable[str], maxlen: int) -> list[list[str]]:
def generate_emojis(maxlen: int) -> list[list[str]]:
"""Generates a lot of UTF8 emojis prepared for babase.Keyboard pages"""
"""Generates a lot of UTF8 emojis prepared for bui.Keyboard pages"""
all_emojis = split([chr(i) for i in range(0x1F601, 0x1F650)], maxlen)
all_emojis += split([chr(i) for i in range(0x2702, 0x27B1)], maxlen)
all_emojis += split([chr(i) for i in range(0x1F680, 0x1F6C1)], maxlen)
return all_emojis
# ba_meta export keyboard
class EnglishKeyboard(babase.Keyboard):
# ba_meta export bauiv1.Keyboard
class EnglishKeyboard(bui.Keyboard):
"""Default English keyboard."""
name = 'English'

View File

@ -501,9 +501,15 @@ class KioskWindow(bui.Window):
def _do_full_menu(self) -> None:
from bauiv1lib.mainmenu import MainMenuWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
assert bui.app.classic is not None
self._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left')
bui.app.classic.did_menu_intro = True # prevent delayed transition-in
bui.app.ui_v1.set_main_menu_window(MainMenuWindow().get_root_widget())
bui.app.ui_v1.set_main_menu_window(
MainMenuWindow().get_root_widget(), from_window=self._root_widget
)

View File

@ -1142,6 +1142,10 @@ class LeagueRankWindow(bui.Window):
def _back(self) -> None:
from bauiv1lib.coop.browser import CoopBrowserWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
self._save_state()
bui.containerwidget(
edit=self._root_widget, transition=self._transition_out
@ -1149,5 +1153,6 @@ class LeagueRankWindow(bui.Window):
if not self._modal:
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
CoopBrowserWindow(transition='in_left').get_root_widget()
CoopBrowserWindow(transition='in_left').get_root_widget(),
from_window=self._root_widget,
)

View File

@ -311,8 +311,8 @@ class MainMenuWindow(bui.Window):
else self._confirm_end_game
),
)
# Assume we're in a client-session.
else:
# Assume we're in a client-session.
bui.buttonwidget(
parent=self._root_widget,
position=(h - self._button_width * 0.5 * scale, v),
@ -360,7 +360,6 @@ class MainMenuWindow(bui.Window):
tilt_scale=0.0,
draw_controller=store_button,
)
self._tdelay += self._t_delay_inc
else:
self._store_button = None
@ -1039,6 +1038,10 @@ class MainMenuWindow(bui.Window):
# pylint: disable=cyclic-import
from bauiv1lib.confirm import QuitWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
# Note: Normally we should go through bui.quit(confirm=True) but
# invoking the window directly lets us scale it up from the
# button.
@ -1048,24 +1051,34 @@ class MainMenuWindow(bui.Window):
# pylint: disable=cyclic-import
from bauiv1lib.kiosk import KioskWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
self._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_right')
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
KioskWindow(transition='in_left').get_root_widget()
KioskWindow(transition='in_left').get_root_widget(),
from_window=self._root_widget,
)
def _show_account_window(self) -> None:
# pylint: disable=cyclic-import
from bauiv1lib.account.settings import AccountSettingsWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
self._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
AccountSettingsWindow(
origin_widget=self._account_button
).get_root_widget()
).get_root_widget(),
from_window=self._root_widget,
)
def _on_store_pressed(self) -> None:
@ -1073,6 +1086,10 @@ class MainMenuWindow(bui.Window):
from bauiv1lib.store.browser import StoreBrowserWindow
from bauiv1lib.account import show_sign_in_prompt
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
plus = bui.app.plus
assert plus is not None
@ -1085,7 +1102,8 @@ class MainMenuWindow(bui.Window):
bui.app.ui_v1.set_main_menu_window(
StoreBrowserWindow(
origin_widget=self._store_button
).get_root_widget()
).get_root_widget(),
from_window=self._root_widget,
)
def _is_benchmark(self) -> bool:
@ -1150,8 +1168,11 @@ class MainMenuWindow(bui.Window):
def _end_game(self) -> None:
assert bui.app.classic is not None
if not self._root_widget:
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
bui.containerwidget(edit=self._root_widget, transition='out_left')
bui.app.classic.return_to_main_menu_session_gracefully(reset_ui=False)
@ -1167,39 +1188,54 @@ class MainMenuWindow(bui.Window):
# pylint: disable=cyclic-import
from bauiv1lib.creditslist import CreditsListWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
self._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
CreditsListWindow(
origin_widget=self._credits_button
).get_root_widget()
).get_root_widget(),
from_window=self._root_widget,
)
def _howtoplay(self) -> None:
# pylint: disable=cyclic-import
from bauiv1lib.helpui import HelpWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
self._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
HelpWindow(
main_menu=True, origin_widget=self._how_to_play_button
).get_root_widget()
).get_root_widget(),
from_window=self._root_widget,
)
def _settings(self) -> None:
# pylint: disable=cyclic-import
from bauiv1lib.settings.allsettings import AllSettingsWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
self._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
AllSettingsWindow(
origin_widget=self._settings_button
).get_root_widget()
).get_root_widget(),
from_window=self._root_widget,
)
def _resume_and_call(self, call: Callable[[], Any]) -> None:
@ -1208,10 +1244,12 @@ class MainMenuWindow(bui.Window):
def _do_game_service_press(self) -> None:
self._save_state()
if bui.app.classic is not None:
bui.app.classic.show_online_score_ui()
if bui.app.plus is not None:
bui.app.plus.show_game_service_ui()
else:
logging.warning('classic is required to show game service ui')
logging.warning(
'plus feature-set is required to show game service ui'
)
def _save_state(self) -> None:
# Don't do this for the in-game menu.
@ -1282,35 +1320,50 @@ class MainMenuWindow(bui.Window):
# pylint: disable=cyclic-import
from bauiv1lib.gather import GatherWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
self._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
GatherWindow(origin_widget=self._gather_button).get_root_widget()
GatherWindow(origin_widget=self._gather_button).get_root_widget(),
from_window=self._root_widget,
)
def _watch_press(self) -> None:
# pylint: disable=cyclic-import
from bauiv1lib.watch import WatchWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
self._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
WatchWindow(origin_widget=self._watch_button).get_root_widget()
WatchWindow(origin_widget=self._watch_button).get_root_widget(),
from_window=self._root_widget,
)
def _play_press(self) -> None:
# pylint: disable=cyclic-import
from bauiv1lib.play import PlayWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
self._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None
bui.app.ui_v1.selecting_private_party_playlist = False
bui.app.ui_v1.set_main_menu_window(
PlayWindow(origin_widget=self._start_button).get_root_widget()
PlayWindow(origin_widget=self._start_button).get_root_widget(),
from_window=self._root_widget,
)
def _resume(self) -> None:
@ -1318,7 +1371,7 @@ class MainMenuWindow(bui.Window):
bui.app.classic.resume()
if self._root_widget:
bui.containerwidget(edit=self._root_widget, transition='out_right')
bui.app.ui_v1.clear_main_menu_window()
bui.app.ui_v1.clear_main_menu_window(transition='out_right')
# If there's callbacks waiting for this window to go away, call them.
for call in bui.app.ui_v1.main_menu_resume_callbacks:

View File

@ -40,6 +40,7 @@ class PartyWindow(bui.Window):
if uiscale is bui.UIScale.MEDIUM
else 600
)
self._display_old_msgs = True
super().__init__(
root_widget=bui.containerwidget(
size=(self._width, self._height),
@ -92,9 +93,10 @@ class PartyWindow(bui.Window):
iconscale=1.2,
)
info = bs.get_connection_to_host_info()
if info.get('name', '') != '':
title = bui.Lstr(value=info['name'])
info = bs.get_connection_to_host_info_2()
if info is not None and info.name != '':
title = bui.Lstr(value=info.name)
else:
title = bui.Lstr(resource=self._r + '.titleText')
@ -142,12 +144,6 @@ class PartyWindow(bui.Window):
)
self._chat_texts: list[bui.Widget] = []
# add all existing messages if chat is not muted
if not bui.app.config.resolve('Chat Muted'):
msgs = bs.get_chat_messages()
for msg in msgs:
self._add_msg(msg)
self._text_field = txt = bui.textwidget(
parent=self._root_widget,
editable=True,
@ -233,6 +229,23 @@ class PartyWindow(bui.Window):
is_muted = bui.app.config.resolve('Chat Muted')
assert bui.app.classic is not None
uiscale = bui.app.ui_v1.uiscale
choices: list[str] = ['unmute' if is_muted else 'mute']
choices_display: list[bui.Lstr] = [
bui.Lstr(resource='chatUnMuteText' if is_muted else 'chatMuteText')
]
# Allow the 'Add to Favorites' option only if we're actually
# connected to a party and if it doesn't seem to be a private
# party (those are dynamically assigned addresses and ports so
# it makes no sense to save them).
server_info = bs.get_connection_to_host_info_2()
if server_info is not None and not server_info.name.startswith(
'Private Party '
):
choices.append('add_to_favorites')
choices_display.append(bui.Lstr(resource='addToFavoritesText'))
PopupMenuWindow(
position=self._menu_button.get_screen_space_center(),
scale=(
@ -242,12 +255,8 @@ class PartyWindow(bui.Window):
if uiscale is bui.UIScale.MEDIUM
else 1.23
),
choices=['unmute' if is_muted else 'mute'],
choices_display=[
bui.Lstr(
resource='chatUnMuteText' if is_muted else 'chatMuteText'
)
],
choices=choices,
choices_display=choices_display,
current_choice='unmute' if is_muted else 'mute',
delegate=self,
)
@ -269,6 +278,12 @@ class PartyWindow(bui.Window):
first.delete()
else:
bui.textwidget(edit=self._muted_text, color=(1, 1, 1, 0.0))
# add all existing messages if chat is not muted
if self._display_old_msgs:
msgs = bs.get_chat_messages()
for msg in msgs:
self._add_msg(msg)
self._display_old_msgs = False
# update roster section
roster = bs.get_game_roster()
@ -466,10 +481,75 @@ class PartyWindow(bui.Window):
cfg = bui.app.config
cfg['Chat Muted'] = choice == 'mute'
cfg.apply_and_commit()
self._display_old_msgs = True
self._update()
if choice == 'add_to_favorites':
info = bs.get_connection_to_host_info_2()
if info is not None:
self._add_to_favorites(
name=info.name,
address=info.address,
port_num=info.port,
)
else:
# We should not allow the user to see this option
# if they aren't in a server; this is our bad.
bui.screenmessage(
bui.Lstr(resource='errorText'), color=(1, 0, 0)
)
bui.getsound('error').play()
else:
print(f'unhandled popup type: {self._popup_type}')
def _add_to_favorites(
self, name: str, address: str | None, port_num: int | None
) -> None:
addr = address
if addr == '':
bui.screenmessage(
bui.Lstr(resource='internal.invalidAddressErrorText'),
color=(1, 0, 0),
)
bui.getsound('error').play()
return
port = port_num if port_num is not None else -1
if port > 65535 or port < 0:
bui.screenmessage(
bui.Lstr(resource='internal.invalidPortErrorText'),
color=(1, 0, 0),
)
bui.getsound('error').play()
return
# Avoid empty names.
if not name:
name = f'{addr}@{port}'
config = bui.app.config
if addr:
if not isinstance(config.get('Saved Servers'), dict):
config['Saved Servers'] = {}
config['Saved Servers'][f'{addr}@{port}'] = {
'addr': addr,
'port': port,
'name': name,
}
config.commit()
bui.getsound('gunCocking').play()
bui.screenmessage(
bui.Lstr(
resource='addedToFavoritesText', subs=[('${NAME}', name)]
),
color=(0, 1, 0),
)
else:
bui.screenmessage(
bui.Lstr(resource='internal.invalidAddressErrorText'),
color=(1, 0, 0),
)
bui.getsound('error').play()
def popup_menu_closing(self, popup_window: PopupWindow) -> None:
"""Called when the popup is closing."""
@ -481,7 +561,8 @@ class PartyWindow(bui.Window):
kick_str = bui.Lstr(resource='kickText')
else:
# kick-votes appeared in build 14248
if bs.get_connection_to_host_info().get('build_number', 0) < 14248:
info = bs.get_connection_to_host_info_2()
if info is None or info.build_number < 14248:
return
kick_str = bui.Lstr(resource='kickVoteText')
assert bui.app.classic is not None
@ -510,9 +591,17 @@ class PartyWindow(bui.Window):
def close(self) -> None:
"""Close the window."""
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
bui.containerwidget(edit=self._root_widget, transition='out_scale')
def close_with_sound(self) -> None:
"""Close the window and make a lovely sound."""
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
bui.getsound('swish').play()
self.close()

View File

@ -32,8 +32,8 @@ class PlayWindow(bui.Window):
self._is_main_menu = not bui.app.ui_v1.selecting_private_party_playlist
uiscale = bui.app.ui_v1.uiscale
width = 1000 if uiscale is bui.UIScale.SMALL else 800
x_offs = 100 if uiscale is bui.UIScale.SMALL else 0
width = 1100 if uiscale is bui.UIScale.SMALL else 800
x_offs = 150 if uiscale is bui.UIScale.SMALL else 0
height = 550
button_width = 400
@ -521,13 +521,19 @@ class PlayWindow(bui.Window):
def _back(self) -> None:
# pylint: disable=cyclic-import
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
if self._is_main_menu:
from bauiv1lib.mainmenu import MainMenuWindow
self._save_state()
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
MainMenuWindow(transition='in_left').get_root_widget()
MainMenuWindow(transition='in_left').get_root_widget(),
from_window=self._root_widget,
)
bui.containerwidget(
edit=self._root_widget, transition=self._transition_out
@ -538,7 +544,8 @@ class PlayWindow(bui.Window):
self._save_state()
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
GatherWindow(transition='in_left').get_root_widget()
GatherWindow(transition='in_left').get_root_widget(),
from_window=self._root_widget,
)
bui.containerwidget(
edit=self._root_widget, transition=self._transition_out
@ -549,6 +556,10 @@ class PlayWindow(bui.Window):
from bauiv1lib.account import show_sign_in_prompt
from bauiv1lib.coop.browser import CoopBrowserWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
plus = bui.app.plus
assert plus is not None
@ -559,26 +570,38 @@ class PlayWindow(bui.Window):
bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
CoopBrowserWindow(origin_widget=self._coop_button).get_root_widget()
CoopBrowserWindow(
origin_widget=self._coop_button
).get_root_widget(),
from_window=self._root_widget,
)
def _team_tourney(self) -> None:
# pylint: disable=cyclic-import
from bauiv1lib.playlist.browser import PlaylistBrowserWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
self._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
PlaylistBrowserWindow(
origin_widget=self._teams_button, sessiontype=bs.DualTeamSession
).get_root_widget()
).get_root_widget(),
from_window=self._root_widget,
)
def _free_for_all(self) -> None:
# pylint: disable=cyclic-import
from bauiv1lib.playlist.browser import PlaylistBrowserWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
self._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None
@ -586,7 +609,8 @@ class PlayWindow(bui.Window):
PlaylistBrowserWindow(
origin_widget=self._free_for_all_button,
sessiontype=bs.FreeForAllSession,
).get_root_widget()
).get_root_widget(),
from_window=self._root_widget,
)
def _draw_dude(

View File

@ -198,7 +198,7 @@ class PlaylistAddGameWindow(bui.Window):
txt = bui.textwidget(
parent=self._column,
position=(0, 0),
size=(self._width - 88, 24),
size=(self._scroll_width * 1.1, 24),
text=gametype.get_display_string(),
h_align='left',
v_align='center',

View File

@ -62,8 +62,8 @@ class PlaylistBrowserWindow(bui.Window):
)
uiscale = bui.app.ui_v1.uiscale
self._width = 900.0 if uiscale is bui.UIScale.SMALL else 800.0
x_inset = 50 if uiscale is bui.UIScale.SMALL else 0
self._width = 1100.0 if uiscale is bui.UIScale.SMALL else 800.0
x_inset = 150 if uiscale is bui.UIScale.SMALL else 0
self._height = (
480
if uiscale is bui.UIScale.SMALL
@ -684,6 +684,10 @@ class PlaylistBrowserWindow(bui.Window):
PlaylistCustomizeBrowserWindow,
)
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
self._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None
@ -691,13 +695,18 @@ class PlaylistBrowserWindow(bui.Window):
PlaylistCustomizeBrowserWindow(
origin_widget=self._customize_button,
sessiontype=self._sessiontype,
).get_root_widget()
).get_root_widget(),
from_window=self._root_widget,
)
def _on_back_press(self) -> None:
# pylint: disable=cyclic-import
from bauiv1lib.play import PlayWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
# Store our selected playlist if that's changed.
if self._selected_playlist is not None:
prev_sel = bui.app.config.get(
@ -716,7 +725,8 @@ class PlaylistBrowserWindow(bui.Window):
)
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
PlayWindow(transition='in_left').get_root_widget()
PlayWindow(transition='in_left').get_root_widget(),
from_window=self._root_widget,
)
def _save_state(self) -> None:

View File

@ -47,8 +47,8 @@ class PlaylistCustomizeBrowserWindow(bui.Window):
self._r = 'gameListWindow'
assert bui.app.classic is not None
uiscale = bui.app.ui_v1.uiscale
self._width = 750.0 if uiscale is bui.UIScale.SMALL else 650.0
x_inset = 50.0 if uiscale is bui.UIScale.SMALL else 0.0
self._width = 850.0 if uiscale is bui.UIScale.SMALL else 650.0
x_inset = 100.0 if uiscale is bui.UIScale.SMALL else 0.0
self._height = (
380.0
if uiscale is bui.UIScale.SMALL
@ -323,6 +323,10 @@ class PlaylistCustomizeBrowserWindow(bui.Window):
# pylint: disable=cyclic-import
from bauiv1lib.playlist import browser
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
if self._selected_playlist_name is not None:
cfg = bui.app.config
cfg[
@ -337,7 +341,8 @@ class PlaylistCustomizeBrowserWindow(bui.Window):
bui.app.ui_v1.set_main_menu_window(
browser.PlaylistBrowserWindow(
transition='in_left', sessiontype=self._sessiontype
).get_root_widget()
).get_root_widget(),
from_window=self._root_widget,
)
def _select(self, name: str, index: int) -> None:

View File

@ -31,8 +31,8 @@ class PlaylistEditWindow(bui.Window):
assert bui.app.classic is not None
uiscale = bui.app.ui_v1.uiscale
self._width = 770 if uiscale is bui.UIScale.SMALL else 670
x_inset = 50 if uiscale is bui.UIScale.SMALL else 0
self._width = 870 if uiscale is bui.UIScale.SMALL else 670
x_inset = 100 if uiscale is bui.UIScale.SMALL else 0
self._height = (
400
if uiscale is bui.UIScale.SMALL
@ -283,6 +283,10 @@ class PlaylistEditWindow(bui.Window):
PlaylistCustomizeBrowserWindow,
)
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
bui.getsound('powerdown01').play()
bui.containerwidget(edit=self._root_widget, transition='out_right')
assert bui.app.classic is not None
@ -293,7 +297,8 @@ class PlaylistEditWindow(bui.Window):
select_playlist=(
self._editcontroller.get_existing_playlist_name()
),
).get_root_widget()
).get_root_widget(),
from_window=self._root_widget,
)
def _add(self) -> None:
@ -315,6 +320,10 @@ class PlaylistEditWindow(bui.Window):
PlaylistCustomizeBrowserWindow,
)
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
plus = bui.app.plus
assert plus is not None
@ -380,7 +389,8 @@ class PlaylistEditWindow(bui.Window):
transition='in_left',
sessiontype=self._editcontroller.get_session_type(),
select_playlist=new_name,
).get_root_widget()
).get_root_widget(),
from_window=self._root_widget,
)
def _save_press_with_sound(self) -> None:

View File

@ -92,7 +92,8 @@ class PlaylistEditController:
bui.app.ui_v1.set_main_menu_window(
PlaylistEditWindow(
editcontroller=self, transition=transition
).get_root_widget()
).get_root_widget(),
from_window=False, # Disable this check.
)
def get_config_name(self) -> str:
@ -150,7 +151,8 @@ class PlaylistEditController:
assert bui.app.classic is not None
bui.app.ui_v1.clear_main_menu_window(transition='out_left')
bui.app.ui_v1.set_main_menu_window(
PlaylistAddGameWindow(editcontroller=self).get_root_widget()
PlaylistAddGameWindow(editcontroller=self).get_root_widget(),
from_window=None,
)
def edit_game_pressed(self) -> None:
@ -175,7 +177,8 @@ class PlaylistEditController:
bui.app.ui_v1.set_main_menu_window(
PlaylistEditWindow(
editcontroller=self, transition='in_left'
).get_root_widget()
).get_root_widget(),
from_window=None,
)
def _show_edit_ui(
@ -205,7 +208,8 @@ class PlaylistEditController:
bui.app.ui_v1.set_main_menu_window(
PlaylistEditWindow(
editcontroller=self, transition='in_left'
).get_root_widget()
).get_root_widget(),
from_window=None,
)
# Otherwise we were adding; go back to the add type choice list.
@ -214,7 +218,8 @@ class PlaylistEditController:
bui.app.ui_v1.set_main_menu_window(
PlaylistAddGameWindow(
editcontroller=self, transition='in_left'
).get_root_widget()
).get_root_widget(),
from_window=None,
)
else:
# Make sure type is in there.
@ -236,5 +241,6 @@ class PlaylistEditController:
bui.app.ui_v1.set_main_menu_window(
PlaylistEditWindow(
editcontroller=self, transition='in_left'
).get_root_widget()
).get_root_widget(),
from_window=None,
)

View File

@ -103,8 +103,8 @@ class PlaylistEditGameWindow(bui.Window):
self._choice_selections: dict[str, int] = {}
uiscale = bui.app.ui_v1.uiscale
width = 720 if uiscale is bui.UIScale.SMALL else 620
x_inset = 50 if uiscale is bui.UIScale.SMALL else 0
width = 820 if uiscale is bui.UIScale.SMALL else 620
x_inset = 100 if uiscale is bui.UIScale.SMALL else 0
height = (
365
if uiscale is bui.UIScale.SMALL
@ -514,6 +514,10 @@ class PlaylistEditGameWindow(bui.Window):
# pylint: disable=cyclic-import
from bauiv1lib.playlist.mapselect import PlaylistMapSelectWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
# Replace ourself with the map-select UI.
bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None
@ -524,7 +528,8 @@ class PlaylistEditGameWindow(bui.Window):
copy.deepcopy(self._getconfig()),
self._edit_info,
self._completion_call,
).get_root_widget()
).get_root_widget(),
from_window=self._root_widget,
)
def _choice_inc(

View File

@ -44,8 +44,8 @@ class PlaylistMapSelectWindow(bui.Window):
assert bui.app.classic is not None
uiscale = bui.app.ui_v1.uiscale
width = 715 if uiscale is bui.UIScale.SMALL else 615
x_inset = 50 if uiscale is bui.UIScale.SMALL else 0
width = 815 if uiscale is bui.UIScale.SMALL else 615
x_inset = 100 if uiscale is bui.UIScale.SMALL else 0
height = (
400
if uiscale is bui.UIScale.SMALL
@ -273,6 +273,10 @@ class PlaylistMapSelectWindow(bui.Window):
def _select(self, map_name: str) -> None:
from bauiv1lib.playlist.editgame import PlaylistEditGameWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
self._config['settings']['map'] = map_name
bui.containerwidget(edit=self._root_widget, transition='out_right')
assert bui.app.classic is not None
@ -285,7 +289,8 @@ class PlaylistMapSelectWindow(bui.Window):
default_selection='map',
transition='in_left',
edit_info=self._edit_info,
).get_root_widget()
).get_root_widget(),
from_window=self._root_widget,
)
def _select_with_delay(self, map_name: str) -> None:
@ -296,6 +301,10 @@ class PlaylistMapSelectWindow(bui.Window):
def _cancel(self) -> None:
from bauiv1lib.playlist.editgame import PlaylistEditGameWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
bui.containerwidget(edit=self._root_widget, transition='out_right')
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
@ -307,5 +316,6 @@ class PlaylistMapSelectWindow(bui.Window):
default_selection='map',
transition='in_left',
edit_info=self._edit_info,
).get_root_widget()
).get_root_widget(),
from_window=self._root_widget,
)

View File

@ -140,7 +140,6 @@ class PlayOptionsWindow(PopupWindow):
if show_shuffle_check_box:
self._height += 40
# Creates our _root_widget.
uiscale = bui.app.ui_v1.uiscale
scale = (
1.69
@ -149,6 +148,7 @@ class PlayOptionsWindow(PopupWindow):
if uiscale is bui.UIScale.MEDIUM
else 0.85
)
# Creates our _root_widget.
super().__init__(
position=scale_origin, size=(self._width, self._height), scale=scale
)
@ -448,6 +448,10 @@ class PlayOptionsWindow(PopupWindow):
self._transition_out()
def _on_ok_press(self) -> None:
# no-op if our underlying widget is dead or on its way out.
if not self.root_widget or self.root_widget.transitioning_out:
return
# Disallow if our playlist has disappeared.
if not self._does_target_playlist_exist():
return
@ -478,8 +482,12 @@ class PlayOptionsWindow(PopupWindow):
cfg['Private Party Host Session Type'] = typename
bui.getsound('gunCocking').play()
assert bui.app.classic is not None
# Note: this is a wonky situation where we aren't actually
# the main window but we set it on behalf of the main window
# that popped us up.
bui.app.ui_v1.set_main_menu_window(
GatherWindow(transition='in_right').get_root_widget()
GatherWindow(transition='in_right').get_root_widget(),
from_window=False, # Disable this test.
)
self._transition_out(transition='out_left')
if self._delegate is not None:

View File

@ -33,8 +33,8 @@ class ProfileBrowserWindow(bui.Window):
back_label = bui.Lstr(resource='doneText')
assert bui.app.classic is not None
uiscale = bui.app.ui_v1.uiscale
self._width = 700.0 if uiscale is bui.UIScale.SMALL else 600.0
x_inset = 50.0 if uiscale is bui.UIScale.SMALL else 0.0
self._width = 800.0 if uiscale is bui.UIScale.SMALL else 600.0
x_inset = 100.0 if uiscale is bui.UIScale.SMALL else 0.0
self._height = (
360.0
if uiscale is bui.UIScale.SMALL
@ -197,8 +197,10 @@ class ProfileBrowserWindow(bui.Window):
bui.containerwidget(
edit=self._root_widget, selected_child=self._scrollwidget
)
self._columnwidget = bui.columnwidget(
parent=self._scrollwidget, border=2, margin=0
self._subcontainer = bui.containerwidget(
parent=self._scrollwidget,
size=(self._scroll_width, 32),
background=False,
)
v -= 255
self._profiles: dict[str, dict[str, Any]] | None = None
@ -212,6 +214,10 @@ class ProfileBrowserWindow(bui.Window):
from bauiv1lib.profile.edit import EditProfileWindow
from bauiv1lib.purchase import PurchaseWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
plus = bui.app.plus
assert plus is not None
@ -252,7 +258,8 @@ class ProfileBrowserWindow(bui.Window):
bui.app.ui_v1.set_main_menu_window(
EditProfileWindow(
existing_profile=None, in_main_menu=self._in_main_menu
).get_root_widget()
).get_root_widget(),
from_window=self._root_widget if self._in_main_menu else False,
)
def _delete_profile(self) -> None:
@ -301,6 +308,10 @@ class ProfileBrowserWindow(bui.Window):
# pylint: disable=cyclic-import
from bauiv1lib.profile.edit import EditProfileWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
if self._selected_profile is None:
bui.getsound('error').play()
bui.screenmessage(
@ -313,7 +324,8 @@ class ProfileBrowserWindow(bui.Window):
bui.app.ui_v1.set_main_menu_window(
EditProfileWindow(
self._selected_profile, in_main_menu=self._in_main_menu
).get_root_widget()
).get_root_widget(),
from_window=self._root_widget if self._in_main_menu else False,
)
def _select(self, name: str, index: int) -> None:
@ -324,6 +336,10 @@ class ProfileBrowserWindow(bui.Window):
# pylint: disable=cyclic-import
from bauiv1lib.account.settings import AccountSettingsWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
assert bui.app.classic is not None
self._save_state()
@ -333,7 +349,8 @@ class ProfileBrowserWindow(bui.Window):
if self._in_main_menu:
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
AccountSettingsWindow(transition='in_left').get_root_widget()
AccountSettingsWindow(transition='in_left').get_root_widget(),
from_window=self._root_widget,
)
# If we're being called up standalone, handle pause/resume ourself.
@ -342,8 +359,10 @@ class ProfileBrowserWindow(bui.Window):
def _refresh(self) -> None:
# pylint: disable=too-many-locals
# pylint: disable=too-many-statements
from efro.util import asserttype
from bascenev1 import PlayerProfilesChangedMessage
from bascenev1lib.actor import spazappearance
assert bui.app.classic is not None
@ -359,14 +378,27 @@ class ProfileBrowserWindow(bui.Window):
assert self._profiles is not None
items = list(self._profiles.items())
items.sort(key=lambda x: asserttype(x[0], str).lower())
spazzes = spazappearance.get_appearances()
spazzes.sort()
icon_textures = [
bui.gettexture(bui.app.classic.spaz_appearances[s].icon_texture)
for s in spazzes
]
icon_tint_textures = [
bui.gettexture(
bui.app.classic.spaz_appearances[s].icon_mask_texture
)
for s in spazzes
]
index = 0
y_val = 35 * (len(self._profiles) - 1)
account_name: str | None
if plus.get_v1_account_state() == 'signed_in':
account_name = plus.get_v1_account_display_string()
else:
account_name = None
widget_to_select = None
for p_name, _ in items:
for p_name, p_info in items:
if p_name == '__account__' and account_name is None:
continue
color, _highlight = bui.app.classic.get_player_profile_colors(
@ -378,16 +410,35 @@ class ProfileBrowserWindow(bui.Window):
if p_name == '__account__'
else bui.app.classic.get_player_profile_icon(p_name) + p_name
)
try:
char_index = spazzes.index(p_info['character'])
except Exception:
char_index = spazzes.index('Spaz')
assert isinstance(tval, str)
character = bui.buttonwidget(
parent=self._subcontainer,
position=(0, y_val),
size=(28, 28),
label='',
color=(1, 1, 1),
mask_texture=bui.gettexture('characterIconMask'),
tint_color=color,
tint2_color=_highlight,
texture=icon_textures[char_index],
tint_texture=icon_tint_textures[char_index],
selectable=False,
)
txtw = bui.textwidget(
parent=self._columnwidget,
position=(0, 32),
size=((self._width - 40) / scl, 28),
parent=self._subcontainer,
position=(35, y_val),
size=((self._width - 210) / scl, 28),
text=bui.Lstr(value=tval),
h_align='left',
v_align='center',
on_select_call=bui.WeakCall(self._select, p_name, index),
maxwidth=self._scroll_width * 0.92,
maxwidth=self._scroll_width * 0.86,
corner_scale=scl,
color=bui.safecolor(color, 0.4),
always_highlight=True,
@ -396,8 +447,11 @@ class ProfileBrowserWindow(bui.Window):
)
if index == 0:
bui.widget(edit=txtw, up_widget=self._back_button)
if self._selected_profile is None:
self._selected_profile = p_name
bui.widget(edit=txtw, show_buffer_top=40, show_buffer_bottom=40)
self._profile_widgets.append(txtw)
self._profile_widgets.append(character)
# Select/show this one if it was previously selected
# (but defer till after this loop since our height is
@ -406,10 +460,15 @@ class ProfileBrowserWindow(bui.Window):
widget_to_select = txtw
index += 1
y_val -= 35
bui.containerwidget(
edit=self._subcontainer,
size=(self._scroll_width, index * 35),
)
if widget_to_select is not None:
bui.columnwidget(
edit=self._columnwidget,
bui.containerwidget(
edit=self._subcontainer,
selected_child=widget_to_select,
visible_child=widget_to_select,
)

View File

@ -18,12 +18,18 @@ class EditProfileWindow(bui.Window):
# FIXME: WILL NEED TO CHANGE THIS FOR UILOCATION.
def reload_window(self) -> None:
"""Transitions out and recreates ourself."""
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
EditProfileWindow(
self.getname(), self._in_main_menu
).get_root_widget()
).get_root_widget(),
from_window=self._root_widget,
)
def __init__(
@ -54,8 +60,8 @@ class EditProfileWindow(bui.Window):
self._highlight,
) = bui.app.classic.get_player_profile_colors(existing_profile)
uiscale = bui.app.ui_v1.uiscale
self._width = width = 780.0 if uiscale is bui.UIScale.SMALL else 680.0
self._x_inset = x_inset = 50.0 if uiscale is bui.UIScale.SMALL else 0.0
self._width = width = 880.0 if uiscale is bui.UIScale.SMALL else 680.0
self._x_inset = x_inset = 100.0 if uiscale is bui.UIScale.SMALL else 0.0
self._height = height = (
350.0
if uiscale is bui.UIScale.SMALL
@ -184,7 +190,7 @@ class EditProfileWindow(bui.Window):
self._clipped_name_text = bui.textwidget(
parent=self._root_widget,
text='',
position=(540 + x_inset, v - 8),
position=(580 + x_inset, v - 8),
flatness=1.0,
shadow=0.0,
scale=0.55,
@ -390,6 +396,16 @@ class EditProfileWindow(bui.Window):
autoselect=True,
on_activate_call=self.upgrade_profile,
)
self._random_name_button = bui.buttonwidget(
parent=self._root_widget,
label=bui.Lstr(resource='randomText'),
size=(30, 20),
position=(495 + x_inset, v - 20),
button_type='square',
color=(0.6, 0.5, 0.65),
autoselect=True,
on_activate_call=self.assign_random_name,
)
self._update_clipped_name()
self._clipped_name_timer = bui.AppTimer(
@ -498,8 +514,17 @@ class EditProfileWindow(bui.Window):
)
self._update_character()
def assign_random_name(self) -> None:
"""Assigning a random name to the player."""
names = bs.get_random_names()
name = names[random.randrange(len(names))]
bui.textwidget(
edit=self._text_field,
text=name,
)
def upgrade_profile(self) -> None:
"""Attempt to ugrade the profile to global."""
"""Attempt to upgrade the profile to global."""
from bauiv1lib import account
from bauiv1lib.profile import upgrade as pupgrade
@ -653,6 +678,10 @@ class EditProfileWindow(bui.Window):
def _cancel(self) -> None:
from bauiv1lib.profile.browser import ProfileBrowserWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
bui.containerwidget(edit=self._root_widget, transition='out_right')
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
@ -660,7 +689,8 @@ class EditProfileWindow(bui.Window):
'in_left',
selected_profile=self._existing_profile,
in_main_menu=self._in_main_menu,
).get_root_widget()
).get_root_widget(),
from_window=self._root_widget,
)
def _set_color(self, color: tuple[float, float, float]) -> None:
@ -759,6 +789,10 @@ class EditProfileWindow(bui.Window):
"""Save has been selected."""
from bauiv1lib.profile.browser import ProfileBrowserWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return False
plus = bui.app.plus
assert plus is not None
@ -808,6 +842,7 @@ class EditProfileWindow(bui.Window):
'in_left',
selected_profile=new_name,
in_main_menu=self._in_main_menu,
).get_root_widget()
).get_root_widget(),
from_window=self._root_widget,
)
return True

View File

@ -26,7 +26,7 @@ class PromoCodeWindow(bui.Window):
transition = 'in_right'
width = 450
height = 230
height = 330
self._modal = modal
self._r = 'promoCodeWindow'
@ -62,17 +62,50 @@ class PromoCodeWindow(bui.Window):
iconscale=1.2,
)
v = height - 74
bui.textwidget(
parent=self._root_widget,
text=bui.Lstr(resource='codesExplainText'),
maxwidth=width * 0.9,
position=(width * 0.5, v),
color=(0.7, 0.7, 0.7, 1.0),
size=(0, 0),
scale=0.8,
h_align='center',
v_align='center',
)
v -= 60
bui.textwidget(
parent=self._root_widget,
text=bui.Lstr(
resource='supportEmailText',
subs=[('${EMAIL}', 'support@froemling.net')],
),
maxwidth=width * 0.9,
position=(width * 0.5, v),
color=(0.7, 0.7, 0.7, 1.0),
size=(0, 0),
scale=0.65,
h_align='center',
v_align='center',
)
v -= 80
bui.textwidget(
parent=self._root_widget,
text=bui.Lstr(resource=self._r + '.codeText'),
position=(22, height - 113),
position=(22, v),
color=(0.8, 0.8, 0.8, 1.0),
size=(90, 30),
h_align='right',
)
v -= 8
self._text_field = bui.textwidget(
parent=self._root_widget,
position=(125, height - 121),
position=(125, v),
size=(280, 46),
text='',
h_align='left',
@ -86,10 +119,11 @@ class PromoCodeWindow(bui.Window):
)
bui.widget(edit=btn, down_widget=self._text_field)
v -= 79
b_width = 200
self._enter_button = btn2 = bui.buttonwidget(
parent=self._root_widget,
position=(width * 0.5 - b_width * 0.5, height - 200),
position=(width * 0.5 - b_width * 0.5, v),
size=(b_width, 60),
scale=1.0,
label=bui.Lstr(
@ -108,13 +142,18 @@ class PromoCodeWindow(bui.Window):
# pylint: disable=cyclic-import
from bauiv1lib.settings.advanced import AdvancedSettingsWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
bui.containerwidget(
edit=self._root_widget, transition=self._transition_out
)
if not self._modal:
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
AdvancedSettingsWindow(transition='in_left').get_root_widget()
AdvancedSettingsWindow(transition='in_left').get_root_widget(),
from_window=self._root_widget,
)
def _activate_enter_button(self) -> None:
@ -124,6 +163,10 @@ class PromoCodeWindow(bui.Window):
# pylint: disable=cyclic-import
from bauiv1lib.settings.advanced import AdvancedSettingsWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
plus = bui.app.plus
assert plus is not None
@ -133,7 +176,8 @@ class PromoCodeWindow(bui.Window):
if not self._modal:
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
AdvancedSettingsWindow(transition='in_left').get_root_widget()
AdvancedSettingsWindow(transition='in_left').get_root_widget(),
from_window=self._root_widget,
)
plus.add_v1_account_transaction(
{

View File

@ -47,8 +47,8 @@ class AdvancedSettingsWindow(bui.Window):
scale_origin = None
uiscale = bui.app.ui_v1.uiscale
self._width = 870.0 if uiscale is bui.UIScale.SMALL else 670.0
x_inset = 100 if uiscale is bui.UIScale.SMALL else 0
self._width = 970.0 if uiscale is bui.UIScale.SMALL else 670.0
x_inset = 150 if uiscale is bui.UIScale.SMALL else 0
self._height = (
390.0
if uiscale is bui.UIScale.SMALL
@ -682,11 +682,16 @@ class AdvancedSettingsWindow(bui.Window):
def _on_vr_test_press(self) -> None:
from bauiv1lib.settings.vrtesting import VRTestingWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
self._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
VRTestingWindow(transition='in_right').get_root_widget()
VRTestingWindow(transition='in_right').get_root_widget(),
from_window=self._root_widget,
)
def _on_net_test_press(self) -> None:
@ -694,6 +699,10 @@ class AdvancedSettingsWindow(bui.Window):
assert plus is not None
from bauiv1lib.settings.nettesting import NetTestingWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
# Net-testing requires a signed in v1 account.
if plus.get_v1_account_state() != 'signed_in':
bui.screenmessage(
@ -706,7 +715,8 @@ class AdvancedSettingsWindow(bui.Window):
bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
NetTestingWindow(transition='in_right').get_root_widget()
NetTestingWindow(transition='in_right').get_root_widget(),
from_window=self._root_widget,
)
def _on_friend_promo_code_press(self) -> None:
@ -724,17 +734,26 @@ class AdvancedSettingsWindow(bui.Window):
def _on_plugins_button_press(self) -> None:
from bauiv1lib.settings.plugins import PluginWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
self._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
PluginWindow(origin_widget=self._plugins_button).get_root_widget()
PluginWindow(origin_widget=self._plugins_button).get_root_widget(),
from_window=self._root_widget,
)
def _on_promo_code_press(self) -> None:
from bauiv1lib.promocode import PromoCodeWindow
from bauiv1lib.account import show_sign_in_prompt
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
plus = bui.app.plus
assert plus is not None
@ -742,23 +761,30 @@ class AdvancedSettingsWindow(bui.Window):
if plus.get_v1_account_state() != 'signed_in':
show_sign_in_prompt()
return
self._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
PromoCodeWindow(
origin_widget=self._promo_code_button
).get_root_widget()
).get_root_widget(),
from_window=self._root_widget,
)
def _on_benchmark_press(self) -> None:
from bauiv1lib.debug import DebugWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
self._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
DebugWindow(transition='in_right').get_root_widget()
DebugWindow(transition='in_right').get_root_widget(),
from_window=self._root_widget,
)
def _save_state(self) -> None:
@ -807,6 +833,8 @@ class AdvancedSettingsWindow(bui.Window):
sel_name = 'ModdingGuide'
elif sel == self._language_inform_checkbox:
sel_name = 'LangInform'
elif sel == self._show_dev_console_button_check_box.widget:
sel_name = 'ShowDevConsole'
else:
raise ValueError(f'unrecognized selection \'{sel}\'')
elif sel == self._back_button:
@ -870,6 +898,8 @@ class AdvancedSettingsWindow(bui.Window):
sel = self._modding_guide_button
elif sel_name == 'LangInform':
sel = self._language_inform_checkbox
elif sel_name == 'ShowDevConsole':
sel = self._show_dev_console_button_check_box.widget
else:
sel = None
if sel is not None:
@ -904,11 +934,16 @@ class AdvancedSettingsWindow(bui.Window):
def _do_back(self) -> None:
from bauiv1lib.settings.allsettings import AllSettingsWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
self._save_state()
bui.containerwidget(
edit=self._root_widget, transition=self._transition_out
)
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
AllSettingsWindow(transition='in_left').get_root_widget()
AllSettingsWindow(transition='in_left').get_root_widget(),
from_window=self._root_widget,
)

View File

@ -40,8 +40,8 @@ class AllSettingsWindow(bui.Window):
scale_origin = None
assert bui.app.classic is not None
uiscale = bui.app.ui_v1.uiscale
width = 900 if uiscale is bui.UIScale.SMALL else 580
x_inset = 75 if uiscale is bui.UIScale.SMALL else 0
width = 1000 if uiscale is bui.UIScale.SMALL else 580
x_inset = 125 if uiscale is bui.UIScale.SMALL else 0
height = 435
self._r = 'settingsWindow'
top_extra = 20 if uiscale is bui.UIScale.SMALL else 0
@ -235,65 +235,90 @@ class AllSettingsWindow(bui.Window):
# pylint: disable=cyclic-import
from bauiv1lib.mainmenu import MainMenuWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
self._save_state()
bui.containerwidget(
edit=self._root_widget, transition=self._transition_out
)
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
MainMenuWindow(transition='in_left').get_root_widget()
MainMenuWindow(transition='in_left').get_root_widget(),
from_window=self._root_widget,
)
def _do_controllers(self) -> None:
# pylint: disable=cyclic-import
from bauiv1lib.settings.controls import ControlsSettingsWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
self._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
ControlsSettingsWindow(
origin_widget=self._controllers_button
).get_root_widget()
).get_root_widget(),
from_window=self._root_widget,
)
def _do_graphics(self) -> None:
# pylint: disable=cyclic-import
from bauiv1lib.settings.graphics import GraphicsSettingsWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
self._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
GraphicsSettingsWindow(
origin_widget=self._graphics_button
).get_root_widget()
).get_root_widget(),
from_window=self._root_widget,
)
def _do_audio(self) -> None:
# pylint: disable=cyclic-import
from bauiv1lib.settings.audio import AudioSettingsWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
self._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
AudioSettingsWindow(
origin_widget=self._audio_button
).get_root_widget()
).get_root_widget(),
from_window=self._root_widget,
)
def _do_advanced(self) -> None:
# pylint: disable=cyclic-import
from bauiv1lib.settings.advanced import AdvancedSettingsWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
self._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
AdvancedSettingsWindow(
origin_widget=self._advanced_button
).get_root_widget()
).get_root_widget(),
from_window=self._root_widget,
)
def _save_state(self) -> None:

View File

@ -121,7 +121,8 @@ class AudioSettingsWindow(bui.Window):
displayname=bui.Lstr(resource=self._r + '.soundVolumeText'),
minval=0.0,
maxval=1.0,
increment=0.1,
increment=0.05,
as_percent=True,
)
if bui.app.ui_v1.use_toolbars:
bui.widget(
@ -137,9 +138,10 @@ class AudioSettingsWindow(bui.Window):
displayname=bui.Lstr(resource=self._r + '.musicVolumeText'),
minval=0.0,
maxval=1.0,
increment=0.1,
increment=0.05,
callback=music.music_volume_changed,
changesound=False,
as_percent=True,
)
v -= 0.5 * spacing
@ -235,6 +237,10 @@ class AudioSettingsWindow(bui.Window):
# pylint: disable=cyclic-import
from bauiv1lib.soundtrack import browser as stb
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
# We require disk access for soundtracks;
# if we don't have it, request it.
if not bui.have_permission(bui.Permission.STORAGE):
@ -254,13 +260,18 @@ class AudioSettingsWindow(bui.Window):
bui.app.ui_v1.set_main_menu_window(
stb.SoundtrackBrowserWindow(
origin_widget=self._soundtrack_button
).get_root_widget()
).get_root_widget(),
from_window=self._root_widget,
)
def _back(self) -> None:
# pylint: disable=cyclic-import
from bauiv1lib.settings import allsettings
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
self._save_state()
bui.containerwidget(
edit=self._root_widget, transition=self._transition_out
@ -269,7 +280,8 @@ class AudioSettingsWindow(bui.Window):
bui.app.ui_v1.set_main_menu_window(
allsettings.AllSettingsWindow(
transition='in_left'
).get_root_widget()
).get_root_widget(),
from_window=self._root_widget,
)
def _save_state(self) -> None:

View File

@ -98,9 +98,11 @@ class ControlsSettingsWindow(bui.Window):
# made-for-iOS/Mac systems
# (we can run into problems where devices register as one of each
# type otherwise)..
# UPDATE: We always use the apple system these days (which should
# support older controllers). So no need for a switch.
show_mac_controller_subsystem = False
if platform == 'mac' and bui.is_xcode_build():
show_mac_controller_subsystem = True
# if platform == 'mac' and bui.is_xcode_build():
# show_mac_controller_subsystem = True
if show_mac_controller_subsystem:
height += spacing * 1.5
@ -311,6 +313,7 @@ class ControlsSettingsWindow(bui.Window):
maxwidth=width * 0.8,
)
v -= spacing
if show_mac_controller_subsystem:
PopupMenu(
parent=self._root_widget,
@ -364,59 +367,84 @@ class ControlsSettingsWindow(bui.Window):
# pylint: disable=cyclic-import
from bauiv1lib.settings.keyboard import ConfigKeyboardWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
self._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
ConfigKeyboardWindow(
bs.getinputdevice('Keyboard', '#1')
).get_root_widget()
).get_root_widget(),
from_window=self._root_widget,
)
def _config_keyboard2(self) -> None:
# pylint: disable=cyclic-import
from bauiv1lib.settings.keyboard import ConfigKeyboardWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
self._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
ConfigKeyboardWindow(
bs.getinputdevice('Keyboard', '#2')
).get_root_widget()
).get_root_widget(),
from_window=self._root_widget,
)
def _do_mobile_devices(self) -> None:
# pylint: disable=cyclic-import
from bauiv1lib.settings.remoteapp import RemoteAppSettingsWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
self._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
RemoteAppSettingsWindow().get_root_widget()
RemoteAppSettingsWindow().get_root_widget(),
from_window=self._root_widget,
)
def _do_gamepads(self) -> None:
# pylint: disable=cyclic-import
from bauiv1lib.settings.gamepadselect import GamepadSelectWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
self._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
GamepadSelectWindow().get_root_widget()
GamepadSelectWindow().get_root_widget(),
from_window=self._root_widget,
)
def _do_touchscreen(self) -> None:
# pylint: disable=cyclic-import
from bauiv1lib.settings.touchscreen import TouchscreenSettingsWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
self._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
TouchscreenSettingsWindow().get_root_widget()
TouchscreenSettingsWindow().get_root_widget(),
from_window=self._root_widget,
)
def _save_state(self) -> None:
@ -463,11 +491,16 @@ class ControlsSettingsWindow(bui.Window):
# pylint: disable=cyclic-import
from bauiv1lib.settings.allsettings import AllSettingsWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
self._save_state()
bui.containerwidget(
edit=self._root_widget, transition=self._transition_out
)
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
AllSettingsWindow(transition='in_left').get_root_widget()
AllSettingsWindow(transition='in_left').get_root_widget(),
from_window=self._root_widget,
)

View File

@ -545,20 +545,24 @@ class GamepadSettingsWindow(bui.Window):
if 'analogStickLR' + self._ext in self._settings
else 5
if self._is_secondary
else 1
else None
)
sval2 = (
self._settings['analogStickUD' + self._ext]
if 'analogStickUD' + self._ext in self._settings
else 6
if self._is_secondary
else 2
)
return (
self._input.get_axis_name(sval1)
+ ' / '
+ self._input.get_axis_name(sval2)
else None
)
assert isinstance(sval1, (int, type(None)))
assert isinstance(sval2, (int, type(None)))
if sval1 is not None and sval2 is not None:
return (
self._input.get_axis_name(sval1)
+ ' / '
+ self._input.get_axis_name(sval2)
)
return bui.Lstr(resource=self._r + '.unsetText')
# If they're looking for triggers.
if control in ['triggerRun1' + self._ext, 'triggerRun2' + self._ext]:
@ -573,7 +577,7 @@ class GamepadSettingsWindow(bui.Window):
return str(1.0)
# For dpad buttons: show individual buttons if any are set.
# Otherwise show whichever dpad is set (defaulting to 1).
# Otherwise show whichever dpad is set.
dpad_buttons = [
'buttonLeft' + self._ext,
'buttonRight' + self._ext,
@ -588,24 +592,28 @@ class GamepadSettingsWindow(bui.Window):
return bui.Lstr(resource=self._r + '.unsetText')
# No dpad buttons - show the dpad number for all 4.
return bui.Lstr(
value='${A} ${B}',
subs=[
('${A}', bui.Lstr(resource=self._r + '.dpadText')),
(
'${B}',
str(
self._settings['dpad' + self._ext]
if 'dpad' + self._ext in self._settings
else 2
if self._is_secondary
else 1
),
),
],
dpadnum = (
self._settings['dpad' + self._ext]
if 'dpad' + self._ext in self._settings
else 2
if self._is_secondary
else None
)
assert isinstance(dpadnum, (int, type(None)))
if dpadnum is not None:
return bui.Lstr(
value='${A} ${B}',
subs=[
('${A}', bui.Lstr(resource=self._r + '.dpadText')),
(
'${B}',
str(dpadnum),
),
],
)
return bui.Lstr(resource=self._r + '.unsetText')
# other buttons..
# Other buttons.
if control in self._settings:
return self._input.get_button_name(self._settings[control])
return bui.Lstr(resource=self._r + '.unsetText')
@ -616,9 +624,7 @@ class GamepadSettingsWindow(bui.Window):
event: dict[str, Any],
dialog: AwaitGamepadInputWindow,
) -> None:
# pylint: disable=too-many-nested-blocks
# pylint: disable=too-many-branches
# pylint: disable=too-many-statements
assert self._settings is not None
ext = self._ext
@ -648,10 +654,6 @@ class GamepadSettingsWindow(bui.Window):
if btn in self._settings:
del self._settings[btn]
if event['hat'] == (2 if self._is_secondary else 1):
# Exclude value in default case.
if 'dpad' + ext in self._settings:
del self._settings['dpad' + ext]
else:
self._settings['dpad' + ext] = event['hat']
# Update the 4 dpad button txt widgets.
@ -680,10 +682,6 @@ class GamepadSettingsWindow(bui.Window):
if abs(event['value']) > 0.5:
axis = event['axis']
if axis == (5 if self._is_secondary else 1):
# Exclude value in default case.
if 'analogStickLR' + ext in self._settings:
del self._settings['analogStickLR' + ext]
else:
self._settings['analogStickLR' + ext] = axis
bui.textwidget(
edit=self._textwidgets['analogStickLR' + ext],
@ -713,10 +711,6 @@ class GamepadSettingsWindow(bui.Window):
lr_axis = 5 if self._is_secondary else 1
if axis != lr_axis:
if axis == (6 if self._is_secondary else 2):
# Exclude value in default case.
if 'analogStickUD' + ext in self._settings:
del self._settings['analogStickUD' + ext]
else:
self._settings['analogStickUD' + ext] = axis
bui.textwidget(
edit=self._textwidgets['analogStickLR' + ext],
@ -795,25 +789,34 @@ class GamepadSettingsWindow(bui.Window):
),
)
bui.apptimer(0, doit)
bui.pushcall(doit)
return btn
def _cancel(self) -> None:
from bauiv1lib.settings.controls import ControlsSettingsWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
bui.containerwidget(
edit=self._root_widget, transition=self._transition_out
)
if self._is_main_menu:
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
ControlsSettingsWindow(transition='in_left').get_root_widget()
ControlsSettingsWindow(transition='in_left').get_root_widget(),
from_window=self._root_widget,
)
def _save(self) -> None:
classic = bui.app.classic
assert classic is not None
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
bui.containerwidget(
edit=self._root_widget, transition=self._transition_out
)
@ -858,7 +861,8 @@ class GamepadSettingsWindow(bui.Window):
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
ControlsSettingsWindow(transition='in_left').get_root_widget()
ControlsSettingsWindow(transition='in_left').get_root_widget(),
from_window=self._root_widget,
)

View File

@ -452,7 +452,7 @@ class GamepadAdvancedSettingsWindow(bui.Window):
),
)
bui.apptimer(0, doit)
bui.pushcall(doit)
return btn, btn2
def _inc(

View File

@ -29,16 +29,17 @@ def gamepad_configure_callback(event: dict[str, Any]) -> None:
logging.exception('Error transitioning out main_menu_window.')
bui.getsound('activateBeep').play()
bui.getsound('swish').play()
inputdevice = event['input_device']
assert isinstance(inputdevice, bs.InputDevice)
if inputdevice.allows_configuring:
device = event['input_device']
assert isinstance(device, bs.InputDevice)
if device.allows_configuring:
bui.app.ui_v1.set_main_menu_window(
gamepad.GamepadSettingsWindow(inputdevice).get_root_widget()
gamepad.GamepadSettingsWindow(device).get_root_widget(),
from_window=None,
)
else:
width = 700
height = 200
button_width = 100
button_width = 80
uiscale = bui.app.ui_v1.uiscale
dlg = bui.containerwidget(
scale=(
@ -51,9 +52,14 @@ def gamepad_configure_callback(event: dict[str, Any]) -> None:
size=(width, height),
transition='in_right',
)
bui.app.ui_v1.set_main_menu_window(dlg)
device_name = inputdevice.name
if device_name == 'iDevice':
bui.app.ui_v1.set_main_menu_window(dlg, from_window=None)
if device.allows_configuring_in_system_settings:
msg = bui.Lstr(
resource='configureDeviceInSystemSettingsText',
subs=[('${DEVICE}', device.name)],
)
elif device.is_controller_app:
msg = bui.Lstr(
resource='bsRemoteConfigureInAppText',
subs=[('${REMOTE_APP_NAME}', bui.get_remote_app_name())],
@ -61,7 +67,7 @@ def gamepad_configure_callback(event: dict[str, Any]) -> None:
else:
msg = bui.Lstr(
resource='cantConfigureDeviceText',
subs=[('${DEVICE}', device_name)],
subs=[('${DEVICE}', device.name)],
)
bui.textwidget(
parent=dlg,
@ -76,12 +82,17 @@ def gamepad_configure_callback(event: dict[str, Any]) -> None:
def _ok() -> None:
from bauiv1lib.settings import controls
# no-op if our underlying widget is dead or on its way out.
if not dlg or dlg.transitioning_out:
return
bui.containerwidget(edit=dlg, transition='out_right')
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
controls.ControlsSettingsWindow(
transition='in_left'
).get_root_widget()
).get_root_widget(),
from_window=dlg,
)
bui.buttonwidget(
@ -186,11 +197,16 @@ class GamepadSelectWindow(bui.Window):
def _back(self) -> None:
from bauiv1lib.settings import controls
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
bs.release_gamepad_input()
bui.containerwidget(edit=self._root_widget, transition='out_right')
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
controls.ControlsSettingsWindow(
transition='in_left'
).get_root_widget()
).get_root_widget(),
from_window=self._root_widget,
)

View File

@ -52,7 +52,7 @@ class GraphicsSettingsWindow(bui.Window):
self._show_fullscreen = False
fullscreen_spacing_top = spacing * 0.2
fullscreen_spacing = spacing * 1.2
if bui.can_toggle_fullscreen():
if bui.fullscreen_control_available():
self._show_fullscreen = True
height += fullscreen_spacing + fullscreen_spacing_top
@ -122,21 +122,29 @@ class GraphicsSettingsWindow(bui.Window):
self._fullscreen_checkbox: bui.Widget | None = None
if self._show_fullscreen:
v -= fullscreen_spacing_top
self._fullscreen_checkbox = ConfigCheckBox(
# Fullscreen control does not necessarily talk to the
# app config so we have to wrangle it manually instead of
# using a config-checkbox.
label = bui.Lstr(resource=f'{self._r}.fullScreenText')
# Show keyboard shortcut alongside the control if they
# provide one.
shortcut = bui.fullscreen_control_key_shortcut()
if shortcut is not None:
label = bui.Lstr(
value='$(NAME) [$(SHORTCUT)]',
subs=[('$(NAME)', label), ('$(SHORTCUT)', shortcut)],
)
self._fullscreen_checkbox = bui.checkboxwidget(
parent=self._root_widget,
position=(100, v),
maxwidth=200,
value=bui.fullscreen_control_get(),
on_value_change_call=bui.fullscreen_control_set,
maxwidth=250,
size=(300, 30),
configkey='Fullscreen',
displayname=bui.Lstr(
resource=self._r
+ (
'.fullScreenCmdText'
if app.classic.platform == 'mac'
else '.fullScreenCtrlText'
)
),
).widget
text=label,
)
if not self._have_selected_child:
bui.containerwidget(
edit=self._root_widget,
@ -259,9 +267,7 @@ class GraphicsSettingsWindow(bui.Window):
bui.Lstr(resource='nativeText'),
]
for res in [1440, 1080, 960, 720, 480]:
# Nav bar is 72px so lets allow for that in what
# choices we show.
if native_res[1] >= res - 72:
if native_res[1] >= res:
res_str = f'{res}p'
choices.append(res_str)
choices_display.append(bui.Lstr(value=res_str))
@ -430,6 +436,10 @@ class GraphicsSettingsWindow(bui.Window):
def _back(self) -> None:
from bauiv1lib.settings import allsettings
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
# Applying max-fps takes a few moments. Apply if it hasn't been
# yet.
self._apply_max_fps()
@ -441,7 +451,8 @@ class GraphicsSettingsWindow(bui.Window):
bui.app.ui_v1.set_main_menu_window(
allsettings.AllSettingsWindow(
transition='in_left'
).get_root_widget()
).get_root_widget(),
from_window=self._root_widget,
)
def _set_quality(self, quality: str) -> None:
@ -528,8 +539,10 @@ class GraphicsSettingsWindow(bui.Window):
and bui.apptime() - self._last_max_fps_set_time > 1.0
):
self._apply_max_fps()
if self._show_fullscreen:
# Keep the fullscreen checkbox up to date with the current value.
bui.checkboxwidget(
edit=self._fullscreen_checkbox,
value=bui.app.config.resolve('Fullscreen'),
value=bui.fullscreen_control_get(),
)

View File

@ -213,6 +213,12 @@ class ConfigKeyboardWindow(bui.Window):
scale=1.0,
)
def _pretty_button_name(self, button_name: str) -> bui.Lstr:
button_id = self._settings[button_name]
if button_id == -1:
return bs.Lstr(resource='configGamepadWindow.unsetText')
return self._input.get_button_name(button_id)
def _capture_button(
self,
pos: tuple[float, float],
@ -250,7 +256,7 @@ class ConfigKeyboardWindow(bui.Window):
v_align='top',
scale=uiscale,
maxwidth=maxwidth,
text=self._input.get_button_name(self._settings[button]),
text=self._pretty_button_name(button),
)
bui.buttonwidget(
edit=btn,
@ -265,15 +271,24 @@ class ConfigKeyboardWindow(bui.Window):
def _cancel(self) -> None:
from bauiv1lib.settings.controls import ControlsSettingsWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
bui.containerwidget(edit=self._root_widget, transition='out_right')
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
ControlsSettingsWindow(transition='in_left').get_root_widget()
ControlsSettingsWindow(transition='in_left').get_root_widget(),
from_window=self._root_widget,
)
def _save(self) -> None:
from bauiv1lib.settings.controls import ControlsSettingsWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
assert bui.app.classic is not None
bui.containerwidget(edit=self._root_widget, transition='out_right')
bui.getsound('gunCocking').play()
@ -308,7 +323,8 @@ class ConfigKeyboardWindow(bui.Window):
)
bui.app.config.apply_and_commit()
bui.app.ui_v1.set_main_menu_window(
ControlsSettingsWindow(transition='in_left').get_root_widget()
ControlsSettingsWindow(transition='in_left').get_root_widget(),
from_window=self._root_widget,
)

Some files were not shown because too many files have changed in this diff Show More