Initial 1.6 builds with private party front-end in place

This commit is contained in:
Eric Froemling 2021-01-26 05:00:34 -08:00
parent ebaf83c502
commit 2a66b5b93f
61 changed files with 1531 additions and 527 deletions

View File

@ -420,7 +420,7 @@
"assets/build/ba_data/audio/zoeOw.ogg": "https://files.ballistica.net/cache/ba1/a9/71/9286d55c45c37877f3267850f90b", "assets/build/ba_data/audio/zoeOw.ogg": "https://files.ballistica.net/cache/ba1/a9/71/9286d55c45c37877f3267850f90b",
"assets/build/ba_data/audio/zoePickup01.ogg": "https://files.ballistica.net/cache/ba1/2f/09/36e691de67eb8f155449a7170861", "assets/build/ba_data/audio/zoePickup01.ogg": "https://files.ballistica.net/cache/ba1/2f/09/36e691de67eb8f155449a7170861",
"assets/build/ba_data/audio/zoeScream01.ogg": "https://files.ballistica.net/cache/ba1/fd/a8/ad50785ce206e8dc3dcc7358b173", "assets/build/ba_data/audio/zoeScream01.ogg": "https://files.ballistica.net/cache/ba1/fd/a8/ad50785ce206e8dc3dcc7358b173",
"assets/build/ba_data/data/langdata.json": "https://files.ballistica.net/cache/ba1/34/93/4989950b5dd035da056a0291dbb2", "assets/build/ba_data/data/langdata.json": "https://files.ballistica.net/cache/ba1/c2/36/59ab3af6b45307c79ba8c296df5b",
"assets/build/ba_data/data/languages/arabic.json": "https://files.ballistica.net/cache/ba1/0d/25/26de912f111de8189f40aeeddee6", "assets/build/ba_data/data/languages/arabic.json": "https://files.ballistica.net/cache/ba1/0d/25/26de912f111de8189f40aeeddee6",
"assets/build/ba_data/data/languages/belarussian.json": "https://files.ballistica.net/cache/ba1/44/ed/5b972fa848cffb73723533c2ccb7", "assets/build/ba_data/data/languages/belarussian.json": "https://files.ballistica.net/cache/ba1/44/ed/5b972fa848cffb73723533c2ccb7",
"assets/build/ba_data/data/languages/chinese.json": "https://files.ballistica.net/cache/ba1/6a/20/57d91c24e0aeb1fe6b7d6dbbcac9", "assets/build/ba_data/data/languages/chinese.json": "https://files.ballistica.net/cache/ba1/6a/20/57d91c24e0aeb1fe6b7d6dbbcac9",
@ -429,27 +429,27 @@
"assets/build/ba_data/data/languages/czech.json": "https://files.ballistica.net/cache/ba1/ce/de/b0f462205cdf687a875abc6f39bd", "assets/build/ba_data/data/languages/czech.json": "https://files.ballistica.net/cache/ba1/ce/de/b0f462205cdf687a875abc6f39bd",
"assets/build/ba_data/data/languages/danish.json": "https://files.ballistica.net/cache/ba1/3f/46/e4da3c1d2b0ebf916df55c608b28", "assets/build/ba_data/data/languages/danish.json": "https://files.ballistica.net/cache/ba1/3f/46/e4da3c1d2b0ebf916df55c608b28",
"assets/build/ba_data/data/languages/dutch.json": "https://files.ballistica.net/cache/ba1/d1/07/37b7adc3dbec7328d26c5325f212", "assets/build/ba_data/data/languages/dutch.json": "https://files.ballistica.net/cache/ba1/d1/07/37b7adc3dbec7328d26c5325f212",
"assets/build/ba_data/data/languages/english.json": "https://files.ballistica.net/cache/ba1/3f/ee/72767c1e922d3f2cf668147ef143", "assets/build/ba_data/data/languages/english.json": "https://files.ballistica.net/cache/ba1/66/d1/8adfe1479fe6b4c30cd0d0e694d6",
"assets/build/ba_data/data/languages/esperanto.json": "https://files.ballistica.net/cache/ba1/6e/fd/685a4e1da031474d47a1d9eb2731", "assets/build/ba_data/data/languages/esperanto.json": "https://files.ballistica.net/cache/ba1/6e/fd/685a4e1da031474d47a1d9eb2731",
"assets/build/ba_data/data/languages/french.json": "https://files.ballistica.net/cache/ba1/18/b2/9c1f6e3ca6e18d6a6fdbdb14e224", "assets/build/ba_data/data/languages/french.json": "https://files.ballistica.net/cache/ba1/18/b2/9c1f6e3ca6e18d6a6fdbdb14e224",
"assets/build/ba_data/data/languages/german.json": "https://files.ballistica.net/cache/ba1/19/ba/b12493cfaa28d27f9bfee0459e20", "assets/build/ba_data/data/languages/german.json": "https://files.ballistica.net/cache/ba1/19/ba/b12493cfaa28d27f9bfee0459e20",
"assets/build/ba_data/data/languages/gibberish.json": "https://files.ballistica.net/cache/ba1/82/9f/bdcff3ac022c125a7232a412568e", "assets/build/ba_data/data/languages/gibberish.json": "https://files.ballistica.net/cache/ba1/43/f3/9e88a199337b7913cb5e7961b1c6",
"assets/build/ba_data/data/languages/greek.json": "https://files.ballistica.net/cache/ba1/51/31/64479524c0ee990b3e97ffdca068", "assets/build/ba_data/data/languages/greek.json": "https://files.ballistica.net/cache/ba1/51/31/64479524c0ee990b3e97ffdca068",
"assets/build/ba_data/data/languages/hindi.json": "https://files.ballistica.net/cache/ba1/ed/98/37d9457755f7e86e2f2875e3b055", "assets/build/ba_data/data/languages/hindi.json": "https://files.ballistica.net/cache/ba1/ed/98/37d9457755f7e86e2f2875e3b055",
"assets/build/ba_data/data/languages/hungarian.json": "https://files.ballistica.net/cache/ba1/87/2d/027aa239eb66ea8f496562f4fd83", "assets/build/ba_data/data/languages/hungarian.json": "https://files.ballistica.net/cache/ba1/87/2d/027aa239eb66ea8f496562f4fd83",
"assets/build/ba_data/data/languages/indonesian.json": "https://files.ballistica.net/cache/ba1/a4/01/1fcc28b303858b3d028d26516907", "assets/build/ba_data/data/languages/indonesian.json": "https://files.ballistica.net/cache/ba1/a4/01/1fcc28b303858b3d028d26516907",
"assets/build/ba_data/data/languages/italian.json": "https://files.ballistica.net/cache/ba1/20/ca/d675783cd094030a625e7ce023cf", "assets/build/ba_data/data/languages/italian.json": "https://files.ballistica.net/cache/ba1/6b/5a/6e8e3692347d9ba01aff607af5a8",
"assets/build/ba_data/data/languages/korean.json": "https://files.ballistica.net/cache/ba1/0a/84/bbb6ed2abf66509406f534cbbb52", "assets/build/ba_data/data/languages/korean.json": "https://files.ballistica.net/cache/ba1/0a/84/bbb6ed2abf66509406f534cbbb52",
"assets/build/ba_data/data/languages/persian.json": "https://files.ballistica.net/cache/ba1/f8/e6/773d3da1cbdb2215956f9d99c348", "assets/build/ba_data/data/languages/persian.json": "https://files.ballistica.net/cache/ba1/f8/e6/773d3da1cbdb2215956f9d99c348",
"assets/build/ba_data/data/languages/polish.json": "https://files.ballistica.net/cache/ba1/29/72/bcf75316f71373a47739a72ad6da", "assets/build/ba_data/data/languages/polish.json": "https://files.ballistica.net/cache/ba1/29/72/bcf75316f71373a47739a72ad6da",
"assets/build/ba_data/data/languages/portuguese.json": "https://files.ballistica.net/cache/ba1/3d/ba/7f4c8846babc5ef59187e87efa3d", "assets/build/ba_data/data/languages/portuguese.json": "https://files.ballistica.net/cache/ba1/29/7d/be03fa23b8d80404b1b56c305edc",
"assets/build/ba_data/data/languages/romanian.json": "https://files.ballistica.net/cache/ba1/44/3c/7cc06ca8d5475e1687d0ed05bdbf", "assets/build/ba_data/data/languages/romanian.json": "https://files.ballistica.net/cache/ba1/44/3c/7cc06ca8d5475e1687d0ed05bdbf",
"assets/build/ba_data/data/languages/russian.json": "https://files.ballistica.net/cache/ba1/07/42/382831beefb1f134eed95d18b20b", "assets/build/ba_data/data/languages/russian.json": "https://files.ballistica.net/cache/ba1/07/42/382831beefb1f134eed95d18b20b",
"assets/build/ba_data/data/languages/serbian.json": "https://files.ballistica.net/cache/ba1/05/5b/910b8963f48cd494dc01d5a18a5c", "assets/build/ba_data/data/languages/serbian.json": "https://files.ballistica.net/cache/ba1/05/5b/910b8963f48cd494dc01d5a18a5c",
"assets/build/ba_data/data/languages/slovak.json": "https://files.ballistica.net/cache/ba1/b7/0a/fab820b96e7aa587ee56427ecdc2", "assets/build/ba_data/data/languages/slovak.json": "https://files.ballistica.net/cache/ba1/b7/0a/fab820b96e7aa587ee56427ecdc2",
"assets/build/ba_data/data/languages/spanish.json": "https://files.ballistica.net/cache/ba1/58/3b/2bdbad7748ab0c76b9cf3d65c70e", "assets/build/ba_data/data/languages/spanish.json": "https://files.ballistica.net/cache/ba1/e0/83/32c5bc6544b647dbc599c11123df",
"assets/build/ba_data/data/languages/swedish.json": "https://files.ballistica.net/cache/ba1/50/9f/be006ba19be6a69a57837eb6dca0", "assets/build/ba_data/data/languages/swedish.json": "https://files.ballistica.net/cache/ba1/50/9f/be006ba19be6a69a57837eb6dca0",
"assets/build/ba_data/data/languages/turkish.json": "https://files.ballistica.net/cache/ba1/19/fe/c97df315575d999ad2bb63478b5f", "assets/build/ba_data/data/languages/turkish.json": "https://files.ballistica.net/cache/ba1/2d/a3/114ca23a3fb0f2885bfbbabb9727",
"assets/build/ba_data/data/languages/ukrainian.json": "https://files.ballistica.net/cache/ba1/66/b0/e1d71e57673a6fc78e0ea181b76b", "assets/build/ba_data/data/languages/ukrainian.json": "https://files.ballistica.net/cache/ba1/66/b0/e1d71e57673a6fc78e0ea181b76b",
"assets/build/ba_data/data/languages/venetian.json": "https://files.ballistica.net/cache/ba1/e4/3e/243eaa0237361b984fc6c56042be", "assets/build/ba_data/data/languages/venetian.json": "https://files.ballistica.net/cache/ba1/e4/3e/243eaa0237361b984fc6c56042be",
"assets/build/ba_data/data/languages/vietnamese.json": "https://files.ballistica.net/cache/ba1/04/52/683a27aaf9aa7c63e7e595f80d08", "assets/build/ba_data/data/languages/vietnamese.json": "https://files.ballistica.net/cache/ba1/04/52/683a27aaf9aa7c63e7e595f80d08",
@ -3932,40 +3932,40 @@
"assets/build/windows/Win32/ucrtbased.dll": "https://files.ballistica.net/cache/ba1/b5/85/f8b6d0558ddb87267f34254b1450", "assets/build/windows/Win32/ucrtbased.dll": "https://files.ballistica.net/cache/ba1/b5/85/f8b6d0558ddb87267f34254b1450",
"assets/build/windows/Win32/vc_redist.x86.exe": "https://files.ballistica.net/cache/ba1/1c/e1/4a1a2eddda2f4aebd5f8b64ab08e", "assets/build/windows/Win32/vc_redist.x86.exe": "https://files.ballistica.net/cache/ba1/1c/e1/4a1a2eddda2f4aebd5f8b64ab08e",
"assets/build/windows/Win32/vcruntime140d.dll": "https://files.ballistica.net/cache/ba1/50/8d/bc2600ac9491f1b14d659709451f", "assets/build/windows/Win32/vcruntime140d.dll": "https://files.ballistica.net/cache/ba1/50/8d/bc2600ac9491f1b14d659709451f",
"build/prefab/full/linux_arm64/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/1c/5f/2e5338ab577aa7dac9942dda3d43", "build/prefab/full/linux_arm64/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/3c/61/69d59f46d61b3aeee054aa5daab1",
"build/prefab/full/linux_arm64/release/ballisticacore": "https://files.ballistica.net/cache/ba1/50/bb/7a9daf22e3a09c6ab40f6b6909f2", "build/prefab/full/linux_arm64/release/ballisticacore": "https://files.ballistica.net/cache/ba1/d2/41/e8362cfe9f6fd6dc882858021874",
"build/prefab/full/linux_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/e5/6f/10a4fc55ee08a4f6e1d8f3b87422", "build/prefab/full/linux_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/83/ff/29cf07587f212d5278bb5ed92e75",
"build/prefab/full/linux_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/f3/fa/ba9a4fd942854e6d330079a5d5b2", "build/prefab/full/linux_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/90/e7/c4834a3b41d8f9d837071dc0800a",
"build/prefab/full/linux_x86_64/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/68/5d/09e80048115eaaa434ffe85a7f8f", "build/prefab/full/linux_x86_64/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/ce/3d/0febcd4db42fe5dedb701c60bddf",
"build/prefab/full/linux_x86_64/release/ballisticacore": "https://files.ballistica.net/cache/ba1/60/0e/804d130b536483882ee3f4299375", "build/prefab/full/linux_x86_64/release/ballisticacore": "https://files.ballistica.net/cache/ba1/c5/d8/3d7ca6668af72200a6eea00c6d84",
"build/prefab/full/linux_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/73/15/f45a3b15110a0cc0493ef17fae4c", "build/prefab/full/linux_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/79/f2/3381ea14e11854a9e8804953bb06",
"build/prefab/full/linux_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/55/e7/618d3ecd8072a67d07b1c1098369", "build/prefab/full/linux_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/80/00/a02fdfe67796bc04a78dbd6ed353",
"build/prefab/full/mac_arm64/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/54/31/bce32b4e1aef2b4bd27dc9336619", "build/prefab/full/mac_arm64/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/19/36/ebf5f0f1c7a2923f7e3c4ee292ad",
"build/prefab/full/mac_arm64/release/ballisticacore": "https://files.ballistica.net/cache/ba1/bf/5a/45640c9cdda02fdffd1c9b3beebe", "build/prefab/full/mac_arm64/release/ballisticacore": "https://files.ballistica.net/cache/ba1/fb/4f/fbddddae2d5963a1ec6f97234db3",
"build/prefab/full/mac_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/ed/ab/1d70d046b60626e74ab6a6d8d733", "build/prefab/full/mac_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/0d/32/8755d570c2e74316497bdd290f0d",
"build/prefab/full/mac_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/cb/fb/135c187787329c51b6770a5f5135", "build/prefab/full/mac_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/24/30/863f0c9948669219f66b117502bc",
"build/prefab/full/mac_x86_64/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/1a/1d/fb869851bf6e54e215d1447d1b68", "build/prefab/full/mac_x86_64/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/3c/dc/317d57caa8b27d5b685193cf3de9",
"build/prefab/full/mac_x86_64/release/ballisticacore": "https://files.ballistica.net/cache/ba1/64/99/58dfc239e6fdac005d583287507f", "build/prefab/full/mac_x86_64/release/ballisticacore": "https://files.ballistica.net/cache/ba1/cd/8a/3fc0cca1383b6af2a4f8a5ae2cfe",
"build/prefab/full/mac_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/00/a6/2727ba6b98c46e4ab1414be214a7", "build/prefab/full/mac_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/20/16/c0cf342b1971dcbc757abd29bdc5",
"build/prefab/full/mac_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/a9/47/954ea39895cad0354cd1b39e4436", "build/prefab/full/mac_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/1b/06/f81485b10ac37b58a012bc158952",
"build/prefab/full/windows_x86/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/ac/62/9c8551a51d8f458e59e856cd4a3a", "build/prefab/full/windows_x86/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/85/44/92172591b72302dc4909b0e91108",
"build/prefab/full/windows_x86/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/75/e9/9674fac783d2827e5daf33b91afc", "build/prefab/full/windows_x86/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/d4/e6/8d5a9ffcf32588f8b770a88a80d5",
"build/prefab/full/windows_x86_server/debug/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/f2/46/c17af441ceb12cd41895c8482475", "build/prefab/full/windows_x86_server/debug/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/c7/9c/e55d58caf88ad6bb88f1a5ebfdbf",
"build/prefab/full/windows_x86_server/release/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/c8/16/000156a1d2728de92b7bcdaa1f22", "build/prefab/full/windows_x86_server/release/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/f2/6e/981553869590e8367854b5df2802",
"build/prefab/lib/linux_arm64/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/65/e2/82162e0ab11caf2634692d2d7a4e", "build/prefab/lib/linux_arm64/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/34/21/016123d9ec8293ce92ba910dd00a",
"build/prefab/lib/linux_arm64/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/6f/4d/baa3f14360c0d379ccb5ae330295", "build/prefab/lib/linux_arm64/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/6f/f8/cf46e7c33a0a237e2c6f19ef4f92",
"build/prefab/lib/linux_arm64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/c9/4a/167038e8603b915bc6bdebc886a5", "build/prefab/lib/linux_arm64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/10/67/5a3d6131d1b1fdf9f629301b68db",
"build/prefab/lib/linux_arm64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/fa/a3/3f9a8d8856f9db3340adc5488d9b", "build/prefab/lib/linux_arm64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/6b/bc/72d32c36d51acb255b2667c4d386",
"build/prefab/lib/linux_x86_64/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/ef/d0/49b99ce32e5e2b01b056fbac5c67", "build/prefab/lib/linux_x86_64/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/7c/9b/af5e799cb4d296074598f11a063f",
"build/prefab/lib/linux_x86_64/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/83/25/980050d75bbea49a84652209050c", "build/prefab/lib/linux_x86_64/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/35/7c/b5f26ca01907df6ea80ff3ae5861",
"build/prefab/lib/linux_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/47/aa/e82233695a50974e7e22db4e7146", "build/prefab/lib/linux_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/8c/76/9ea770a68773fd4a08fb78855fbb",
"build/prefab/lib/linux_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/2b/45/7f9fbae208890455fce2fbc172d3", "build/prefab/lib/linux_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/1d/b3/329956f15f5cb76a63bd4fd3e0cc",
"build/prefab/lib/mac_arm64/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/fc/75/8ea8b8eeb9a1c47c534bfb9e5d3f", "build/prefab/lib/mac_arm64/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/30/04/7b7c4f847fd992b23719f755981a",
"build/prefab/lib/mac_arm64/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/69/a9/01af1b4a126cf517e9bbbfade412", "build/prefab/lib/mac_arm64/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/bc/20/e13686c62052e3e388431d4859aa",
"build/prefab/lib/mac_arm64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/d5/f5/7c740da86b84653a3d7b23cb11d7", "build/prefab/lib/mac_arm64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/a3/a1/1bb8a1926628e34054aeca5a0178",
"build/prefab/lib/mac_arm64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/47/64/f4a9fb9a0c338dd0daa0dc0e52a6", "build/prefab/lib/mac_arm64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/5a/33/c232d2c633bf70915e1a8eecdf95",
"build/prefab/lib/mac_x86_64/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/74/66/c94da5b860d0581b20c9679d183f", "build/prefab/lib/mac_x86_64/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/84/8c/2930553d642210c81b6342bffb9f",
"build/prefab/lib/mac_x86_64/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/0f/c1/90db918c2dce94bd94725b25b2b4", "build/prefab/lib/mac_x86_64/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/68/22/cc580cef75b9e452de66095ba62b",
"build/prefab/lib/mac_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/0f/49/4269f4e88a55e6712841b7b56f5a", "build/prefab/lib/mac_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/93/2d/b6482a7ce6957156fe050f4cc9dc",
"build/prefab/lib/mac_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/f7/6c/fe17cf1a3f98cacbe946549631c5" "build/prefab/lib/mac_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/d1/a2/1d592f5d21dd39d7b16da8f1c8c4"
} }

View File

@ -45,6 +45,7 @@
<w>adbcfaca</w> <w>adbcfaca</w>
<w>adbpath</w> <w>adbpath</w>
<w>addcall</w> <w>addcall</w>
<w>addchars</w>
<w>addgame</w> <w>addgame</w>
<w>addlevel</w> <w>addlevel</w>
<w>addr</w> <w>addr</w>
@ -98,6 +99,7 @@
<w>archs</w> <w>archs</w>
<w>argh</w> <w>argh</w>
<w>argparse</w> <w>argparse</w>
<w>argsjoined</w>
<w>argtypes</w> <w>argtypes</w>
<w>argval</w> <w>argval</w>
<w>armeabi</w> <w>armeabi</w>
@ -146,6 +148,7 @@
<w>autoretain</w> <w>autoretain</w>
<w>autoselect</w> <w>autoselect</w>
<w>autotools</w> <w>autotools</w>
<w>availmins</w>
<w>availplug</w> <w>availplug</w>
<w>aval</w> <w>aval</w>
<w>axismotion</w> <w>axismotion</w>
@ -244,6 +247,7 @@
<w>bsuffix</w> <w>bsuffix</w>
<w>bsui</w> <w>bsui</w>
<w>btnh</w> <w>btnh</w>
<w>btnlabel</w>
<w>btnv</w> <w>btnv</w>
<w>btnx</w> <w>btnx</w>
<w>btype</w> <w>btype</w>
@ -279,6 +283,7 @@
<w>cameraflash</w> <w>cameraflash</w>
<w>camerashake</w> <w>camerashake</w>
<w>campaignname</w> <w>campaignname</w>
<w>cancelbtn</w>
<w>capb</w> <w>capb</w>
<w>capturetheflag</w> <w>capturetheflag</w>
<w>carentity</w> <w>carentity</w>
@ -286,6 +291,7 @@
<w>cbits</w> <w>cbits</w>
<w>cbot</w> <w>cbot</w>
<w>cbtn</w> <w>cbtn</w>
<w>cbtnoffs</w>
<w>ccfgs</w> <w>ccfgs</w>
<w>ccode</w> <w>ccode</w>
<w>ccompiler</w> <w>ccompiler</w>
@ -433,6 +439,7 @@
<w>crashlytics</w> <w>crashlytics</w>
<w>creationflags</w> <w>creationflags</w>
<w>creditslist</w> <w>creditslist</w>
<w>cresult</w>
<w>cryptmodule</w> <w>cryptmodule</w>
<w>cspbd</w> <w>cspbd</w>
<w>cspnf</w> <w>cspnf</w>
@ -782,6 +789,7 @@
<w>freeforallendscreen</w> <w>freeforallendscreen</w>
<w>freeforallsession</w> <w>freeforallsession</w>
<w>freeforallvictory</w> <w>freeforallvictory</w>
<w>freemins</w>
<w>freepik</w> <w>freepik</w>
<w>freesound</w> <w>freesound</w>
<w>froemling</w> <w>froemling</w>
@ -966,6 +974,7 @@
<w>homebrew</w> <w>homebrew</w>
<w>hometest</w> <w>hometest</w>
<w>hostconfig</w> <w>hostconfig</w>
<w>hostingstate</w>
<w>hostuser</w> <w>hostuser</w>
<w>hout</w> <w>hout</w>
<w>howtoplay</w> <w>howtoplay</w>
@ -1201,6 +1210,7 @@
<w>locationlist</w> <w>locationlist</w>
<w>locationsingles</w> <w>locationsingles</w>
<w>locationval</w> <w>locationval</w>
<w>lockpath</w>
<w>lockstr</w> <w>lockstr</w>
<w>locktype</w> <w>locktype</w>
<w>locs</w> <w>locs</w>
@ -1417,6 +1427,7 @@
<w>nosynctool</w> <w>nosynctool</w>
<w>nosynctools</w> <w>nosynctools</w>
<w>notdir</w> <w>notdir</w>
<w>nowtickets</w>
<w>npos</w> <w>npos</w>
<w>nprocessors</w> <w>nprocessors</w>
<w>ntpath</w> <w>ntpath</w>
@ -1439,6 +1450,8 @@
<w>oculus</w> <w>oculus</w>
<w>oenval</w> <w>oenval</w>
<w>offsanchor</w> <w>offsanchor</w>
<w>offsx</w>
<w>offsy</w>
<w>ofval</w> <w>ofval</w>
<w>oggenc</w> <w>oggenc</w>
<w>oghash</w> <w>oghash</w>
@ -1591,6 +1604,7 @@
<w>powervr</w> <w>powervr</w>
<w>ppos</w> <w>ppos</w>
<w>pproxy</w> <w>pproxy</w>
<w>pptabcom</w>
<w>pragmas</w> <w>pragmas</w>
<w>prch</w> <w>prch</w>
<w>prec</w> <w>prec</w>
@ -1619,6 +1633,7 @@
<w>printobjects</w> <w>printobjects</w>
<w>printpaths</w> <w>printpaths</w>
<w>priv</w> <w>priv</w>
<w>privatetab</w>
<w>proactor</w> <w>proactor</w>
<w>proc</w> <w>proc</w>
<w>procs</w> <w>procs</w>
@ -1628,6 +1643,7 @@
<w>profilenames</w> <w>profilenames</w>
<w>proj</w> <w>proj</w>
<w>projconfig</w> <w>projconfig</w>
<w>projdir</w>
<w>projectpath</w> <w>projectpath</w>
<w>projectroot</w> <w>projectroot</w>
<w>projroot</w> <w>projroot</w>
@ -1751,6 +1767,7 @@
<w>reqtype</w> <w>reqtype</w>
<w>reqtypes</w> <w>reqtypes</w>
<w>resample</w> <w>resample</w>
<w>resetbtn</w>
<w>resetinput</w> <w>resetinput</w>
<w>resourcetypeinfo</w> <w>resourcetypeinfo</w>
<w>respawn</w> <w>respawn</w>
@ -1802,6 +1819,7 @@
<w>samsung</w> <w>samsung</w>
<w>sandboxing</w> <w>sandboxing</w>
<w>sandyrb</w> <w>sandyrb</w>
<w>savebtn</w>
<w>savebutton</w> <w>savebutton</w>
<w>saxutils</w> <w>saxutils</w>
<w>sbblk</w> <w>sbblk</w>
@ -1844,7 +1862,11 @@
<w>sdkcheck</w> <w>sdkcheck</w>
<w>sdkutils</w> <w>sdkutils</w>
<w>sdtk</w> <w>sdtk</w>
<w>selchild</w>
<w>selectmodule</w> <w>selectmodule</w>
<w>selindex</w>
<w>selwidget</w>
<w>selwidgets</w>
<w>senze</w> <w>senze</w>
<w>seqtype</w> <w>seqtype</w>
<w>seqtypestr</w> <w>seqtypestr</w>
@ -2145,6 +2167,7 @@
<w>this'll</w> <w>this'll</w>
<w>threadtype</w> <w>threadtype</w>
<w>throwiness</w> <w>throwiness</w>
<w>ticon</w>
<w>timedisplay</w> <w>timedisplay</w>
<w>timeformat</w> <w>timeformat</w>
<w>timemax</w> <w>timemax</w>

View File

@ -1,7 +1,14 @@
### 1.5.30 (20263) ### 1.6.0 (20268)
- Added private parties functionality (cloud hosted parties with associated codes making it easier to play with friends)
- The meta subsystem now enables new plugins by default in headless builds. - The meta subsystem now enables new plugins by default in headless builds.
- Added option to save party in Manual tab - Added option to save party in Manual tab
- Slight tidying on the tourney entry popup - Slight tidying on the tourney entry popup
- Env var to override UI scale is now BA_UI_SCALE instead of BA_FORCE_UI_SCALE.
- Fixed an issue where ba.storagename() could prevent objects on the stack from getting released cleanly
- Improvements to documentation generation such as link to some external base types.
- Added ba.clipboard_* functions for copying and pasting text on supported platforms.
- Implemented clipboard functionality on SDL based builds (such as prefab)
- Fixed an issue where click locations on scaled text fields could be incorrectly calculated.
### 1.5.29 (20246) ### 1.5.29 (20246)
- Exposed ba method/class initing in public C++ layer. - Exposed ba method/class initing in public C++ layer.

View File

@ -364,16 +364,14 @@
"ba_data/python/bastd/ui/gather/__init__.py", "ba_data/python/bastd/ui/gather/__init__.py",
"ba_data/python/bastd/ui/gather/__pycache__/__init__.cpython-38.opt-1.pyc", "ba_data/python/bastd/ui/gather/__pycache__/__init__.cpython-38.opt-1.pyc",
"ba_data/python/bastd/ui/gather/__pycache__/abouttab.cpython-38.opt-1.pyc", "ba_data/python/bastd/ui/gather/__pycache__/abouttab.cpython-38.opt-1.pyc",
"ba_data/python/bastd/ui/gather/__pycache__/bases.cpython-38.opt-1.pyc",
"ba_data/python/bastd/ui/gather/__pycache__/googleplaytab.cpython-38.opt-1.pyc",
"ba_data/python/bastd/ui/gather/__pycache__/manualtab.cpython-38.opt-1.pyc", "ba_data/python/bastd/ui/gather/__pycache__/manualtab.cpython-38.opt-1.pyc",
"ba_data/python/bastd/ui/gather/__pycache__/nearbytab.cpython-38.opt-1.pyc", "ba_data/python/bastd/ui/gather/__pycache__/nearbytab.cpython-38.opt-1.pyc",
"ba_data/python/bastd/ui/gather/__pycache__/privatetab.cpython-38.opt-1.pyc",
"ba_data/python/bastd/ui/gather/__pycache__/publictab.cpython-38.opt-1.pyc", "ba_data/python/bastd/ui/gather/__pycache__/publictab.cpython-38.opt-1.pyc",
"ba_data/python/bastd/ui/gather/abouttab.py", "ba_data/python/bastd/ui/gather/abouttab.py",
"ba_data/python/bastd/ui/gather/bases.py",
"ba_data/python/bastd/ui/gather/googleplaytab.py",
"ba_data/python/bastd/ui/gather/manualtab.py", "ba_data/python/bastd/ui/gather/manualtab.py",
"ba_data/python/bastd/ui/gather/nearbytab.py", "ba_data/python/bastd/ui/gather/nearbytab.py",
"ba_data/python/bastd/ui/gather/privatetab.py",
"ba_data/python/bastd/ui/gather/publictab.py", "ba_data/python/bastd/ui/gather/publictab.py",
"ba_data/python/bastd/ui/getcurrency.py", "ba_data/python/bastd/ui/getcurrency.py",
"ba_data/python/bastd/ui/getremote.py", "ba_data/python/bastd/ui/getremote.py",

View File

@ -296,10 +296,9 @@ SCRIPT_TARGETS_PY_PUBLIC = \
build/ba_data/python/bastd/ui/fileselector.py \ build/ba_data/python/bastd/ui/fileselector.py \
build/ba_data/python/bastd/ui/gather/__init__.py \ build/ba_data/python/bastd/ui/gather/__init__.py \
build/ba_data/python/bastd/ui/gather/abouttab.py \ build/ba_data/python/bastd/ui/gather/abouttab.py \
build/ba_data/python/bastd/ui/gather/bases.py \
build/ba_data/python/bastd/ui/gather/googleplaytab.py \
build/ba_data/python/bastd/ui/gather/manualtab.py \ build/ba_data/python/bastd/ui/gather/manualtab.py \
build/ba_data/python/bastd/ui/gather/nearbytab.py \ build/ba_data/python/bastd/ui/gather/nearbytab.py \
build/ba_data/python/bastd/ui/gather/privatetab.py \
build/ba_data/python/bastd/ui/gather/publictab.py \ build/ba_data/python/bastd/ui/gather/publictab.py \
build/ba_data/python/bastd/ui/getcurrency.py \ build/ba_data/python/bastd/ui/getcurrency.py \
build/ba_data/python/bastd/ui/getremote.py \ build/ba_data/python/bastd/ui/getremote.py \
@ -541,10 +540,9 @@ SCRIPT_TARGETS_PYC_PUBLIC = \
build/ba_data/python/bastd/ui/__pycache__/fileselector.cpython-38.opt-1.pyc \ build/ba_data/python/bastd/ui/__pycache__/fileselector.cpython-38.opt-1.pyc \
build/ba_data/python/bastd/ui/gather/__pycache__/__init__.cpython-38.opt-1.pyc \ build/ba_data/python/bastd/ui/gather/__pycache__/__init__.cpython-38.opt-1.pyc \
build/ba_data/python/bastd/ui/gather/__pycache__/abouttab.cpython-38.opt-1.pyc \ build/ba_data/python/bastd/ui/gather/__pycache__/abouttab.cpython-38.opt-1.pyc \
build/ba_data/python/bastd/ui/gather/__pycache__/bases.cpython-38.opt-1.pyc \
build/ba_data/python/bastd/ui/gather/__pycache__/googleplaytab.cpython-38.opt-1.pyc \
build/ba_data/python/bastd/ui/gather/__pycache__/manualtab.cpython-38.opt-1.pyc \ build/ba_data/python/bastd/ui/gather/__pycache__/manualtab.cpython-38.opt-1.pyc \
build/ba_data/python/bastd/ui/gather/__pycache__/nearbytab.cpython-38.opt-1.pyc \ build/ba_data/python/bastd/ui/gather/__pycache__/nearbytab.cpython-38.opt-1.pyc \
build/ba_data/python/bastd/ui/gather/__pycache__/privatetab.cpython-38.opt-1.pyc \
build/ba_data/python/bastd/ui/gather/__pycache__/publictab.cpython-38.opt-1.pyc \ build/ba_data/python/bastd/ui/gather/__pycache__/publictab.cpython-38.opt-1.pyc \
build/ba_data/python/bastd/ui/__pycache__/getcurrency.cpython-38.opt-1.pyc \ build/ba_data/python/bastd/ui/__pycache__/getcurrency.cpython-38.opt-1.pyc \
build/ba_data/python/bastd/ui/__pycache__/getremote.cpython-38.opt-1.pyc \ build/ba_data/python/bastd/ui/__pycache__/getremote.cpython-38.opt-1.pyc \

View File

@ -996,7 +996,7 @@ class Timer:
time: length of time (in seconds by default) that the timer will wait time: length of time (in seconds by default) that the timer will wait
before firing. Note that the actual delay experienced may vary before firing. Note that the actual delay experienced may vary
depending on the timetype. (see below) depending on the timetype. (see below)
call: A callable Python object. Note that the timer will retain a call: A callable Python object. Note that the timer will retain a
strong reference to the callable for as long as it exists, so you strong reference to the callable for as long as it exists, so you
@ -1006,28 +1006,11 @@ class Timer:
repeat: if True, the timer will fire repeatedly, with each successive repeat: if True, the timer will fire repeatedly, with each successive
firing having the same delay as the first. firing having the same delay as the first.
timetype can be either 'sim', 'base', or 'real'. It defaults to timetype: A ba.TimeType value determining which timeline the timer is
'sim'. Types are explained below: placed onto.
'sim' time maps to local simulation time in ba.Activity or ba.Session timeformat: A ba.TimeFormat value determining how the passed time is
Contexts. This means that it may progress slower in slow-motion play interpreted.
modes, stop when the game is paused, etc. This time type is not
available in UI contexts.
'base' time is also linked to gameplay in ba.Activity or ba.Session
Contexts, but it progresses at a constant rate regardless of
slow-motion states or pausing. It can, however, slow down or stop
in certain cases such as network outages or game slowdowns due to
cpu load. Like 'sim' time, this is unavailable in UI contexts.
'real' time always maps to actual clock time with a bit of filtering
added, regardless of Context. (the filtering prevents it from going
backwards or jumping forward by large amounts due to the app being
backgrounded, system time changing, etc.)
Real time timers are currently only available in the UI context.
the 'timeformat' arg defaults to SECONDS but can also be MILLISECONDS
if you want to pass time as milliseconds.
# Example: use a Timer object to print repeatedly for a few seconds: # Example: use a Timer object to print repeatedly for a few seconds:
def say_it(): def say_it():
@ -1035,9 +1018,9 @@ class Timer:
def stop_saying_it(): def stop_saying_it():
self.t = None self.t = None
ba.screenmessage('MUSHROOM MUSHROOM!') ba.screenmessage('MUSHROOM MUSHROOM!')
# create our timer; it will run as long as we hold self.t # Create our timer; it will run as long as we have the self.t ref.
self.t = ba.Timer(0.3, say_it, repeat=True) self.t = ba.Timer(0.3, say_it, repeat=True)
# now fire off a one-shot timer to kill it # Now fire off a one-shot timer to kill it.
ba.timer(3.89, stop_saying_it) ba.timer(3.89, stop_saying_it)
""" """
@ -1579,6 +1562,58 @@ def client_info_query_response(token: str, response: Any) -> None:
return None return None
def clipboard_get_text() -> str:
"""clipboard_get_text() -> str
Return text currently on the system clipboard.
Category: General Utility Functions
Ensure that ba.clipboard_has_text() returns True before calling
this function.
"""
return str()
def clipboard_has_text() -> bool:
"""clipboard_has_text() -> bool
Return whether there is currently text on the clipboard.
Category: General Utility Functions
This will return False if no system clipboard is available; no need
to call ba.clipboard_available() separately.
"""
return bool()
def clipboard_is_supported() -> bool:
"""clipboard_is_supported() -> bool
Return whether this platform supports clipboard operations at all.
Category: General Utility Functions
If this returns False, UIs should not show 'copy to clipboard'
buttons, etc.
"""
return bool()
def clipboard_set_text(value: str) -> None:
"""clipboard_set_text(value: str) -> None
Copy a string to the system clipboard.
Category: General Utility Functions
Ensure that ba.clipboard_available() returns True before adding
buttons/etc. that make use of this functionality.
"""
return None
def columnwidget(edit: ba.Widget = None, def columnwidget(edit: ba.Widget = None,
parent: ba.Widget = None, parent: ba.Widget = None,
size: Sequence[float] = None, size: Sequence[float] = None,
@ -3293,24 +3328,24 @@ def restore_purchases() -> None:
return None return None
def rowwidget(edit: Widget = None, def rowwidget(edit: ba.Widget = None,
parent: Widget = None, parent: ba.Widget = None,
size: Sequence[float] = None, size: Sequence[float] = None,
position: Sequence[float] = None, position: Sequence[float] = None,
background: bool = None, background: bool = None,
selected_child: Widget = None, selected_child: ba.Widget = None,
visible_child: Widget = None, visible_child: ba.Widget = None,
claims_left_right: bool = None, claims_left_right: bool = None,
claims_tab: bool = None, claims_tab: bool = None,
selection_loops_to_parent: bool = None) -> Widget: selection_loops_to_parent: bool = None) -> ba.Widget:
"""rowwidget(edit: Widget = None, parent: Widget = None, """rowwidget(edit: ba.Widget = None, parent: ba.Widget = None,
size: Sequence[float] = None, size: Sequence[float] = None,
position: Sequence[float] = None, position: Sequence[float] = None,
background: bool = None, selected_child: Widget = None, background: bool = None, selected_child: ba.Widget = None,
visible_child: Widget = None, visible_child: ba.Widget = None,
claims_left_right: bool = None, claims_left_right: bool = None,
claims_tab: bool = None, claims_tab: bool = None,
selection_loops_to_parent: bool = None) -> Widget selection_loops_to_parent: bool = None) -> ba.Widget
Create or edit a row widget. Create or edit a row widget.
@ -3320,7 +3355,8 @@ def rowwidget(edit: Widget = None,
a new one is created and returned. Arguments that are not set to None a new one is created and returned. Arguments that are not set to None
are applied to the Widget. are applied to the Widget.
""" """
return Widget() import ba # pylint: disable=cyclic-import
return ba.Widget()
def run_transactions() -> None: def run_transactions() -> None:
@ -3775,8 +3811,8 @@ def submit_score(game: str,
return None return None
def textwidget(edit: Widget = None, def textwidget(edit: ba.Widget = None,
parent: Widget = None, parent: ba.Widget = None,
size: Sequence[float] = None, size: Sequence[float] = None,
position: Sequence[float] = None, position: Sequence[float] = None,
text: Union[str, ba.Lstr] = None, text: Union[str, ba.Lstr] = None,
@ -3787,13 +3823,13 @@ def textwidget(edit: Widget = None,
on_return_press_call: Callable[[], None] = None, on_return_press_call: Callable[[], None] = None,
on_activate_call: Callable[[], None] = None, on_activate_call: Callable[[], None] = None,
selectable: bool = None, selectable: bool = None,
query: Widget = None, query: ba.Widget = None,
max_chars: int = None, max_chars: int = None,
color: Sequence[float] = None, color: Sequence[float] = None,
click_activate: bool = None, click_activate: bool = None,
on_select_call: Callable[[], None] = None, on_select_call: Callable[[], None] = None,
always_highlight: bool = None, always_highlight: bool = None,
draw_controller: Widget = None, draw_controller: ba.Widget = None,
scale: float = None, scale: float = None,
corner_scale: float = None, corner_scale: float = None,
description: Union[str, ba.Lstr] = None, description: Union[str, ba.Lstr] = None,
@ -3810,16 +3846,16 @@ def textwidget(edit: Widget = None,
big: bool = None, big: bool = None,
extra_touch_border_scale: float = None, extra_touch_border_scale: float = None,
res_scale: float = None) -> Widget: res_scale: float = None) -> Widget:
"""textwidget(edit: Widget = None, parent: Widget = None, """textwidget(edit: ba.Widget = None, parent: ba.Widget = None,
size: Sequence[float] = None, position: Sequence[float] = None, size: Sequence[float] = None, position: Sequence[float] = None,
text: Union[str, ba.Lstr] = None, v_align: str = None, text: Union[str, ba.Lstr] = None, v_align: str = None,
h_align: str = None, editable: bool = None, padding: float = None, h_align: str = None, editable: bool = None, padding: float = None,
on_return_press_call: Callable[[], None] = None, on_return_press_call: Callable[[], None] = None,
on_activate_call: Callable[[], None] = None, on_activate_call: Callable[[], None] = None,
selectable: bool = None, query: Widget = None, max_chars: int = None, selectable: bool = None, query: ba.Widget = None, max_chars: int = None,
color: Sequence[float] = None, click_activate: bool = None, color: Sequence[float] = None, click_activate: bool = None,
on_select_call: Callable[[], None] = None, on_select_call: Callable[[], None] = None,
always_highlight: bool = None, draw_controller: Widget = None, always_highlight: bool = None, draw_controller: ba.Widget = None,
scale: float = None, corner_scale: float = None, scale: float = None, corner_scale: float = None,
description: Union[str, ba.Lstr] = None, description: Union[str, ba.Lstr] = None,
transition_delay: float = None, maxwidth: float = None, transition_delay: float = None, maxwidth: float = None,

View File

@ -9,16 +9,16 @@ In some specific cases you may need to pull in individual submodules instead.
# pylint: disable=unused-import # pylint: disable=unused-import
# pylint: disable=redefined-builtin # pylint: disable=redefined-builtin
from _ba import (CollideModel, Context, ContextCall, Data, InputDevice, from _ba import (
Material, Model, Node, SessionPlayer, Sound, Texture, Timer, CollideModel, Context, ContextCall, Data, InputDevice, Material, Model,
Vec3, Widget, buttonwidget, camerashake, checkboxwidget, Node, SessionPlayer, Sound, Texture, Timer, Vec3, Widget, buttonwidget,
columnwidget, containerwidget, do_once, emitfx, getactivity, camerashake, checkboxwidget, columnwidget, containerwidget, do_once,
getcollidemodel, getmodel, getnodes, getsession, getsound, emitfx, getactivity, getcollidemodel, getmodel, getnodes, getsession,
gettexture, hscrollwidget, imagewidget, log, newactivity, getsound, gettexture, hscrollwidget, imagewidget, log, newactivity,
newnode, playsound, printnodes, printobjects, pushcall, quit, newnode, playsound, printnodes, printobjects, pushcall, quit, rowwidget,
rowwidget, safecolor, screenmessage, scrollwidget, safecolor, screenmessage, scrollwidget, set_analytics_screen, charstr,
set_analytics_screen, charstr, textwidget, time, timer, textwidget, time, timer, open_url, widget, clipboard_is_supported,
open_url, widget) clipboard_has_text, clipboard_get_text, clipboard_set_text)
from ba._activity import Activity from ba._activity import Activity
from ba._plugin import PotentialPlugin, Plugin, PluginSubsystem from ba._plugin import PotentialPlugin, Plugin, PluginSubsystem
from ba._actor import Actor from ba._actor import Actor

View File

@ -192,7 +192,7 @@ class Actor:
"""Return the ba.Activity this Actor is associated with. """Return the ba.Activity this Actor is associated with.
If the Activity no longer exists, raises a ba.ActivityNotFoundError If the Activity no longer exists, raises a ba.ActivityNotFoundError
or returns None depending on whether 'doraise' is set. or returns None depending on whether 'doraise' is True.
""" """
activity = self._activity() activity = self._activity()
if activity is None and doraise: if activity is None and doraise:

View File

@ -24,7 +24,7 @@ class AppConfig(dict):
AppConfig data is stored as json on disk on so make sure to only place AppConfig data is stored as json on disk on so make sure to only place
json-friendly values in it (dict, list, str, float, int, bool). json-friendly values in it (dict, list, str, float, int, bool).
Be aware that tuples will be quietly converted to lists. Be aware that tuples will be quietly converted to lists when stored.
""" """
def resolve(self, key: str) -> Any: def resolve(self, key: str) -> Any:

View File

@ -38,16 +38,18 @@ T = TypeVar('T')
def existing(obj: Optional[ExistableType]) -> Optional[ExistableType]: def existing(obj: Optional[ExistableType]) -> Optional[ExistableType]:
"""Convert invalid references to None for any ba.Existable type. """Convert invalid references to None for any ba.Existable object.
Category: Gameplay Functions Category: Gameplay Functions
To best support type checking, it is important that invalid references To best support type checking, it is important that invalid references
not be passed around and instead get converted to values of None. not be passed around and instead get converted to values of None.
That way the type checker can properly flag attempts to pass dead That way the type checker can properly flag attempts to pass dead
objects into functions expecting only live ones, etc. objects (Optional[FooType]) into functions expecting only live ones
This call can be used on any 'existable' object (one with an exists() (FooType), etc. This call can be used on any 'existable' object
method) and will convert it to a None value if it does not exist. (one with an exists() method) and will convert it to a None value
if it does not exist.
For more info, see notes on 'existables' here: For more info, see notes on 'existables' here:
https://ballistica.net/wiki/Coding-Style-Guide https://ballistica.net/wiki/Coding-Style-Guide
""" """
@ -358,15 +360,17 @@ def _verify_object_death(wref: ReferenceType) -> None:
def storagename(suffix: str = None) -> str: def storagename(suffix: str = None) -> str:
"""Generate a (hopefully) unique name for storing things in public places. """Generate a unique name for storing class data in shared places.
Category: General Utility Functions Category: General Utility Functions
This consists of a leading underscore, the module path at the This consists of a leading underscore, the module path at the
call site with dots replaced by underscores, the class name, and call site with dots replaced by underscores, the containing class's
the provided suffix. When storing data in public places such as qualified name, and the provided suffix. When storing data in public
'customdata' dicts, this minimizes the chance of collisions if a places such as 'customdata' dicts, this minimizes the chance of
module or class is duplicated or renamed. collisions with other similarly named classes.
Note that this will function even if called in the class definition.
# Example: generate a unique name for storage purposes: # Example: generate a unique name for storage purposes:
class MyThingie: class MyThingie:
@ -374,14 +378,21 @@ def storagename(suffix: str = None) -> str:
# This will give something like '_mymodule_submodule_mythingie_data'. # This will give something like '_mymodule_submodule_mythingie_data'.
_STORENAME = ba.storagename('data') _STORENAME = ba.storagename('data')
# Use that name to store some data in the Activity we were passed.
def __init__(self, activity): def __init__(self, activity):
# Store some data in the Activity we were passed
activity.customdata[self._STORENAME] = {} activity.customdata[self._STORENAME] = {}
""" """
frame = inspect.currentframe() frame = inspect.currentframe()
if frame is None: if frame is None:
raise RuntimeError('Cannot get current stack frame.') raise RuntimeError('Cannot get current stack frame.')
fback = frame.f_back fback = frame.f_back
# Note: We need to explicitly clear frame here to avoid a ref-loop
# that keeps all function-dicts in the stack alive until the next
# full GC cycle (the stack frame refers to this function's dict,
# which refers to the stack frame).
del frame
if fback is None: if fback is None:
raise RuntimeError('Cannot get parent stack frame.') raise RuntimeError('Cannot get parent stack frame.')
modulepath = fback.f_globals.get('__name__') modulepath = fback.f_globals.get('__name__')

View File

@ -12,6 +12,7 @@ import _ba
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Callable, Any, Optional, Dict, Union, Type from typing import Callable, Any, Optional, Dict, Union, Type
import ba
class MusicType(Enum): class MusicType(Enum):
@ -469,8 +470,9 @@ class MusicPlayer:
self._actually_playing = False self._actually_playing = False
def setmusic(musictype: Optional[MusicType], continuous: bool = False) -> None: def setmusic(musictype: Optional[ba.MusicType],
"""Tell the game to play (or stop playing) a certain type of music. continuous: bool = False) -> None:
"""Set the app to play (or stop playing) a certain type of music.
category: Gameplay Functions category: Gameplay Functions

View File

@ -284,8 +284,8 @@ class EmptyPlayer(Player['ba.EmptyTeam']):
Category: Gameplay Classes Category: Gameplay Classes
ba.Player and ba.Team are 'Generic' types, and so passing them as ba.Player and ba.Team are 'Generic' types, and so passing those top level
type arguments when defining a ba.Activity reduces type safety. classes as type arguments when defining a ba.Activity reduces type safety.
For example, activity.teams[0].player will have type 'Any' in that case. For example, activity.teams[0].player will have type 'Any' in that case.
For that reason, it is better to pass EmptyPlayer and EmptyTeam when For that reason, it is better to pass EmptyPlayer and EmptyTeam when
defining a ba.Activity that does not need custom types of its own. defining a ba.Activity that does not need custom types of its own.

View File

@ -17,7 +17,7 @@ if TYPE_CHECKING:
class Session: class Session:
"""Defines a high level series of activities with a common purpose. """Defines a high level series of ba.Activities with a common purpose.
category: Gameplay Classes category: Gameplay Classes

View File

@ -200,8 +200,8 @@ class EmptyTeam(Team['ba.EmptyPlayer']):
Category: Gameplay Classes Category: Gameplay Classes
ba.Player and ba.Team are 'Generic' types, and so passing them as ba.Player and ba.Team are 'Generic' types, and so passing those top level
type arguments when defining a ba.Activity reduces type safety. classes as type arguments when defining a ba.Activity reduces type safety.
For example, activity.teams[0].player will have type 'Any' in that case. For example, activity.teams[0].player will have type 'Any' in that case.
For that reason, it is better to pass EmptyPlayer and EmptyTeam when For that reason, it is better to pass EmptyPlayer and EmptyTeam when
defining a ba.Activity that does not need custom types of its own. defining a ba.Activity that does not need custom types of its own.

View File

@ -10,7 +10,7 @@ import _ba
from ba._enums import UIScale from ba._enums import UIScale
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Optional, Dict, Any, Callable, List from typing import Optional, Dict, Any, Callable, List, Type
from ba.ui import UICleanupCheck from ba.ui import UICleanupCheck
import ba import ba
@ -43,7 +43,7 @@ class UISubsystem:
else: else:
raise RuntimeError(f'Invalid UIScale value: {interfacetype}') raise RuntimeError(f'Invalid UIScale value: {interfacetype}')
self.window_states: Dict = {} # FIXME: Kill this. self.window_states: Dict[Type, Any] = {} # FIXME: Kill this.
self.main_menu_selection: Optional[str] = None # FIXME: Kill this. self.main_menu_selection: Optional[str] = None # FIXME: Kill this.
self.have_party_queue_window = False self.have_party_queue_window = False
self.quit_window: Any = None self.quit_window: Any = None
@ -76,7 +76,7 @@ class UISubsystem:
# this holds true at all aspect ratios. # this holds true at all aspect ratios.
# UPDATE: A better way to test this is now by setting the environment # UPDATE: A better way to test this is now by setting the environment
# variable BA_FORCE_UI_SCALE to "small", "medium", or "large". # variable BA_UI_SCALE to "small", "medium", or "large".
# This will affect system UIs not covered by the values below such # This will affect system UIs not covered by the values below such
# as screen-messages. The below values remain functional, however, # as screen-messages. The below values remain functional, however,
# for cases such as Android where environment variables can't be set # for cases such as Android where environment variables can't be set

View File

@ -118,7 +118,7 @@ class UIEntry:
class UIController: class UIController:
"""Wrangles UILocations. """Wrangles ba.UILocations.
Category: User Interface Classes Category: User Interface Classes
""" """

View File

@ -1088,13 +1088,13 @@ class AccountSettingsWindow(ba.Window):
sel_name = 'Scroll' sel_name = 'Scroll'
else: else:
raise ValueError('unrecognized selection') raise ValueError('unrecognized selection')
ba.app.ui.window_states[self.__class__.__name__] = sel_name ba.app.ui.window_states[type(self)] = sel_name
except Exception: except Exception:
ba.print_exception(f'Error saving state for {self}.') ba.print_exception(f'Error saving state for {self}.')
def _restore_state(self) -> None: def _restore_state(self) -> None:
try: try:
sel_name = ba.app.ui.window_states.get(self.__class__.__name__) sel_name = ba.app.ui.window_states.get(type(self))
if sel_name == 'Back': if sel_name == 'Back':
sel = self._back_button sel = self._back_button
elif sel_name == 'Scroll': elif sel_name == 'Scroll':

View File

@ -1542,7 +1542,7 @@ class CoopBrowserWindow(ba.Window):
def _restore_state(self) -> None: def _restore_state(self) -> None:
try: try:
sel_name = ba.app.ui.window_states.get(self.__class__.__name__, sel_name = ba.app.ui.window_states.get(type(self),
{}).get('sel_name') {}).get('sel_name')
if sel_name == 'Back': if sel_name == 'Back':
sel = self._back_button sel = self._back_button
@ -1572,9 +1572,7 @@ class CoopBrowserWindow(ba.Window):
sel_name = 'Scroll' sel_name = 'Scroll'
else: else:
raise ValueError('unrecognized selection') raise ValueError('unrecognized selection')
ba.app.ui.window_states[self.__class__.__name__] = { ba.app.ui.window_states[type(self)] = {'sel_name': sel_name}
'sel_name': sel_name
}
except Exception: except Exception:
ba.print_exception(f'Error saving state for {self}.') ba.print_exception(f'Error saving state for {self}.')

View File

@ -4,22 +4,56 @@
from __future__ import annotations from __future__ import annotations
import weakref
from enum import Enum from enum import Enum
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
import _ba import _ba
import ba import ba
from bastd.ui.gather.abouttab import AboutGatherTab
from bastd.ui.gather.manualtab import ManualGatherTab
from bastd.ui.gather.googleplaytab import GooglePlayGatherTab
from bastd.ui.gather.publictab import PublicGatherTab
from bastd.ui.gather.nearbytab import NearbyGatherTab
from bastd.ui.tabs import TabRow from bastd.ui.tabs import TabRow
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import (Any, Optional, Tuple, Dict, List, Union, Callable, from typing import (Any, Optional, Tuple, Dict, List, Union, Callable,
Type) Type)
from bastd.ui.gather.bases import GatherTab
class GatherTab:
"""Defines a tab for use in the gather UI."""
def __init__(self, window: GatherWindow) -> None:
self._window = weakref.ref(window)
@property
def window(self) -> GatherWindow:
"""The GatherWindow that this tab belongs to."""
window = self._window()
if window is None:
raise ba.NotFoundError("GatherTab's window no longer exists.")
return window
def on_activate(
self,
parent_widget: ba.Widget,
tab_button: ba.Widget,
region_width: float,
region_height: float,
region_left: float,
region_bottom: float,
) -> ba.Widget:
"""Called when the tab becomes the active one.
The tab should create and return a container widget covering the
specified region.
"""
def on_deactivate(self) -> None:
"""Called when the tab will no longer be the active one."""
def save_state(self) -> None:
"""Called when the parent window is saving state."""
def restore_state(self) -> None:
"""Called when the parent window is restoring state."""
class GatherWindow(ba.Window): class GatherWindow(ba.Window):
@ -29,8 +63,8 @@ class GatherWindow(ba.Window):
"""Our available tab types.""" """Our available tab types."""
ABOUT = 'about' ABOUT = 'about'
INTERNET = 'internet' INTERNET = 'internet'
GOOGLE_PLAY = 'google_play' PRIVATE = 'private'
LOCAL_NETWORK = 'local_network' NEARBY = 'nearby'
MANUAL = 'manual' MANUAL = 'manual'
def __init__(self, def __init__(self,
@ -38,6 +72,13 @@ class GatherWindow(ba.Window):
origin_widget: ba.Widget = None): origin_widget: ba.Widget = None):
# pylint: disable=too-many-statements # pylint: disable=too-many-statements
# pylint: disable=too-many-locals # pylint: disable=too-many-locals
# pylint: disable=cyclic-import
from bastd.ui.gather.abouttab import AboutGatherTab
from bastd.ui.gather.manualtab import ManualGatherTab
from bastd.ui.gather.privatetab import PrivateGatherTab
from bastd.ui.gather.publictab import PublicGatherTab
from bastd.ui.gather.nearbytab import NearbyGatherTab
ba.set_analytics_screen('Gather Window') ba.set_analytics_screen('Gather Window')
scale_origin: Optional[Tuple[float, float]] scale_origin: Optional[Tuple[float, float]]
if origin_widget is not None: if origin_widget is not None:
@ -104,9 +145,6 @@ class GatherWindow(ba.Window):
text=ba.Lstr(resource=self._r + '.titleText'), text=ba.Lstr(resource=self._r + '.titleText'),
maxwidth=550) maxwidth=550)
platform = ba.app.platform
subplatform = ba.app.subplatform
scroll_buffer_h = 130 + 2 * x_offs scroll_buffer_h = 130 + 2 * x_offs
tab_buffer_h = ((320 if condensed else 250) + 2 * x_offs) tab_buffer_h = ((320 if condensed else 250) + 2 * x_offs)
@ -117,11 +155,10 @@ class GatherWindow(ba.Window):
if _ba.get_account_misc_read_val('enablePublicParties', True): if _ba.get_account_misc_read_val('enablePublicParties', True):
tabdefs.append((self.TabID.INTERNET, tabdefs.append((self.TabID.INTERNET,
ba.Lstr(resource=self._r + '.publicText'))) ba.Lstr(resource=self._r + '.publicText')))
if platform == 'android' and subplatform == 'google': tabdefs.append(
tabdefs.append((self.TabID.GOOGLE_PLAY, (self.TabID.PRIVATE, ba.Lstr(resource=self._r + '.privateText')))
ba.Lstr(resource=self._r + '.googlePlayText'))) tabdefs.append(
tabdefs.append((self.TabID.LOCAL_NETWORK, (self.TabID.NEARBY, ba.Lstr(resource=self._r + '.nearbyText')))
ba.Lstr(resource=self._r + '.nearbyText')))
tabdefs.append( tabdefs.append(
(self.TabID.MANUAL, ba.Lstr(resource=self._r + '.manualText'))) (self.TabID.MANUAL, ba.Lstr(resource=self._r + '.manualText')))
@ -139,9 +176,9 @@ class GatherWindow(ba.Window):
tabtypes: Dict[GatherWindow.TabID, Type[GatherTab]] = { tabtypes: Dict[GatherWindow.TabID, Type[GatherTab]] = {
self.TabID.ABOUT: AboutGatherTab, self.TabID.ABOUT: AboutGatherTab,
self.TabID.MANUAL: ManualGatherTab, self.TabID.MANUAL: ManualGatherTab,
self.TabID.GOOGLE_PLAY: GooglePlayGatherTab, self.TabID.PRIVATE: PrivateGatherTab,
self.TabID.INTERNET: PublicGatherTab, self.TabID.INTERNET: PublicGatherTab,
self.TabID.LOCAL_NETWORK: NearbyGatherTab self.TabID.NEARBY: NearbyGatherTab
} }
self._tabs: Dict[GatherWindow.TabID, GatherTab] = {} self._tabs: Dict[GatherWindow.TabID, GatherTab] = {}
for tab_id in self._tab_row.tabs: for tab_id in self._tab_row.tabs:
@ -234,7 +271,7 @@ class GatherWindow(ba.Window):
sel_name = 'TabContainer' sel_name = 'TabContainer'
else: else:
raise ValueError(f'unrecognized selection: \'{sel}\'') raise ValueError(f'unrecognized selection: \'{sel}\'')
ba.app.ui.window_states[self.__class__.__name__] = { ba.app.ui.window_states[type(self)] = {
'sel_name': sel_name, 'sel_name': sel_name,
} }
except Exception: except Exception:
@ -247,7 +284,7 @@ class GatherWindow(ba.Window):
tab.restore_state() tab.restore_state()
sel: Optional[ba.Widget] sel: Optional[ba.Widget]
winstate = ba.app.ui.window_states.get(self.__class__.__name__, {}) winstate = ba.app.ui.window_states.get(type(self), {})
sel_name = winstate.get('sel_name', None) sel_name = winstate.get('sel_name', None)
assert isinstance(sel_name, (str, type(None))) assert isinstance(sel_name, (str, type(None)))
current_tab = self.TabID.ABOUT current_tab = self.TabID.ABOUT

View File

@ -8,7 +8,7 @@ from typing import TYPE_CHECKING
import ba import ba
import _ba import _ba
from bastd.ui.gather.bases import GatherTab from bastd.ui.gather import GatherTab
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Optional from typing import Optional

View File

@ -1,52 +0,0 @@
# Released under the MIT License. See LICENSE for details.
#
"""Provides UI for inviting/joining friends."""
from __future__ import annotations
import weakref
from typing import TYPE_CHECKING
import ba
if TYPE_CHECKING:
from bastd.ui.gather import GatherWindow
class GatherTab:
"""Defines a tab for use in the gather UI."""
def __init__(self, window: GatherWindow) -> None:
self._window = weakref.ref(window)
@property
def window(self) -> GatherWindow:
"""The GatherWindow that this tab belongs to."""
window = self._window()
if window is None:
raise ba.NotFoundError("GatherTab's window no longer exists.")
return window
def on_activate(
self,
parent_widget: ba.Widget,
tab_button: ba.Widget,
region_width: float,
region_height: float,
region_left: float,
region_bottom: float,
) -> ba.Widget:
"""Called when the tab becomes the active one.
The tab should create and return a container widget covering the
specified region.
"""
def on_deactivate(self) -> None:
"""Called when the tab will no longer be the active one."""
def save_state(self) -> None:
"""Called when the parent window is saving state."""
def restore_state(self) -> None:
"""Called when the parent window is restoring state."""

View File

@ -1,86 +0,0 @@
# Released under the MIT License. See LICENSE for details.
#
"""Defines the Google Play tab in the gather UI."""
from __future__ import annotations
from typing import TYPE_CHECKING
import _ba
import ba
from bastd.ui.gather.bases import GatherTab
if TYPE_CHECKING:
from typing import Optional
from bastd.ui.gather import GatherWindow
class GooglePlayGatherTab(GatherTab):
"""The public tab in the gather UI"""
def __init__(self, window: GatherWindow) -> None:
super().__init__(window)
self._container: Optional[ba.Widget] = None
def on_activate(
self,
parent_widget: ba.Widget,
tab_button: ba.Widget,
region_width: float,
region_height: float,
region_left: float,
region_bottom: float,
) -> ba.Widget:
c_width = region_width
c_height = 380.0
self._container = ba.containerwidget(
parent=parent_widget,
position=(region_left,
region_bottom + (region_height - c_height) * 0.5),
size=(c_width, c_height),
background=False,
selection_loops_to_parent=True)
v = c_height - 30.0
ba.textwidget(
parent=self._container,
position=(c_width * 0.5, v - 140.0),
color=(0.6, 1.0, 0.6),
scale=1.3,
size=(0.0, 0.0),
maxwidth=c_width * 0.9,
h_align='center',
v_align='center',
text=ba.Lstr(resource='googleMultiplayerDiscontinuedText'))
return self._container
def _on_google_play_show_invites_press(self) -> None:
from bastd.ui import account
if (_ba.get_account_state() != 'signed_in'
or _ba.get_account_type() != 'Google Play'):
account.show_sign_in_prompt('Google Play')
else:
_ba.show_invites_ui()
def _on_google_play_invite_press(self) -> None:
from bastd.ui.confirm import ConfirmWindow
from bastd.ui.account import show_sign_in_prompt
if (_ba.get_account_state() != 'signed_in'
or _ba.get_account_type() != 'Google Play'):
show_sign_in_prompt('Google Play')
else:
# If there's google play people connected to us, inform the user
# that they will get disconnected. Otherwise just go ahead.
google_player_count = (_ba.get_google_play_party_client_count())
if google_player_count > 0:
ConfirmWindow(
ba.Lstr(resource='gatherWindow.'
'googlePlayReInviteText',
subs=[('${COUNT}', str(google_player_count))]),
lambda: ba.timer(
0.2, _ba.invite_players, timetype=ba.TimeType.REAL),
width=500,
height=150,
ok_text=ba.Lstr(resource='gatherWindow.'
'googlePlayInviteText'))
else:
ba.timer(0.1, _ba.invite_players, timetype=ba.TimeType.REAL)

View File

@ -9,7 +9,7 @@ from typing import TYPE_CHECKING, cast
from enum import Enum from enum import Enum
from dataclasses import dataclass from dataclasses import dataclass
from bastd.ui.gather.bases import GatherTab from bastd.ui.gather import GatherTab
import _ba import _ba
import ba import ba
@ -161,11 +161,10 @@ class ManualGatherTab(GatherTab):
return self._container return self._container
def save_state(self) -> None: def save_state(self) -> None:
ba.app.ui.window_states[self.__class__.__name__] = State( ba.app.ui.window_states[type(self)] = State(sub_tab=self._sub_tab)
sub_tab=self._sub_tab)
def restore_state(self) -> None: def restore_state(self) -> None:
state = ba.app.ui.window_states.get(self.__class__.__name__) state = ba.app.ui.window_states.get(type(self))
if state is None: if state is None:
state = State() state = State()
assert isinstance(state, State) assert isinstance(state, State)

View File

@ -9,7 +9,7 @@ from typing import TYPE_CHECKING
import ba import ba
import _ba import _ba
from bastd.ui.gather.bases import GatherTab from bastd.ui.gather import GatherTab
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Optional, Dict, Any from typing import Optional, Dict, Any

View File

@ -0,0 +1,755 @@
# Released under the MIT License. See LICENSE for details.
#
"""Defines the Private tab in the gather UI."""
from __future__ import annotations
import os
import copy
import time
from enum import Enum
from dataclasses import dataclass
from typing import TYPE_CHECKING, cast
import ba
import _ba
from efro.dataclasses import dataclass_from_dict
from bastd.ui.gather import GatherTab
from bastd.ui import getcurrency
if TYPE_CHECKING:
from typing import Optional, Dict, Any, List
from bastd.ui.gather import GatherWindow
# Print a bit of info about queries, etc.
DEBUG_SERVER_COMMUNICATION = os.environ.get('BA_DEBUG_PPTABCOM') == '1'
class SubTabType(Enum):
"""Available sub-tabs."""
JOIN = 'join'
HOST = 'host'
@dataclass
class State:
"""Our core state that persists while the app is running."""
sub_tab: SubTabType = SubTabType.JOIN
@dataclass
class ConnectResult:
"""Info about a server we get back when connecting."""
error: Optional[str] = None
addr: Optional[str] = None
port: Optional[int] = None
@dataclass
class HostingState:
"""Our combined state of whether we're hosting, whether we can, etc."""
unavailable_error: Optional[str] = None
party_code: Optional[str] = None
able_to_host: bool = False
tickets_to_host_now: int = 0
minutes_until_free_host: Optional[float] = None
free_host_minutes_remaining: Optional[float] = None
class PrivateGatherTab(GatherTab):
"""The private tab in the gather UI"""
def __init__(self, window: GatherWindow) -> None:
super().__init__(window)
self._container: Optional[ba.Widget] = None
self._state: State = State()
self._hostingstate = HostingState()
self._join_sub_tab_text: Optional[ba.Widget] = None
self._host_sub_tab_text: Optional[ba.Widget] = None
self._update_timer: Optional[ba.Timer] = None
self._join_party_code_text: Optional[ba.Widget] = None
self._c_width: float = 0.0
self._c_height: float = 0.0
self._last_hosting_state_query_time: Optional[float] = None
self._initial_waiting_for_hosting_state = True
self._waiting_for_hosting_state = True
self._host_playlist_button: Optional[ba.Widget] = None
self._host_copy_button: Optional[ba.Widget] = None
self._host_connect_button: Optional[ba.Widget] = None
self._host_start_stop_button: Optional[ba.Widget] = None
self._get_tickets_button: Optional[ba.Widget] = None
self._ticket_count_text: Optional[ba.Widget] = None
self._showing_not_signed_in_screen = False
self._create_time = time.time()
self._last_action_send_time: Optional[float] = None
def on_activate(
self,
parent_widget: ba.Widget,
tab_button: ba.Widget,
region_width: float,
region_height: float,
region_left: float,
region_bottom: float,
) -> ba.Widget:
self._c_width = region_width
self._c_height = region_height - 20
self._container = ba.containerwidget(
parent=parent_widget,
position=(region_left,
region_bottom + (region_height - self._c_height) * 0.5),
size=(self._c_width, self._c_height),
background=False,
selection_loops_to_parent=True)
v = self._c_height - 30.0
self._join_sub_tab_text = ba.textwidget(
parent=self._container,
position=(self._c_width * 0.5 - 245, v - 13),
color=(0.6, 1.0, 0.6),
scale=1.3,
size=(200, 30),
maxwidth=250,
h_align='left',
v_align='center',
click_activate=True,
selectable=True,
autoselect=True,
on_activate_call=lambda: self._set_sub_tab(
SubTabType.JOIN,
playsound=True,
),
text=ba.Lstr(resource='gatherWindow.privatePartyJoinText'))
self._host_sub_tab_text = ba.textwidget(
parent=self._container,
position=(self._c_width * 0.5 + 45, v - 13),
color=(0.6, 1.0, 0.6),
scale=1.3,
size=(200, 30),
maxwidth=250,
h_align='left',
v_align='center',
click_activate=True,
selectable=True,
autoselect=True,
on_activate_call=lambda: self._set_sub_tab(
SubTabType.HOST,
playsound=True,
),
text=ba.Lstr(resource='gatherWindow.privatePartyHostText'))
ba.widget(edit=self._join_sub_tab_text, up_widget=tab_button)
ba.widget(edit=self._host_sub_tab_text,
left_widget=self._join_sub_tab_text,
up_widget=tab_button)
ba.widget(edit=self._join_sub_tab_text,
right_widget=self._host_sub_tab_text)
self._update_timer = ba.Timer(1.0,
ba.WeakCall(self._update),
repeat=True,
timetype=ba.TimeType.REAL)
# Get a new state query kicked off immediately and show nothing
# until it is back.
self._initial_waiting_for_hosting_state = True
self._last_hosting_state_query_time = None
self._last_action_send_time = None # So we don't ignore response.
self._update()
self._set_sub_tab(self._state.sub_tab)
return self._container
def on_deactivate(self) -> None:
self._update_timer = None
def _update_currency_ui(self) -> None:
# Keep currency count up to date if applicable.
try:
t_str = str(_ba.get_account_ticket_count())
except Exception:
t_str = '?'
if self._get_tickets_button:
ba.buttonwidget(edit=self._get_tickets_button,
label=ba.charstr(ba.SpecialChar.TICKET) + t_str)
if self._ticket_count_text:
ba.textwidget(edit=self._ticket_count_text,
text=ba.charstr(ba.SpecialChar.TICKET) + t_str)
def _update(self) -> None:
"""Periodic updating."""
now = ba.time(ba.TimeType.REAL)
self._update_currency_ui()
if self._state.sub_tab is SubTabType.HOST:
# If we're not signed in, just refresh to show that.
if (_ba.get_account_state() != 'signed_in'
and self._showing_not_signed_in_screen):
self._refresh_sub_tab()
else:
# Query an updated state periodically.
if (self._last_hosting_state_query_time is None
or now - self._last_hosting_state_query_time > 15.0):
self._debug_server_comm('querying private party state')
if _ba.get_account_state() == 'signed_in':
_ba.add_transaction(
{'type': 'PRIVATE_PARTY_QUERY'},
callback=ba.WeakCall(
self._hosting_state_idle_response),
)
_ba.run_transactions()
else:
self._hosting_state_idle_response(None)
self._last_hosting_state_query_time = now
def _hosting_state_idle_response(self,
result: Optional[Dict[str, Any]]) -> None:
# This simply passes through to our standard response handler.
# The one exception is if we've recently sent an action to the
# server (start/stop hosting/etc.) In that case we want to ignore
# idle background updates and wait for the response to our action.
# (this keeps the button showing 'one moment...' until the change
# takes effect, etc.)
if (self._last_action_send_time is not None
and time.time() - self._last_action_send_time < 5.0):
self._debug_server_comm('ignoring private party state response'
' due to recent action')
return
self._hosting_state_response(result)
def _hosting_state_response(self, result: Optional[Dict[str,
Any]]) -> None:
state: Optional[HostingState] = None
if result is not None:
self._debug_server_comm('got private party state response')
try:
state = dataclass_from_dict(HostingState, result)
except Exception:
pass
else:
self._debug_server_comm('private party state response errored')
# Hmm I guess let's just ignore failed responses?...
# Or should we show some sort of error state to the user?...
if result is None or state is None:
return
self._initial_waiting_for_hosting_state = False
self._waiting_for_hosting_state = False
self._hostingstate = state
self._refresh_sub_tab()
def _set_sub_tab(self, value: SubTabType, playsound: bool = False) -> None:
assert self._container
if playsound:
ba.playsound(ba.getsound('click01'))
# If switching from join to host, do a fresh state query.
if self._state.sub_tab is SubTabType.JOIN and value is SubTabType.HOST:
self._last_hosting_state_query_time = None
self._initial_waiting_for_hosting_state = True
self._last_action_send_time = None # So we don't ignore response.
self._update()
self._state.sub_tab = value
active_color = (0.6, 1.0, 0.6)
inactive_color = (0.5, 0.4, 0.5)
ba.textwidget(
edit=self._join_sub_tab_text,
color=active_color if value is SubTabType.JOIN else inactive_color)
ba.textwidget(
edit=self._host_sub_tab_text,
color=active_color if value is SubTabType.HOST else inactive_color)
self._refresh_sub_tab()
# Kick off an update to get any needed messages sent/etc.
ba.pushcall(self._update)
def _selwidgets(self) -> List[Optional[ba.Widget]]:
"""An indexed list of widgets we can use for saving/restoring sel."""
return [
self._host_playlist_button, self._host_copy_button,
self._host_connect_button, self._host_start_stop_button,
self._get_tickets_button
]
def _refresh_sub_tab(self) -> None:
assert self._container
# Store an index for our current selection so we can
# reselect the equivalent recreated widget if possible.
selindex: Optional[int] = None
selchild = self._container.get_selected_child()
if selchild is not None:
try:
selindex = self._selwidgets().index(selchild)
except ValueError:
pass
# Clear anything existing in the old sub-tab.
for widget in self._container.get_children():
if widget and widget not in {
self._host_sub_tab_text,
self._join_sub_tab_text,
}:
widget.delete()
if self._state.sub_tab is SubTabType.JOIN:
self._build_join_tab()
elif self._state.sub_tab is SubTabType.HOST:
self._build_host_tab()
else:
raise RuntimeError('Invalid state.')
# Select the new equivalent widget if there is one.
if selindex is not None:
selwidget = self._selwidgets()[selindex]
if selwidget:
ba.containerwidget(edit=self._container,
selected_child=selwidget)
def _build_join_tab(self) -> None:
ba.textwidget(parent=self._container,
position=(self._c_width * 0.5, self._c_height - 140),
color=(0.5, 0.46, 0.5),
scale=1.5,
size=(0, 0),
maxwidth=250,
h_align='center',
v_align='center',
text=ba.Lstr(resource='gatherWindow.partyCodeText'))
self._join_party_code_text = ba.textwidget(
parent=self._container,
position=(self._c_width * 0.5 - 150, self._c_height - 250),
flatness=1.0,
scale=1.5,
size=(300, 50),
editable=True,
description=ba.Lstr(resource='gatherWindow.partyCodeText'),
autoselect=True,
maxwidth=250,
h_align='left',
v_align='center',
text='')
btn = ba.buttonwidget(parent=self._container,
size=(300, 70),
label=ba.Lstr(resource='gatherWindow.'
'manualConnectText'),
position=(self._c_width * 0.5 - 150,
self._c_height - 350),
on_activate_call=self._connect_press,
autoselect=True)
ba.textwidget(edit=self._join_party_code_text,
on_return_press_call=btn.activate)
def _on_get_tickets_press(self) -> None:
if self._waiting_for_hosting_state:
return
# Bring up get-tickets window and then kill ourself (we're on the
# overlay layer so we'd show up above it).
getcurrency.GetCurrencyWindow(modal=True,
origin_widget=self._get_tickets_button)
# self._transition_out()
def _build_host_tab(self) -> None:
# pylint: disable=too-many-branches
# pylint: disable=too-many-statements
if _ba.get_account_state() != 'signed_in':
ba.textwidget(parent=self._container,
size=(0, 0),
h_align='center',
v_align='center',
maxwidth=200,
scale=0.8,
color=(0.6, 0.56, 0.6),
position=(self._c_width * 0.5, self._c_height * 0.5),
text=ba.Lstr(resource='notSignedInErrorText'))
self._showing_not_signed_in_screen = True
return
self._showing_not_signed_in_screen = False
# At first we don't want to show anything until we've gotten a state.
if self._initial_waiting_for_hosting_state:
ba.textwidget(
parent=self._container,
size=(0, 0),
h_align='center',
v_align='center',
maxwidth=200,
scale=0.8,
color=(0.6, 0.56, 0.6),
position=(self._c_width * 0.5, self._c_height * 0.5),
text=ba.Lstr(
value='${A}...',
subs=[('${A}', ba.Lstr(resource='store.loadingText'))],
),
)
return
# If we're not currently hosting and hosting requires tickets,
# Show our count (possibly with a link to purchase more).
if (self._hostingstate.party_code is None
and self._hostingstate.tickets_to_host_now != 0):
if not ba.app.ui.use_toolbars:
if ba.app.allow_ticket_purchases:
self._get_tickets_button = ba.buttonwidget(
parent=self._container,
position=(self._c_width - 210 + 125,
self._c_height - 44),
autoselect=True,
scale=0.6,
size=(120, 60),
textcolor=(0.2, 1, 0.2),
label=ba.charstr(ba.SpecialChar.TICKET),
color=(0.65, 0.5, 0.8),
on_activate_call=self._on_get_tickets_press)
else:
self._ticket_count_text = ba.textwidget(
parent=self._container,
scale=0.6,
position=(self._c_width - 210 + 125,
self._c_height - 44),
color=(0.2, 1, 0.2),
h_align='center',
v_align='center')
# Set initial ticket count.
self._update_currency_ui()
v = self._c_height - 90
if self._hostingstate.party_code is None:
ba.textwidget(
parent=self._container,
size=(0, 0),
h_align='center',
v_align='center',
maxwidth=self._c_width * 0.9,
scale=0.7,
flatness=1.0,
color=(0.5, 0.46, 0.5),
position=(self._c_width * 0.5, v),
text=ba.Lstr(
resource='gatherWindow.privatePartyCloudDescriptionText'))
v -= 100
if self._hostingstate.party_code is None:
# We've got no current party running; show options to set one up.
ba.textwidget(parent=self._container,
size=(0, 0),
h_align='right',
v_align='center',
maxwidth=200,
scale=0.8,
color=(0.6, 0.56, 0.6),
position=(self._c_width * 0.5 - 210, v),
text=ba.Lstr(resource='playlistText'))
self._host_playlist_button = ba.buttonwidget(
parent=self._container,
size=(400, 70),
color=(0.6, 0.5, 0.6),
textcolor=(0.8, 0.75, 0.8),
label='Default Free-For-All Playlist',
on_activate_call=lambda: ba.screenmessage(
'TODO: WIRE UP PLAYLIST SELECTION'),
position=(self._c_width * 0.5 - 200, v - 35),
up_widget=self._host_sub_tab_text,
autoselect=True)
else:
# We've got a current party; show its info.
ba.textwidget(
parent=self._container,
size=(0, 0),
h_align='center',
v_align='center',
maxwidth=600,
scale=0.9,
color=(0.7, 0.64, 0.7),
position=(self._c_width * 0.5, v + 90),
text=ba.Lstr(resource='gatherWindow.partyServerRunningText'))
ba.textwidget(parent=self._container,
size=(0, 0),
h_align='center',
v_align='center',
maxwidth=600,
scale=0.7,
color=(0.7, 0.64, 0.7),
position=(self._c_width * 0.5, v + 50),
text=ba.Lstr(resource='gatherWindow.partyCodeText'))
ba.textwidget(parent=self._container,
size=(0, 0),
h_align='center',
v_align='center',
scale=2.0,
color=(0.0, 1.0, 0.0),
position=(self._c_width * 0.5, v + 10),
text=self._hostingstate.party_code)
# Also action buttons to copy it and connect to it.
if ba.clipboard_is_supported():
cbtnoffs = 10
self._host_copy_button = ba.buttonwidget(
parent=self._container,
size=(140, 40),
color=(0.6, 0.5, 0.6),
textcolor=(0.8, 0.75, 0.8),
label=ba.Lstr(resource='gatherWindow.copyCodeText'),
on_activate_call=self._host_copy_press,
position=(self._c_width * 0.5 - 150, v - 70),
autoselect=True)
else:
cbtnoffs = -70
self._host_connect_button = ba.buttonwidget(
parent=self._container,
size=(140, 40),
color=(0.6, 0.5, 0.6),
textcolor=(0.8, 0.75, 0.8),
label=ba.Lstr(resource='gatherWindow.manualConnectText'),
on_activate_call=self._host_connect_press,
position=(self._c_width * 0.5 + cbtnoffs, v - 70),
autoselect=True)
v -= 120
# Line above the main action button:
# If hosting is unavailable, show the associated reason.
if self._hostingstate.unavailable_error is not None:
ba.textwidget(
parent=self._container,
size=(0, 0),
h_align='center',
v_align='center',
maxwidth=self._c_width * 0.9,
scale=0.7,
flatness=1.0,
color=(1.0, 0.0, 0.0),
position=(self._c_width * 0.5, v),
text=ba.Lstr(translate=('serverResponses',
self._hostingstate.unavailable_error)))
elif self._hostingstate.free_host_minutes_remaining is not None:
# If we've been pre-approved to start/stop for free, show that.
ba.textwidget(
parent=self._container,
size=(0, 0),
h_align='center',
v_align='center',
maxwidth=self._c_width * 0.9,
scale=0.7,
flatness=1.0,
color=((0.7, 0.64, 0.7) if self._hostingstate.party_code else
(0.0, 1.0, 0.0)),
position=(self._c_width * 0.5, v),
text=ba.Lstr(
resource='gatherWindow.startStopHostingMinutesText',
subs=[(
'${MINUTES}',
f'{self._hostingstate.free_host_minutes_remaining:.0f}'
)]))
else:
# Otherwise tell whether the free cloud server is available
# or will be at some point.
if self._hostingstate.party_code is None:
if self._hostingstate.tickets_to_host_now == 0:
ba.textwidget(
parent=self._container,
size=(0, 0),
h_align='center',
v_align='center',
maxwidth=self._c_width * 0.9,
scale=0.7,
flatness=1.0,
color=(0.0, 1.0, 0.0),
position=(self._c_width * 0.5, v),
text=ba.Lstr(
resource=
'gatherWindow.freeCloudServerAvailableNowText'))
else:
if self._hostingstate.minutes_until_free_host is None:
ba.textwidget(
parent=self._container,
size=(0, 0),
h_align='center',
v_align='center',
maxwidth=self._c_width * 0.9,
scale=0.7,
flatness=1.0,
color=(1.0, 0.6, 0.0),
position=(self._c_width * 0.5, v),
text=ba.Lstr(
resource=
'gatherWindow.freeCloudServerNotAvailableText')
)
else:
availmins = self._hostingstate.minutes_until_free_host
ba.textwidget(
parent=self._container,
size=(0, 0),
h_align='center',
v_align='center',
maxwidth=self._c_width * 0.9,
scale=0.7,
flatness=1.0,
color=(1.0, 0.6, 0.0),
position=(self._c_width * 0.5, v),
text=ba.Lstr(resource='gatherWindow.'
'freeCloudServerAvailableMinutesText',
subs=[('${MINUTES}',
f'{availmins:.0f}')]))
v -= 100
if self._waiting_for_hosting_state:
btnlabel = ba.Lstr(resource='oneMomentText')
else:
if self._hostingstate.unavailable_error is not None:
btnlabel = ba.Lstr(
resource='gatherWindow.hostingUnavailableText')
elif self._hostingstate.party_code is None:
ticon = _ba.charstr(ba.SpecialChar.TICKET)
nowtickets = self._hostingstate.tickets_to_host_now
if nowtickets > 0:
btnlabel = ba.Lstr(
resource='gatherWindow.startHostingPaidText',
subs=[('${COST}', f'{ticon}{nowtickets}')])
else:
btnlabel = ba.Lstr(
resource='gatherWindow.startHostingText')
else:
btnlabel = ba.Lstr(resource='gatherWindow.stopHostingText')
self._host_start_stop_button = ba.buttonwidget(
parent=self._container,
size=(400, 80),
color=((0.6, 0.6, 0.6)
if self._hostingstate.unavailable_error is not None else
(0.5, 1.0,
0.5) if self._waiting_for_hosting_state else None),
enable_sound=False,
label=btnlabel,
textcolor=((0.7, 0.7, 0.7)
if self._hostingstate.unavailable_error else None),
position=(self._c_width * 0.5 - 200, v),
on_activate_call=self._host_button_press,
autoselect=True)
def _host_copy_press(self) -> None:
assert self._hostingstate.party_code is not None
ba.clipboard_set_text(self._hostingstate.party_code)
ba.screenmessage(ba.Lstr(resource='gatherWindow.copyCodeConfirmText'))
def _host_connect_press(self) -> None:
assert self._hostingstate.party_code is not None
self._connect_to_party_code(self._hostingstate.party_code)
def _debug_server_comm(self, msg: str) -> None:
if DEBUG_SERVER_COMMUNICATION:
print(f'PPTABCOM: {msg} at time '
f'{time.time()-self._create_time:.2f}')
def _connect_to_party_code(self, code: str) -> None:
self._debug_server_comm('sending private party connect')
_ba.add_transaction(
{
'type': 'PRIVATE_PARTY_CONNECT',
'code': code
},
callback=ba.WeakCall(self._connect_response),
)
_ba.run_transactions()
def _host_button_press(self) -> None:
if self._waiting_for_hosting_state:
return
if _ba.get_account_state() != 'signed_in':
ba.screenmessage(ba.Lstr(resource='notSignedInErrorText'))
ba.playsound(ba.getsound('error'))
self._refresh_sub_tab()
return
if self._hostingstate.unavailable_error is not None:
ba.playsound(ba.getsound('error'))
return
# If we're not hosting, start.
if self._hostingstate.party_code is None:
# If there's a ticket cost, make sure we have enough tickets.
if self._hostingstate.tickets_to_host_now > 0:
ticket_count: Optional[int]
try:
ticket_count = _ba.get_account_ticket_count()
except Exception:
# FIXME: should add a ba.NotSignedInError we can use here.
ticket_count = None
ticket_cost = self._hostingstate.tickets_to_host_now
if ticket_count is not None and ticket_count < ticket_cost:
getcurrency.show_get_tickets_prompt()
ba.playsound(ba.getsound('error'))
return
self._last_action_send_time = time.time()
_ba.add_transaction({'type': 'PRIVATE_PARTY_START'},
callback=ba.WeakCall(
self._hosting_state_response))
_ba.run_transactions()
else:
self._last_action_send_time = time.time()
_ba.add_transaction({'type': 'PRIVATE_PARTY_STOP'},
callback=ba.WeakCall(
self._hosting_state_response))
_ba.run_transactions()
ba.playsound(ba.getsound('click01'))
self._waiting_for_hosting_state = True
self._refresh_sub_tab()
def _connect_press(self) -> None:
code: Optional[str] = None
if self._join_party_code_text:
code = cast(str, ba.textwidget(query=self._join_party_code_text))
if not code:
ba.screenmessage(
ba.Lstr(resource='internal.invalidAddressErrorText'),
color=(1, 0, 0))
ba.playsound(ba.getsound('error'))
return
self._connect_to_party_code(code)
def _connect_response(self, result: Optional[Dict[str, Any]]) -> None:
try:
if result is None:
raise RuntimeError()
cresult = dataclass_from_dict(ConnectResult, result)
if cresult.error is not None:
self._debug_server_comm('got error connect response')
ba.screenmessage(
ba.Lstr(translate=('serverResponses', cresult.error)),
(1, 0, 0))
ba.playsound(ba.getsound('error'))
return
self._debug_server_comm('got valid connect response')
assert cresult.addr is not None and cresult.port is not None
_ba.connect_to_party(cresult.addr, port=cresult.port)
except Exception:
self._debug_server_comm('got connect response error')
ba.playsound(ba.getsound('error'))
def save_state(self) -> None:
ba.app.ui.window_states[type(self)] = copy.deepcopy(self._state)
def restore_state(self) -> None:
state = ba.app.ui.window_states.get(type(self))
if state is None:
state = State()
assert isinstance(state, State)
self._state = state

View File

@ -14,7 +14,7 @@ from typing import TYPE_CHECKING, cast
import _ba import _ba
import ba import ba
from bastd.ui.gather.bases import GatherTab from bastd.ui.gather import GatherTab
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Callable, Any, Optional, Dict, Union, Tuple, List from typing import Callable, Any, Optional, Dict, Union, Tuple, List
@ -448,7 +448,7 @@ class PublicGatherTab(GatherTab):
# display these immediately when our UI comes back up which should # display these immediately when our UI comes back up which should
# be enough to make things feel nice and crisp while we do a full # be enough to make things feel nice and crisp while we do a full
# server re-query or whatnot. # server re-query or whatnot.
ba.app.ui.window_states[self.__class__.__name__] = State( ba.app.ui.window_states[type(self)] = State(
sub_tab=self._sub_tab, sub_tab=self._sub_tab,
parties=[(i, copy.copy(p)) for i, p in self._parties_sorted[:40]], parties=[(i, copy.copy(p)) for i, p in self._parties_sorted[:40]],
next_entry_index=self._next_entry_index, next_entry_index=self._next_entry_index,
@ -457,7 +457,7 @@ class PublicGatherTab(GatherTab):
have_valid_server_list=self._have_valid_server_list) have_valid_server_list=self._have_valid_server_list)
def restore_state(self) -> None: def restore_state(self) -> None:
state = ba.app.ui.window_states.get(self.__class__.__name__) state = ba.app.ui.window_states.get(type(self))
if state is None: if state is None:
state = State() state = State()
assert isinstance(state, State) assert isinstance(state, State)
@ -544,9 +544,8 @@ class PublicGatherTab(GatherTab):
position=(270, v + 13), position=(270, v + 13),
maxwidth=150, maxwidth=150,
scale=0.8, scale=0.8,
color=(0.5, 0.5, 0.5), color=(0.5, 0.46, 0.5),
flatness=1.0, flatness=1.0,
shadow=0.0,
h_align='right', h_align='right',
v_align='center') v_align='center')
@ -556,9 +555,8 @@ class PublicGatherTab(GatherTab):
position=(90, v - 8), position=(90, v - 8),
maxwidth=60, maxwidth=60,
scale=0.6, scale=0.6,
color=(0.5, 0.5, 0.5), color=(0.5, 0.46, 0.5),
flatness=1.0, flatness=1.0,
shadow=0.0,
h_align='center', h_align='center',
v_align='center') v_align='center')
ba.textwidget(text=ba.Lstr(resource='gatherWindow.partySizeText'), ba.textwidget(text=ba.Lstr(resource='gatherWindow.partySizeText'),
@ -567,9 +565,8 @@ class PublicGatherTab(GatherTab):
position=(755, v - 8), position=(755, v - 8),
maxwidth=60, maxwidth=60,
scale=0.6, scale=0.6,
color=(0.5, 0.5, 0.5), color=(0.5, 0.46, 0.5),
flatness=1.0, flatness=1.0,
shadow=0.0,
h_align='center', h_align='center',
v_align='center') v_align='center')
ba.textwidget(text=ba.Lstr(resource='gatherWindow.pingText'), ba.textwidget(text=ba.Lstr(resource='gatherWindow.pingText'),
@ -578,9 +575,8 @@ class PublicGatherTab(GatherTab):
position=(825, v - 8), position=(825, v - 8),
maxwidth=60, maxwidth=60,
scale=0.6, scale=0.6,
color=(0.5, 0.5, 0.5), color=(0.5, 0.46, 0.5),
flatness=1.0, flatness=1.0,
shadow=0.0,
h_align='center', h_align='center',
v_align='center') v_align='center')
v -= sub_scroll_height + 23 v -= sub_scroll_height + 23
@ -617,6 +613,20 @@ class PublicGatherTab(GatherTab):
v -= 25 v -= 25
is_public_enabled = _ba.get_public_party_enabled() is_public_enabled = _ba.get_public_party_enabled()
v -= 30 v -= 30
ba.textwidget(
parent=self._container,
size=(0, 0),
h_align='center',
v_align='center',
maxwidth=c_width * 0.9,
scale=0.7,
flatness=1.0,
color=(0.5, 0.46, 0.5),
position=(region_width * 0.5, v + 10),
text=ba.Lstr(resource='gatherWindow.publicHostRouterConfigText'))
v -= 30
party_name_text = ba.Lstr( party_name_text = ba.Lstr(
resource='gatherWindow.partyNameText', resource='gatherWindow.partyNameText',
fallback_resource='editGameListWindow.nameText') fallback_resource='editGameListWindow.nameText')
@ -710,11 +720,10 @@ class PublicGatherTab(GatherTab):
size=(0, 0), size=(0, 0),
scale=0.7, scale=0.7,
flatness=1.0, flatness=1.0,
shadow=0.0,
h_align='center', h_align='center',
v_align='top', v_align='top',
maxwidth=c_width, maxwidth=c_width * 0.9,
color=(0.6, 0.6, 0.6), color=(0.6, 0.56, 0.6),
position=(c_width * 0.5, v)) position=(c_width * 0.5, v))
v -= 90 v -= 90
ba.textwidget( ba.textwidget(
@ -723,11 +732,10 @@ class PublicGatherTab(GatherTab):
size=(0, 0), size=(0, 0),
scale=0.7, scale=0.7,
flatness=1.0, flatness=1.0,
shadow=0.0,
h_align='center', h_align='center',
v_align='center', v_align='center',
maxwidth=c_width * 0.9, maxwidth=c_width * 0.9,
color=ba.app.ui.infotextcolor, color=(0.5, 0.46, 0.5),
position=(c_width * 0.5, v)) position=(c_width * 0.5, v))
# If public sharing is already on, # If public sharing is already on,
@ -750,8 +758,6 @@ class PublicGatherTab(GatherTab):
self._have_valid_server_list = True self._have_valid_server_list = True
parties_in = result['l'] parties_in = result['l']
# parties_in.reverse()
assert isinstance(parties_in, list) assert isinstance(parties_in, list)
self._pending_party_infos += parties_in self._pending_party_infos += parties_in
@ -1060,7 +1066,6 @@ class PublicGatherTab(GatherTab):
callback=ba.WeakCall(self._on_public_party_query_result)) callback=ba.WeakCall(self._on_public_party_query_result))
_ba.run_transactions() _ba.run_transactions()
else: else:
# This will kick us over to a 'not signed in' message.
self._on_public_party_query_result(None) self._on_public_party_query_result(None)
def _ping_parties_periodically(self) -> None: def _ping_parties_periodically(self) -> None:

View File

@ -316,7 +316,7 @@ class KioskWindow(ba.Window):
repeat=True) repeat=True)
def _restore_state(self) -> None: def _restore_state(self) -> None:
sel_name = ba.app.ui.window_states.get(self.__class__.__name__) sel_name = ba.app.ui.window_states.get(type(self))
sel: Optional[ba.Widget] sel: Optional[ba.Widget]
if sel_name == 'b1': if sel_name == 'b1':
sel = self._b1 sel = self._b1
@ -355,7 +355,7 @@ class KioskWindow(ba.Window):
sel_name = 'b7' sel_name = 'b7'
else: else:
sel_name = 'b1' sel_name = 'b1'
ba.app.ui.window_states[self.__class__.__name__] = sel_name ba.app.ui.window_states[type(self)] = sel_name
def _update(self) -> None: def _update(self) -> None:
# Kiosk-mode is designed to be used signed-out... try for force # Kiosk-mode is designed to be used signed-out... try for force

View File

@ -540,13 +540,13 @@ class PlayWindow(ba.Window):
sel_name = 'Back' sel_name = 'Back'
else: else:
raise ValueError(f'unrecognized selection {sel}') raise ValueError(f'unrecognized selection {sel}')
ba.app.ui.window_states[self.__class__.__name__] = sel_name ba.app.ui.window_states[type(self)] = sel_name
except Exception: except Exception:
ba.print_exception(f'Error saving state for {self}.') ba.print_exception(f'Error saving state for {self}.')
def _restore_state(self) -> None: def _restore_state(self) -> None:
try: try:
sel_name = ba.app.ui.window_states.get(self.__class__.__name__) sel_name = ba.app.ui.window_states.get(type(self))
if sel_name == 'Team Games': if sel_name == 'Team Games':
sel = self._teams_button sel = self._teams_button
elif sel_name == 'Co-op Games': elif sel_name == 'Co-op Games':

View File

@ -628,13 +628,13 @@ class PlaylistBrowserWindow(ba.Window):
sel_name = 'Scroll' sel_name = 'Scroll'
else: else:
raise Exception('unrecognized selected widget') raise Exception('unrecognized selected widget')
ba.app.ui.window_states[self.__class__.__name__] = sel_name ba.app.ui.window_states[type(self)] = sel_name
except Exception: except Exception:
ba.print_exception(f'Error saving state for {self}.') ba.print_exception(f'Error saving state for {self}.')
def _restore_state(self) -> None: def _restore_state(self) -> None:
try: try:
sel_name = ba.app.ui.window_states.get(self.__class__.__name__) sel_name = ba.app.ui.window_states.get(type(self))
if sel_name == 'Back': if sel_name == 'Back':
sel = self._back_button sel = self._back_button
elif sel_name == 'Scroll': elif sel_name == 'Scroll':

View File

@ -347,13 +347,13 @@ class ProfileBrowserWindow(ba.Window):
sel_name = 'Scroll' sel_name = 'Scroll'
else: else:
sel_name = 'Back' sel_name = 'Back'
ba.app.ui.window_states[self.__class__.__name__] = sel_name ba.app.ui.window_states[type(self)] = sel_name
except Exception: except Exception:
ba.print_exception(f'Error saving state for {self}.') ba.print_exception(f'Error saving state for {self}.')
def _restore_state(self) -> None: def _restore_state(self) -> None:
try: try:
sel_name = ba.app.ui.window_states.get(self.__class__.__name__) sel_name = ba.app.ui.window_states.get(type(self))
if sel_name == 'Scroll': if sel_name == 'Scroll':
sel = self._scrollwidget sel = self._scrollwidget
elif sel_name == 'New': elif sel_name == 'New':

View File

@ -626,16 +626,14 @@ class AdvancedSettingsWindow(ba.Window):
sel_name = 'Back' sel_name = 'Back'
else: else:
raise ValueError(f'unrecognized selection \'{sel}\'') raise ValueError(f'unrecognized selection \'{sel}\'')
ba.app.ui.window_states[self.__class__.__name__] = { ba.app.ui.window_states[type(self)] = {'sel_name': sel_name}
'sel_name': sel_name
}
except Exception: except Exception:
ba.print_exception(f'Error saving state for {self.__class__}') ba.print_exception(f'Error saving state for {self.__class__}')
def _restore_state(self) -> None: def _restore_state(self) -> None:
# pylint: disable=too-many-branches # pylint: disable=too-many-branches
try: try:
sel_name = ba.app.ui.window_states.get(self.__class__.__name__, sel_name = ba.app.ui.window_states.get(type(self),
{}).get('sel_name') {}).get('sel_name')
if sel_name == 'Back': if sel_name == 'Back':
sel = self._back_button sel = self._back_button

View File

@ -256,15 +256,13 @@ class AllSettingsWindow(ba.Window):
sel_name = 'Back' sel_name = 'Back'
else: else:
raise ValueError(f'unrecognized selection \'{sel}\'') raise ValueError(f'unrecognized selection \'{sel}\'')
ba.app.ui.window_states[self.__class__.__name__] = { ba.app.ui.window_states[type(self)] = {'sel_name': sel_name}
'sel_name': sel_name
}
except Exception: except Exception:
ba.print_exception(f'Error saving state for {self}.') ba.print_exception(f'Error saving state for {self}.')
def _restore_state(self) -> None: def _restore_state(self) -> None:
try: try:
sel_name = ba.app.ui.window_states.get(self.__class__.__name__, sel_name = ba.app.ui.window_states.get(type(self),
{}).get('sel_name') {}).get('sel_name')
sel: Optional[ba.Widget] sel: Optional[ba.Widget]
if sel_name == 'Controllers': if sel_name == 'Controllers':

View File

@ -252,13 +252,13 @@ class AudioSettingsWindow(ba.Window):
sel_name = 'VRHeadRelative' sel_name = 'VRHeadRelative'
else: else:
raise ValueError(f'unrecognized selection \'{sel}\'') raise ValueError(f'unrecognized selection \'{sel}\'')
ba.app.ui.window_states[self.__class__.__name__] = sel_name ba.app.ui.window_states[type(self)] = sel_name
except Exception: except Exception:
ba.print_exception(f'Error saving state for {self.__class__}.') ba.print_exception(f'Error saving state for {self.__class__}.')
def _restore_state(self) -> None: def _restore_state(self) -> None:
try: try:
sel_name = ba.app.ui.window_states.get(self.__class__.__name__) sel_name = ba.app.ui.window_states.get(type(self))
sel: Optional[ba.Widget] sel: Optional[ba.Widget]
if sel_name == 'SoundMinus': if sel_name == 'SoundMinus':
sel = self._sound_volume_numedit.minusbutton sel = self._sound_volume_numedit.minusbutton

View File

@ -457,10 +457,10 @@ class ControlsSettingsWindow(ba.Window):
sel_name = 'Wiimotes' sel_name = 'Wiimotes'
else: else:
sel_name = 'Back' sel_name = 'Back'
ba.app.ui.window_states[self.__class__.__name__] = sel_name ba.app.ui.window_states[type(self)] = sel_name
def _restore_state(self) -> None: def _restore_state(self) -> None:
sel_name = ba.app.ui.window_states.get(self.__class__.__name__) sel_name = ba.app.ui.window_states.get(type(self))
if sel_name == 'GamePads': if sel_name == 'GamePads':
sel = self._gamepads_button sel = self._gamepads_button
elif sel_name == 'Touch': elif sel_name == 'Touch':

View File

@ -463,13 +463,13 @@ class SoundtrackBrowserWindow(ba.Window):
sel_name = 'Back' sel_name = 'Back'
else: else:
raise ValueError(f'unrecognized selection \'{sel}\'') raise ValueError(f'unrecognized selection \'{sel}\'')
ba.app.ui.window_states[self.__class__.__name__] = sel_name ba.app.ui.window_states[type(self)] = sel_name
except Exception: except Exception:
ba.print_exception(f'Error saving state for {self}.') ba.print_exception(f'Error saving state for {self}.')
def _restore_state(self) -> None: def _restore_state(self) -> None:
try: try:
sel_name = ba.app.ui.window_states.get(self.__class__.__name__) sel_name = ba.app.ui.window_states.get(type(self))
if sel_name == 'Scroll': if sel_name == 'Scroll':
sel = self._scrollwidget sel = self._scrollwidget
elif sel_name == 'New': elif sel_name == 'New':

View File

@ -1011,7 +1011,7 @@ class StoreBrowserWindow(ba.Window):
sel_name = f'Tab:{selected_tab_ids[0].value}' sel_name = f'Tab:{selected_tab_ids[0].value}'
else: else:
raise ValueError(f'unrecognized selection \'{sel}\'') raise ValueError(f'unrecognized selection \'{sel}\'')
ba.app.ui.window_states[self.__class__.__name__] = { ba.app.ui.window_states[type(self)] = {
'sel_name': sel_name, 'sel_name': sel_name,
} }
except Exception: except Exception:
@ -1021,7 +1021,7 @@ class StoreBrowserWindow(ba.Window):
from efro.util import enum_by_value from efro.util import enum_by_value
try: try:
sel: Optional[ba.Widget] sel: Optional[ba.Widget]
sel_name = ba.app.ui.window_states.get(self.__class__.__name__, sel_name = ba.app.ui.window_states.get(type(self),
{}).get('sel_name') {}).get('sel_name')
assert isinstance(sel_name, (str, type(None))) assert isinstance(sel_name, (str, type(None)))

View File

@ -35,6 +35,7 @@ class TeamNamesColorsWindow(popup.PopupWindow):
appconfig = ba.app.config appconfig = ba.app.config
self._names = list( self._names = list(
appconfig.get('Custom Team Names', DEFAULT_TEAM_NAMES)) appconfig.get('Custom Team Names', DEFAULT_TEAM_NAMES))
# We need to flatten the translation since it will be an # We need to flatten the translation since it will be an
# editable string. # editable string.
self._names = [ self._names = [
@ -46,7 +47,7 @@ class TeamNamesColorsWindow(popup.PopupWindow):
self._color_buttons: List[ba.Widget] = [] self._color_buttons: List[ba.Widget] = []
self._color_text_fields: List[ba.Widget] = [] self._color_text_fields: List[ba.Widget] = []
ba.buttonwidget( resetbtn = ba.buttonwidget(
parent=self.root_widget, parent=self.root_widget,
label=ba.Lstr(resource='settingsWindowAdvanced.resetText'), label=ba.Lstr(resource='settingsWindowAdvanced.resetText'),
autoselect=True, autoselect=True,
@ -77,20 +78,27 @@ class TeamNamesColorsWindow(popup.PopupWindow):
description=ba.Lstr(resource='nameText'), description=ba.Lstr(resource='nameText'),
editable=True, editable=True,
padding=4)) padding=4))
ba.buttonwidget(parent=self.root_widget, ba.widget(edit=self._color_text_fields[0],
label=ba.Lstr(resource='cancelText'), down_widget=self._color_text_fields[1])
autoselect=True, ba.widget(edit=self._color_text_fields[1],
on_activate_call=self._on_cancel_press, up_widget=self._color_text_fields[0])
size=(150, 50), ba.widget(edit=self._color_text_fields[0], up_widget=resetbtn)
position=(self._width * 0.5 - 200, 20))
ba.buttonwidget(parent=self.root_widget, cancelbtn = ba.buttonwidget(parent=self.root_widget,
label=ba.Lstr(resource='saveText'), label=ba.Lstr(resource='cancelText'),
autoselect=True, autoselect=True,
on_activate_call=self._save, on_activate_call=self._on_cancel_press,
size=(150, 50), size=(150, 50),
position=(self._width * 0.5 + 50, 20)) position=(self._width * 0.5 - 200, 20))
savebtn = ba.buttonwidget(parent=self.root_widget,
label=ba.Lstr(resource='saveText'),
autoselect=True,
on_activate_call=self._save,
size=(150, 50),
position=(self._width * 0.5 + 50, 20))
ba.containerwidget(edit=self.root_widget, ba.containerwidget(edit=self.root_widget,
selected_child=self._color_buttons[0]) selected_child=self._color_buttons[0])
ba.widget(edit=savebtn, left_widget=cancelbtn)
self._update() self._update()
def _color_click(self, i: int) -> None: def _color_click(self, i: int) -> None:

View File

@ -496,9 +496,7 @@ class WatchWindow(ba.Window):
sel_name = 'TabContainer' sel_name = 'TabContainer'
else: else:
raise ValueError(f'unrecognized selection {sel}') raise ValueError(f'unrecognized selection {sel}')
ba.app.ui.window_states[self.__class__.__name__] = { ba.app.ui.window_states[type(self)] = {'sel_name': sel_name}
'sel_name': sel_name
}
except Exception: except Exception:
ba.print_exception(f'Error saving state for {self}.') ba.print_exception(f'Error saving state for {self}.')
@ -506,7 +504,7 @@ class WatchWindow(ba.Window):
from efro.util import enum_by_value from efro.util import enum_by_value
try: try:
sel: Optional[ba.Widget] sel: Optional[ba.Widget]
sel_name = ba.app.ui.window_states.get(self.__class__.__name__, sel_name = ba.app.ui.window_states.get(type(self),
{}).get('sel_name') {}).get('sel_name')
assert isinstance(sel_name, (str, type(None))) assert isinstance(sel_name, (str, type(None)))
try: try:

View File

@ -17,6 +17,7 @@
<w>aclass's</w> <w>aclass's</w>
<w>activityplayer</w> <w>activityplayer</w>
<w>addcall</w> <w>addcall</w>
<w>addchars</w>
<w>addrs</w> <w>addrs</w>
<w>adjoint</w> <w>adjoint</w>
<w>adminset</w> <w>adminset</w>
@ -47,6 +48,7 @@
<w>appname</w> <w>appname</w>
<w>appnameupper</w> <w>appnameupper</w>
<w>appstate</w> <w>appstate</w>
<w>argsjoined</w>
<w>asci</w> <w>asci</w>
<w>assigninput</w> <w>assigninput</w>
<w>athome</w> <w>athome</w>
@ -54,6 +56,7 @@
<w>audiocache</w> <w>audiocache</w>
<w>automagically</w> <w>automagically</w>
<w>autoselect</w> <w>autoselect</w>
<w>availmins</w>
<w>avel</w> <w>avel</w>
<w>avels</w> <w>avels</w>
<w>axismotion</w> <w>axismotion</w>
@ -114,6 +117,7 @@
<w>bsstd</w> <w>bsstd</w>
<w>bstat</w> <w>bstat</w>
<w>bsuuid</w> <w>bsuuid</w>
<w>btnlabel</w>
<w>bufs</w> <w>bufs</w>
<w>buildconfig</w> <w>buildconfig</w>
<w>buildnumber</w> <w>buildnumber</w>
@ -131,8 +135,10 @@
<w>camalign</w> <w>camalign</w>
<w>camelback</w> <w>camelback</w>
<w>camerashake</w> <w>camerashake</w>
<w>cancelbtn</w>
<w>capitan</w> <w>capitan</w>
<w>cargs</w> <w>cargs</w>
<w>cbtnoffs</w>
<w>ccdd</w> <w>ccdd</w>
<w>ccontext</w> <w>ccontext</w>
<w>ccylinder</w> <w>ccylinder</w>
@ -177,6 +183,7 @@
<w>cpuid</w> <w>cpuid</w>
<w>crashenv</w> <w>crashenv</w>
<w>crashlytics</w> <w>crashlytics</w>
<w>cresult</w>
<w>crom</w> <w>crom</w>
<w>crosswire</w> <w>crosswire</w>
<w>crvel</w> <w>crvel</w>
@ -329,6 +336,7 @@
<w>fread</w> <w>fread</w>
<w>freeform</w> <w>freeform</w>
<w>freeifaddrs</w> <w>freeifaddrs</w>
<w>freemins</w>
<w>freqs</w> <w>freqs</w>
<w>froemling</w> <w>froemling</w>
<w>fromini</w> <w>fromini</w>
@ -411,6 +419,7 @@
<w>hostactivity</w> <w>hostactivity</w>
<w>hostcmd</w> <w>hostcmd</w>
<w>hostinfo</w> <w>hostinfo</w>
<w>hostingstate</w>
<w>hotkeys</w> <w>hotkeys</w>
<w>hotplug</w> <w>hotplug</w>
<w>hscrollwidget</w> <w>hscrollwidget</w>
@ -491,6 +500,7 @@
<w>linearsize</w> <w>linearsize</w>
<w>listobj</w> <w>listobj</w>
<w>llock</w> <w>llock</w>
<w>lockpath</w>
<w>lockstr</w> <w>lockstr</w>
<w>locktype</w> <w>locktype</w>
<w>logmsg</w> <w>logmsg</w>
@ -596,6 +606,7 @@
<w>nonlint</w> <w>nonlint</w>
<w>noone</w> <w>noone</w>
<w>nothin</w> <w>nothin</w>
<w>nowtickets</w>
<w>nptr</w> <w>nptr</w>
<w>nsize</w> <w>nsize</w>
<w>ntoa</w> <w>ntoa</w>
@ -613,6 +624,8 @@
<w>obvs</w> <w>obvs</w>
<w>oculus</w> <w>oculus</w>
<w>oenval</w> <w>oenval</w>
<w>offsx</w>
<w>offsy</w>
<w>oiffsss</w> <w>oiffsss</w>
<w>oldname</w> <w>oldname</w>
<w>oooo</w> <w>oooo</w>
@ -668,6 +681,7 @@
<w>positivez</w> <w>positivez</w>
<w>postinit</w> <w>postinit</w>
<w>powerup</w> <w>powerup</w>
<w>pptabcom</w>
<w>precalc</w> <w>precalc</w>
<w>predeclare</w> <w>predeclare</w>
<w>prefs</w> <w>prefs</w>
@ -679,9 +693,11 @@
<w>printnodes</w> <w>printnodes</w>
<w>printobjects</w> <w>printobjects</w>
<w>priv</w> <w>priv</w>
<w>privatetab</w>
<w>profilers</w> <w>profilers</w>
<w>prog</w> <w>prog</w>
<w>proj</w> <w>proj</w>
<w>projdir</w>
<w>prolly</w> <w>prolly</w>
<w>psmx</w> <w>psmx</w>
<w>pspec</w> <w>pspec</w>
@ -739,6 +755,7 @@
<w>reprfunc</w> <w>reprfunc</w>
<w>rerase</w> <w>rerase</w>
<w>resends</w> <w>resends</w>
<w>resetbtn</w>
<w>resetinput</w> <w>resetinput</w>
<w>resync</w> <w>resync</w>
<w>retrysecs</w> <w>retrysecs</w>
@ -759,6 +776,7 @@
<w>safecolor</w> <w>safecolor</w>
<w>samsung</w> <w>samsung</w>
<w>sapspace</w> <w>sapspace</w>
<w>savebtn</w>
<w>savebutton</w> <w>savebutton</w>
<w>scancode</w> <w>scancode</w>
<w>scenetime</w> <w>scenetime</w>
@ -767,6 +785,10 @@
<w>sdkcheck</w> <w>sdkcheck</w>
<w>sdl's</w> <w>sdl's</w>
<w>sdlk</w> <w>sdlk</w>
<w>selchild</w>
<w>selindex</w>
<w>selwidget</w>
<w>selwidgets</w>
<w>seqlen</w> <w>seqlen</w>
<w>seqtype</w> <w>seqtype</w>
<w>seqtypestr</w> <w>seqtypestr</w>
@ -884,6 +906,7 @@
<w>theres</w> <w>theres</w>
<w>threadname</w> <w>threadname</w>
<w>threadtype</w> <w>threadtype</w>
<w>ticon</w>
<w>tiltage</w> <w>tiltage</w>
<w>timedisplay</w> <w>timedisplay</w>
<w>timeformat</w> <w>timeformat</w>

View File

@ -30,7 +30,8 @@
"requests", "requests",
"typing_extensions", "typing_extensions",
"cpplint", "cpplint",
"ansiwrap" "ansiwrap",
"filelock"
], ],
"python_paths": [ "python_paths": [
"assets/src/ba_data/python", "assets/src/ba_data/python",

View File

@ -1,5 +1,5 @@
<!-- THIS FILE IS AUTO GENERATED; DO NOT EDIT BY HAND --> <!-- THIS FILE IS AUTO GENERATED; DO NOT EDIT BY HAND -->
<h4><em>last updated on 2021-01-15 for Ballistica version 1.5.30 build 20267</em></h4> <h4><em>last updated on 2021-01-26 for Ballistica version 1.6.0 build 20278</em></h4>
<p>This page documents the Python classes and functions in the 'ba' module, <p>This page documents the Python classes and functions in the 'ba' module,
which are the ones most relevant to modding in Ballistica. If you come across something you feel should be included here or could be better explained, please <a href="mailto:support@froemling.net">let me know</a>. Happy modding!</p> which are the ones most relevant to modding in Ballistica. If you come across something you feel should be included here or could be better explained, please <a href="mailto:support@froemling.net">let me know</a>. Happy modding!</p>
<hr> <hr>
@ -85,6 +85,10 @@
<h4><a name="function_category_General_Utility_Functions">General Utility Functions</a></h4> <h4><a name="function_category_General_Utility_Functions">General Utility Functions</a></h4>
<ul> <ul>
<li><a href="#function_ba_charstr">ba.charstr()</a></li> <li><a href="#function_ba_charstr">ba.charstr()</a></li>
<li><a href="#function_ba_clipboard_get_text">ba.clipboard_get_text()</a></li>
<li><a href="#function_ba_clipboard_has_text">ba.clipboard_has_text()</a></li>
<li><a href="#function_ba_clipboard_is_supported">ba.clipboard_is_supported()</a></li>
<li><a href="#function_ba_clipboard_set_text">ba.clipboard_set_text()</a></li>
<li><a href="#function_ba_do_once">ba.do_once()</a></li> <li><a href="#function_ba_do_once">ba.do_once()</a></li>
<li><a href="#function_ba_garbage_collect">ba.garbage_collect()</a></li> <li><a href="#function_ba_garbage_collect">ba.garbage_collect()</a></li>
<li><a href="#function_ba_getclass">ba.getclass()</a></li> <li><a href="#function_ba_getclass">ba.getclass()</a></li>
@ -395,7 +399,7 @@ actually award achievements.</p>
</dl> </dl>
<hr> <hr>
<h2><strong><a name="class_ba_Activity">ba.Activity</a></strong></h3> <h2><strong><a name="class_ba_Activity">ba.Activity</a></strong></h3>
<p>Inherits from: <a href="#class_ba_DependencyComponent">ba.DependencyComponent</a>, <a href="#class_typing_Generic">typing.Generic</a></p> <p>Inherits from: <a href="#class_ba_DependencyComponent">ba.DependencyComponent</a>, <a href="https://docs.python.org/3/library/typing.html#typing.Generic">typing.Generic</a></p>
<p>Units of execution wrangled by a <a href="#class_ba_Session">ba.Session</a>.</p> <p>Units of execution wrangled by a <a href="#class_ba_Session">ba.Session</a>.</p>
<p>Category: <a href="#class_category_Gameplay_Classes">Gameplay Classes</a></p> <p>Category: <a href="#class_category_Gameplay_Classes">Gameplay Classes</a></p>
@ -659,7 +663,7 @@ is a convenient way to access this same functionality.</p>
</dl> </dl>
<hr> <hr>
<h2><strong><a name="class_ba_ActivityNotFoundError">ba.ActivityNotFoundError</a></strong></h3> <h2><strong><a name="class_ba_ActivityNotFoundError">ba.ActivityNotFoundError</a></strong></h3>
<p>Inherits from: <a href="#class_ba_NotFoundError">ba.NotFoundError</a>, Exception, BaseException</p> <p>Inherits from: <a href="#class_ba_NotFoundError">ba.NotFoundError</a>, <a href="https://docs.python.org/3/library/exceptions.html#Exception">Exception</a>, <a href="https://docs.python.org/3/library/exceptions.html#BaseException">BaseException</a></p>
<p>Exception raised when an expected <a href="#class_ba_Activity">ba.Activity</a> does not exist.</p> <p>Exception raised when an expected <a href="#class_ba_Activity">ba.Activity</a> does not exist.</p>
<p>Category: <a href="#class_category_Exception_Classes">Exception Classes</a> <p>Category: <a href="#class_category_Exception_Classes">Exception Classes</a>
@ -784,7 +788,7 @@ even if myactor is set to None.</p>
<p>Return the <a href="#class_ba_Activity">ba.Activity</a> this Actor is associated with.</p> <p>Return the <a href="#class_ba_Activity">ba.Activity</a> this Actor is associated with.</p>
<p>If the Activity no longer exists, raises a <a href="#class_ba_ActivityNotFoundError">ba.ActivityNotFoundError</a> <p>If the Activity no longer exists, raises a <a href="#class_ba_ActivityNotFoundError">ba.ActivityNotFoundError</a>
or returns None depending on whether 'doraise' is set.</p> or returns None depending on whether 'doraise' is True.</p>
</dd> </dd>
<dt><h4><a name="method_ba_Actor__handlemessage">handlemessage()</a></dt></h4><dd> <dt><h4><a name="method_ba_Actor__handlemessage">handlemessage()</a></dt></h4><dd>
@ -823,7 +827,7 @@ likely result in errors.</p>
</dl> </dl>
<hr> <hr>
<h2><strong><a name="class_ba_ActorNotFoundError">ba.ActorNotFoundError</a></strong></h3> <h2><strong><a name="class_ba_ActorNotFoundError">ba.ActorNotFoundError</a></strong></h3>
<p>Inherits from: <a href="#class_ba_NotFoundError">ba.NotFoundError</a>, Exception, BaseException</p> <p>Inherits from: <a href="#class_ba_NotFoundError">ba.NotFoundError</a>, <a href="https://docs.python.org/3/library/exceptions.html#Exception">Exception</a>, <a href="https://docs.python.org/3/library/exceptions.html#BaseException">BaseException</a></p>
<p>Exception raised when an expected <a href="#class_ba_Actor">ba.Actor</a> does not exist.</p> <p>Exception raised when an expected <a href="#class_ba_Actor">ba.Actor</a> does not exist.</p>
<p>Category: <a href="#class_category_Exception_Classes">Exception Classes</a> <p>Category: <a href="#class_category_Exception_Classes">Exception Classes</a>
@ -1010,7 +1014,7 @@ to resume.</p>
</dl> </dl>
<hr> <hr>
<h2><strong><a name="class_ba_AppConfig">ba.AppConfig</a></strong></h3> <h2><strong><a name="class_ba_AppConfig">ba.AppConfig</a></strong></h3>
<p>Inherits from: dict</p> <p>Inherits from: builtins.dict</p>
<p>A special dict that holds the game's persistent configuration values.</p> <p>A special dict that holds the game's persistent configuration values.</p>
<p>Category: <a href="#class_category_App_Classes">App Classes</a></p> <p>Category: <a href="#class_category_App_Classes">App Classes</a></p>
@ -1023,7 +1027,7 @@ to resume.</p>
<p> AppConfig data is stored as json on disk on so make sure to only place <p> AppConfig data is stored as json on disk on so make sure to only place
json-friendly values in it (dict, list, str, float, int, bool). json-friendly values in it (dict, list, str, float, int, bool).
Be aware that tuples will be quietly converted to lists. Be aware that tuples will be quietly converted to lists when stored.
</p> </p>
<h3>Methods Defined or Overridden:</h3> <h3>Methods Defined or Overridden:</h3>
@ -1596,7 +1600,7 @@ start_long_action(callback_when_done=<a href="#class_ba_ContextCall">ba.ContextC
<hr> <hr>
<h2><strong><a name="class_ba_ContextError">ba.ContextError</a></strong></h3> <h2><strong><a name="class_ba_ContextError">ba.ContextError</a></strong></h3>
<p>Inherits from: Exception, BaseException</p> <p>Inherits from: <a href="https://docs.python.org/3/library/exceptions.html#Exception">Exception</a>, <a href="https://docs.python.org/3/library/exceptions.html#BaseException">BaseException</a></p>
<p>Exception raised when a call is made in an invalid context.</p> <p>Exception raised when a call is made in an invalid context.</p>
<p>Category: <a href="#class_category_Exception_Classes">Exception Classes</a></p> <p>Category: <a href="#class_category_Exception_Classes">Exception Classes</a></p>
@ -1606,10 +1610,10 @@ start_long_action(callback_when_done=<a href="#class_ba_ContextCall">ba.ContextC
</p> </p>
<h3>Methods:</h3> <h3>Methods:</h3>
<p>&lt;all methods inherited from <a href="#class_builtins_Exception">builtins.Exception</a>&gt;</p> <p>&lt;all methods inherited from <a href="https://docs.python.org/3/library/exceptions.html#Exception">Exception</a>&gt;</p>
<hr> <hr>
<h2><strong><a name="class_ba_CoopGameActivity">ba.CoopGameActivity</a></strong></h3> <h2><strong><a name="class_ba_CoopGameActivity">ba.CoopGameActivity</a></strong></h3>
<p>Inherits from: <a href="#class_ba_GameActivity">ba.GameActivity</a>, <a href="#class_ba_Activity">ba.Activity</a>, <a href="#class_ba_DependencyComponent">ba.DependencyComponent</a>, <a href="#class_typing_Generic">typing.Generic</a></p> <p>Inherits from: <a href="#class_ba_GameActivity">ba.GameActivity</a>, <a href="#class_ba_Activity">ba.Activity</a>, <a href="#class_ba_DependencyComponent">ba.DependencyComponent</a>, <a href="https://docs.python.org/3/library/typing.html#typing.Generic">typing.Generic</a></p>
<p>Base class for cooperative-mode games.</p> <p>Base class for cooperative-mode games.</p>
<p>Category: <a href="#class_category_Gameplay_Classes">Gameplay Classes</a> <p>Category: <a href="#class_category_Gameplay_Classes">Gameplay Classes</a>
@ -1841,7 +1845,7 @@ the data object is requested and when it's value is accessed.</p>
</dl> </dl>
<hr> <hr>
<h2><strong><a name="class_ba_DeathType">ba.DeathType</a></strong></h3> <h2><strong><a name="class_ba_DeathType">ba.DeathType</a></strong></h3>
<p>Inherits from: enum.Enum</p> <p>Inherits from: <a href="https://docs.python.org/3/library/enum.html#enum.Enum">enum.Enum</a></p>
<p>A reason for a death.</p> <p>A reason for a death.</p>
<p>Category: <a href="#class_category_Enums">Enums</a> <p>Category: <a href="#class_category_Enums">Enums</a>
@ -1858,7 +1862,7 @@ the data object is requested and when it's value is accessed.</p>
</ul> </ul>
<hr> <hr>
<h2><strong><a name="class_ba_DelegateNotFoundError">ba.DelegateNotFoundError</a></strong></h3> <h2><strong><a name="class_ba_DelegateNotFoundError">ba.DelegateNotFoundError</a></strong></h3>
<p>Inherits from: <a href="#class_ba_NotFoundError">ba.NotFoundError</a>, Exception, BaseException</p> <p>Inherits from: <a href="#class_ba_NotFoundError">ba.NotFoundError</a>, <a href="https://docs.python.org/3/library/exceptions.html#Exception">Exception</a>, <a href="https://docs.python.org/3/library/exceptions.html#BaseException">BaseException</a></p>
<p>Exception raised when an expected delegate object does not exist.</p> <p>Exception raised when an expected delegate object does not exist.</p>
<p>Category: <a href="#class_category_Exception_Classes">Exception Classes</a> <p>Category: <a href="#class_category_Exception_Classes">Exception Classes</a>
@ -1868,7 +1872,7 @@ the data object is requested and when it's value is accessed.</p>
<p>&lt;all methods inherited from <a href="#class_ba_NotFoundError">ba.NotFoundError</a>&gt;</p> <p>&lt;all methods inherited from <a href="#class_ba_NotFoundError">ba.NotFoundError</a>&gt;</p>
<hr> <hr>
<h2><strong><a name="class_ba_Dependency">ba.Dependency</a></strong></h3> <h2><strong><a name="class_ba_Dependency">ba.Dependency</a></strong></h3>
<p>Inherits from: <a href="#class_typing_Generic">typing.Generic</a></p> <p>Inherits from: <a href="https://docs.python.org/3/library/typing.html#typing.Generic">typing.Generic</a></p>
<p>A dependency on a DependencyComponent (with an optional config).</p> <p>A dependency on a DependencyComponent (with an optional config).</p>
<p>Category: <a href="#class_category_Dependency_Classes">Dependency Classes</a></p> <p>Category: <a href="#class_category_Dependency_Classes">Dependency Classes</a></p>
@ -1941,7 +1945,7 @@ on the dep config value. (for instance a map required by a game type)</p>
</dl> </dl>
<hr> <hr>
<h2><strong><a name="class_ba_DependencyError">ba.DependencyError</a></strong></h3> <h2><strong><a name="class_ba_DependencyError">ba.DependencyError</a></strong></h3>
<p>Inherits from: Exception, BaseException</p> <p>Inherits from: <a href="https://docs.python.org/3/library/exceptions.html#Exception">Exception</a>, <a href="https://docs.python.org/3/library/exceptions.html#BaseException">BaseException</a></p>
<p>Exception raised when one or more <a href="#class_ba_Dependency">ba.Dependency</a> items are missing.</p> <p>Exception raised when one or more <a href="#class_ba_Dependency">ba.Dependency</a> items are missing.</p>
<p>Category: <a href="#class_category_Exception_Classes">Exception Classes</a></p> <p>Category: <a href="#class_category_Exception_Classes">Exception Classes</a></p>
@ -1968,7 +1972,7 @@ on the dep config value. (for instance a map required by a game type)</p>
</dl> </dl>
<hr> <hr>
<h2><strong><a name="class_ba_DependencySet">ba.DependencySet</a></strong></h3> <h2><strong><a name="class_ba_DependencySet">ba.DependencySet</a></strong></h3>
<p>Inherits from: <a href="#class_typing_Generic">typing.Generic</a></p> <p>Inherits from: <a href="https://docs.python.org/3/library/typing.html#typing.Generic">typing.Generic</a></p>
<p>Set of resolved dependencies and their associated data.</p> <p>Set of resolved dependencies and their associated data.</p>
<p>Category: <a href="#class_category_Dependency_Classes">Dependency Classes</a></p> <p>Category: <a href="#class_category_Dependency_Classes">Dependency Classes</a></p>
@ -2129,13 +2133,13 @@ its time with lingering corpses, sound effects, etc.</p>
</dl> </dl>
<hr> <hr>
<h2><strong><a name="class_ba_EmptyPlayer">ba.EmptyPlayer</a></strong></h3> <h2><strong><a name="class_ba_EmptyPlayer">ba.EmptyPlayer</a></strong></h3>
<p>Inherits from: <a href="#class_ba_Player">ba.Player</a>, <a href="#class_typing_Generic">typing.Generic</a></p> <p>Inherits from: <a href="#class_ba_Player">ba.Player</a>, <a href="https://docs.python.org/3/library/typing.html#typing.Generic">typing.Generic</a></p>
<p>An empty player for use by Activities that don't need to define one.</p> <p>An empty player for use by Activities that don't need to define one.</p>
<p>Category: <a href="#class_category_Gameplay_Classes">Gameplay Classes</a></p> <p>Category: <a href="#class_category_Gameplay_Classes">Gameplay Classes</a></p>
<p> <a href="#class_ba_Player">ba.Player</a> and <a href="#class_ba_Team">ba.Team</a> are 'Generic' types, and so passing them as <p> <a href="#class_ba_Player">ba.Player</a> and <a href="#class_ba_Team">ba.Team</a> are 'Generic' types, and so passing those top level
type arguments when defining a <a href="#class_ba_Activity">ba.Activity</a> reduces type safety. classes as type arguments when defining a <a href="#class_ba_Activity">ba.Activity</a> reduces type safety.
For example, activity.teams[0].player will have type 'Any' in that case. For example, activity.teams[0].player will have type 'Any' in that case.
For that reason, it is better to pass EmptyPlayer and EmptyTeam when For that reason, it is better to pass EmptyPlayer and EmptyTeam when
defining a <a href="#class_ba_Activity">ba.Activity</a> that does not need custom types of its own.</p> defining a <a href="#class_ba_Activity">ba.Activity</a> that does not need custom types of its own.</p>
@ -2192,13 +2196,13 @@ its time with lingering corpses, sound effects, etc.</p>
<p>&lt;all methods inherited from <a href="#class_ba_Player">ba.Player</a>&gt;</p> <p>&lt;all methods inherited from <a href="#class_ba_Player">ba.Player</a>&gt;</p>
<hr> <hr>
<h2><strong><a name="class_ba_EmptyTeam">ba.EmptyTeam</a></strong></h3> <h2><strong><a name="class_ba_EmptyTeam">ba.EmptyTeam</a></strong></h3>
<p>Inherits from: <a href="#class_ba_Team">ba.Team</a>, <a href="#class_typing_Generic">typing.Generic</a></p> <p>Inherits from: <a href="#class_ba_Team">ba.Team</a>, <a href="https://docs.python.org/3/library/typing.html#typing.Generic">typing.Generic</a></p>
<p>An empty player for use by Activities that don't need to define one.</p> <p>An empty player for use by Activities that don't need to define one.</p>
<p>Category: <a href="#class_category_Gameplay_Classes">Gameplay Classes</a></p> <p>Category: <a href="#class_category_Gameplay_Classes">Gameplay Classes</a></p>
<p> <a href="#class_ba_Player">ba.Player</a> and <a href="#class_ba_Team">ba.Team</a> are 'Generic' types, and so passing them as <p> <a href="#class_ba_Player">ba.Player</a> and <a href="#class_ba_Team">ba.Team</a> are 'Generic' types, and so passing those top level
type arguments when defining a <a href="#class_ba_Activity">ba.Activity</a> reduces type safety. classes as type arguments when defining a <a href="#class_ba_Activity">ba.Activity</a> reduces type safety.
For example, activity.teams[0].player will have type 'Any' in that case. For example, activity.teams[0].player will have type 'Any' in that case.
For that reason, it is better to pass EmptyPlayer and EmptyTeam when For that reason, it is better to pass EmptyPlayer and EmptyTeam when
defining a <a href="#class_ba_Activity">ba.Activity</a> that does not need custom types of its own.</p> defining a <a href="#class_ba_Activity">ba.Activity</a> that does not need custom types of its own.</p>
@ -2234,7 +2238,7 @@ its time with lingering corpses, sound effects, etc.</p>
<p>&lt;all methods inherited from <a href="#class_ba_Team">ba.Team</a>&gt;</p> <p>&lt;all methods inherited from <a href="#class_ba_Team">ba.Team</a>&gt;</p>
<hr> <hr>
<h2><strong><a name="class_ba_Existable">ba.Existable</a></strong></h3> <h2><strong><a name="class_ba_Existable">ba.Existable</a></strong></h3>
<p>Inherits from: <a href="#class_typing_Protocol">typing.Protocol</a>, <a href="#class_typing_Generic">typing.Generic</a></p> <p>Inherits from: <a href="https://docs.python.org/3/library/typing.html#typing.Protocol">typing.Protocol</a>, <a href="https://docs.python.org/3/library/typing.html#typing.Generic">typing.Generic</a></p>
<p>A Protocol for objects supporting an exists() method.</p> <p>A Protocol for objects supporting an exists() method.</p>
<p>Category: <a href="#class_category_Protocols">Protocols</a> <p>Category: <a href="#class_category_Protocols">Protocols</a>
@ -2337,8 +2341,8 @@ its time with lingering corpses, sound effects, etc.</p>
</dl> </dl>
<hr> <hr>
<h2><strong><a name="class_ba_GameActivity">ba.GameActivity</a></strong></h3> <h2><strong><a name="class_ba_GameActivity">ba.GameActivity</a></strong></h3>
<p>Inherits from: <a href="#class_ba_Activity">ba.Activity</a>, <a href="#class_ba_DependencyComponent">ba.DependencyComponent</a>, <a href="#class_typing_Generic">typing.Generic</a></p> <p>Inherits from: <a href="#class_ba_Activity">ba.Activity</a>, <a href="#class_ba_DependencyComponent">ba.DependencyComponent</a>, <a href="https://docs.python.org/3/library/typing.html#typing.Generic">typing.Generic</a></p>
<p>Common base class for all game ba.Activities.</p> <p>Common base class for all game <a href="#class_ba_Activity">ba.Activities</a>.</p>
<p>Category: <a href="#class_category_Gameplay_Classes">Gameplay Classes</a> <p>Category: <a href="#class_category_Gameplay_Classes">Gameplay Classes</a>
</p> </p>
@ -2990,7 +2994,7 @@ prefs, etc.</p>
</dl> </dl>
<hr> <hr>
<h2><strong><a name="class_ba_InputDeviceNotFoundError">ba.InputDeviceNotFoundError</a></strong></h3> <h2><strong><a name="class_ba_InputDeviceNotFoundError">ba.InputDeviceNotFoundError</a></strong></h3>
<p>Inherits from: <a href="#class_ba_NotFoundError">ba.NotFoundError</a>, Exception, BaseException</p> <p>Inherits from: <a href="#class_ba_NotFoundError">ba.NotFoundError</a>, <a href="https://docs.python.org/3/library/exceptions.html#Exception">Exception</a>, <a href="https://docs.python.org/3/library/exceptions.html#BaseException">BaseException</a></p>
<p>Exception raised when an expected <a href="#class_ba_InputDevice">ba.InputDevice</a> does not exist.</p> <p>Exception raised when an expected <a href="#class_ba_InputDevice">ba.InputDevice</a> does not exist.</p>
<p>Category: <a href="#class_category_Exception_Classes">Exception Classes</a> <p>Category: <a href="#class_category_Exception_Classes">Exception Classes</a>
@ -3000,7 +3004,7 @@ prefs, etc.</p>
<p>&lt;all methods inherited from <a href="#class_ba_NotFoundError">ba.NotFoundError</a>&gt;</p> <p>&lt;all methods inherited from <a href="#class_ba_NotFoundError">ba.NotFoundError</a>&gt;</p>
<hr> <hr>
<h2><strong><a name="class_ba_InputType">ba.InputType</a></strong></h3> <h2><strong><a name="class_ba_InputType">ba.InputType</a></strong></h3>
<p>Inherits from: enum.Enum</p> <p>Inherits from: <a href="https://docs.python.org/3/library/enum.html#enum.Enum">enum.Enum</a></p>
<p>Types of input a controller can send to the game.</p> <p>Types of input a controller can send to the game.</p>
<p>Category: <a href="#class_category_Enums">Enums</a></p> <p>Category: <a href="#class_category_Enums">Enums</a></p>
@ -4053,7 +4057,7 @@ signify that the default soundtrack should be used..</p>
</dl> </dl>
<hr> <hr>
<h2><strong><a name="class_ba_MusicPlayMode">ba.MusicPlayMode</a></strong></h3> <h2><strong><a name="class_ba_MusicPlayMode">ba.MusicPlayMode</a></strong></h3>
<p>Inherits from: enum.Enum</p> <p>Inherits from: <a href="https://docs.python.org/3/library/enum.html#enum.Enum">enum.Enum</a></p>
<p>Influences behavior when playing music.</p> <p>Influences behavior when playing music.</p>
<p>Category: <a href="#class_category_Enums">Enums</a> <p>Category: <a href="#class_category_Enums">Enums</a>
@ -4156,7 +4160,7 @@ account what is supported locally.</p>
</dl> </dl>
<hr> <hr>
<h2><strong><a name="class_ba_MusicType">ba.MusicType</a></strong></h3> <h2><strong><a name="class_ba_MusicType">ba.MusicType</a></strong></h3>
<p>Inherits from: enum.Enum</p> <p>Inherits from: <a href="https://docs.python.org/3/library/enum.html#enum.Enum">enum.Enum</a></p>
<p>Types of music available to play in-game.</p> <p>Types of music available to play in-game.</p>
<p>Category: <a href="#class_category_Enums">Enums</a></p> <p>Category: <a href="#class_category_Enums">Enums</a></p>
@ -4368,7 +4372,7 @@ even if myactor is set to None.</p>
</dl> </dl>
<hr> <hr>
<h2><strong><a name="class_ba_NodeNotFoundError">ba.NodeNotFoundError</a></strong></h3> <h2><strong><a name="class_ba_NodeNotFoundError">ba.NodeNotFoundError</a></strong></h3>
<p>Inherits from: <a href="#class_ba_NotFoundError">ba.NotFoundError</a>, Exception, BaseException</p> <p>Inherits from: <a href="#class_ba_NotFoundError">ba.NotFoundError</a>, <a href="https://docs.python.org/3/library/exceptions.html#Exception">Exception</a>, <a href="https://docs.python.org/3/library/exceptions.html#BaseException">BaseException</a></p>
<p>Exception raised when an expected <a href="#class_ba_Node">ba.Node</a> does not exist.</p> <p>Exception raised when an expected <a href="#class_ba_Node">ba.Node</a> does not exist.</p>
<p>Category: <a href="#class_category_Exception_Classes">Exception Classes</a> <p>Category: <a href="#class_category_Exception_Classes">Exception Classes</a>
@ -4378,14 +4382,14 @@ even if myactor is set to None.</p>
<p>&lt;all methods inherited from <a href="#class_ba_NotFoundError">ba.NotFoundError</a>&gt;</p> <p>&lt;all methods inherited from <a href="#class_ba_NotFoundError">ba.NotFoundError</a>&gt;</p>
<hr> <hr>
<h2><strong><a name="class_ba_NotFoundError">ba.NotFoundError</a></strong></h3> <h2><strong><a name="class_ba_NotFoundError">ba.NotFoundError</a></strong></h3>
<p>Inherits from: Exception, BaseException</p> <p>Inherits from: <a href="https://docs.python.org/3/library/exceptions.html#Exception">Exception</a>, <a href="https://docs.python.org/3/library/exceptions.html#BaseException">BaseException</a></p>
<p>Exception raised when a referenced object does not exist.</p> <p>Exception raised when a referenced object does not exist.</p>
<p>Category: <a href="#class_category_Exception_Classes">Exception Classes</a> <p>Category: <a href="#class_category_Exception_Classes">Exception Classes</a>
</p> </p>
<h3>Methods:</h3> <h3>Methods:</h3>
<p>&lt;all methods inherited from <a href="#class_builtins_Exception">builtins.Exception</a>&gt;</p> <p>&lt;all methods inherited from <a href="https://docs.python.org/3/library/exceptions.html#Exception">Exception</a>&gt;</p>
<hr> <hr>
<h2><strong><a name="class_ba_OutOfBoundsMessage">ba.OutOfBoundsMessage</a></strong></h3> <h2><strong><a name="class_ba_OutOfBoundsMessage">ba.OutOfBoundsMessage</a></strong></h3>
<p><em>&lt;top level class&gt;</em> <p><em>&lt;top level class&gt;</em>
@ -4404,7 +4408,7 @@ even if myactor is set to None.</p>
</dl> </dl>
<hr> <hr>
<h2><strong><a name="class_ba_Permission">ba.Permission</a></strong></h3> <h2><strong><a name="class_ba_Permission">ba.Permission</a></strong></h3>
<p>Inherits from: enum.Enum</p> <p>Inherits from: <a href="https://docs.python.org/3/library/enum.html#enum.Enum">enum.Enum</a></p>
<p>Permissions that can be requested from the OS.</p> <p>Permissions that can be requested from the OS.</p>
<p>Category: <a href="#class_category_Enums">Enums</a> <p>Category: <a href="#class_category_Enums">Enums</a>
@ -4462,7 +4466,7 @@ even if myactor is set to None.</p>
</dl> </dl>
<hr> <hr>
<h2><strong><a name="class_ba_Player">ba.Player</a></strong></h3> <h2><strong><a name="class_ba_Player">ba.Player</a></strong></h3>
<p>Inherits from: <a href="#class_typing_Generic">typing.Generic</a></p> <p>Inherits from: <a href="https://docs.python.org/3/library/typing.html#typing.Generic">typing.Generic</a></p>
<p>A player in a specific <a href="#class_ba_Activity">ba.Activity</a>.</p> <p>A player in a specific <a href="#class_ba_Activity">ba.Activity</a>.</p>
<p>Category: <a href="#class_category_Gameplay_Classes">Gameplay Classes</a></p> <p>Category: <a href="#class_category_Gameplay_Classes">Gameplay Classes</a></p>
@ -4657,7 +4661,7 @@ the type-checker properly identifies the returned value as one.</p>
</dl> </dl>
<hr> <hr>
<h2><strong><a name="class_ba_PlayerNotFoundError">ba.PlayerNotFoundError</a></strong></h3> <h2><strong><a name="class_ba_PlayerNotFoundError">ba.PlayerNotFoundError</a></strong></h3>
<p>Inherits from: <a href="#class_ba_NotFoundError">ba.NotFoundError</a>, Exception, BaseException</p> <p>Inherits from: <a href="#class_ba_NotFoundError">ba.NotFoundError</a>, <a href="https://docs.python.org/3/library/exceptions.html#Exception">Exception</a>, <a href="https://docs.python.org/3/library/exceptions.html#BaseException">BaseException</a></p>
<p>Exception raised when an expected <a href="#class_ba_Player">ba.Player</a> does not exist.</p> <p>Exception raised when an expected <a href="#class_ba_Player">ba.Player</a> does not exist.</p>
<p>Category: <a href="#class_category_Exception_Classes">Exception Classes</a> <p>Category: <a href="#class_category_Exception_Classes">Exception Classes</a>
@ -4944,7 +4948,7 @@ change this. Defaults to an empty string.</p>
</dl> </dl>
<hr> <hr>
<h2><strong><a name="class_ba_ScoreType">ba.ScoreType</a></strong></h3> <h2><strong><a name="class_ba_ScoreType">ba.ScoreType</a></strong></h3>
<p>Inherits from: enum.Enum</p> <p>Inherits from: <a href="https://docs.python.org/3/library/enum.html#enum.Enum">enum.Enum</a></p>
<p>Type of scores.</p> <p>Type of scores.</p>
<p>Category: <a href="#class_category_Enums">Enums</a> <p>Category: <a href="#class_category_Enums">Enums</a>
@ -5010,7 +5014,7 @@ Pass 0 or a negative number for no ban time.</p>
<h2><strong><a name="class_ba_Session">ba.Session</a></strong></h3> <h2><strong><a name="class_ba_Session">ba.Session</a></strong></h3>
<p><em>&lt;top level class&gt;</em> <p><em>&lt;top level class&gt;</em>
</p> </p>
<p>Defines a high level series of activities with a common purpose.</p> <p>Defines a high level series of <a href="#class_ba_Activity">ba.Activities</a> with a common purpose.</p>
<p>Category: <a href="#class_category_Gameplay_Classes">Gameplay Classes</a></p> <p>Category: <a href="#class_category_Gameplay_Classes">Gameplay Classes</a></p>
@ -5201,7 +5205,7 @@ session.setactivity(foo) and then <a href="#function_ba_newnode">ba.newnode</a>(
</dl> </dl>
<hr> <hr>
<h2><strong><a name="class_ba_SessionNotFoundError">ba.SessionNotFoundError</a></strong></h3> <h2><strong><a name="class_ba_SessionNotFoundError">ba.SessionNotFoundError</a></strong></h3>
<p>Inherits from: <a href="#class_ba_NotFoundError">ba.NotFoundError</a>, Exception, BaseException</p> <p>Inherits from: <a href="#class_ba_NotFoundError">ba.NotFoundError</a>, <a href="https://docs.python.org/3/library/exceptions.html#Exception">Exception</a>, <a href="https://docs.python.org/3/library/exceptions.html#BaseException">BaseException</a></p>
<p>Exception raised when an expected <a href="#class_ba_Session">ba.Session</a> does not exist.</p> <p>Exception raised when an expected <a href="#class_ba_Session">ba.Session</a> does not exist.</p>
<p>Category: <a href="#class_category_Exception_Classes">Exception Classes</a> <p>Category: <a href="#class_category_Exception_Classes">Exception Classes</a>
@ -5345,7 +5349,7 @@ other players.</p>
</dl> </dl>
<hr> <hr>
<h2><strong><a name="class_ba_SessionPlayerNotFoundError">ba.SessionPlayerNotFoundError</a></strong></h3> <h2><strong><a name="class_ba_SessionPlayerNotFoundError">ba.SessionPlayerNotFoundError</a></strong></h3>
<p>Inherits from: <a href="#class_ba_NotFoundError">ba.NotFoundError</a>, Exception, BaseException</p> <p>Inherits from: <a href="#class_ba_NotFoundError">ba.NotFoundError</a>, <a href="https://docs.python.org/3/library/exceptions.html#Exception">Exception</a>, <a href="https://docs.python.org/3/library/exceptions.html#BaseException">BaseException</a></p>
<p>Exception raised when an expected <a href="#class_ba_SessionPlayer">ba.SessionPlayer</a> does not exist.</p> <p>Exception raised when an expected <a href="#class_ba_SessionPlayer">ba.SessionPlayer</a> does not exist.</p>
<p>Category: <a href="#class_category_Exception_Classes">Exception Classes</a> <p>Category: <a href="#class_category_Exception_Classes">Exception Classes</a>
@ -5411,7 +5415,7 @@ of the session.</p>
</dl> </dl>
<hr> <hr>
<h2><strong><a name="class_ba_SessionTeamNotFoundError">ba.SessionTeamNotFoundError</a></strong></h3> <h2><strong><a name="class_ba_SessionTeamNotFoundError">ba.SessionTeamNotFoundError</a></strong></h3>
<p>Inherits from: <a href="#class_ba_NotFoundError">ba.NotFoundError</a>, Exception, BaseException</p> <p>Inherits from: <a href="#class_ba_NotFoundError">ba.NotFoundError</a>, <a href="https://docs.python.org/3/library/exceptions.html#Exception">Exception</a>, <a href="https://docs.python.org/3/library/exceptions.html#BaseException">BaseException</a></p>
<p>Exception raised when an expected <a href="#class_ba_SessionTeam">ba.SessionTeam</a> does not exist.</p> <p>Exception raised when an expected <a href="#class_ba_SessionTeam">ba.SessionTeam</a> does not exist.</p>
<p>Category: <a href="#class_category_Exception_Classes">Exception Classes</a> <p>Category: <a href="#class_category_Exception_Classes">Exception Classes</a>
@ -5463,7 +5467,7 @@ of the session.</p>
<hr> <hr>
<h2><strong><a name="class_ba_SpecialChar">ba.SpecialChar</a></strong></h3> <h2><strong><a name="class_ba_SpecialChar">ba.SpecialChar</a></strong></h3>
<p>Inherits from: enum.Enum</p> <p>Inherits from: <a href="https://docs.python.org/3/library/enum.html#enum.Enum">enum.Enum</a></p>
<p>Special characters the game can print.</p> <p>Special characters the game can print.</p>
<p>Category: <a href="#class_category_Enums">Enums</a> <p>Category: <a href="#class_category_Enums">Enums</a>
@ -5680,7 +5684,7 @@ of the session.</p>
</dl> </dl>
<hr> <hr>
<h2><strong><a name="class_ba_Team">ba.Team</a></strong></h3> <h2><strong><a name="class_ba_Team">ba.Team</a></strong></h3>
<p>Inherits from: <a href="#class_typing_Generic">typing.Generic</a></p> <p>Inherits from: <a href="https://docs.python.org/3/library/typing.html#typing.Generic">typing.Generic</a></p>
<p>A team in a specific <a href="#class_ba_Activity">ba.Activity</a>.</p> <p>A team in a specific <a href="#class_ba_Activity">ba.Activity</a>.</p>
<p>Category: <a href="#class_category_Gameplay_Classes">Gameplay Classes</a></p> <p>Category: <a href="#class_category_Gameplay_Classes">Gameplay Classes</a></p>
@ -5729,7 +5733,7 @@ of the session.</p>
</dl> </dl>
<hr> <hr>
<h2><strong><a name="class_ba_TeamGameActivity">ba.TeamGameActivity</a></strong></h3> <h2><strong><a name="class_ba_TeamGameActivity">ba.TeamGameActivity</a></strong></h3>
<p>Inherits from: <a href="#class_ba_GameActivity">ba.GameActivity</a>, <a href="#class_ba_Activity">ba.Activity</a>, <a href="#class_ba_DependencyComponent">ba.DependencyComponent</a>, <a href="#class_typing_Generic">typing.Generic</a></p> <p>Inherits from: <a href="#class_ba_GameActivity">ba.GameActivity</a>, <a href="#class_ba_Activity">ba.Activity</a>, <a href="#class_ba_DependencyComponent">ba.DependencyComponent</a>, <a href="https://docs.python.org/3/library/typing.html#typing.Generic">typing.Generic</a></p>
<p>Base class for teams and free-for-all mode games.</p> <p>Base class for teams and free-for-all mode games.</p>
<p>Category: <a href="#class_category_Gameplay_Classes">Gameplay Classes</a></p> <p>Category: <a href="#class_category_Gameplay_Classes">Gameplay Classes</a></p>
@ -5859,7 +5863,7 @@ False otherwise.</p>
</dl> </dl>
<hr> <hr>
<h2><strong><a name="class_ba_TeamNotFoundError">ba.TeamNotFoundError</a></strong></h3> <h2><strong><a name="class_ba_TeamNotFoundError">ba.TeamNotFoundError</a></strong></h3>
<p>Inherits from: <a href="#class_ba_NotFoundError">ba.NotFoundError</a>, Exception, BaseException</p> <p>Inherits from: <a href="#class_ba_NotFoundError">ba.NotFoundError</a>, <a href="https://docs.python.org/3/library/exceptions.html#Exception">Exception</a>, <a href="https://docs.python.org/3/library/exceptions.html#BaseException">BaseException</a></p>
<p>Exception raised when an expected <a href="#class_ba_Team">ba.Team</a> does not exist.</p> <p>Exception raised when an expected <a href="#class_ba_Team">ba.Team</a> does not exist.</p>
<p>Category: <a href="#class_category_Exception_Classes">Exception Classes</a> <p>Category: <a href="#class_category_Exception_Classes">Exception Classes</a>
@ -5895,7 +5899,7 @@ False otherwise.</p>
</dl> </dl>
<hr> <hr>
<h2><strong><a name="class_ba_TimeFormat">ba.TimeFormat</a></strong></h3> <h2><strong><a name="class_ba_TimeFormat">ba.TimeFormat</a></strong></h3>
<p>Inherits from: enum.Enum</p> <p>Inherits from: <a href="https://docs.python.org/3/library/enum.html#enum.Enum">enum.Enum</a></p>
<p>Specifies the format time values are provided in.</p> <p>Specifies the format time values are provided in.</p>
<p>Category: <a href="#class_category_Enums">Enums</a> <p>Category: <a href="#class_category_Enums">Enums</a>
@ -5927,7 +5931,7 @@ you should use the <a href="#function_ba_timer">ba.timer</a>() function instead.
<p>time: length of time (in seconds by default) that the timer will wait <p>time: length of time (in seconds by default) that the timer will wait
before firing. Note that the actual delay experienced may vary before firing. Note that the actual delay experienced may vary
depending on the timetype. (see below)</p> depending on the timetype. (see below)</p>
<p>call: A callable Python object. Note that the timer will retain a <p>call: A callable Python object. Note that the timer will retain a
strong reference to the callable for as long as it exists, so you strong reference to the callable for as long as it exists, so you
@ -5937,28 +5941,11 @@ desired.</p>
<p>repeat: if True, the timer will fire repeatedly, with each successive <p>repeat: if True, the timer will fire repeatedly, with each successive
firing having the same delay as the first.</p> firing having the same delay as the first.</p>
<p>timetype can be either 'sim', 'base', or 'real'. It defaults to <p>timetype: A <a href="#class_ba_TimeType">ba.TimeType</a> value determining which timeline the timer is
'sim'. Types are explained below:</p> placed onto.</p>
<p>'sim' time maps to local simulation time in <a href="#class_ba_Activity">ba.Activity</a> or <a href="#class_ba_Session">ba.Session</a> <p>timeformat: A <a href="#class_ba_TimeFormat">ba.TimeFormat</a> value determining how the passed time is
Contexts. This means that it may progress slower in slow-motion play interpreted.</p>
modes, stop when the game is paused, etc. This time type is not
available in UI contexts.</p>
<p>'base' time is also linked to gameplay in <a href="#class_ba_Activity">ba.Activity</a> or <a href="#class_ba_Session">ba.Session</a>
Contexts, but it progresses at a constant rate regardless of
slow-motion states or pausing. It can, however, slow down or stop
in certain cases such as network outages or game slowdowns due to
cpu load. Like 'sim' time, this is unavailable in UI contexts.</p>
<p>'real' time always maps to actual clock time with a bit of filtering
added, regardless of Context. (the filtering prevents it from going
backwards or jumping forward by large amounts due to the app being
backgrounded, system time changing, etc.)
Real time timers are currently only available in the UI context.</p>
<p>the 'timeformat' arg defaults to SECONDS but can also be MILLISECONDS
if you want to pass time as milliseconds.</p>
<pre><span><em><small># Example: use a Timer object to print repeatedly for a few seconds:</small></em></span> <pre><span><em><small># Example: use a Timer object to print repeatedly for a few seconds:</small></em></span>
def say_it(): def say_it():
@ -5966,14 +5953,14 @@ def say_it():
def stop_saying_it(): def stop_saying_it():
self.t = None self.t = None
<a href="#function_ba_screenmessage">ba.screenmessage</a>('MUSHROOM MUSHROOM!') <a href="#function_ba_screenmessage">ba.screenmessage</a>('MUSHROOM MUSHROOM!')
<span><em><small># create our timer; it will run as long as we hold self.t</small></em></span> <span><em><small># Create our timer; it will run as long as we have the self.t ref.</small></em></span>
self.t = <a href="#class_ba_Timer">ba.Timer</a>(0.3, say_it, repeat=True) self.t = <a href="#class_ba_Timer">ba.Timer</a>(0.3, say_it, repeat=True)
<span><em><small># now fire off a one-shot timer to kill it</small></em></span> <span><em><small># Now fire off a one-shot timer to kill it.</small></em></span>
<a href="#function_ba_timer">ba.timer</a>(3.89, stop_saying_it)</pre> <a href="#function_ba_timer">ba.timer</a>(3.89, stop_saying_it)</pre>
<hr> <hr>
<h2><strong><a name="class_ba_TimeType">ba.TimeType</a></strong></h3> <h2><strong><a name="class_ba_TimeType">ba.TimeType</a></strong></h3>
<p>Inherits from: enum.Enum</p> <p>Inherits from: <a href="https://docs.python.org/3/library/enum.html#enum.Enum">enum.Enum</a></p>
<p>Specifies the type of time for various operations to target/use.</p> <p>Specifies the type of time for various operations to target/use.</p>
<p>Category: <a href="#class_category_Enums">Enums</a></p> <p>Category: <a href="#class_category_Enums">Enums</a></p>
@ -6001,7 +5988,7 @@ self.t = <a href="#class_ba_Timer">ba.Timer</a>(0.3, say_it, repeat=True)
<h2><strong><a name="class_ba_UIController">ba.UIController</a></strong></h3> <h2><strong><a name="class_ba_UIController">ba.UIController</a></strong></h3>
<p><em>&lt;top level class&gt;</em> <p><em>&lt;top level class&gt;</em>
</p> </p>
<p>Wrangles UILocations.</p> <p>Wrangles ba.UILocations.</p>
<p>Category: <a href="#class_category_User_Interface_Classes">User Interface Classes</a> <p>Category: <a href="#class_category_User_Interface_Classes">User Interface Classes</a>
</p> </p>
@ -6022,7 +6009,7 @@ self.t = <a href="#class_ba_Timer">ba.Timer</a>(0.3, say_it, repeat=True)
</dl> </dl>
<hr> <hr>
<h2><strong><a name="class_ba_UIScale">ba.UIScale</a></strong></h3> <h2><strong><a name="class_ba_UIScale">ba.UIScale</a></strong></h3>
<p>Inherits from: enum.Enum</p> <p>Inherits from: <a href="https://docs.python.org/3/library/enum.html#enum.Enum">enum.Enum</a></p>
<p>The overall scale the UI is being rendered for. Note that this is <p>The overall scale the UI is being rendered for. Note that this is
independent of pixel resolution. For example, a phone and a desktop PC independent of pixel resolution. For example, a phone and a desktop PC
might render the game at similar pixel resolutions but the size they might render the game at similar pixel resolutions but the size they
@ -6306,7 +6293,7 @@ widgets.</p>
</dl> </dl>
<hr> <hr>
<h2><strong><a name="class_ba_WidgetNotFoundError">ba.WidgetNotFoundError</a></strong></h3> <h2><strong><a name="class_ba_WidgetNotFoundError">ba.WidgetNotFoundError</a></strong></h3>
<p>Inherits from: <a href="#class_ba_NotFoundError">ba.NotFoundError</a>, Exception, BaseException</p> <p>Inherits from: <a href="#class_ba_NotFoundError">ba.NotFoundError</a>, <a href="https://docs.python.org/3/library/exceptions.html#Exception">Exception</a>, <a href="https://docs.python.org/3/library/exceptions.html#BaseException">BaseException</a></p>
<p>Exception raised when an expected <a href="#class_ba_Widget">ba.Widget</a> does not exist.</p> <p>Exception raised when an expected <a href="#class_ba_Widget">ba.Widget</a> does not exist.</p>
<p>Category: <a href="#class_category_Exception_Classes">Exception Classes</a> <p>Category: <a href="#class_category_Exception_Classes">Exception Classes</a>
@ -6473,6 +6460,50 @@ them elsewhere will be meaningless.</p>
a new one is created and returned. Arguments that are not set to None a new one is created and returned. Arguments that are not set to None
are applied to the Widget.</p> are applied to the Widget.</p>
<hr>
<h2><strong><a name="function_ba_clipboard_get_text">ba.clipboard_get_text()</a></strong></h3>
<p><span>clipboard_get_text() -&gt; str</span></p>
<p>Return text currently on the system clipboard.</p>
<p>Category: <a href="#function_category_General_Utility_Functions">General Utility Functions</a></p>
<p>Ensure that <a href="#function_ba_clipboard_has_text">ba.clipboard_has_text</a>() returns True before calling
this function.</p>
<hr>
<h2><strong><a name="function_ba_clipboard_has_text">ba.clipboard_has_text()</a></strong></h3>
<p><span>clipboard_has_text() -&gt; bool</span></p>
<p>Return whether there is currently text on the clipboard.</p>
<p>Category: <a href="#function_category_General_Utility_Functions">General Utility Functions</a></p>
<p>This will return False if no system clipboard is available; no need
to call ba.clipboard_available() separately.</p>
<hr>
<h2><strong><a name="function_ba_clipboard_is_supported">ba.clipboard_is_supported()</a></strong></h3>
<p><span>clipboard_is_supported() -&gt; bool</span></p>
<p>Return whether this platform supports clipboard operations at all.</p>
<p>Category: <a href="#function_category_General_Utility_Functions">General Utility Functions</a></p>
<p>If this returns False, UIs should not show 'copy to clipboard'
buttons, etc.</p>
<hr>
<h2><strong><a name="function_ba_clipboard_set_text">ba.clipboard_set_text()</a></strong></h3>
<p><span>clipboard_set_text(value: str) -&gt; None</span></p>
<p>Copy a string to the system clipboard.</p>
<p>Category: <a href="#function_category_General_Utility_Functions">General Utility Functions</a></p>
<p>Ensure that ba.clipboard_available() returns True before adding
buttons/etc. that make use of this functionality.</p>
<hr> <hr>
<h2><strong><a name="function_ba_columnwidget">ba.columnwidget()</a></strong></h3> <h2><strong><a name="function_ba_columnwidget">ba.columnwidget()</a></strong></h3>
<p><span>columnwidget(edit: <a href="#class_ba_Widget">ba.Widget</a> = None, <p><span>columnwidget(edit: <a href="#class_ba_Widget">ba.Widget</a> = None,
@ -6582,17 +6613,19 @@ settings, exiting element counts, or other factors.</p>
<h2><strong><a name="function_ba_existing">ba.existing()</a></strong></h3> <h2><strong><a name="function_ba_existing">ba.existing()</a></strong></h3>
<p><span>existing(obj: Optional[ExistableType]) -&gt; Optional[ExistableType]</span></p> <p><span>existing(obj: Optional[ExistableType]) -&gt; Optional[ExistableType]</span></p>
<p>Convert invalid references to None for any <a href="#class_ba_Existable">ba.Existable</a> type.</p> <p>Convert invalid references to None for any <a href="#class_ba_Existable">ba.Existable</a> object.</p>
<p>Category: <a href="#function_category_Gameplay_Functions">Gameplay Functions</a></p> <p>Category: <a href="#function_category_Gameplay_Functions">Gameplay Functions</a></p>
<p>To best support type checking, it is important that invalid references <p>To best support type checking, it is important that invalid references
not be passed around and instead get converted to values of None. not be passed around and instead get converted to values of None.
That way the type checker can properly flag attempts to pass dead That way the type checker can properly flag attempts to pass dead
objects into functions expecting only live ones, etc. objects (Optional[FooType]) into functions expecting only live ones
This call can be used on any 'existable' object (one with an exists() (FooType), etc. This call can be used on any 'existable' object
method) and will convert it to a None value if it does not exist. (one with an exists() method) and will convert it to a None value
For more info, see notes on 'existables' here: if it does not exist.</p>
<p>For more info, see notes on 'existables' here:
https://ballistica.net/wiki/Coding-Style-Guide</p> https://ballistica.net/wiki/Coding-Style-Guide</p>
<hr> <hr>
@ -6989,14 +7022,14 @@ app running.</p>
<hr> <hr>
<h2><strong><a name="function_ba_rowwidget">ba.rowwidget()</a></strong></h3> <h2><strong><a name="function_ba_rowwidget">ba.rowwidget()</a></strong></h3>
<p><span>rowwidget(edit: Widget = None, parent: Widget = None, <p><span>rowwidget(edit: <a href="#class_ba_Widget">ba.Widget</a> = None, parent: <a href="#class_ba_Widget">ba.Widget</a> = None,
size: Sequence[float] = None, size: Sequence[float] = None,
position: Sequence[float] = None, position: Sequence[float] = None,
background: bool = None, selected_child: Widget = None, background: bool = None, selected_child: <a href="#class_ba_Widget">ba.Widget</a> = None,
visible_child: Widget = None, visible_child: <a href="#class_ba_Widget">ba.Widget</a> = None,
claims_left_right: bool = None, claims_left_right: bool = None,
claims_tab: bool = None, claims_tab: bool = None,
selection_loops_to_parent: bool = None) -&gt; Widget</span></p> selection_loops_to_parent: bool = None) -&gt; <a href="#class_ba_Widget">ba.Widget</a></span></p>
<p>Create or edit a row widget.</p> <p>Create or edit a row widget.</p>
@ -7076,9 +7109,9 @@ are applied to the Widget.</p>
<hr> <hr>
<h2><strong><a name="function_ba_setmusic">ba.setmusic()</a></strong></h3> <h2><strong><a name="function_ba_setmusic">ba.setmusic()</a></strong></h3>
<p><span>setmusic(musictype: Optional[MusicType], continuous: bool = False) -&gt; None</span></p> <p><span>setmusic(musictype: Optional[<a href="#class_ba_MusicType">ba.MusicType</a>], continuous: bool = False) -&gt; None</span></p>
<p>Tell the game to play (or stop playing) a certain type of music.</p> <p>Set the app to play (or stop playing) a certain type of music.</p>
<p>Category: <a href="#function_category_Gameplay_Functions">Gameplay Functions</a></p> <p>Category: <a href="#function_category_Gameplay_Functions">Gameplay Functions</a></p>
@ -7103,15 +7136,17 @@ playing, the playing track will not be restarted.</p>
<h2><strong><a name="function_ba_storagename">ba.storagename()</a></strong></h3> <h2><strong><a name="function_ba_storagename">ba.storagename()</a></strong></h3>
<p><span>storagename(suffix: str = None) -&gt; str</span></p> <p><span>storagename(suffix: str = None) -&gt; str</span></p>
<p>Generate a (hopefully) unique name for storing things in public places.</p> <p>Generate a unique name for storing class data in shared places.</p>
<p>Category: <a href="#function_category_General_Utility_Functions">General Utility Functions</a></p> <p>Category: <a href="#function_category_General_Utility_Functions">General Utility Functions</a></p>
<p>This consists of a leading underscore, the module path at the <p>This consists of a leading underscore, the module path at the
call site with dots replaced by underscores, the class name, and call site with dots replaced by underscores, the containing class's
the provided suffix. When storing data in public places such as qualified name, and the provided suffix. When storing data in public
'customdata' dicts, this minimizes the chance of collisions if a places such as 'customdata' dicts, this minimizes the chance of
module or class is duplicated or renamed.</p> collisions with other similarly named classes.</p>
<p>Note that this will function even if called in the class definition.</p>
<pre><span><em><small># Example: generate a unique name for storage purposes:</small></em></span> <pre><span><em><small># Example: generate a unique name for storage purposes:</small></em></span>
class MyThingie:</pre> class MyThingie:</pre>
@ -7119,22 +7154,22 @@ class MyThingie:</pre>
<pre><span><em><small> # This will give something like '_mymodule_submodule_mythingie_data'.</small></em></span> <pre><span><em><small> # This will give something like '_mymodule_submodule_mythingie_data'.</small></em></span>
_STORENAME = <a href="#function_ba_storagename">ba.storagename</a>('data')</pre> _STORENAME = <a href="#function_ba_storagename">ba.storagename</a>('data')</pre>
<p> def __init__(self, activity): <pre><span><em><small> # Use that name to store some data in the Activity we were passed.</small></em></span>
# Store some data in the Activity we were passed def __init__(self, activity):
activity.customdata[self._STORENAME] = {}</p> activity.customdata[self._STORENAME] = {}</pre>
<hr> <hr>
<h2><strong><a name="function_ba_textwidget">ba.textwidget()</a></strong></h3> <h2><strong><a name="function_ba_textwidget">ba.textwidget()</a></strong></h3>
<p><span>textwidget(edit: Widget = None, parent: Widget = None, <p><span>textwidget(edit: <a href="#class_ba_Widget">ba.Widget</a> = None, parent: <a href="#class_ba_Widget">ba.Widget</a> = None,
size: Sequence[float] = None, position: Sequence[float] = None, size: Sequence[float] = None, position: Sequence[float] = None,
text: Union[str, <a href="#class_ba_Lstr">ba.Lstr</a>] = None, v_align: str = None, text: Union[str, <a href="#class_ba_Lstr">ba.Lstr</a>] = None, v_align: str = None,
h_align: str = None, editable: bool = None, padding: float = None, h_align: str = None, editable: bool = None, padding: float = None,
on_return_press_call: Callable[[], None] = None, on_return_press_call: Callable[[], None] = None,
on_activate_call: Callable[[], None] = None, on_activate_call: Callable[[], None] = None,
selectable: bool = None, query: Widget = None, max_chars: int = None, selectable: bool = None, query: <a href="#class_ba_Widget">ba.Widget</a> = None, max_chars: int = None,
color: Sequence[float] = None, click_activate: bool = None, color: Sequence[float] = None, click_activate: bool = None,
on_select_call: Callable[[], None] = None, on_select_call: Callable[[], None] = None,
always_highlight: bool = None, draw_controller: Widget = None, always_highlight: bool = None, draw_controller: <a href="#class_ba_Widget">ba.Widget</a> = None,
scale: float = None, corner_scale: float = None, scale: float = None, corner_scale: float = None,
description: Union[str, <a href="#class_ba_Lstr">ba.Lstr</a>] = None, description: Union[str, <a href="#class_ba_Lstr">ba.Lstr</a>] = None,
transition_delay: float = None, maxwidth: float = None, transition_delay: float = None, maxwidth: float = None,

View File

@ -21,8 +21,8 @@
namespace ballistica { namespace ballistica {
// These are set automatically via script; don't change here. // These are set automatically via script; don't change here.
const int kAppBuildNumber = 20268; const int kAppBuildNumber = 20279;
const char* kAppVersion = "1.5.30"; const char* kAppVersion = "1.6.0";
// Our standalone globals. // Our standalone globals.
// These are separated out for easy access. // These are separated out for easy access.

View File

@ -263,9 +263,6 @@ class Graphics {
internal_components_inited_ = val; internal_components_inited_ = val;
} }
auto set_gyro_vals(const Vector3f& vals) -> void { gyro_vals_ = vals; } auto set_gyro_vals(const Vector3f& vals) -> void { gyro_vals_ = vals; }
// auto draw_overlay_bounds() const -> bool { return draw_overlay_bounds_; }
// auto set_draw_overlay_bounds(bool val) -> void { draw_overlay_bounds_ =
// val; }
auto show_net_info() const -> bool { return show_net_info_; } auto show_net_info() const -> bool { return show_net_info_; }
auto set_show_net_info(bool val) -> void { show_net_info_ = val; } auto set_show_net_info(bool val) -> void { show_net_info_ = val; }
auto debug_graph_1() const -> NetGraph* { return debug_graph_1_.get(); } auto debug_graph_1() const -> NetGraph* { return debug_graph_1_.get(); }

View File

@ -286,8 +286,8 @@ void GraphicsServer::SetScreen(bool fullscreen, int width, int height,
// we request fullscreen-windows for full-screen situations and that's it. // we request fullscreen-windows for full-screen situations and that's it.
// (otherwise we may wind up with huge windows due to passing in desktop // (otherwise we may wind up with huge windows due to passing in desktop
// resolutions and retina wonkiness) // resolutions and retina wonkiness)
width = 800; width = static_cast<int>(kBaseVirtualResX * 0.8f);
height = 600; height = static_cast<int>(kBaseVirtualResY * 0.8f);
// We should never have to recreate the context after the initial time on // We should never have to recreate the context after the initial time on
// our modern builds. // our modern builds.

View File

@ -1209,13 +1209,24 @@ void Input::HandleKeyPress(const SDL_Keysym* keysym) {
if (!repeat_press && keysym->sym == SDLK_q if (!repeat_press && keysym->sym == SDLK_q
&& ((keysym->mod & KMOD_CTRL) || (keysym->mod & KMOD_GUI))) { // NOLINT && ((keysym->mod & KMOD_CTRL) || (keysym->mod & KMOD_GUI))) { // NOLINT
g_game->PushConfirmQuitCall(); g_game->PushConfirmQuitCall();
return;
} }
} }
// Let the console intercept stuff if it wants at this point.
if (g_app_globals->console != nullptr if (g_app_globals->console != nullptr
&& g_app_globals->console->HandleKeyPress(keysym)) { && g_app_globals->console->HandleKeyPress(keysym)) {
return; return;
} }
// Ctrl-V or Cmd-V sends paste commands to any interested text fields.
// Command-Q or Control-Q quits.
if (!repeat_press && keysym->sym == SDLK_v
&& ((keysym->mod & KMOD_CTRL) || (keysym->mod & KMOD_GUI))) { // NOLINT
g_ui->SendWidgetMessage(WidgetMessage(WidgetMessage::Type::kPaste));
return;
}
bool handled = false; bool handled = false;
// None of the following stuff accepts key repeats. // None of the following stuff accepts key repeats.

View File

@ -1361,4 +1361,88 @@ auto Platform::GetCurrentSeconds() -> int64_t {
.count(); .count();
} }
auto Platform::ClipboardIsSupported() -> bool {
// We only call our actual virtual function once.
if (!have_clipboard_is_supported_) {
clipboard_is_supported_ = DoClipboardIsSupported();
have_clipboard_is_supported_ = true;
}
return clipboard_is_supported_;
}
auto Platform::ClipboardHasText() -> bool {
// If subplatform says they don't support clipboards, don't even ask.
if (!ClipboardIsSupported()) {
return false;
}
return DoClipboardHasText();
}
auto Platform::ClipboardSetText(const std::string& text) -> void {
// If subplatform says they don't support clipboards, this is an error.
if (!ClipboardIsSupported()) {
throw Exception("ClipboardSetText called with no clipboard support.",
PyExcType::kRuntime);
}
DoClipboardSetText(text);
}
auto Platform::ClipboardGetText() -> std::string {
// If subplatform says they don't support clipboards, this is an error.
if (!ClipboardIsSupported()) {
throw Exception("ClipboardGetText called with no clipboard support.",
PyExcType::kRuntime);
}
return DoClipboardGetText();
}
auto Platform::DoClipboardIsSupported() -> bool {
// Go through SDL functionality on SDL based platforms;
// otherwise default to no clipboard.
#if BA_SDL2_BUILD && !BA_OSTYPE_IOS_TVOS
return true;
#else
return false;
#endif
}
auto Platform::DoClipboardHasText() -> bool {
// Go through SDL functionality on SDL based platforms;
// otherwise default to no clipboard.
#if BA_SDL2_BUILD && !BA_OSTYPE_IOS_TVOS
return SDL_HasClipboardText();
#else
// Shouldn't get here since we default to no clipboard support.
FatalError("Shouldn't get here.");
return false;
#endif
}
auto Platform::DoClipboardSetText(const std::string& text) -> void {
// Go through SDL functionality on SDL based platforms;
// otherwise default to no clipboard.
#if BA_SDL2_BUILD && !BA_OSTYPE_IOS_TVOS
SDL_SetClipboardText(text.c_str());
#else
// Shouldn't get here since we default to no clipboard support.
FatalError("Shouldn't get here.");
#endif
}
auto Platform::DoClipboardGetText() -> std::string {
// Go through SDL functionality on SDL based platforms;
// otherwise default to no clipboard.
#if BA_SDL2_BUILD && !BA_OSTYPE_IOS_TVOS
char* out = SDL_GetClipboardText();
if (out == nullptr) {
throw Exception("Error fetching clipboard contents.", PyExcType::kRuntime);
}
return out;
#else
// Shouldn't get here since we default to no clipboard support.
FatalError("Shouldn't get here.");
return "";
#endif
}
} // namespace ballistica } // namespace ballistica

View File

@ -103,7 +103,25 @@ class Platform {
virtual auto GetCWD() -> std::string; virtual auto GetCWD() -> std::string;
// Unlink a file. // Unlink a file.
virtual void Unlink(const char* path); virtual auto Unlink(const char* path) -> void;
#pragma mark CLIPBOARD ---------------------------------------------------------
/// Return whether clipboard operations are supported at all.
/// This gets called when determining whether to display clipboard related
/// UI elements/etc.
auto ClipboardIsSupported() -> bool;
/// Return whether there is currently text on the clipboard.
auto ClipboardHasText() -> bool;
/// Set current clipboard text. Raises an Exception if clipboard is
/// unsupported.
auto ClipboardSetText(const std::string& text) -> void;
/// Return current text from the clipboard. Raises an Exception if
/// clipboard is unsupported or if there's no text on the clipboard.
auto ClipboardGetText() -> std::string;
#pragma mark PRINTING/LOGGING -------------------------------------------------- #pragma mark PRINTING/LOGGING --------------------------------------------------
@ -493,6 +511,11 @@ class Platform {
// Generate a random UUID string. // Generate a random UUID string.
virtual auto GenerateUUID() -> std::string; virtual auto GenerateUUID() -> std::string;
virtual auto DoClipboardIsSupported() -> bool;
virtual auto DoClipboardHasText() -> bool;
virtual auto DoClipboardSetText(const std::string& text) -> void;
virtual auto DoClipboardGetText() -> std::string;
private: private:
int py_call_num_{}; int py_call_num_{};
bool using_custom_app_python_dir_{}; bool using_custom_app_python_dir_{};
@ -500,6 +523,8 @@ class Platform {
bool have_has_touchscreen_value_{}; bool have_has_touchscreen_value_{};
bool have_touchscreen_{}; bool have_touchscreen_{};
bool is_tegra_k1_{}; bool is_tegra_k1_{};
bool have_clipboard_is_supported_{};
bool clipboard_is_supported_{};
millisecs_t starttime_{}; millisecs_t starttime_{};
std::string device_uuid_; std::string device_uuid_;
bool have_device_uuid_{}; bool have_device_uuid_{};

View File

@ -32,7 +32,7 @@ void PythonClassTimer::SetupType(PyTypeObject* obj) {
"you should use the ba.timer() function instead.\n" "you should use the ba.timer() function instead.\n"
"\n" "\n"
"time: length of time (in seconds by default) that the timer will wait\n" "time: length of time (in seconds by default) that the timer will wait\n"
"before firing. Note that the actual delay experienced may vary\n " "before firing. Note that the actual delay experienced may vary\n"
"depending on the timetype. (see below)\n" "depending on the timetype. (see below)\n"
"\n" "\n"
"call: A callable Python object. Note that the timer will retain a\n" "call: A callable Python object. Note that the timer will retain a\n"
@ -43,28 +43,11 @@ void PythonClassTimer::SetupType(PyTypeObject* obj) {
"repeat: if True, the timer will fire repeatedly, with each successive\n" "repeat: if True, the timer will fire repeatedly, with each successive\n"
"firing having the same delay as the first.\n" "firing having the same delay as the first.\n"
"\n" "\n"
"timetype can be either 'sim', 'base', or 'real'. It defaults to\n" "timetype: A ba.TimeType value determining which timeline the timer is\n"
"'sim'. Types are explained below:\n" "placed onto.\n"
"\n" "\n"
"'sim' time maps to local simulation time in ba.Activity or ba.Session\n" "timeformat: A ba.TimeFormat value determining how the passed time is\n"
"Contexts. This means that it may progress slower in slow-motion play\n" "interpreted.\n"
"modes, stop when the game is paused, etc. This time type is not\n"
"available in UI contexts.\n"
"\n"
"'base' time is also linked to gameplay in ba.Activity or ba.Session\n"
"Contexts, but it progresses at a constant rate regardless of\n "
"slow-motion states or pausing. It can, however, slow down or stop\n"
"in certain cases such as network outages or game slowdowns due to\n"
"cpu load. Like 'sim' time, this is unavailable in UI contexts.\n"
"\n"
"'real' time always maps to actual clock time with a bit of filtering\n"
"added, regardless of Context. (the filtering prevents it from going\n"
"backwards or jumping forward by large amounts due to the app being\n"
"backgrounded, system time changing, etc.)\n"
"Real time timers are currently only available in the UI context.\n"
"\n"
"the 'timeformat' arg defaults to SECONDS but can also be MILLISECONDS\n"
"if you want to pass time as milliseconds.\n"
"\n" "\n"
"# Example: use a Timer object to print repeatedly for a few seconds:\n" "# Example: use a Timer object to print repeatedly for a few seconds:\n"
"def say_it():\n" "def say_it():\n"
@ -72,9 +55,9 @@ void PythonClassTimer::SetupType(PyTypeObject* obj) {
"def stop_saying_it():\n" "def stop_saying_it():\n"
" self.t = None\n" " self.t = None\n"
" ba.screenmessage('MUSHROOM MUSHROOM!')\n" " ba.screenmessage('MUSHROOM MUSHROOM!')\n"
"# create our timer; it will run as long as we hold self.t\n" "# Create our timer; it will run as long as we have the self.t ref.\n"
"self.t = ba.Timer(0.3, say_it, repeat=True)\n" "self.t = ba.Timer(0.3, say_it, repeat=True)\n"
"# now fire off a one-shot timer to kill it\n" "# Now fire off a one-shot timer to kill it.\n"
"ba.timer(3.89, stop_saying_it)"; "ba.timer(3.89, stop_saying_it)";
obj->tp_new = tp_new; obj->tp_new = tp_new;
obj->tp_dealloc = (destructor)tp_dealloc; obj->tp_dealloc = (destructor)tp_dealloc;

View File

@ -16,4 +16,5 @@ class PythonMethodsApp {
}; };
} // namespace ballistica } // namespace ballistica
#endif // BALLISTICA_PYTHON_METHODS_PYTHON_METHODS_APP_H_ #endif // BALLISTICA_PYTHON_METHODS_PYTHON_METHODS_APP_H_

View File

@ -16,4 +16,5 @@ class PythonMethodsGameplay {
}; };
} // namespace ballistica } // namespace ballistica
#endif // BALLISTICA_PYTHON_METHODS_PYTHON_METHODS_GAMEPLAY_H_ #endif // BALLISTICA_PYTHON_METHODS_PYTHON_METHODS_GAMEPLAY_H_

View File

@ -16,4 +16,5 @@ class PythonMethodsGraphics {
}; };
} // namespace ballistica } // namespace ballistica
#endif // BALLISTICA_PYTHON_METHODS_PYTHON_METHODS_GRAPHICS_H_ #endif // BALLISTICA_PYTHON_METHODS_PYTHON_METHODS_GRAPHICS_H_

View File

@ -32,6 +32,46 @@ namespace ballistica {
#pragma ide diagnostic ignored "hicpp-signed-bitwise" #pragma ide diagnostic ignored "hicpp-signed-bitwise"
#pragma ide diagnostic ignored "RedundantCast" #pragma ide diagnostic ignored "RedundantCast"
auto PyClipboardIsSupported(PyObject* self) -> PyObject* {
BA_PYTHON_TRY;
if (g_platform->ClipboardIsSupported()) {
Py_RETURN_TRUE;
}
Py_RETURN_FALSE;
BA_PYTHON_CATCH;
}
auto PyClipboardHasText(PyObject* self) -> PyObject* {
BA_PYTHON_TRY;
if (g_platform->ClipboardHasText()) {
Py_RETURN_TRUE;
}
Py_RETURN_FALSE;
BA_PYTHON_CATCH;
}
auto PyClipboardSetText(PyObject* self, PyObject* args, PyObject* keywds)
-> PyObject* {
BA_PYTHON_TRY;
Platform::SetLastPyCall("clipboard_set_text");
const char* value;
static const char* kwlist[] = {"value", nullptr};
if (!PyArg_ParseTupleAndKeywords(args, keywds, "s",
const_cast<char**>(kwlist), &value)) {
return nullptr;
}
g_platform->ClipboardSetText(value);
Py_RETURN_NONE;
BA_PYTHON_CATCH;
}
auto PyClipboardGetText(PyObject* self) -> PyObject* {
BA_PYTHON_TRY;
return PyUnicode_FromString(g_platform->ClipboardGetText().c_str());
Py_RETURN_FALSE;
BA_PYTHON_CATCH;
}
auto PyIsRunningOnOuya(PyObject* self, PyObject* args) -> PyObject* { auto PyIsRunningOnOuya(PyObject* self, PyObject* args) -> PyObject* {
BA_PYTHON_TRY; BA_PYTHON_TRY;
Platform::SetLastPyCall("is_running_on_ouya"); Platform::SetLastPyCall("is_running_on_ouya");
@ -743,6 +783,44 @@ auto PyApp(PyObject* self, PyObject* args, PyObject* keywds) -> PyObject* {
auto PythonMethodsSystem::GetMethods() -> std::vector<PyMethodDef> { auto PythonMethodsSystem::GetMethods() -> std::vector<PyMethodDef> {
return { return {
{"clipboard_is_supported", (PyCFunction)PyClipboardIsSupported,
METH_NOARGS,
"clipboard_is_supported() -> bool\n"
"\n"
"Return whether this platform supports clipboard operations at all.\n"
"\n"
"Category: General Utility Functions\n"
"\n"
"If this returns False, UIs should not show 'copy to clipboard'\n"
"buttons, etc."},
{"clipboard_has_text", (PyCFunction)PyClipboardHasText, METH_NOARGS,
"clipboard_has_text() -> bool\n"
"\n"
"Return whether there is currently text on the clipboard.\n"
"\n"
"Category: General Utility Functions\n"
"\n"
"This will return False if no system clipboard is available; no need\n"
" to call ba.clipboard_available() separately."},
{"clipboard_set_text", (PyCFunction)PyClipboardSetText,
METH_VARARGS | METH_KEYWORDS,
"clipboard_set_text(value: str) -> None\n"
"\n"
"Copy a string to the system clipboard.\n"
"\n"
"Category: General Utility Functions\n"
"\n"
"Ensure that ba.clipboard_available() returns True before adding\n"
" buttons/etc. that make use of this functionality."},
{"clipboard_get_text", (PyCFunction)PyClipboardGetText, METH_NOARGS,
"clipboard_get_text() -> str\n"
"\n"
"Return text currently on the system clipboard.\n"
"\n"
"Category: General Utility Functions\n"
"\n"
"Ensure that ba.clipboard_has_text() returns True before calling\n"
" this function."},
{"printobjects", (PyCFunction)PyPrintObjects, {"printobjects", (PyCFunction)PyPrintObjects,
METH_VARARGS | METH_KEYWORDS, METH_VARARGS | METH_KEYWORDS,
"printobjects() -> None\n" "printobjects() -> None\n"

View File

@ -2636,14 +2636,14 @@ auto PythonMethodsUI::GetMethods() -> std::vector<PyMethodDef> {
"are applied to the Widget."}, "are applied to the Widget."},
{"rowwidget", (PyCFunction)PyRowWidget, METH_VARARGS | METH_KEYWORDS, {"rowwidget", (PyCFunction)PyRowWidget, METH_VARARGS | METH_KEYWORDS,
"rowwidget(edit: Widget = None, parent: Widget = None,\n" "rowwidget(edit: ba.Widget = None, parent: ba.Widget = None,\n"
" size: Sequence[float] = None,\n" " size: Sequence[float] = None,\n"
" position: Sequence[float] = None,\n" " position: Sequence[float] = None,\n"
" background: bool = None, selected_child: Widget = None,\n" " background: bool = None, selected_child: ba.Widget = None,\n"
" visible_child: Widget = None,\n" " visible_child: ba.Widget = None,\n"
" claims_left_right: bool = None,\n" " claims_left_right: bool = None,\n"
" claims_tab: bool = None,\n" " claims_tab: bool = None,\n"
" selection_loops_to_parent: bool = None) -> Widget\n" " selection_loops_to_parent: bool = None) -> ba.Widget\n"
"\n" "\n"
"Create or edit a row widget.\n" "Create or edit a row widget.\n"
"\n" "\n"
@ -2699,17 +2699,17 @@ auto PythonMethodsUI::GetMethods() -> std::vector<PyMethodDef> {
"are applied to the Widget."}, "are applied to the Widget."},
{"textwidget", (PyCFunction)PyTextWidget, METH_VARARGS | METH_KEYWORDS, {"textwidget", (PyCFunction)PyTextWidget, METH_VARARGS | METH_KEYWORDS,
"textwidget(edit: Widget = None, parent: Widget = None,\n" "textwidget(edit: ba.Widget = None, parent: ba.Widget = None,\n"
" size: Sequence[float] = None, position: Sequence[float] = None,\n" " size: Sequence[float] = None, position: Sequence[float] = None,\n"
" text: Union[str, ba.Lstr] = None, v_align: str = None,\n" " text: Union[str, ba.Lstr] = None, v_align: str = None,\n"
" h_align: str = None, editable: bool = None, padding: float = None,\n" " h_align: str = None, editable: bool = None, padding: float = None,\n"
" on_return_press_call: Callable[[], None] = None,\n" " on_return_press_call: Callable[[], None] = None,\n"
" on_activate_call: Callable[[], None] = None,\n" " on_activate_call: Callable[[], None] = None,\n"
" selectable: bool = None, query: Widget = None, max_chars: int = " " selectable: bool = None, query: ba.Widget = None, max_chars: int = "
"None,\n" "None,\n"
" color: Sequence[float] = None, click_activate: bool = None,\n" " color: Sequence[float] = None, click_activate: bool = None,\n"
" on_select_call: Callable[[], None] = None,\n" " on_select_call: Callable[[], None] = None,\n"
" always_highlight: bool = None, draw_controller: Widget = None,\n" " always_highlight: bool = None, draw_controller: ba.Widget = None,\n"
" scale: float = None, corner_scale: float = None,\n" " scale: float = None, corner_scale: float = None,\n"
" description: Union[str, ba.Lstr] = None,\n" " description: Union[str, ba.Lstr] = None,\n"
" transition_delay: float = None, maxwidth: float = None,\n" " transition_delay: float = None, maxwidth: float = None,\n"

View File

@ -29,7 +29,7 @@ UI::UI() {
assert(g_platform); assert(g_platform);
// Allow overriding via an environment variable. // Allow overriding via an environment variable.
auto* ui_override = getenv("BA_FORCE_UI_SCALE"); auto* ui_override = getenv("BA_UI_SCALE");
bool force_test_small{}; bool force_test_small{};
bool force_test_medium{}; bool force_test_medium{};
bool force_test_large{}; bool force_test_large{};

View File

@ -296,6 +296,7 @@ auto ContainerWidget::HandleMessage(const WidgetMessage& m) -> bool {
switch (m.type) { switch (m.type) {
case WidgetMessage::Type::kTextInput: case WidgetMessage::Type::kTextInput:
case WidgetMessage::Type::kKey: case WidgetMessage::Type::kKey:
case WidgetMessage::Type::kPaste:
if (selected_widget_) { if (selected_widget_) {
bool val = selected_widget_->HandleMessage(m); bool val = selected_widget_->HandleMessage(m);
if (val != 0) { if (val != 0) {
@ -1218,8 +1219,7 @@ void ContainerWidget::SetTransition(TransitionType t) {
// stack, update the toolbar for the new topmost input-accepting window // stack, update the toolbar for the new topmost input-accepting window
// *immediately* (otherwise we'd have to wait for our transition to complete // *immediately* (otherwise we'd have to wait for our transition to complete
// before the toolbar switches). // before the toolbar switches).
if (transitioning_ && transitioning_out_ && parent != nullptr if (transitioning_ && transitioning_out_ && parent->is_main_window_stack_) {
&& parent->is_main_window_stack_) {
g_ui->root_widget()->UpdateForFocusedWindow(); g_ui->root_widget()->UpdateForFocusedWindow();
} }
} }

View File

@ -116,8 +116,8 @@ void TextWidget::Draw(RenderPass* pass, bool draw_transparent) {
// Center-scale. // Center-scale.
{ {
// We should really be scaling our bounds and things, but for now lets just // We should really be scaling our bounds and things,
// do a hacky overall scale. // but for now lets just do a hacky overall scale.
EmptyComponent c(pass); EmptyComponent c(pass);
c.SetTransparent(true); c.SetTransparent(true);
c.PushTransform(); c.PushTransform();
@ -178,6 +178,7 @@ void TextWidget::Draw(RenderPass* pass, bool draw_transparent) {
highlight_center_y_ = b2 - b_border + highlight_height_ * 0.5f; highlight_center_y_ = b2 - b_border + highlight_height_ * 0.5f;
highlight_dirty_ = false; highlight_dirty_ = false;
} }
SimpleComponent c(pass); SimpleComponent c(pass);
c.SetTransparent(true); c.SetTransparent(true);
c.SetPremultiplied(true); c.SetPremultiplied(true);
@ -596,6 +597,16 @@ auto TextWidget::HandleMessage(const WidgetMessage& m) -> bool {
bottom_overlap = 3.0f * extra_touch_border_scale_; bottom_overlap = 3.0f * extra_touch_border_scale_;
} }
// If we're doing inline editing, handle clipboard paste.
if (editable() && !ShouldUseStringEditDialog()
&& m.type == WidgetMessage::Type::kPaste) {
if (g_platform->ClipboardIsSupported()) {
if (g_platform->ClipboardHasText()) {
// Just enter it char by char as if we had typed it...
AddCharsToText(g_platform->ClipboardGetText());
}
}
}
// If we're doing inline editing, handle some key events. // If we're doing inline editing, handle some key events.
if (m.has_keysym && !ShouldUseStringEditDialog()) { if (m.has_keysym && !ShouldUseStringEditDialog()) {
last_carat_change_time_ = g_game->master_time(); last_carat_change_time_ = g_game->master_time();
@ -769,22 +780,7 @@ auto TextWidget::HandleMessage(const WidgetMessage& m) -> bool {
} else { } else {
// Otherwise apply the text directly. // Otherwise apply the text directly.
if (editable() && m.sval != nullptr) { if (editable() && m.sval != nullptr) {
std::vector<uint32_t> unichars = AddCharsToText(*m.sval);
Utils::UnicodeFromUTF8(text_raw_, "jcjwf8f");
int len = static_cast<int>(unichars.size());
std::vector<uint32_t> sval =
Utils::UnicodeFromUTF8(*m.sval, "j4958fbv");
for (unsigned int i : sval) {
if (len < max_chars_) {
text_group_dirty_ = true;
if (carat_position_ > len) carat_position_ = len;
unichars.insert(unichars.begin() + carat_position_, i);
len++;
carat_position_++;
}
}
text_raw_ = Utils::UTF8FromUnicode(unichars);
text_translation_dirty_ = true;
return true; return true;
} }
} }
@ -794,8 +790,8 @@ auto TextWidget::HandleMessage(const WidgetMessage& m) -> bool {
if (!IsSelectable()) { if (!IsSelectable()) {
return false; return false;
} }
float x = m.fval1; float x{ScaleAdjustedX(m.fval1)};
float y = m.fval2; float y{ScaleAdjustedY(m.fval2)};
bool claimed = (m.fval3 > 0.0f); bool claimed = (m.fval3 > 0.0f);
if (claimed) { if (claimed) {
mouse_over_ = clear_mouse_over_ = false; mouse_over_ = clear_mouse_over_ = false;
@ -813,8 +809,9 @@ auto TextWidget::HandleMessage(const WidgetMessage& m) -> bool {
if (!IsSelectable()) { if (!IsSelectable()) {
return false; return false;
} }
float x = m.fval1; float x{ScaleAdjustedX(m.fval1)};
float y = m.fval2; float y{ScaleAdjustedY(m.fval2)};
auto click_count = static_cast<int>(m.fval3); auto click_count = static_cast<int>(m.fval3);
// See if a click is in our clear button. // See if a click is in our clear button.
@ -856,8 +853,8 @@ auto TextWidget::HandleMessage(const WidgetMessage& m) -> bool {
} }
} }
case WidgetMessage::Type::kMouseUp: { case WidgetMessage::Type::kMouseUp: {
float x = m.fval1; float x{ScaleAdjustedX(m.fval1)};
float y = m.fval2; float y{ScaleAdjustedY(m.fval2)};
bool claimed = (m.fval3 > 0.0f); bool claimed = (m.fval3 > 0.0f);
if (clear_pressed_ && !claimed && editable() if (clear_pressed_ && !claimed && editable()
@ -903,6 +900,38 @@ auto TextWidget::HandleMessage(const WidgetMessage& m) -> bool {
return false; return false;
} }
auto TextWidget::ScaleAdjustedX(float x) -> float {
// Account for our center_scale_ value.
float offsx = x - width_ * 0.5f;
return width_ * 0.5f + offsx / center_scale_;
}
auto TextWidget::ScaleAdjustedY(float y) -> float {
// Account for our center_scale_ value.
float offsy = y - height_ * 0.5f;
return height_ * 0.5f + offsy / center_scale_;
}
auto TextWidget::AddCharsToText(const std::string& addchars) -> void {
assert(editable());
std::vector<uint32_t> unichars = Utils::UnicodeFromUTF8(text_raw_, "jcjwf8f");
int len = static_cast<int>(unichars.size());
std::vector<uint32_t> sval = Utils::UnicodeFromUTF8(addchars, "j4958fbv");
for (unsigned int i : sval) {
if (len < max_chars_) {
text_group_dirty_ = true;
if (carat_position_ > len) {
carat_position_ = len;
}
unichars.insert(unichars.begin() + carat_position_, i);
len++;
carat_position_++;
}
}
text_raw_ = Utils::UTF8FromUnicode(unichars);
text_translation_dirty_ = true;
}
void TextWidget::UpdateTranslation() { void TextWidget::UpdateTranslation() {
// Apply subs/resources to get our actual text if need be. // Apply subs/resources to get our actual text if need be.
if (text_translation_dirty_) { if (text_translation_dirty_) {

View File

@ -89,6 +89,9 @@ class TextWidget : public Widget {
} }
private: private:
auto ScaleAdjustedX(float x) -> float;
auto ScaleAdjustedY(float y) -> float;
auto AddCharsToText(const std::string& addchars) -> void;
auto ShouldUseStringEditDialog() const -> bool; auto ShouldUseStringEditDialog() const -> bool;
void BringUpEditDialog(); void BringUpEditDialog();
void UpdateTranslation(); void UpdateTranslation();

View File

@ -37,7 +37,8 @@ struct WidgetMessage {
kMouseWheelVelocityH, kMouseWheelVelocityH,
kMouseMove, kMouseMove,
kScrollMouseDown, kScrollMouseDown,
kTextInput kTextInput,
kPaste
}; };
Type type{}; Type type{};

View File

@ -30,10 +30,10 @@ class PipRequirement:
PIP_REQUIREMENTS = [ PIP_REQUIREMENTS = [
PipRequirement(modulename='pylint', minversion=[2, 6, 0]), PipRequirement(modulename='pylint', minversion=[2, 6, 0]),
PipRequirement(modulename='mypy', minversion=[0, 790]), PipRequirement(modulename='mypy', minversion=[0, 800]),
PipRequirement(modulename='yapf', minversion=[0, 30, 0]), PipRequirement(modulename='yapf', minversion=[0, 30, 0]),
PipRequirement(modulename='cpplint', minversion=[1, 5, 4]), PipRequirement(modulename='cpplint', minversion=[1, 5, 4]),
PipRequirement(modulename='pytest', minversion=[6, 1, 2]), PipRequirement(modulename='pytest', minversion=[6, 2, 1]),
PipRequirement(modulename='typing_extensions'), PipRequirement(modulename='typing_extensions'),
PipRequirement(modulename='pytz'), PipRequirement(modulename='pytz'),
PipRequirement(modulename='ansiwrap'), PipRequirement(modulename='ansiwrap'),

View File

@ -112,7 +112,7 @@ def _windows_enable_color() -> bool:
# open CONOUT$ instead # open CONOUT$ instead
fdout = os.open('CONOUT$', os.O_RDWR) fdout = os.open('CONOUT$', os.O_RDWR)
try: try:
hout = msvcrt.get_osfhandle(fdout) hout = msvcrt.get_osfhandle(fdout) # type: ignore
old_mode = wintypes.DWORD() old_mode = wintypes.DWORD()
kernel32.GetConsoleMode(hout, ctypes.byref(old_mode)) kernel32.GetConsoleMode(hout, ctypes.byref(old_mode))
mode = (new_mode & mask) | (old_mode.value & ~mask) mode = (new_mode & mask) | (old_mode.value & ~mask)

View File

@ -703,12 +703,10 @@ def _run_idea_inspections(projroot: Path,
if result.returncode != 0: if result.returncode != 0:
# In verbose mode this stuff got printed already. # In verbose mode this stuff got printed already.
if not verbose: if not verbose:
stdout = ( stdout = (result.stdout.decode() if isinstance(
result.stdout.decode() if isinstance( # type: ignore result.stdout, bytes) else str(result.stdout))
result.stdout, bytes) else str(result.stdout)) stderr = (result.stderr.decode() if isinstance(
stderr = ( result.stdout, bytes) else str(result.stdout))
result.stderr.decode() if isinstance( # type: ignore
result.stdout, bytes) else str(result.stdout))
print(f'{displayname} inspection failure stdout:\n{stdout}' + print(f'{displayname} inspection failure stdout:\n{stdout}' +
f'{displayname} inspection failure stderr:\n{stderr}') f'{displayname} inspection failure stderr:\n{stderr}')
raise RuntimeError(f'{displayname} inspection failed.') raise RuntimeError(f'{displayname} inspection failed.')