mirror of
https://github.com/RYDE-WORK/ballistica.git
synced 2026-01-19 13:25:31 +08:00
initial 1.7 build
This commit is contained in:
parent
b506c2c9b3
commit
a7a32a3f63
112
.efrocachemap
112
.efrocachemap
@ -420,15 +420,15 @@
|
||||
"assets/build/ba_data/audio/zoeOw.ogg": "https://files.ballistica.net/cache/ba1/51/eb/0a567253cc08c94c5d315a64d9af",
|
||||
"assets/build/ba_data/audio/zoePickup01.ogg": "https://files.ballistica.net/cache/ba1/bc/8f/a9c51a09c418136e386b7fdf21c7",
|
||||
"assets/build/ba_data/audio/zoeScream01.ogg": "https://files.ballistica.net/cache/ba1/02/e5/84916e123f47ccf11ddda380d699",
|
||||
"assets/build/ba_data/data/langdata.json": "https://files.ballistica.net/cache/ba1/f9/ed/20354b5a613c3e168d9a3b92ed05",
|
||||
"assets/build/ba_data/data/langdata.json": "https://files.ballistica.net/cache/ba1/bd/6c/48e30d0a2215958f8ae1a02805ba",
|
||||
"assets/build/ba_data/data/languages/arabic.json": "https://files.ballistica.net/cache/ba1/ca/75/3de74bd6e498113b99bbf9eda645",
|
||||
"assets/build/ba_data/data/languages/belarussian.json": "https://files.ballistica.net/cache/ba1/55/8c/8d0a0585e434b94865ae4befc090",
|
||||
"assets/build/ba_data/data/languages/belarussian.json": "https://files.ballistica.net/cache/ba1/61/03/89070ca765e06da3a419a579f503",
|
||||
"assets/build/ba_data/data/languages/chinese.json": "https://files.ballistica.net/cache/ba1/f6/21/951b7ff02b0ad14b1f0ac55763c4",
|
||||
"assets/build/ba_data/data/languages/chinesetraditional.json": "https://files.ballistica.net/cache/ba1/61/e6/caf06ce99017fdf5d2da0c038445",
|
||||
"assets/build/ba_data/data/languages/croatian.json": "https://files.ballistica.net/cache/ba1/66/bf/6e98398016da261296b8c306560e",
|
||||
"assets/build/ba_data/data/languages/croatian.json": "https://files.ballistica.net/cache/ba1/28/46/3a36628a033da4d4b4ea65b78a28",
|
||||
"assets/build/ba_data/data/languages/czech.json": "https://files.ballistica.net/cache/ba1/87/84/9f3d39610453b3bf350698a23316",
|
||||
"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/97/90/39ba65c2ad714429aec82ea1ae3e",
|
||||
"assets/build/ba_data/data/languages/dutch.json": "https://files.ballistica.net/cache/ba1/68/93/da8e9874f41a786edf52ba4ccaad",
|
||||
"assets/build/ba_data/data/languages/english.json": "https://files.ballistica.net/cache/ba1/99/2a/bdcfa0932cf73e5cf63fd8113b1b",
|
||||
"assets/build/ba_data/data/languages/esperanto.json": "https://files.ballistica.net/cache/ba1/4c/c7/0184b8178869d1a3827a1bfcd5bb",
|
||||
"assets/build/ba_data/data/languages/filipino.json": "https://files.ballistica.net/cache/ba1/de/5c/631a09d9192e40c99c07c6191b7c",
|
||||
@ -437,25 +437,25 @@
|
||||
"assets/build/ba_data/data/languages/gibberish.json": "https://files.ballistica.net/cache/ba1/03/6a/4db89c5bf1ced8eb5a5615a4ae64",
|
||||
"assets/build/ba_data/data/languages/greek.json": "https://files.ballistica.net/cache/ba1/82/eb/37ff44af76812097f9c98f05c730",
|
||||
"assets/build/ba_data/data/languages/hindi.json": "https://files.ballistica.net/cache/ba1/08/3b/68cea4d16f7020d932829af85323",
|
||||
"assets/build/ba_data/data/languages/hungarian.json": "https://files.ballistica.net/cache/ba1/2d/e5/3737c6c3979cf381321c5472bea5",
|
||||
"assets/build/ba_data/data/languages/hungarian.json": "https://files.ballistica.net/cache/ba1/b0/48/e1ebe08bfdfc94fcb61a16b851e5",
|
||||
"assets/build/ba_data/data/languages/indonesian.json": "https://files.ballistica.net/cache/ba1/75/70/e33e6ee95830052e8f36cd2135f7",
|
||||
"assets/build/ba_data/data/languages/italian.json": "https://files.ballistica.net/cache/ba1/c7/16/e31ce16d1b4150c271401669f24f",
|
||||
"assets/build/ba_data/data/languages/korean.json": "https://files.ballistica.net/cache/ba1/07/37/ab65ccee3a555bd40e9661860c58",
|
||||
"assets/build/ba_data/data/languages/persian.json": "https://files.ballistica.net/cache/ba1/02/ab/e310f81582b6dc2ae93348d45166",
|
||||
"assets/build/ba_data/data/languages/polish.json": "https://files.ballistica.net/cache/ba1/d5/fe/422745cdbe51ccb4f2ced6f5554a",
|
||||
"assets/build/ba_data/data/languages/portuguese.json": "https://files.ballistica.net/cache/ba1/26/41/f1246ab56c6b7853f605c3a95889",
|
||||
"assets/build/ba_data/data/languages/romanian.json": "https://files.ballistica.net/cache/ba1/82/12/57bf144e12be229a9b70da9c45cb",
|
||||
"assets/build/ba_data/data/languages/romanian.json": "https://files.ballistica.net/cache/ba1/39/2b/27822a4e66093ca8bfb968099507",
|
||||
"assets/build/ba_data/data/languages/russian.json": "https://files.ballistica.net/cache/ba1/b2/46/89ae228342f20ca4937ee254197b",
|
||||
"assets/build/ba_data/data/languages/serbian.json": "https://files.ballistica.net/cache/ba1/a5/48/47d5eb30535158610cdace1edfcd",
|
||||
"assets/build/ba_data/data/languages/slovak.json": "https://files.ballistica.net/cache/ba1/9f/a6/a2c9d7f3f90a2320aa45ccfd65cd",
|
||||
"assets/build/ba_data/data/languages/serbian.json": "https://files.ballistica.net/cache/ba1/13/19/828be486951be254445263f36c6e",
|
||||
"assets/build/ba_data/data/languages/slovak.json": "https://files.ballistica.net/cache/ba1/f9/4b/d9f01814224066856695452ef57c",
|
||||
"assets/build/ba_data/data/languages/spanish.json": "https://files.ballistica.net/cache/ba1/87/5d/d36a8a2e9cb0f02731a3fd7af000",
|
||||
"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/91/0a/35c4baf539d5951fc03a794c0e0b",
|
||||
"assets/build/ba_data/data/languages/tamil.json": "https://files.ballistica.net/cache/ba1/cb/11/e11957be752c3dc552898b60ab20",
|
||||
"assets/build/ba_data/data/languages/thai.json": "https://files.ballistica.net/cache/ba1/74/3d/c3d40a1e5ee1edf82555da05eda9",
|
||||
"assets/build/ba_data/data/languages/thai.json": "https://files.ballistica.net/cache/ba1/9d/51/f699dbd4beb88bc3cff699a287a7",
|
||||
"assets/build/ba_data/data/languages/turkish.json": "https://files.ballistica.net/cache/ba1/0a/4f/90fcd63bd12a7648b2a1e9b01586",
|
||||
"assets/build/ba_data/data/languages/ukrainian.json": "https://files.ballistica.net/cache/ba1/7f/bb/6239adeb551be5e09f3457d7b411",
|
||||
"assets/build/ba_data/data/languages/venetian.json": "https://files.ballistica.net/cache/ba1/e2/e1/b815d9f2e9b2c3a4daddaf728225",
|
||||
"assets/build/ba_data/data/languages/vietnamese.json": "https://files.ballistica.net/cache/ba1/0b/24/3cc2b5a6ebe4bca1e01b40f8ed09",
|
||||
"assets/build/ba_data/data/languages/vietnamese.json": "https://files.ballistica.net/cache/ba1/f2/af/afd1503c7a10cacaa15bc02369b2",
|
||||
"assets/build/ba_data/data/maps/big_g.json": "https://files.ballistica.net/cache/ba1/47/0a/a617cc85d927b576c4e6fc1091ed",
|
||||
"assets/build/ba_data/data/maps/bridgit.json": "https://files.ballistica.net/cache/ba1/03/4b/57ee9b42854b26f23f81bd8c58ef",
|
||||
"assets/build/ba_data/data/maps/courtyard.json": "https://files.ballistica.net/cache/ba1/03/38/344dd05bfef7bbdf464035ec5aa2",
|
||||
@ -3971,50 +3971,50 @@
|
||||
"assets/src/ba_data/python/ba/_generated/__init__.py": "https://files.ballistica.net/cache/ba1/ee/e8/cad05aa531c7faf7ff7b96db7f6e",
|
||||
"assets/src/ba_data/python/ba/_generated/enums.py": "https://files.ballistica.net/cache/ba1/b2/e5/0ee0561e16257a32830645239f34",
|
||||
"ballisticacore-windows/Generic/BallisticaCore.ico": "https://files.ballistica.net/cache/ba1/89/c0/e32c7d2a35dc9aef57cc73b0911a",
|
||||
"build/prefab/full/linux_arm64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/62/59/1d88d726a5a1d8300ff9543910f0",
|
||||
"build/prefab/full/linux_arm64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/48/24/e7b9b3c1a082760cfc6db0f10ca3",
|
||||
"build/prefab/full/linux_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/1b/a6/92035aea2ab0dd03949256a5349e",
|
||||
"build/prefab/full/linux_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/27/07/1d7e2e0a8f9a2ebdf8cc33e56820",
|
||||
"build/prefab/full/linux_x86_64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/53/7a/c90a65dbb653cc1f311d815aac58",
|
||||
"build/prefab/full/linux_x86_64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/a1/7d/5c334610fe346280e5c6dc372a57",
|
||||
"build/prefab/full/linux_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/f9/8f/941afe4781fddf0bd979fd2fd758",
|
||||
"build/prefab/full/linux_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/a8/04/1fe1a6bb38814c83097137fa73da",
|
||||
"build/prefab/full/mac_arm64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/82/13/65edadabda615923ff4d7627853e",
|
||||
"build/prefab/full/mac_arm64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/b3/a2/8b5be76d52e33e3628da19fbc1a9",
|
||||
"build/prefab/full/mac_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/1f/e6/8531ec0f9a53326891238cffd702",
|
||||
"build/prefab/full/mac_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/8a/e5/c69c71030ef703a7a0637c7c73a5",
|
||||
"build/prefab/full/mac_x86_64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/da/68/acf38798fe82e9ead9844ae53757",
|
||||
"build/prefab/full/mac_x86_64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/e4/c6/84d27c2ee2095d6955323bff63f6",
|
||||
"build/prefab/full/mac_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/12/aa/e7201b0067e820d095c8c58db855",
|
||||
"build/prefab/full/mac_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/cf/1c/20e854d1a3cdb9640bfb7770f2f7",
|
||||
"build/prefab/full/windows_x86_gui/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/aa/11/4d3b2e9761e951a9205a913d6842",
|
||||
"build/prefab/full/windows_x86_gui/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/09/ff/8b7133310bbf869f19fc79368ee8",
|
||||
"build/prefab/full/windows_x86_server/debug/dist/BallisticaCoreHeadless.exe": "https://files.ballistica.net/cache/ba1/56/ec/0e501fe0ba3c0a497795b7bde490",
|
||||
"build/prefab/full/windows_x86_server/release/dist/BallisticaCoreHeadless.exe": "https://files.ballistica.net/cache/ba1/47/da/a765bbbc77ae3442500e025c4342",
|
||||
"build/prefab/lib/linux_arm64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/68/d3/a4b1c6366ad425d34ef196f1c49e",
|
||||
"build/prefab/lib/linux_arm64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/2f/42/aff90e076f427bd20e6925d54641",
|
||||
"build/prefab/lib/linux_arm64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/c3/8b/afafe8e0c5896aee9c6177e3619a",
|
||||
"build/prefab/lib/linux_arm64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/c5/7d/ae70ac9d5162235143cd560934f9",
|
||||
"build/prefab/lib/linux_x86_64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/96/30/3b85c7c4d9af95b1f29899603f71",
|
||||
"build/prefab/lib/linux_x86_64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/31/63/1f22739856a91e9e0c8786f0e5e6",
|
||||
"build/prefab/lib/linux_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/cb/67/06449e9e5fe93cfa070325619c6f",
|
||||
"build/prefab/lib/linux_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/de/e4/2ef7d74099133e0fc7272f852fc3",
|
||||
"build/prefab/lib/mac_arm64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/00/e5/439c0214aec11ee65be93843e623",
|
||||
"build/prefab/lib/mac_arm64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/37/00/a75ab04600c3934e11644a63d864",
|
||||
"build/prefab/lib/mac_arm64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/74/8c/06a79d42b719af94e8c634b1480f",
|
||||
"build/prefab/lib/mac_arm64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/91/b8/ab7e097db2d0f1c1c58c9e37ca27",
|
||||
"build/prefab/lib/mac_x86_64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/97/e5/92ca0edbed82a9b2081db73a6a67",
|
||||
"build/prefab/lib/mac_x86_64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/17/ef/6c0510ef98fa3611b09f359f6018",
|
||||
"build/prefab/lib/mac_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/be/bc/035279dcad567eb7ad4afefe8ed2",
|
||||
"build/prefab/lib/mac_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/8f/1a/1349780e52df2d59e9ea46266700",
|
||||
"build/prefab/lib/windows/Debug_Win32/BallisticaCoreGenericInternal.lib": "https://files.ballistica.net/cache/ba1/7c/29/bd985633e24a27703a80ce7e8cb6",
|
||||
"build/prefab/lib/windows/Debug_Win32/BallisticaCoreGenericInternal.pdb": "https://files.ballistica.net/cache/ba1/a1/69/726b6f61333fffb7862a5c5ad2ad",
|
||||
"build/prefab/lib/windows/Debug_Win32/BallisticaCoreHeadlessInternal.lib": "https://files.ballistica.net/cache/ba1/40/fa/425f21d8391bb784eff0e2f84c7a",
|
||||
"build/prefab/lib/windows/Debug_Win32/BallisticaCoreHeadlessInternal.pdb": "https://files.ballistica.net/cache/ba1/88/7e/b155b80d3f4cf3148d0b3c991a84",
|
||||
"build/prefab/lib/windows/Release_Win32/BallisticaCoreGenericInternal.lib": "https://files.ballistica.net/cache/ba1/6d/f6/347590d969e154335b282e8a35aa",
|
||||
"build/prefab/lib/windows/Release_Win32/BallisticaCoreGenericInternal.pdb": "https://files.ballistica.net/cache/ba1/23/2c/bd8bac99b2dc6232d603d0fd9570",
|
||||
"build/prefab/lib/windows/Release_Win32/BallisticaCoreHeadlessInternal.lib": "https://files.ballistica.net/cache/ba1/27/28/1b0efd8cb83ce3ca96f9612a3112",
|
||||
"build/prefab/lib/windows/Release_Win32/BallisticaCoreHeadlessInternal.pdb": "https://files.ballistica.net/cache/ba1/e8/67/cd7f25ef0dbac04538ca3d7bebce",
|
||||
"src/ballistica/generated/python_embedded/binding.inc": "https://files.ballistica.net/cache/ba1/b3/15/7c6d580b3482870b5b058858624c",
|
||||
"build/prefab/full/linux_arm64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/96/42/d33846f67af0646f256aeaa7c30c",
|
||||
"build/prefab/full/linux_arm64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/51/a5/ddad12c2da1f7b877250464784b2",
|
||||
"build/prefab/full/linux_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/ef/d6/16e0a8dc48749f6e17bda544c445",
|
||||
"build/prefab/full/linux_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/f3/53/09b469e8a14b6134cf6cd4c35363",
|
||||
"build/prefab/full/linux_x86_64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/ab/6a/e451f155bc9ff8929dad3de8a146",
|
||||
"build/prefab/full/linux_x86_64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/3c/f2/75d582f428ad9ff19833a2db6ac5",
|
||||
"build/prefab/full/linux_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/c5/4c/be6956268b8869711533d2904a93",
|
||||
"build/prefab/full/linux_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/1a/19/47dbd27b3f00daf33b6dd08cad36",
|
||||
"build/prefab/full/mac_arm64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/9f/65/d4ea67fc0670e35c594f8b831019",
|
||||
"build/prefab/full/mac_arm64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/c6/2d/57c333a4a46df3a4e98c549c0b89",
|
||||
"build/prefab/full/mac_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/68/57/3f9909fc12ae0dd0a44e60ce3c9d",
|
||||
"build/prefab/full/mac_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/3e/d4/28c439dac6a74e0451dbcf6075f1",
|
||||
"build/prefab/full/mac_x86_64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/9d/67/bd41584cfb10f2e205be19b849d9",
|
||||
"build/prefab/full/mac_x86_64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/4b/20/5f79657c50de3c0a5bdce6469d80",
|
||||
"build/prefab/full/mac_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/99/b3/6399f861f42b57b5d6ec627adc1f",
|
||||
"build/prefab/full/mac_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/d1/dd/8d87e134c239e65b63a2e20b925a",
|
||||
"build/prefab/full/windows_x86_gui/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/c5/da/6df0f75b231b5b61b4d56f188c85",
|
||||
"build/prefab/full/windows_x86_gui/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/37/42/427de40168eb72f75feff3e919da",
|
||||
"build/prefab/full/windows_x86_server/debug/dist/BallisticaCoreHeadless.exe": "https://files.ballistica.net/cache/ba1/dc/4d/fe90d8c2f0e10688881dd8841a05",
|
||||
"build/prefab/full/windows_x86_server/release/dist/BallisticaCoreHeadless.exe": "https://files.ballistica.net/cache/ba1/cb/94/746781e8cef4564254c3baeedd2d",
|
||||
"build/prefab/lib/linux_arm64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/cb/a6/560fb2889c78f14c47f7185b284c",
|
||||
"build/prefab/lib/linux_arm64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/24/29/b23ac44bf2a76fcb5b4920d76bb2",
|
||||
"build/prefab/lib/linux_arm64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/2e/1e/d4b6c04e0f7f277487bc21d88c44",
|
||||
"build/prefab/lib/linux_arm64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/18/87/1a907cb4fbde86c26b295da615ef",
|
||||
"build/prefab/lib/linux_x86_64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/a8/22/0215aaf316e7841a001840181f30",
|
||||
"build/prefab/lib/linux_x86_64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/5b/76/b68f63c60efb32d18bfda5ac2125",
|
||||
"build/prefab/lib/linux_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/19/01/ca0d7b90ca48daa28a8ff7b5f9e7",
|
||||
"build/prefab/lib/linux_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/c0/a7/d748772aa00b604fc43bb9ef0c21",
|
||||
"build/prefab/lib/mac_arm64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/8b/e9/437a4918cf4d7eb6498f24a9ff16",
|
||||
"build/prefab/lib/mac_arm64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/ae/44/9bdd10f4bb0b2fdc67bc57624872",
|
||||
"build/prefab/lib/mac_arm64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/12/5d/3a5acb1cbf7b390fe561aacecaaa",
|
||||
"build/prefab/lib/mac_arm64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/16/ae/e32e6075da6ced98216aa83c87e3",
|
||||
"build/prefab/lib/mac_x86_64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/8c/38/d67105c92308fc168e2f82eb89db",
|
||||
"build/prefab/lib/mac_x86_64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/f5/9b/e02bc0842ec9422cdd1332928805",
|
||||
"build/prefab/lib/mac_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/91/8a/03c904a39698ba5804365bec8ae2",
|
||||
"build/prefab/lib/mac_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/44/a4/f76c3f28464408d10e0b4ba2b33c",
|
||||
"build/prefab/lib/windows/Debug_Win32/BallisticaCoreGenericInternal.lib": "https://files.ballistica.net/cache/ba1/46/ed/7a596e5d725752af99f08e39878b",
|
||||
"build/prefab/lib/windows/Debug_Win32/BallisticaCoreGenericInternal.pdb": "https://files.ballistica.net/cache/ba1/c1/d7/b2368705dbbe68720c318778afc7",
|
||||
"build/prefab/lib/windows/Debug_Win32/BallisticaCoreHeadlessInternal.lib": "https://files.ballistica.net/cache/ba1/0f/4f/f12ef3415aa4339e650330cc310b",
|
||||
"build/prefab/lib/windows/Debug_Win32/BallisticaCoreHeadlessInternal.pdb": "https://files.ballistica.net/cache/ba1/0f/67/5fea68f30efc01d82687be7fd452",
|
||||
"build/prefab/lib/windows/Release_Win32/BallisticaCoreGenericInternal.lib": "https://files.ballistica.net/cache/ba1/da/c0/0707d6d205e9dfe4f613ba672918",
|
||||
"build/prefab/lib/windows/Release_Win32/BallisticaCoreGenericInternal.pdb": "https://files.ballistica.net/cache/ba1/a8/e1/726ea2e9710a12bb375bd5b8c43d",
|
||||
"build/prefab/lib/windows/Release_Win32/BallisticaCoreHeadlessInternal.lib": "https://files.ballistica.net/cache/ba1/28/4f/38d2d59336874ec660cb909aad4b",
|
||||
"build/prefab/lib/windows/Release_Win32/BallisticaCoreHeadlessInternal.pdb": "https://files.ballistica.net/cache/ba1/57/9b/99e8f77f47dd8ce0272d59e990e0",
|
||||
"src/ballistica/generated/python_embedded/binding.inc": "https://files.ballistica.net/cache/ba1/6e/6f/004b696e9a13b083069374e4bb6a",
|
||||
"src/ballistica/generated/python_embedded/bootstrap.inc": "https://files.ballistica.net/cache/ba1/d3/db/e73d4dcf1280d5f677c3cf8b47c3"
|
||||
}
|
||||
53
.idea/dictionaries/ericf.xml
generated
53
.idea/dictionaries/ericf.xml
generated
@ -21,8 +21,11 @@
|
||||
<w>abouttab</w>
|
||||
<w>abtn</w>
|
||||
<w>accesstime</w>
|
||||
<w>accountclientv</w>
|
||||
<w>accountname</w>
|
||||
<w>accountui</w>
|
||||
<w>accountv</w>
|
||||
<w>accountvalues</w>
|
||||
<w>accum</w>
|
||||
<w>accumkillcount</w>
|
||||
<w>accumkilledcount</w>
|
||||
@ -42,6 +45,7 @@
|
||||
<w>activityteam</w>
|
||||
<w>activitytypes</w>
|
||||
<w>activityutils</w>
|
||||
<w>actool</w>
|
||||
<w>actorclass</w>
|
||||
<w>adbcfaca</w>
|
||||
<w>adbpath</w>
|
||||
@ -111,6 +115,7 @@
|
||||
<w>argh</w>
|
||||
<w>argparse</w>
|
||||
<w>argsjoined</w>
|
||||
<w>argstr</w>
|
||||
<w>argtypes</w>
|
||||
<w>argval</w>
|
||||
<w>armeabi</w>
|
||||
@ -206,6 +211,7 @@
|
||||
<w>bastd</w>
|
||||
<w>batools</w>
|
||||
<w>batoolsinternal</w>
|
||||
<w>baworker</w>
|
||||
<w>bbbb</w>
|
||||
<w>bblk</w>
|
||||
<w>bblu</w>
|
||||
@ -276,6 +282,7 @@
|
||||
<w>bsstd</w>
|
||||
<w>bstat</w>
|
||||
<w>bstournament</w>
|
||||
<w>bstr</w>
|
||||
<w>bsuffix</w>
|
||||
<w>bsui</w>
|
||||
<w>btnh</w>
|
||||
@ -374,6 +381,7 @@
|
||||
<w>checkins</w>
|
||||
<w>checkpaths</w>
|
||||
<w>checkroundover</w>
|
||||
<w>checksummed</w>
|
||||
<w>checksums</w>
|
||||
<w>checktype</w>
|
||||
<w>childanntype</w>
|
||||
@ -443,7 +451,13 @@
|
||||
<w>comms</w>
|
||||
<w>compat</w>
|
||||
<w>compileall</w>
|
||||
<w>compileassetcatalog</w>
|
||||
<w>compilec</w>
|
||||
<w>compilelocations</w>
|
||||
<w>compilemetalfile</w>
|
||||
<w>compilestoryboard</w>
|
||||
<w>compileswift</w>
|
||||
<w>compileswiftsources</w>
|
||||
<w>completeargs</w>
|
||||
<w>completecmd</w>
|
||||
<w>compounddict</w>
|
||||
@ -475,9 +489,12 @@
|
||||
<w>coopscorescreen</w>
|
||||
<w>coopsession</w>
|
||||
<w>coords</w>
|
||||
<w>copypng</w>
|
||||
<w>copyreg</w>
|
||||
<w>copyrightline</w>
|
||||
<w>copyrule</w>
|
||||
<w>copystringsfile</w>
|
||||
<w>copyswiftlibs</w>
|
||||
<w>cornerpin</w>
|
||||
<w>coroutines</w>
|
||||
<w>countdownsounds</w>
|
||||
@ -492,6 +509,7 @@
|
||||
<w>cpus</w>
|
||||
<w>cpython</w>
|
||||
<w>crashlytics</w>
|
||||
<w>createbuilddirectory</w>
|
||||
<w>createtime</w>
|
||||
<w>creationflags</w>
|
||||
<w>creditslist</w>
|
||||
@ -505,6 +523,7 @@
|
||||
<w>csspbt</w>
|
||||
<w>cstr</w>
|
||||
<w>csum</w>
|
||||
<w>csval</w>
|
||||
<w>ctest</w>
|
||||
<w>ctex</w>
|
||||
<w>ctracker</w>
|
||||
@ -530,6 +549,7 @@
|
||||
<w>darwiin</w>
|
||||
<w>darwiinremote</w>
|
||||
<w>datab</w>
|
||||
<w>databytes</w>
|
||||
<w>dataclassio</w>
|
||||
<w>dataclassutils</w>
|
||||
<w>datamodule</w>
|
||||
@ -1102,11 +1122,15 @@
|
||||
<w>hurtiness</w>
|
||||
<w>hval</w>
|
||||
<w>iasset</w>
|
||||
<w>ibtool</w>
|
||||
<w>ibtoold</w>
|
||||
<w>icls</w>
|
||||
<w>icns</w>
|
||||
<w>iconpicker</w>
|
||||
<w>iconscale</w>
|
||||
<w>iconset</w>
|
||||
<w>iconsstorename</w>
|
||||
<w>iconutil</w>
|
||||
<w>ident</w>
|
||||
<w>idevices</w>
|
||||
<w>ifeq</w>
|
||||
@ -1231,6 +1255,7 @@
|
||||
<w>keyfilt</w>
|
||||
<w>keyint</w>
|
||||
<w>keylayout</w>
|
||||
<w>keylen</w>
|
||||
<w>keypresses</w>
|
||||
<w>keystr</w>
|
||||
<w>keytype</w>
|
||||
@ -1319,6 +1344,7 @@
|
||||
<w>linearstep</w>
|
||||
<w>linebits</w>
|
||||
<w>lineheight</w>
|
||||
<w>linemax</w>
|
||||
<w>lineno</w>
|
||||
<w>linenum</w>
|
||||
<w>linenumber</w>
|
||||
@ -1326,6 +1352,7 @@
|
||||
<w>linetype</w>
|
||||
<w>linetypes</w>
|
||||
<w>linflav</w>
|
||||
<w>linkstoryboards</w>
|
||||
<w>linkto</w>
|
||||
<w>lintable</w>
|
||||
<w>lintcode</w>
|
||||
@ -1377,6 +1404,7 @@
|
||||
<w>lshort</w>
|
||||
<w>lsprof</w>
|
||||
<w>lsqlite</w>
|
||||
<w>lsregister</w>
|
||||
<w>lssl</w>
|
||||
<w>lstart</w>
|
||||
<w>lstr</w>
|
||||
@ -1442,6 +1470,7 @@
|
||||
<w>memfunctions</w>
|
||||
<w>menubar</w>
|
||||
<w>messagetype</w>
|
||||
<w>metallink</w>
|
||||
<w>metamakefile</w>
|
||||
<w>metaprogramming</w>
|
||||
<w>metascan</w>
|
||||
@ -1472,6 +1501,7 @@
|
||||
<w>mkflags</w>
|
||||
<w>mlen</w>
|
||||
<w>mline</w>
|
||||
<w>mmacosx</w>
|
||||
<w>mmapmodule</w>
|
||||
<w>mmult</w>
|
||||
<w>mname</w>
|
||||
@ -1581,6 +1611,7 @@
|
||||
<w>newdbpath</w>
|
||||
<w>newnode</w>
|
||||
<w>newpath</w>
|
||||
<w>newtoken</w>
|
||||
<w>nextcall</w>
|
||||
<w>nextfilenum</w>
|
||||
<w>nextlevel</w>
|
||||
@ -1655,6 +1686,7 @@
|
||||
<w>okbtn</w>
|
||||
<w>oldbook</w>
|
||||
<w>oldlady</w>
|
||||
<w>oldtoken</w>
|
||||
<w>onln</w>
|
||||
<w>onscreencountdown</w>
|
||||
<w>onscreenkeyboard</w>
|
||||
@ -1685,10 +1717,12 @@
|
||||
<w>ourself</w>
|
||||
<w>outdata</w>
|
||||
<w>outdelay</w>
|
||||
<w>outdict</w>
|
||||
<w>outext</w>
|
||||
<w>outfilename</w>
|
||||
<w>outfilepath</w>
|
||||
<w>outhashpath</w>
|
||||
<w>outmsg</w>
|
||||
<w>outname</w>
|
||||
<w>outpath</w>
|
||||
<w>outputter</w>
|
||||
@ -1742,6 +1776,7 @@
|
||||
<w>perma</w>
|
||||
<w>perrdetail</w>
|
||||
<w>phasers</w>
|
||||
<w>phasescriptexecution</w>
|
||||
<w>phello</w>
|
||||
<w>photoshop</w>
|
||||
<w>phrea</w>
|
||||
@ -1856,6 +1891,9 @@
|
||||
<w>privatetab</w>
|
||||
<w>proactor</w>
|
||||
<w>proc</w>
|
||||
<w>processinfoplistfile</w>
|
||||
<w>processpch</w>
|
||||
<w>processpchplusplus</w>
|
||||
<w>procs</w>
|
||||
<w>profileindex</w>
|
||||
<w>profilekey</w>
|
||||
@ -1871,6 +1909,7 @@
|
||||
<w>projroot</w>
|
||||
<w>projs</w>
|
||||
<w>promocode</w>
|
||||
<w>proxykey</w>
|
||||
<w>prunedir</w>
|
||||
<w>prval</w>
|
||||
<w>pstats</w>
|
||||
@ -1999,6 +2038,8 @@
|
||||
<w>redist</w>
|
||||
<w>redistributables</w>
|
||||
<w>regionid</w>
|
||||
<w>registerexecutionpolicyexception</w>
|
||||
<w>registerwithlaunchservices</w>
|
||||
<w>regtp</w>
|
||||
<w>reimported</w>
|
||||
<w>relfut</w>
|
||||
@ -2094,6 +2135,7 @@
|
||||
<w>sbtn</w>
|
||||
<w>sbwht</w>
|
||||
<w>sbylw</w>
|
||||
<w>sbytes</w>
|
||||
<w>scenefile</w>
|
||||
<w>scenefiles</w>
|
||||
<w>scenename</w>
|
||||
@ -2123,6 +2165,7 @@
|
||||
<w>sdkcheck</w>
|
||||
<w>sdkutils</w>
|
||||
<w>sdtk</w>
|
||||
<w>sectionchanged</w>
|
||||
<w>selchild</w>
|
||||
<w>selectmodule</w>
|
||||
<w>selindex</w>
|
||||
@ -2278,6 +2321,7 @@
|
||||
<w>starscale</w>
|
||||
<w>startercache</w>
|
||||
<w>startscan</w>
|
||||
<w>startsplits</w>
|
||||
<w>starttime</w>
|
||||
<w>statictest</w>
|
||||
<w>statictestfiles</w>
|
||||
@ -2349,6 +2393,7 @@
|
||||
<w>svne</w>
|
||||
<w>svvv</w>
|
||||
<w>swht</w>
|
||||
<w>swiftc</w>
|
||||
<w>swip</w>
|
||||
<w>swipsound</w>
|
||||
<w>sylw</w>
|
||||
@ -2450,10 +2495,12 @@
|
||||
<w>textwidgets</w>
|
||||
<w>tfin</w>
|
||||
<w>thanvannispen</w>
|
||||
<w>thats</w>
|
||||
<w>thelaststand</w>
|
||||
<w>themself</w>
|
||||
<w>thingie</w>
|
||||
<w>this'll</w>
|
||||
<w>thislinelen</w>
|
||||
<w>thismodule</w>
|
||||
<w>threadpool</w>
|
||||
<w>threadtype</w>
|
||||
@ -2699,6 +2746,7 @@
|
||||
<w>wpath</w>
|
||||
<w>wprjp</w>
|
||||
<w>wref</w>
|
||||
<w>writeauxiliaryfile</w>
|
||||
<w>writeclasses</w>
|
||||
<w>writefuncs</w>
|
||||
<w>wslpath</w>
|
||||
@ -2711,7 +2759,10 @@
|
||||
<w>xbox</w>
|
||||
<w>xcarchive</w>
|
||||
<w>xcassets</w>
|
||||
<w>xcframework</w>
|
||||
<w>xcodebuild</w>
|
||||
<w>xcodebuildverbose</w>
|
||||
<w>xcoderun</w>
|
||||
<w>xcpretty</w>
|
||||
<w>xcprojpath</w>
|
||||
<w>xcrun</w>
|
||||
@ -2733,6 +2784,7 @@
|
||||
<w>xmore</w>
|
||||
<w>xoffs</w>
|
||||
<w>xoffset</w>
|
||||
<w>xors</w>
|
||||
<w>xpos</w>
|
||||
<w>xres</w>
|
||||
<w>xscl</w>
|
||||
@ -2758,6 +2810,7 @@
|
||||
<w>zaggy</w>
|
||||
<w>zimbot</w>
|
||||
<w>zipapp</w>
|
||||
<w>zipdata</w>
|
||||
<w>zlib</w>
|
||||
<w>zlibmodule</w>
|
||||
<w>zoneid</w>
|
||||
|
||||
21
CHANGELOG.md
21
CHANGELOG.md
@ -1,3 +1,22 @@
|
||||
### 1.7.0 (20577, 2022-05-28)
|
||||
- V2 accounts are now available (woohoo!). These are called 'BombSquad Accounts' in the account section. V2 accounts communicate with a completely new server and will be the foundation for lots of new functionality in the future. However they also function as a V1 account so existing functionality should still work. Note that the new 'workspaces' V2-account is not yet available in this build, but it will be coming very soon. Also note that account types such as GameCenter and Google-Play will be 'upgraded' to V2 accounts in the future so there is no need to try this out if you use one of those. But if you use device-accounts you might want to create yourself a V2 account. You can also reserve a nice account-tag by jumping on this now.
|
||||
- Legacy account subsystem has been renamed from `ba.app.accounts` to `ba.app.accounts_v1`
|
||||
- Added `ba.app.accounts_v2` subsystem for working with V2 accounts.
|
||||
- `ba.SessionPlayer.get_account_id()` is now `ba.SessionPlayer.get_v1_account_id()`
|
||||
- `ba.InputDevice.get_account_id()` is now `ba.InputDevice.get_v1_account_id()`
|
||||
- `_ba.sign_in()` is now `_ba.sign_in_v1()`
|
||||
- `_ba.sign_out()` is now `_ba.sign_out_v1()`
|
||||
- `_ba.get_account_name()` is now `_ba.get_v1_account_name()`
|
||||
- `_ba.get_account_type()` is now `_ba.get_v1_account_type()`
|
||||
- `_ba.get_account_state()` is now `_ba.get_v1_account_state()`
|
||||
- `_ba.get_account_state_num()` is now `_ba.get_v1_account_state_num()`
|
||||
- `_ba.get_account_display_string()` is now `_ba.get_v1_account_display_string()`
|
||||
- `_ba.get_account_misc_val()` is now `_ba.get_v1_account_misc_val()`
|
||||
- `_ba.get_account_misc_read_val()` is now `_ba.get_v1_account_misc_read_val()`
|
||||
- `_ba.get_account_misc_read_val_2()` is now `_ba.get_v1_account_misc_read_val_2()`
|
||||
- `_ba.get_account_ticket_count()` is now `_ba.get_v1_account_ticket_count()`
|
||||
|
||||
|
||||
### 1.6.12 (20567, 2022-05-04)
|
||||
- More internal work on V2 master-server communication
|
||||
|
||||
@ -106,7 +125,7 @@
|
||||
- `ba.get_valid_languages()` is now an attr: `ba.app.lang.available_languages`
|
||||
- Achievement functionality has been consolidated into an AchievementSubsystem object at ba.app.ach
|
||||
- Plugin functionality has been consolidated into a PluginSubsystem obj at ba.app.plugins
|
||||
- Ditto with AccountSubsystem and ba.app.accounts
|
||||
- Ditto with AccountV1Subsystem and ba.app.accounts
|
||||
- Ditto with MetadataSubsystem and ba.app.meta
|
||||
- Ditto with AdsSubsystem and ba.app.ads
|
||||
- Revamped tab-button functionality into a cleaner type-safe class (bastd.ui.tabs.TabRow)
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
[
|
||||
"ba_data/python/ba/__init__.py",
|
||||
"ba_data/python/ba/__pycache__/__init__.cpython-39.opt-1.pyc",
|
||||
"ba_data/python/ba/__pycache__/_account.cpython-39.opt-1.pyc",
|
||||
"ba_data/python/ba/__pycache__/_accountv1.cpython-39.opt-1.pyc",
|
||||
"ba_data/python/ba/__pycache__/_accountv2.cpython-39.opt-1.pyc",
|
||||
"ba_data/python/ba/__pycache__/_achievement.cpython-39.opt-1.pyc",
|
||||
"ba_data/python/ba/__pycache__/_activity.cpython-39.opt-1.pyc",
|
||||
"ba_data/python/ba/__pycache__/_activitytypes.cpython-39.opt-1.pyc",
|
||||
@ -58,12 +59,14 @@
|
||||
"ba_data/python/ba/__pycache__/_tips.cpython-39.opt-1.pyc",
|
||||
"ba_data/python/ba/__pycache__/_tournament.cpython-39.opt-1.pyc",
|
||||
"ba_data/python/ba/__pycache__/_ui.cpython-39.opt-1.pyc",
|
||||
"ba_data/python/ba/__pycache__/cloud.cpython-39.opt-1.pyc",
|
||||
"ba_data/python/ba/__pycache__/deprecated.cpython-39.opt-1.pyc",
|
||||
"ba_data/python/ba/__pycache__/internal.cpython-39.opt-1.pyc",
|
||||
"ba_data/python/ba/__pycache__/macmusicapp.cpython-39.opt-1.pyc",
|
||||
"ba_data/python/ba/__pycache__/modutils.cpython-39.opt-1.pyc",
|
||||
"ba_data/python/ba/__pycache__/osmusic.cpython-39.opt-1.pyc",
|
||||
"ba_data/python/ba/_account.py",
|
||||
"ba_data/python/ba/_accountv1.py",
|
||||
"ba_data/python/ba/_accountv2.py",
|
||||
"ba_data/python/ba/_achievement.py",
|
||||
"ba_data/python/ba/_activity.py",
|
||||
"ba_data/python/ba/_activitytypes.py",
|
||||
@ -124,6 +127,7 @@
|
||||
"ba_data/python/ba/_tips.py",
|
||||
"ba_data/python/ba/_tournament.py",
|
||||
"ba_data/python/ba/_ui.py",
|
||||
"ba_data/python/ba/cloud.py",
|
||||
"ba_data/python/ba/deprecated.py",
|
||||
"ba_data/python/ba/internal.py",
|
||||
"ba_data/python/ba/macmusicapp.py",
|
||||
@ -136,11 +140,13 @@
|
||||
"ba_data/python/bacommon/__pycache__/assets.cpython-39.opt-1.pyc",
|
||||
"ba_data/python/bacommon/__pycache__/bacloud.cpython-39.opt-1.pyc",
|
||||
"ba_data/python/bacommon/__pycache__/build.cpython-39.opt-1.pyc",
|
||||
"ba_data/python/bacommon/__pycache__/cloud.cpython-39.opt-1.pyc",
|
||||
"ba_data/python/bacommon/__pycache__/net.cpython-39.opt-1.pyc",
|
||||
"ba_data/python/bacommon/__pycache__/servermanager.cpython-39.opt-1.pyc",
|
||||
"ba_data/python/bacommon/assets.py",
|
||||
"ba_data/python/bacommon/bacloud.py",
|
||||
"ba_data/python/bacommon/build.py",
|
||||
"ba_data/python/bacommon/cloud.py",
|
||||
"ba_data/python/bacommon/net.py",
|
||||
"ba_data/python/bacommon/servermanager.py",
|
||||
"ba_data/python/bastd/__init__.py",
|
||||
|
||||
@ -133,7 +133,8 @@ endef
|
||||
|
||||
SCRIPT_TARGETS_PY_PUBLIC = \
|
||||
build/ba_data/python/ba/__init__.py \
|
||||
build/ba_data/python/ba/_account.py \
|
||||
build/ba_data/python/ba/_accountv1.py \
|
||||
build/ba_data/python/ba/_accountv2.py \
|
||||
build/ba_data/python/ba/_achievement.py \
|
||||
build/ba_data/python/ba/_activity.py \
|
||||
build/ba_data/python/ba/_activitytypes.py \
|
||||
@ -192,6 +193,7 @@ SCRIPT_TARGETS_PY_PUBLIC = \
|
||||
build/ba_data/python/ba/_tips.py \
|
||||
build/ba_data/python/ba/_tournament.py \
|
||||
build/ba_data/python/ba/_ui.py \
|
||||
build/ba_data/python/ba/cloud.py \
|
||||
build/ba_data/python/ba/deprecated.py \
|
||||
build/ba_data/python/ba/internal.py \
|
||||
build/ba_data/python/ba/macmusicapp.py \
|
||||
@ -378,7 +380,8 @@ SCRIPT_TARGETS_PY_PUBLIC = \
|
||||
|
||||
SCRIPT_TARGETS_PYC_PUBLIC = \
|
||||
build/ba_data/python/ba/__pycache__/__init__.cpython-39.opt-1.pyc \
|
||||
build/ba_data/python/ba/__pycache__/_account.cpython-39.opt-1.pyc \
|
||||
build/ba_data/python/ba/__pycache__/_accountv1.cpython-39.opt-1.pyc \
|
||||
build/ba_data/python/ba/__pycache__/_accountv2.cpython-39.opt-1.pyc \
|
||||
build/ba_data/python/ba/__pycache__/_achievement.cpython-39.opt-1.pyc \
|
||||
build/ba_data/python/ba/__pycache__/_activity.cpython-39.opt-1.pyc \
|
||||
build/ba_data/python/ba/__pycache__/_activitytypes.cpython-39.opt-1.pyc \
|
||||
@ -437,6 +440,7 @@ SCRIPT_TARGETS_PYC_PUBLIC = \
|
||||
build/ba_data/python/ba/__pycache__/_tips.cpython-39.opt-1.pyc \
|
||||
build/ba_data/python/ba/__pycache__/_tournament.cpython-39.opt-1.pyc \
|
||||
build/ba_data/python/ba/__pycache__/_ui.cpython-39.opt-1.pyc \
|
||||
build/ba_data/python/ba/__pycache__/cloud.cpython-39.opt-1.pyc \
|
||||
build/ba_data/python/ba/__pycache__/deprecated.cpython-39.opt-1.pyc \
|
||||
build/ba_data/python/ba/__pycache__/internal.cpython-39.opt-1.pyc \
|
||||
build/ba_data/python/ba/__pycache__/macmusicapp.cpython-39.opt-1.pyc \
|
||||
@ -640,6 +644,7 @@ SCRIPT_TARGETS_PY_PUBLIC_TOOLS = \
|
||||
build/ba_data/python/bacommon/assets.py \
|
||||
build/ba_data/python/bacommon/bacloud.py \
|
||||
build/ba_data/python/bacommon/build.py \
|
||||
build/ba_data/python/bacommon/cloud.py \
|
||||
build/ba_data/python/bacommon/net.py \
|
||||
build/ba_data/python/bacommon/servermanager.py \
|
||||
build/ba_data/python/efro/__init__.py \
|
||||
@ -668,6 +673,7 @@ SCRIPT_TARGETS_PYC_PUBLIC_TOOLS = \
|
||||
build/ba_data/python/bacommon/__pycache__/assets.cpython-39.opt-1.pyc \
|
||||
build/ba_data/python/bacommon/__pycache__/bacloud.cpython-39.opt-1.pyc \
|
||||
build/ba_data/python/bacommon/__pycache__/build.cpython-39.opt-1.pyc \
|
||||
build/ba_data/python/bacommon/__pycache__/cloud.cpython-39.opt-1.pyc \
|
||||
build/ba_data/python/bacommon/__pycache__/net.cpython-39.opt-1.pyc \
|
||||
build/ba_data/python/bacommon/__pycache__/servermanager.cpython-39.opt-1.pyc \
|
||||
build/ba_data/python/efro/__pycache__/__init__.cpython-39.opt-1.pyc \
|
||||
|
||||
@ -1 +1 @@
|
||||
263249044076897294312459199917994463006
|
||||
14398100813069830297938811166218395775
|
||||
@ -263,13 +263,6 @@ class InputDevice:
|
||||
"""
|
||||
return bool()
|
||||
|
||||
def get_account_name(self, full: bool) -> str:
|
||||
"""Returns the account name associated with this device.
|
||||
|
||||
(can be used to get account names for remote players)
|
||||
"""
|
||||
return str()
|
||||
|
||||
def get_axis_name(self, axis_id: int) -> str:
|
||||
"""Given an axis ID, return the name of the axis on this device.
|
||||
|
||||
@ -297,6 +290,13 @@ class InputDevice:
|
||||
"""(internal)"""
|
||||
return dict()
|
||||
|
||||
def get_v1_account_name(self, full: bool) -> str:
|
||||
"""Returns the account name associated with this device.
|
||||
|
||||
(can be used to get account names for remote players)
|
||||
"""
|
||||
return str()
|
||||
|
||||
def is_connected_to_remote_player(self) -> bool:
|
||||
"""(internal)"""
|
||||
return bool()
|
||||
@ -788,16 +788,6 @@ class SessionPlayer:
|
||||
"""Return whether the underlying player is still in the game."""
|
||||
return bool()
|
||||
|
||||
def get_account_id(self) -> str:
|
||||
"""Return the Account ID this player is signed in under, if
|
||||
there is one and it can be determined with relative certainty.
|
||||
Returns None otherwise. Note that this may require an active
|
||||
internet connection (especially for network-connected players)
|
||||
and may return None for a short while after a player initially
|
||||
joins (while verification occurs).
|
||||
"""
|
||||
return str()
|
||||
|
||||
def get_icon(self) -> dict[str, Any]:
|
||||
"""Returns the character's icon (images, colors, etc contained
|
||||
in a dict.
|
||||
@ -808,6 +798,16 @@ class SessionPlayer:
|
||||
"""(internal)"""
|
||||
return {'foo': 'bar'}
|
||||
|
||||
def get_v1_account_id(self) -> str:
|
||||
"""Return the V1 Account ID this player is signed in under, if
|
||||
there is one and it can be determined with relative certainty.
|
||||
Returns None otherwise. Note that this may require an active
|
||||
internet connection (especially for network-connected players)
|
||||
and may return None for a short while after a player initially
|
||||
joins (while verification occurs).
|
||||
"""
|
||||
return str()
|
||||
|
||||
def getname(self, full: bool = False, icon: bool = True) -> str:
|
||||
"""Returns the player's name. If icon is True, the long version of the
|
||||
name may include an icon.
|
||||
@ -1570,54 +1570,6 @@ def game_service_has_leaderboard(game: str, config: str) -> bool:
|
||||
return bool()
|
||||
|
||||
|
||||
def get_account_display_string(full: bool = True) -> str:
|
||||
"""(internal)"""
|
||||
return str()
|
||||
|
||||
|
||||
def get_account_misc_read_val(name: str, default_value: Any) -> Any:
|
||||
"""(internal)"""
|
||||
return _uninferrable()
|
||||
|
||||
|
||||
def get_account_misc_read_val_2(name: str, default_value: Any) -> Any:
|
||||
"""(internal)"""
|
||||
return _uninferrable()
|
||||
|
||||
|
||||
def get_account_misc_val(name: str, default_value: Any) -> Any:
|
||||
"""(internal)"""
|
||||
return _uninferrable()
|
||||
|
||||
|
||||
def get_account_name() -> str:
|
||||
"""(internal)"""
|
||||
return str()
|
||||
|
||||
|
||||
def get_account_state() -> str:
|
||||
"""(internal)"""
|
||||
return str()
|
||||
|
||||
|
||||
def get_account_state_num() -> int:
|
||||
"""(internal)"""
|
||||
return int()
|
||||
|
||||
|
||||
def get_account_ticket_count() -> int:
|
||||
"""(internal)
|
||||
|
||||
Returns the number of tickets for the current account.
|
||||
"""
|
||||
return int()
|
||||
|
||||
|
||||
def get_account_type() -> str:
|
||||
"""(internal)"""
|
||||
return str()
|
||||
|
||||
|
||||
def get_appconfig_builtin_keys() -> list[str]:
|
||||
"""(internal)"""
|
||||
return ['blah', 'blah2']
|
||||
@ -1920,6 +1872,54 @@ def get_ui_input_device() -> ba.InputDevice:
|
||||
return ba.InputDevice()
|
||||
|
||||
|
||||
def get_v1_account_display_string(full: bool = True) -> str:
|
||||
"""(internal)"""
|
||||
return str()
|
||||
|
||||
|
||||
def get_v1_account_misc_read_val(name: str, default_value: Any) -> Any:
|
||||
"""(internal)"""
|
||||
return _uninferrable()
|
||||
|
||||
|
||||
def get_v1_account_misc_read_val_2(name: str, default_value: Any) -> Any:
|
||||
"""(internal)"""
|
||||
return _uninferrable()
|
||||
|
||||
|
||||
def get_v1_account_misc_val(name: str, default_value: Any) -> Any:
|
||||
"""(internal)"""
|
||||
return _uninferrable()
|
||||
|
||||
|
||||
def get_v1_account_name() -> str:
|
||||
"""(internal)"""
|
||||
return str()
|
||||
|
||||
|
||||
def get_v1_account_state() -> str:
|
||||
"""(internal)"""
|
||||
return str()
|
||||
|
||||
|
||||
def get_v1_account_state_num() -> int:
|
||||
"""(internal)"""
|
||||
return int()
|
||||
|
||||
|
||||
def get_v1_account_ticket_count() -> int:
|
||||
"""(internal)
|
||||
|
||||
Returns the number of tickets for the current account.
|
||||
"""
|
||||
return int()
|
||||
|
||||
|
||||
def get_v1_account_type() -> str:
|
||||
"""(internal)"""
|
||||
return str()
|
||||
|
||||
|
||||
def get_v2_fleet() -> str:
|
||||
"""(internal)"""
|
||||
return str()
|
||||
@ -2949,7 +2949,7 @@ def show_progress_bar() -> None:
|
||||
return None
|
||||
|
||||
|
||||
def sign_in(account_type: str) -> None:
|
||||
def sign_in_v1(account_type: str) -> None:
|
||||
"""(internal)
|
||||
|
||||
Category: General Utility Functions
|
||||
@ -2957,7 +2957,7 @@ def sign_in(account_type: str) -> None:
|
||||
return None
|
||||
|
||||
|
||||
def sign_out() -> None:
|
||||
def sign_out_v1(v2_embedded: bool = False) -> None:
|
||||
"""(internal)
|
||||
|
||||
Category: General Utility Functions
|
||||
|
||||
@ -14,12 +14,12 @@ if TYPE_CHECKING:
|
||||
from typing import Any, Optional
|
||||
|
||||
|
||||
class AccountSubsystem:
|
||||
"""Subsystem for account handling in the app.
|
||||
class AccountV1Subsystem:
|
||||
"""Subsystem for legacy account handling in the app.
|
||||
|
||||
Category: **App Classes**
|
||||
|
||||
Access the single shared instance of this class at 'ba.app.plugins'.
|
||||
Access the single shared instance of this class at 'ba.app.accounts_v1'.
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
@ -41,7 +41,7 @@ class AccountSubsystem:
|
||||
def do_auto_sign_in() -> None:
|
||||
if _ba.app.headless_mode or _ba.app.config.get(
|
||||
'Auto Account State') == 'Local':
|
||||
_ba.sign_in('Local')
|
||||
_ba.sign_in_v1('Local')
|
||||
|
||||
_ba.pushcall(do_auto_sign_in)
|
||||
|
||||
@ -108,8 +108,8 @@ class AccountSubsystem:
|
||||
|
||||
if data['p']:
|
||||
pro_mult = 1.0 + float(
|
||||
_ba.get_account_misc_read_val('proPowerRankingBoost',
|
||||
0.0)) * 0.01
|
||||
_ba.get_v1_account_misc_read_val('proPowerRankingBoost',
|
||||
0.0)) * 0.01
|
||||
else:
|
||||
pro_mult = 1.0
|
||||
|
||||
@ -135,7 +135,7 @@ class AccountSubsystem:
|
||||
"""(internal)"""
|
||||
# pylint: disable=cyclic-import
|
||||
from ba import _store
|
||||
if _ba.get_account_state() != 'signed_in':
|
||||
if _ba.get_v1_account_state() != 'signed_in':
|
||||
return []
|
||||
icons = []
|
||||
store_items = _store.get_store_items()
|
||||
@ -152,12 +152,12 @@ class AccountSubsystem:
|
||||
(internal)
|
||||
"""
|
||||
# This only applies when we're signed in.
|
||||
if _ba.get_account_state() != 'signed_in':
|
||||
if _ba.get_v1_account_state() != 'signed_in':
|
||||
return
|
||||
|
||||
# If the short version of our account name currently cant be
|
||||
# displayed by the game, cancel.
|
||||
if not _ba.have_chars(_ba.get_account_display_string(full=False)):
|
||||
if not _ba.have_chars(_ba.get_v1_account_display_string(full=False)):
|
||||
return
|
||||
|
||||
config = _ba.app.config
|
||||
@ -199,7 +199,7 @@ class AccountSubsystem:
|
||||
# or also if we've been grandfathered in or are using ballistica-core
|
||||
# builds.
|
||||
return self.have_pro() or bool(
|
||||
_ba.get_account_misc_read_val_2('proOptionsUnlocked', False)
|
||||
_ba.get_v1_account_misc_read_val_2('proOptionsUnlocked', False)
|
||||
or _ba.app.config.get('lc14292', 0) > 1)
|
||||
|
||||
def show_post_purchase_message(self) -> None:
|
||||
@ -221,7 +221,8 @@ class AccountSubsystem:
|
||||
from ba._language import Lstr
|
||||
|
||||
# Run any pending promo codes we had queued up while not signed in.
|
||||
if _ba.get_account_state() == 'signed_in' and self.pending_promo_codes:
|
||||
if _ba.get_v1_account_state(
|
||||
) == 'signed_in' and self.pending_promo_codes:
|
||||
for code in self.pending_promo_codes:
|
||||
_ba.screenmessage(Lstr(resource='submittingPromoCodeText'),
|
||||
color=(0, 1, 0))
|
||||
@ -241,7 +242,7 @@ class AccountSubsystem:
|
||||
# If we're not signed in, queue up the code to run the next time we
|
||||
# are and issue a warning if we haven't signed in within the next
|
||||
# few seconds.
|
||||
if _ba.get_account_state() != 'signed_in':
|
||||
if _ba.get_v1_account_state() != 'signed_in':
|
||||
|
||||
def check_pending_codes() -> None:
|
||||
"""(internal)"""
|
||||
51
assets/src/ba_data/python/ba/_accountv2.py
Normal file
51
assets/src/ba_data/python/ba/_accountv2.py
Normal file
@ -0,0 +1,51 @@
|
||||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Account related functionality."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Optional
|
||||
|
||||
|
||||
class AccountV2Subsystem:
|
||||
"""Subsystem for modern account handling in the app.
|
||||
|
||||
Category: **App Classes**
|
||||
|
||||
Access the single shared instance of this class at 'ba.app.accounts_v2'.
|
||||
"""
|
||||
|
||||
def on_app_launch(self) -> None:
|
||||
"""Should be called at standard on_app_launch time."""
|
||||
|
||||
def set_primary_credentials(self, credentials: Optional[str]) -> None:
|
||||
"""Set credentials for the primary app account."""
|
||||
raise RuntimeError('This should be overridden.')
|
||||
|
||||
def have_primary_credentials(self) -> bool:
|
||||
"""Are credentials currently set for the primary app account?
|
||||
|
||||
Note that this does not mean these credentials are currently valid;
|
||||
only that they exist. If/when credentials are validated, the 'primary'
|
||||
account handle will be set.
|
||||
"""
|
||||
raise RuntimeError('This should be overridden.')
|
||||
|
||||
@property
|
||||
def primary(self) -> Optional[AccountV2Handle]:
|
||||
"""The primary account for the app, or None if not logged in."""
|
||||
return None
|
||||
|
||||
def get_primary(self) -> Optional[AccountV2Handle]:
|
||||
"""Internal - should be overridden by subclass."""
|
||||
return None
|
||||
|
||||
|
||||
class AccountV2Handle:
|
||||
"""Handle for interacting with a v2 account."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.tag = '?'
|
||||
@ -409,9 +409,9 @@ def _get_ach_mult(include_pro_bonus: bool = False) -> int:
|
||||
|
||||
(just for display; changing this here won't affect actual rewards)
|
||||
"""
|
||||
val: int = _ba.get_account_misc_read_val('achAwardMult', 5)
|
||||
val: int = _ba.get_v1_account_misc_read_val('achAwardMult', 5)
|
||||
assert isinstance(val, int)
|
||||
if include_pro_bonus and _ba.app.accounts.have_pro():
|
||||
if include_pro_bonus and _ba.app.accounts_v1.have_pro():
|
||||
val *= 2
|
||||
return val
|
||||
|
||||
@ -496,7 +496,7 @@ class Achievement:
|
||||
# signed in, lets not show them (otherwise we tend to get
|
||||
# confusing 'controller connected' achievements popping up while
|
||||
# waiting to log in which can be confusing).
|
||||
if _ba.get_account_state() != 'signed_in':
|
||||
if _ba.get_v1_account_state() != 'signed_in':
|
||||
return
|
||||
|
||||
# If we're being freshly complete, display/report it and whatnot.
|
||||
@ -592,8 +592,8 @@ class Achievement:
|
||||
|
||||
def get_award_ticket_value(self, include_pro_bonus: bool = False) -> int:
|
||||
"""Get the ticket award value for this achievement."""
|
||||
val: int = (_ba.get_account_misc_read_val('achAward.' + self._name,
|
||||
self._award) *
|
||||
val: int = (_ba.get_v1_account_misc_read_val('achAward.' + self._name,
|
||||
self._award) *
|
||||
_get_ach_mult(include_pro_bonus))
|
||||
assert isinstance(val, int)
|
||||
return val
|
||||
@ -601,7 +601,7 @@ class Achievement:
|
||||
@property
|
||||
def power_ranking_value(self) -> int:
|
||||
"""Get the power-ranking award value for this achievement."""
|
||||
val: int = _ba.get_account_misc_read_val(
|
||||
val: int = _ba.get_v1_account_misc_read_val(
|
||||
'achLeaguePoints.' + self._name, self._award)
|
||||
assert isinstance(val, int)
|
||||
return val
|
||||
@ -1176,7 +1176,7 @@ class Achievement:
|
||||
objt.node.host_only = True
|
||||
|
||||
# Add the 'x 2' if we've got pro.
|
||||
if app.accounts.have_pro():
|
||||
if app.accounts_v1.have_pro():
|
||||
objt = Text('x 2',
|
||||
position=(-120 - 180 + 45, 80 + y_offs - 50),
|
||||
v_attach=Text.VAttach.BOTTOM,
|
||||
|
||||
@ -77,7 +77,7 @@ class AdsSubsystem:
|
||||
# No ads without net-connections, etc.
|
||||
if not _ba.can_show_ad():
|
||||
show = False
|
||||
if app.accounts.have_pro():
|
||||
if app.accounts_v1.have_pro():
|
||||
show = False # Pro disables interstitials.
|
||||
try:
|
||||
session = _ba.get_foreground_host_session()
|
||||
@ -93,15 +93,15 @@ class AdsSubsystem:
|
||||
launch_count = app.config.get('launchCount', 0)
|
||||
|
||||
# If we're seeing short ads we may want to space them differently.
|
||||
interval_mult = (_ba.get_account_misc_read_val(
|
||||
interval_mult = (_ba.get_v1_account_misc_read_val(
|
||||
'ads.shortIntervalMult', 1.0)
|
||||
if self.last_ad_was_short else 1.0)
|
||||
if self.ad_amt is None:
|
||||
if launch_count <= 1:
|
||||
self.ad_amt = _ba.get_account_misc_read_val(
|
||||
self.ad_amt = _ba.get_v1_account_misc_read_val(
|
||||
'ads.startVal1', 0.99)
|
||||
else:
|
||||
self.ad_amt = _ba.get_account_misc_read_val(
|
||||
self.ad_amt = _ba.get_v1_account_misc_read_val(
|
||||
'ads.startVal2', 1.0)
|
||||
interval = None
|
||||
else:
|
||||
@ -110,15 +110,15 @@ class AdsSubsystem:
|
||||
# (we reach our threshold faster the longer we've been
|
||||
# playing).
|
||||
base = 'ads' if _ba.has_video_ads() else 'ads2'
|
||||
min_lc = _ba.get_account_misc_read_val(base + '.minLC', 0.0)
|
||||
max_lc = _ba.get_account_misc_read_val(base + '.maxLC', 5.0)
|
||||
min_lc_scale = (_ba.get_account_misc_read_val(
|
||||
min_lc = _ba.get_v1_account_misc_read_val(base + '.minLC', 0.0)
|
||||
max_lc = _ba.get_v1_account_misc_read_val(base + '.maxLC', 5.0)
|
||||
min_lc_scale = (_ba.get_v1_account_misc_read_val(
|
||||
base + '.minLCScale', 0.25))
|
||||
max_lc_scale = (_ba.get_account_misc_read_val(
|
||||
max_lc_scale = (_ba.get_v1_account_misc_read_val(
|
||||
base + '.maxLCScale', 0.34))
|
||||
min_lc_interval = (_ba.get_account_misc_read_val(
|
||||
min_lc_interval = (_ba.get_v1_account_misc_read_val(
|
||||
base + '.minLCInterval', 360))
|
||||
max_lc_interval = (_ba.get_account_misc_read_val(
|
||||
max_lc_interval = (_ba.get_v1_account_misc_read_val(
|
||||
base + '.maxLCInterval', 300))
|
||||
if launch_count < min_lc:
|
||||
lc_amt = 0.0
|
||||
|
||||
@ -7,6 +7,7 @@ import random
|
||||
import logging
|
||||
from enum import Enum
|
||||
from typing import TYPE_CHECKING
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
|
||||
import _ba
|
||||
from ba._music import MusicSubsystem
|
||||
@ -14,16 +15,20 @@ from ba._language import LanguageSubsystem
|
||||
from ba._ui import UISubsystem
|
||||
from ba._achievement import AchievementSubsystem
|
||||
from ba._plugin import PluginSubsystem
|
||||
from ba._account import AccountSubsystem
|
||||
from ba._accountv1 import AccountV1Subsystem
|
||||
from ba._meta import MetadataSubsystem
|
||||
from ba._ads import AdsSubsystem
|
||||
from ba._net import NetworkSubsystem
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import ba
|
||||
from bastd.actor import spazappearance
|
||||
import asyncio
|
||||
from typing import Optional, Any, Callable
|
||||
|
||||
import ba
|
||||
from ba.cloud import CloudSubsystem
|
||||
from bastd.actor import spazappearance
|
||||
from ba._accountv2 import AccountV2Subsystem
|
||||
|
||||
|
||||
class App:
|
||||
"""A class for high level app functionality and state.
|
||||
@ -38,6 +43,10 @@ class App:
|
||||
|
||||
# pylint: disable=too-many-public-methods
|
||||
|
||||
# Implementations for these will be filled in by internal libs.
|
||||
accounts_v2: AccountV2Subsystem
|
||||
cloud: CloudSubsystem
|
||||
|
||||
class State(Enum):
|
||||
"""High level state the app can be in."""
|
||||
LAUNCHING = 0
|
||||
@ -45,6 +54,20 @@ class App:
|
||||
PAUSED = 2
|
||||
SHUTTING_DOWN = 3
|
||||
|
||||
@property
|
||||
def aioloop(self) -> asyncio.AbstractEventLoop:
|
||||
"""The Logic Thread's Asyncio Event Loop.
|
||||
|
||||
This allow async tasks to be run in the logic thread.
|
||||
Note that, at this time, the asyncio loop is encapsulated
|
||||
and explicitly stepped by the engine's logic thread loop and
|
||||
thus things like asyncio.get_running_loop() will not return this
|
||||
loop from most places in the logic thread; only from within a
|
||||
task explicitly created in this loop.
|
||||
"""
|
||||
assert self._aioloop is not None
|
||||
return self._aioloop
|
||||
|
||||
@property
|
||||
def build_number(self) -> int:
|
||||
"""Integer build number.
|
||||
@ -196,6 +219,8 @@ class App:
|
||||
# refreshed/etc.
|
||||
self.fg_state = 0
|
||||
|
||||
self._aioloop: Optional[asyncio.AbstractEventLoop] = None
|
||||
|
||||
self._env = _ba.env()
|
||||
self.protocol_version: int = self._env['protocol_version']
|
||||
assert isinstance(self.protocol_version, int)
|
||||
@ -211,6 +236,11 @@ class App:
|
||||
assert isinstance(self.iircade_mode, bool)
|
||||
self.allow_ticket_purchases: bool = not self.iircade_mode
|
||||
|
||||
# Default executor which can be used for misc background processing.
|
||||
# It should also be passed to any asyncio loops we create so that
|
||||
# everything shares the same single set of threads.
|
||||
self.threadpool = ThreadPoolExecutor(thread_name_prefix='baworker')
|
||||
|
||||
# Misc.
|
||||
self.tips: list[str] = []
|
||||
self.stress_test_reset_timer: Optional[ba.Timer] = None
|
||||
@ -235,7 +265,7 @@ class App:
|
||||
self.server: Optional[ba.ServerController] = None
|
||||
|
||||
self.meta = MetadataSubsystem()
|
||||
self.accounts = AccountSubsystem()
|
||||
self.accounts_v1 = AccountV1Subsystem()
|
||||
self.plugins = PluginSubsystem()
|
||||
self.music = MusicSubsystem()
|
||||
self.lang = LanguageSubsystem()
|
||||
@ -286,6 +316,8 @@ class App:
|
||||
|
||||
(internal)"""
|
||||
# pylint: disable=cyclic-import
|
||||
# pylint: disable=too-many-locals
|
||||
from ba import _asyncio
|
||||
from ba import _apputils
|
||||
from ba import _appconfig
|
||||
from ba import _map
|
||||
@ -295,6 +327,8 @@ class App:
|
||||
from bastd.actor import spazappearance
|
||||
from ba._generated.enums import TimeType
|
||||
|
||||
self._aioloop = _asyncio.setup_asyncio()
|
||||
|
||||
cfg = self.config
|
||||
|
||||
self.delegate = appdelegate.AppDelegate()
|
||||
@ -365,7 +399,8 @@ class App:
|
||||
_ba.timer(3.0, check_special_offer, timetype=TimeType.REAL)
|
||||
|
||||
self.meta.on_app_launch()
|
||||
self.accounts.on_app_launch()
|
||||
self.accounts_v2.on_app_launch()
|
||||
self.accounts_v1.on_app_launch()
|
||||
self.plugins.on_app_launch()
|
||||
|
||||
# See note below in on_app_pause.
|
||||
@ -403,7 +438,7 @@ class App:
|
||||
self._app_paused = False
|
||||
self._update_state()
|
||||
self.fg_state += 1
|
||||
self.accounts.on_app_resume()
|
||||
self.accounts_v1.on_app_resume()
|
||||
self.music.on_app_resume()
|
||||
self.plugins.on_app_resume()
|
||||
|
||||
@ -569,7 +604,7 @@ class App:
|
||||
appname = _ba.appname()
|
||||
if url.startswith(f'{appname}://code/'):
|
||||
code = url.replace(f'{appname}://code/', '')
|
||||
self.accounts.add_pending_promo_code(code)
|
||||
self.accounts_v1.add_pending_promo_code(code)
|
||||
else:
|
||||
_ba.screenmessage(Lstr(resource='errorText'), color=(1, 0, 0))
|
||||
_ba.playsound(_ba.getsound('error'))
|
||||
|
||||
@ -21,11 +21,12 @@ _asyncio_timer: Optional[ba.Timer] = None
|
||||
_asyncio_event_loop: Optional[asyncio.AbstractEventLoop] = None
|
||||
|
||||
|
||||
def setup_asyncio() -> None:
|
||||
"""Setup asyncio functionality for our game thread."""
|
||||
def setup_asyncio() -> asyncio.AbstractEventLoop:
|
||||
"""Setup asyncio functionality for the logic thread."""
|
||||
# pylint: disable=global-statement
|
||||
|
||||
import _ba
|
||||
import ba
|
||||
from ba._generated.enums import TimeType
|
||||
|
||||
assert _ba.in_game_thread()
|
||||
@ -40,6 +41,7 @@ def setup_asyncio() -> None:
|
||||
|
||||
global _asyncio_event_loop # pylint: disable=invalid-name
|
||||
_asyncio_event_loop = asyncio.new_event_loop()
|
||||
_asyncio_event_loop.set_default_executor(ba.app.threadpool)
|
||||
|
||||
# Ideally we should integrate asyncio into our C++ Thread class's
|
||||
# low level event loop so that asyncio timers/sockets/etc. could
|
||||
@ -70,3 +72,5 @@ def setup_asyncio() -> None:
|
||||
print('TEST AIO TASK ENDING')
|
||||
|
||||
_asyncio_event_loop.create_task(aio_test())
|
||||
|
||||
return _asyncio_event_loop
|
||||
|
||||
@ -239,11 +239,11 @@ class GameActivity(Activity[PlayerType, TeamType]):
|
||||
self._zoom_message_times: dict[int, float] = {}
|
||||
self._is_waiting_for_continue = False
|
||||
|
||||
self._continue_cost = _ba.get_account_misc_read_val(
|
||||
self._continue_cost = _ba.get_v1_account_misc_read_val(
|
||||
'continueStartCost', 25)
|
||||
self._continue_cost_mult = _ba.get_account_misc_read_val(
|
||||
self._continue_cost_mult = _ba.get_v1_account_misc_read_val(
|
||||
'continuesMult', 2)
|
||||
self._continue_cost_offset = _ba.get_account_misc_read_val(
|
||||
self._continue_cost_offset = _ba.get_v1_account_misc_read_val(
|
||||
'continuesOffset', 0)
|
||||
|
||||
@property
|
||||
@ -390,7 +390,7 @@ class GameActivity(Activity[PlayerType, TeamType]):
|
||||
from ba._generated.enums import TimeType
|
||||
|
||||
try:
|
||||
if _ba.get_account_misc_read_val('enableContinues', False):
|
||||
if _ba.get_v1_account_misc_read_val('enableContinues', False):
|
||||
session = self.session
|
||||
|
||||
# We only support continuing in non-tournament games.
|
||||
@ -467,7 +467,7 @@ class GameActivity(Activity[PlayerType, TeamType]):
|
||||
data_t = data['t'] # This used to be the whole payload.
|
||||
|
||||
# Keep our cached tourney info up to date
|
||||
_ba.app.accounts.cache_tournament_info(data_t)
|
||||
_ba.app.accounts_v1.cache_tournament_info(data_t)
|
||||
self._setup_tournament_time_limit(
|
||||
max(5, data_t[0]['timeRemaining']))
|
||||
|
||||
|
||||
@ -24,12 +24,11 @@ if TYPE_CHECKING:
|
||||
|
||||
def finish_bootstrapping() -> None:
|
||||
"""Do final bootstrapping related bits."""
|
||||
from ba._asyncio import setup_asyncio
|
||||
assert _ba.in_game_thread()
|
||||
|
||||
# Kick off our asyncio event handling, allowing us to use coroutines
|
||||
# in our game thread alongside our internal event handling.
|
||||
setup_asyncio()
|
||||
# setup_asyncio()
|
||||
|
||||
# Ok, bootstrapping is done; time to get the show started.
|
||||
_ba.app.on_app_launch()
|
||||
@ -362,3 +361,8 @@ def hash_strings(inputs: list[str]) -> str:
|
||||
sha.update(inp.encode())
|
||||
|
||||
return sha.hexdigest()
|
||||
|
||||
|
||||
def have_account_v2_credentials() -> bool:
|
||||
"""Do we have primary account-v2 credentials set?"""
|
||||
return _ba.app.accounts_v2.have_primary_credentials()
|
||||
|
||||
@ -639,5 +639,5 @@ def get_last_player_name_from_input_device(device: ba.InputDevice) -> str:
|
||||
if profilename == '_random':
|
||||
profilename = device.get_default_player_name()
|
||||
if profilename == '__account__':
|
||||
profilename = _ba.get_account_display_string()
|
||||
profilename = _ba.get_v1_account_display_string()
|
||||
return profilename
|
||||
|
||||
@ -452,7 +452,8 @@ class Chooser:
|
||||
clamp = not full
|
||||
elif name == '__account__':
|
||||
try:
|
||||
name = self._sessionplayer.inputdevice.get_account_name(full)
|
||||
name = self._sessionplayer.inputdevice.get_v1_account_name(
|
||||
full)
|
||||
except Exception:
|
||||
print_exception('Error getting account name for chooser.')
|
||||
name = 'Invalid'
|
||||
@ -894,7 +895,7 @@ class Lobby:
|
||||
self.character_names_local_unlocked.sort(key=lambda x: x.lower())
|
||||
|
||||
# Do any overall prep we need to such as creating account profile.
|
||||
_ba.app.accounts.ensure_have_account_player_profile()
|
||||
_ba.app.accounts_v1.ensure_have_account_player_profile()
|
||||
for chooser in self.choosers:
|
||||
try:
|
||||
chooser.reload_profiles()
|
||||
|
||||
@ -227,7 +227,7 @@ class ServerController:
|
||||
|
||||
def _prepare_to_serve(self) -> None:
|
||||
"""Run in a timer to do prep before beginning to serve."""
|
||||
signed_in = _ba.get_account_state() == 'signed_in'
|
||||
signed_in = _ba.get_v1_account_state() == 'signed_in'
|
||||
if not signed_in:
|
||||
|
||||
# Signing in to the local server account should not take long;
|
||||
@ -302,7 +302,7 @@ class ServerController:
|
||||
appcfg = app.config
|
||||
sessiontype = self._get_session_type()
|
||||
|
||||
if _ba.get_account_state() != 'signed_in':
|
||||
if _ba.get_v1_account_state() != 'signed_in':
|
||||
print('WARNING: launch_server_session() expects to run '
|
||||
'with a signed in server account')
|
||||
|
||||
|
||||
@ -366,11 +366,11 @@ def get_store_layout() -> dict[str, list[dict[str, Any]]]:
|
||||
'games.ninja_fight', 'games.meteor_shower', 'games.target_practice'
|
||||
]
|
||||
}]
|
||||
if _ba.get_account_misc_read_val('xmas', False):
|
||||
if _ba.get_v1_account_misc_read_val('xmas', False):
|
||||
store_layout['characters'][0]['items'].append('characters.santa')
|
||||
store_layout['characters'][0]['items'].append('characters.wizard')
|
||||
store_layout['characters'][0]['items'].append('characters.cyborg')
|
||||
if _ba.get_account_misc_read_val('easter', False):
|
||||
if _ba.get_v1_account_misc_read_val('easter', False):
|
||||
store_layout['characters'].append({
|
||||
'title': 'store.holidaySpecialText',
|
||||
'items': ['characters.bunny']
|
||||
@ -401,10 +401,10 @@ def get_clean_price(price_string: str) -> str:
|
||||
def get_available_purchase_count(tab: str = None) -> int:
|
||||
"""(internal)"""
|
||||
try:
|
||||
if _ba.get_account_state() != 'signed_in':
|
||||
if _ba.get_v1_account_state() != 'signed_in':
|
||||
return 0
|
||||
count = 0
|
||||
our_tickets = _ba.get_account_ticket_count()
|
||||
our_tickets = _ba.get_v1_account_ticket_count()
|
||||
store_data = get_store_layout()
|
||||
if tab is not None:
|
||||
tabs = [(tab, store_data[tab])]
|
||||
@ -425,7 +425,8 @@ def _calc_count_for_tab(tabval: list[dict[str, Any]], our_tickets: int,
|
||||
count: int) -> int:
|
||||
for section in tabval:
|
||||
for item in section['items']:
|
||||
ticket_cost = _ba.get_account_misc_read_val('price.' + item, None)
|
||||
ticket_cost = _ba.get_v1_account_misc_read_val(
|
||||
'price.' + item, None)
|
||||
if ticket_cost is not None:
|
||||
if (our_tickets >= ticket_cost
|
||||
and not _ba.get_purchased(item)):
|
||||
@ -447,7 +448,7 @@ def get_available_sale_time(tab: str) -> Optional[int]:
|
||||
# Calc time for our pro sale (old special case).
|
||||
if tab == 'extras':
|
||||
config = app.config
|
||||
if app.accounts.have_pro():
|
||||
if app.accounts_v1.have_pro():
|
||||
return None
|
||||
|
||||
# If we haven't calced/loaded start times yet.
|
||||
@ -462,7 +463,7 @@ def get_available_sale_time(tab: str) -> Optional[int]:
|
||||
|
||||
# We start the timer once we get the duration from
|
||||
# the server.
|
||||
start_duration = _ba.get_account_misc_read_val(
|
||||
start_duration = _ba.get_v1_account_misc_read_val(
|
||||
'proSaleDurationMinutes', None)
|
||||
if start_duration is not None:
|
||||
app.pro_sale_start_time = int(
|
||||
@ -488,7 +489,7 @@ def get_available_sale_time(tab: str) -> Optional[int]:
|
||||
sale_times.append(val)
|
||||
|
||||
# Now look for sales in this tab.
|
||||
sales_raw = _ba.get_account_misc_read_val('sales', {})
|
||||
sales_raw = _ba.get_v1_account_misc_read_val('sales', {})
|
||||
store_layout = get_store_layout()
|
||||
for section in store_layout[tab]:
|
||||
for item in section['items']:
|
||||
|
||||
93
assets/src/ba_data/python/ba/cloud.py
Normal file
93
assets/src/ba_data/python/ba/cloud.py
Normal file
@ -0,0 +1,93 @@
|
||||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Functionality related to the cloud."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, overload
|
||||
|
||||
import _ba
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Union, Callable, Any
|
||||
|
||||
from efro.message import Message
|
||||
import bacommon.cloud
|
||||
|
||||
# TODO: Should make it possible to define a protocol in bacommon.cloud and
|
||||
# autogenerate this. That would give us type safety between this and
|
||||
# internal protocols.
|
||||
|
||||
|
||||
class CloudSubsystem:
|
||||
"""Used for communicating with the cloud."""
|
||||
|
||||
def is_connected(self) -> bool:
|
||||
"""Return whether a connection to the cloud is present.
|
||||
|
||||
This is a good indicator (though not for certain) that sending
|
||||
messages will succeed.
|
||||
"""
|
||||
return False # Needs to be overridden
|
||||
|
||||
@overload
|
||||
def send_message(
|
||||
self,
|
||||
msg: bacommon.cloud.LoginProxyRequestMessage,
|
||||
on_response: Callable[
|
||||
[Union[bacommon.cloud.LoginProxyRequestResponse,
|
||||
Exception]], None],
|
||||
) -> None:
|
||||
...
|
||||
|
||||
@overload
|
||||
def send_message(
|
||||
self,
|
||||
msg: bacommon.cloud.LoginProxyStateQueryMessage,
|
||||
on_response: Callable[
|
||||
[Union[bacommon.cloud.LoginProxyStateQueryResponse,
|
||||
Exception]], None],
|
||||
) -> None:
|
||||
...
|
||||
|
||||
@overload
|
||||
def send_message(
|
||||
self,
|
||||
msg: bacommon.cloud.LoginProxyCompleteMessage,
|
||||
on_response: Callable[[Union[None, Exception]], None],
|
||||
) -> None:
|
||||
...
|
||||
|
||||
@overload
|
||||
def send_message(
|
||||
self,
|
||||
msg: bacommon.cloud.CredentialsCheckMessage,
|
||||
on_response: Callable[
|
||||
[Union[bacommon.cloud.CredentialsCheckResponse, Exception]], None],
|
||||
) -> None:
|
||||
...
|
||||
|
||||
@overload
|
||||
def send_message(
|
||||
self,
|
||||
msg: bacommon.cloud.AccountSessionReleaseMessage,
|
||||
on_response: Callable[[Union[None, Exception]], None],
|
||||
) -> None:
|
||||
...
|
||||
|
||||
def send_message(
|
||||
self,
|
||||
msg: Message,
|
||||
on_response: Callable[[Any], None],
|
||||
) -> None:
|
||||
"""Asynchronously send a message to the cloud from the game thread.
|
||||
|
||||
The provided on_response call will be run in the logic thread
|
||||
and passed either the response or the error that occurred.
|
||||
"""
|
||||
from ba._general import Call
|
||||
del msg # Unused.
|
||||
|
||||
_ba.pushcall(
|
||||
Call(on_response,
|
||||
RuntimeError('Cloud functionality is not available.')))
|
||||
@ -52,8 +52,9 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]):
|
||||
ba.app.ach.achievements_for_coop_level(self._campaign.name + ':' +
|
||||
settings['level']))
|
||||
|
||||
self._account_type = (_ba.get_account_type() if
|
||||
_ba.get_account_state() == 'signed_in' else None)
|
||||
self._account_type = (_ba.get_v1_account_type()
|
||||
if _ba.get_v1_account_state() == 'signed_in' else
|
||||
None)
|
||||
|
||||
self._game_service_icon_color: Optional[Sequence[float]]
|
||||
self._game_service_achievements_texture: Optional[ba.Texture]
|
||||
@ -631,7 +632,7 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]):
|
||||
if ba.app.server is None:
|
||||
# If we're running in normal non-headless build, show this text
|
||||
# because only host can continue the game.
|
||||
adisp = _ba.get_account_display_string()
|
||||
adisp = _ba.get_v1_account_display_string()
|
||||
txt = Text(ba.Lstr(resource='waitingForHostText',
|
||||
subs=[('${HOST}', adisp)]),
|
||||
maxwidth=300,
|
||||
@ -732,7 +733,7 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]):
|
||||
'scoreVersion': sver,
|
||||
'scores': our_high_scores_all
|
||||
})
|
||||
if _ba.get_account_state() != 'signed_in':
|
||||
if _ba.get_v1_account_state() != 'signed_in':
|
||||
# We expect this only in kiosk mode; complain otherwise.
|
||||
if not (ba.app.demo_mode or ba.app.arcade_mode):
|
||||
print('got not-signed-in at score-submit; unexpected')
|
||||
@ -1260,8 +1261,8 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]):
|
||||
try:
|
||||
tournament_id = self.session.tournament_id
|
||||
if tournament_id is not None:
|
||||
if tournament_id in ba.app.accounts.tournament_info:
|
||||
tourney_info = ba.app.accounts.tournament_info[
|
||||
if tournament_id in ba.app.accounts_v1.tournament_info:
|
||||
tourney_info = ba.app.accounts_v1.tournament_info[
|
||||
tournament_id]
|
||||
# pylint: disable=unbalanced-tuple-unpacking
|
||||
pr1, pv1, pr2, pv2, pr3, pv3 = (
|
||||
|
||||
@ -208,14 +208,14 @@ class SpazFactory:
|
||||
|
||||
# Lets load some basic rules.
|
||||
# (allows them to be tweaked from the master server)
|
||||
self.shield_decay_rate = _ba.get_account_misc_read_val('rsdr', 10.0)
|
||||
self.punch_cooldown = _ba.get_account_misc_read_val('rpc', 400)
|
||||
self.punch_cooldown_gloves = (_ba.get_account_misc_read_val(
|
||||
self.shield_decay_rate = _ba.get_v1_account_misc_read_val('rsdr', 10.0)
|
||||
self.punch_cooldown = _ba.get_v1_account_misc_read_val('rpc', 400)
|
||||
self.punch_cooldown_gloves = (_ba.get_v1_account_misc_read_val(
|
||||
'rpcg', 300))
|
||||
self.punch_power_scale = _ba.get_account_misc_read_val('rpp', 1.2)
|
||||
self.punch_power_scale_gloves = (_ba.get_account_misc_read_val(
|
||||
self.punch_power_scale = _ba.get_v1_account_misc_read_val('rpp', 1.2)
|
||||
self.punch_power_scale_gloves = (_ba.get_v1_account_misc_read_val(
|
||||
'rppg', 1.4))
|
||||
self.max_shield_spillover_damage = (_ba.get_account_misc_read_val(
|
||||
self.max_shield_spillover_damage = (_ba.get_v1_account_misc_read_val(
|
||||
'rsms', 500))
|
||||
|
||||
def get_style(self, character: str) -> str:
|
||||
|
||||
@ -67,7 +67,7 @@ class MainMenuActivity(ba.Activity[ba.Player, ba.Team]):
|
||||
# host is navigating menus while they're just staring at an
|
||||
# empty-ish screen.
|
||||
tval = ba.Lstr(resource='hostIsNavigatingMenusText',
|
||||
subs=[('${HOST}', _ba.get_account_display_string())])
|
||||
subs=[('${HOST}', _ba.get_v1_account_display_string())])
|
||||
self._host_is_navigating_text = ba.NodeActor(
|
||||
ba.newnode('text',
|
||||
attrs={
|
||||
@ -274,7 +274,7 @@ class MainMenuActivity(ba.Activity[ba.Player, ba.Team]):
|
||||
|
||||
# We now want to wait until we're signed in before fetching news.
|
||||
def _try_fetching_news(self) -> None:
|
||||
if _ba.get_account_state() == 'signed_in':
|
||||
if _ba.get_v1_account_state() == 'signed_in':
|
||||
self._fetch_news()
|
||||
self._fetch_timer = None
|
||||
|
||||
@ -282,7 +282,7 @@ class MainMenuActivity(ba.Activity[ba.Player, ba.Team]):
|
||||
ba.app.main_menu_last_news_fetch_time = time.time()
|
||||
|
||||
# UPDATE - We now just pull news from MRVs.
|
||||
news = _ba.get_account_misc_read_val('n', None)
|
||||
news = _ba.get_v1_account_misc_read_val('n', None)
|
||||
if news is not None:
|
||||
self._got_news(news)
|
||||
|
||||
@ -757,7 +757,7 @@ class MainMenuActivity(ba.Activity[ba.Player, ba.Team]):
|
||||
})
|
||||
|
||||
def _get_custom_logo_tex_name(self) -> Optional[str]:
|
||||
if _ba.get_account_misc_read_val('easter', False):
|
||||
if _ba.get_v1_account_misc_read_val('easter', False):
|
||||
return 'logoEaster'
|
||||
return None
|
||||
|
||||
|
||||
@ -15,7 +15,7 @@ def show_sign_in_prompt(account_type: str = None) -> None:
|
||||
if account_type == 'Google Play':
|
||||
ConfirmWindow(
|
||||
ba.Lstr(resource='notSignedInGooglePlayErrorText'),
|
||||
lambda: _ba.sign_in('Google Play'),
|
||||
lambda: _ba.sign_in_v1('Google Play'),
|
||||
ok_text=ba.Lstr(resource='accountSettingsWindow.signInText'),
|
||||
width=460,
|
||||
height=130)
|
||||
|
||||
@ -50,7 +50,7 @@ class AccountLinkWindow(ba.Window):
|
||||
autoselect=True,
|
||||
icon=ba.gettexture('crossOut'),
|
||||
iconscale=1.2)
|
||||
maxlinks = _ba.get_account_misc_read_val('maxLinkAccounts', 5)
|
||||
maxlinks = _ba.get_v1_account_misc_read_val('maxLinkAccounts', 5)
|
||||
ba.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(self._width * 0.5, self._height * 0.56),
|
||||
@ -84,7 +84,7 @@ class AccountLinkWindow(ba.Window):
|
||||
|
||||
def _generate_press(self) -> None:
|
||||
from bastd.ui import account
|
||||
if _ba.get_account_state() != 'signed_in':
|
||||
if _ba.get_v1_account_state() != 'signed_in':
|
||||
account.show_sign_in_prompt()
|
||||
return
|
||||
ba.screenmessage(
|
||||
|
||||
@ -45,10 +45,11 @@ class AccountSettingsWindow(ba.Window):
|
||||
self._r = 'accountSettingsWindow'
|
||||
self._modal = modal
|
||||
self._needs_refresh = False
|
||||
self._signed_in = (_ba.get_account_state() == 'signed_in')
|
||||
self._account_state_num = _ba.get_account_state_num()
|
||||
self._show_linked = (self._signed_in and _ba.get_account_misc_read_val(
|
||||
'allowAccountLinking2', False))
|
||||
self._signed_in = (_ba.get_v1_account_state() == 'signed_in')
|
||||
self._account_state_num = _ba.get_v1_account_state_num()
|
||||
self._show_linked = (self._signed_in
|
||||
and _ba.get_v1_account_misc_read_val(
|
||||
'allowAccountLinking2', False))
|
||||
self._check_sign_in_timer = ba.Timer(1.0,
|
||||
ba.WeakCall(self._update),
|
||||
timetype=ba.TimeType.REAL,
|
||||
@ -57,7 +58,7 @@ class AccountSettingsWindow(ba.Window):
|
||||
# Currently we can only reset achievements on game-center.
|
||||
account_type: Optional[str]
|
||||
if self._signed_in:
|
||||
account_type = _ba.get_account_type()
|
||||
account_type = _ba.get_v1_account_type()
|
||||
else:
|
||||
account_type = None
|
||||
self._can_reset_achievements = (account_type == 'Game Center')
|
||||
@ -91,7 +92,7 @@ class AccountSettingsWindow(ba.Window):
|
||||
self._show_sign_in_buttons.append('Local')
|
||||
|
||||
# Ditto with shiny new V2 ones.
|
||||
if bool(False):
|
||||
if bool(True):
|
||||
self._show_sign_in_buttons.append('V2')
|
||||
|
||||
top_extra = 15 if uiscale is ba.UIScale.SMALL else 0
|
||||
@ -158,10 +159,10 @@ class AccountSettingsWindow(ba.Window):
|
||||
# Hmm should update this to use get_account_state_num.
|
||||
# Theoretically if we switch from one signed-in account to another
|
||||
# in the background this would break.
|
||||
account_state_num = _ba.get_account_state_num()
|
||||
account_state = _ba.get_account_state()
|
||||
account_state_num = _ba.get_v1_account_state_num()
|
||||
account_state = _ba.get_v1_account_state()
|
||||
|
||||
show_linked = (self._signed_in and _ba.get_account_misc_read_val(
|
||||
show_linked = (self._signed_in and _ba.get_v1_account_misc_read_val(
|
||||
'allowAccountLinking2', False))
|
||||
|
||||
if (account_state_num != self._account_state_num
|
||||
@ -190,8 +191,8 @@ class AccountSettingsWindow(ba.Window):
|
||||
# pylint: disable=cyclic-import
|
||||
from bastd.ui import confirm
|
||||
|
||||
account_state = _ba.get_account_state()
|
||||
account_type = (_ba.get_account_type()
|
||||
account_state = _ba.get_v1_account_state()
|
||||
account_type = (_ba.get_v1_account_type()
|
||||
if account_state == 'signed_in' else 'unknown')
|
||||
|
||||
is_google = account_type == 'Google Play'
|
||||
@ -225,7 +226,7 @@ class AccountSettingsWindow(ba.Window):
|
||||
game_service_button_space = 60.0
|
||||
|
||||
show_linked_accounts_text = (self._signed_in
|
||||
and _ba.get_account_misc_read_val(
|
||||
and _ba.get_v1_account_misc_read_val(
|
||||
'allowAccountLinking2', False))
|
||||
linked_accounts_text_space = 60.0
|
||||
|
||||
@ -254,17 +255,22 @@ class AccountSettingsWindow(ba.Window):
|
||||
player_profiles_button_space = 100.0
|
||||
|
||||
show_link_accounts_button = (self._signed_in
|
||||
and _ba.get_account_misc_read_val(
|
||||
and _ba.get_v1_account_misc_read_val(
|
||||
'allowAccountLinking2', False))
|
||||
link_accounts_button_space = 70.0
|
||||
|
||||
show_unlink_accounts_button = show_link_accounts_button
|
||||
unlink_accounts_button_space = 90.0
|
||||
|
||||
show_sign_out_button = (self._signed_in
|
||||
and account_type in ['Local', 'Google Play'])
|
||||
show_sign_out_button = (self._signed_in and account_type
|
||||
in ['Local', 'Google Play', 'V2'])
|
||||
sign_out_button_space = 70.0
|
||||
|
||||
show_cancel_v2_sign_in_button = (
|
||||
account_state == 'signing_in'
|
||||
and ba.app.accounts_v2.have_primary_credentials())
|
||||
cancel_v2_sign_in_button_space = 70.0
|
||||
|
||||
if self._subcontainer is not None:
|
||||
self._subcontainer.delete()
|
||||
self._sub_height = 60.0
|
||||
@ -308,6 +314,8 @@ class AccountSettingsWindow(ba.Window):
|
||||
self._sub_height += unlink_accounts_button_space
|
||||
if show_sign_out_button:
|
||||
self._sub_height += sign_out_button_space
|
||||
if show_cancel_v2_sign_in_button:
|
||||
self._sub_height += cancel_v2_sign_in_button_space
|
||||
self._subcontainer = ba.containerwidget(parent=self._scrollwidget,
|
||||
size=(self._sub_width,
|
||||
self._sub_height),
|
||||
@ -327,7 +335,7 @@ class AccountSettingsWindow(ba.Window):
|
||||
size=(0, 0),
|
||||
text=ba.Lstr(
|
||||
resource='accountSettingsWindow.deviceSpecificAccountText',
|
||||
subs=[('${NAME}', _ba.get_account_display_string())]),
|
||||
subs=[('${NAME}', _ba.get_v1_account_display_string())]),
|
||||
scale=0.7,
|
||||
color=(0.5, 0.5, 0.6),
|
||||
maxwidth=self._sub_width * 0.9,
|
||||
@ -581,7 +589,7 @@ class AccountSettingsWindow(ba.Window):
|
||||
if show_game_service_button:
|
||||
button_width = 300
|
||||
v -= game_service_button_space * 0.85
|
||||
account_type = _ba.get_account_type()
|
||||
account_type = _ba.get_v1_account_type()
|
||||
if account_type == 'Game Center':
|
||||
account_type_name = ba.Lstr(resource='gameCenterText')
|
||||
elif account_type == 'Game Circle':
|
||||
@ -849,6 +857,24 @@ class AccountSettingsWindow(ba.Window):
|
||||
right_widget=_ba.get_special_widget('party_button'))
|
||||
ba.widget(edit=btn, left_widget=bbtn, show_buffer_bottom=15)
|
||||
|
||||
if show_cancel_v2_sign_in_button:
|
||||
v -= cancel_v2_sign_in_button_space
|
||||
self._cancel_v2_sign_in_button = btn = ba.buttonwidget(
|
||||
parent=self._subcontainer,
|
||||
position=((self._sub_width - button_width) * 0.5, v),
|
||||
size=(button_width, 60),
|
||||
label=ba.Lstr(resource='cancelText'),
|
||||
color=(0.55, 0.5, 0.6),
|
||||
textcolor=(0.75, 0.7, 0.8),
|
||||
autoselect=True,
|
||||
on_activate_call=self._cancel_v2_sign_in_press)
|
||||
if first_selectable is None:
|
||||
first_selectable = btn
|
||||
if ba.app.ui.use_toolbars:
|
||||
ba.widget(edit=btn,
|
||||
right_widget=_ba.get_special_widget('party_button'))
|
||||
ba.widget(edit=btn, left_widget=bbtn, show_buffer_bottom=15)
|
||||
|
||||
# Whatever the topmost selectable thing is, we want it to scroll all
|
||||
# the way up when we select it.
|
||||
if first_selectable is not None:
|
||||
@ -863,8 +889,8 @@ class AccountSettingsWindow(ba.Window):
|
||||
def _on_achievements_press(self) -> None:
|
||||
# pylint: disable=cyclic-import
|
||||
from bastd.ui import achievements
|
||||
account_state = _ba.get_account_state()
|
||||
account_type = (_ba.get_account_type()
|
||||
account_state = _ba.get_v1_account_state()
|
||||
account_type = (_ba.get_v1_account_type()
|
||||
if account_state == 'signed_in' else 'unknown')
|
||||
# for google play we use the built-in UI; otherwise pop up our own
|
||||
if account_type == 'Google Play':
|
||||
@ -889,7 +915,7 @@ class AccountSettingsWindow(ba.Window):
|
||||
# let's not proceed..
|
||||
if _ba.get_public_login_id() is None:
|
||||
return False
|
||||
accounts = _ba.get_account_misc_read_val_2('linkedAccounts', [])
|
||||
accounts = _ba.get_v1_account_misc_read_val_2('linkedAccounts', [])
|
||||
return len(accounts) > 1
|
||||
|
||||
def _update_unlink_accounts_button(self) -> None:
|
||||
@ -911,8 +937,8 @@ class AccountSettingsWindow(ba.Window):
|
||||
num = int(time.time()) % 4
|
||||
accounts_str = num * '.' + (4 - num) * ' '
|
||||
else:
|
||||
accounts = _ba.get_account_misc_read_val_2('linkedAccounts', [])
|
||||
# our_account = _bs.get_account_display_string()
|
||||
accounts = _ba.get_v1_account_misc_read_val_2('linkedAccounts', [])
|
||||
# our_account = _bs.get_v1_account_display_string()
|
||||
# accounts = [a for a in accounts if a != our_account]
|
||||
# accounts_str = u', '.join(accounts) if accounts else
|
||||
# ba.Lstr(translate=('settingNames', 'None'))
|
||||
@ -951,7 +977,7 @@ class AccountSettingsWindow(ba.Window):
|
||||
if self._tickets_text is None:
|
||||
return
|
||||
try:
|
||||
tc_str = str(_ba.get_account_ticket_count())
|
||||
tc_str = str(_ba.get_v1_account_ticket_count())
|
||||
except Exception:
|
||||
ba.print_exception()
|
||||
tc_str = '-'
|
||||
@ -963,7 +989,7 @@ class AccountSettingsWindow(ba.Window):
|
||||
if self._account_name_text is None:
|
||||
return
|
||||
try:
|
||||
name_str = _ba.get_account_display_string()
|
||||
name_str = _ba.get_v1_account_display_string()
|
||||
except Exception:
|
||||
ba.print_exception()
|
||||
name_str = '??'
|
||||
@ -1005,22 +1031,37 @@ class AccountSettingsWindow(ba.Window):
|
||||
pbrowser.ProfileBrowserWindow(
|
||||
origin_widget=self._player_profiles_button)
|
||||
|
||||
def _cancel_v2_sign_in_press(self) -> None:
|
||||
# Just say we don't wanna be signed in anymore.
|
||||
ba.app.accounts_v2.set_primary_credentials(None)
|
||||
|
||||
# Speed UI updates along.
|
||||
ba.timer(0.1, ba.WeakCall(self._update), timetype=ba.TimeType.REAL)
|
||||
|
||||
def _sign_out_press(self) -> None:
|
||||
_ba.sign_out()
|
||||
|
||||
if ba.app.accounts_v2.have_primary_credentials():
|
||||
ba.app.accounts_v2.set_primary_credentials(None)
|
||||
else:
|
||||
_ba.sign_out_v1()
|
||||
|
||||
cfg = ba.app.config
|
||||
|
||||
# Take note that its our *explicit* intention to not be signed in at
|
||||
# this point.
|
||||
# Also take note that its our *explicit* intention to not be
|
||||
# signed in at this point (affects v1 accounts).
|
||||
cfg['Auto Account State'] = 'signed_out'
|
||||
cfg.commit()
|
||||
ba.buttonwidget(edit=self._sign_out_button,
|
||||
label=ba.Lstr(resource=self._r + '.signingOutText'))
|
||||
|
||||
# Speed UI updates along.
|
||||
ba.timer(0.1, ba.WeakCall(self._update), timetype=ba.TimeType.REAL)
|
||||
|
||||
def _sign_in_press(self,
|
||||
account_type: str,
|
||||
show_test_warning: bool = True) -> None:
|
||||
del show_test_warning # unused
|
||||
_ba.sign_in(account_type)
|
||||
_ba.sign_in_v1(account_type)
|
||||
|
||||
# Make note of the type account we're *wanting* to be signed in with.
|
||||
cfg = ba.app.config
|
||||
|
||||
@ -81,7 +81,7 @@ class AccountUnlinkWindow(ba.Window):
|
||||
if our_login_id is None:
|
||||
entries = []
|
||||
else:
|
||||
account_infos = _ba.get_account_misc_read_val_2(
|
||||
account_infos = _ba.get_v1_account_misc_read_val_2(
|
||||
'linkedAccounts2', [])
|
||||
entries = [{
|
||||
'name': ai['d'],
|
||||
|
||||
@ -4,25 +4,30 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import ba
|
||||
import _ba
|
||||
|
||||
from efro.error import CommunicationError
|
||||
import bacommon.cloud
|
||||
|
||||
if TYPE_CHECKING:
|
||||
pass
|
||||
from typing import Union, Optional
|
||||
|
||||
STATUS_CHECK_INTERVAL_SECONDS = 2.0
|
||||
|
||||
|
||||
class V2SignInWindow(ba.Window):
|
||||
"""A window allowing signing in to a v2 account."""
|
||||
|
||||
def __init__(self, origin_widget: ba.Widget):
|
||||
from ba.internal import is_browser_likely_available
|
||||
logincode = '1412345'
|
||||
address = (
|
||||
f'{_ba.get_master_server_address(version=2)}?login={logincode}')
|
||||
self._width = 600
|
||||
self._height = 500
|
||||
self._proxyid: Optional[str] = None
|
||||
self._proxykey: Optional[str] = None
|
||||
|
||||
uiscale = ba.app.ui.uiscale
|
||||
super().__init__(root_widget=ba.containerwidget(
|
||||
size=(self._width, self._height),
|
||||
@ -31,6 +36,53 @@ class V2SignInWindow(ba.Window):
|
||||
scale=(1.25 if uiscale is ba.UIScale.SMALL else
|
||||
1.0 if uiscale is ba.UIScale.MEDIUM else 0.85)))
|
||||
|
||||
self._loading_text = ba.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(self._width * 0.5, self._height * 0.5),
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
size=(0, 0),
|
||||
maxwidth=0.9 * self._width,
|
||||
text=ba.Lstr(value='${A}...',
|
||||
subs=[('${A}', ba.Lstr(resource='loadingText'))]),
|
||||
)
|
||||
|
||||
self._cancel_button = ba.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
position=(30, self._height - 55),
|
||||
size=(130, 50),
|
||||
scale=0.8,
|
||||
label=ba.Lstr(resource='cancelText'),
|
||||
on_activate_call=self._done,
|
||||
autoselect=True,
|
||||
textcolor=(0.75, 0.7, 0.8),
|
||||
)
|
||||
ba.containerwidget(edit=self._root_widget,
|
||||
cancel_button=self._cancel_button)
|
||||
|
||||
self._update_timer: Optional[ba.Timer] = None
|
||||
|
||||
# Ask the cloud for a proxy login id.
|
||||
ba.app.cloud.send_message(bacommon.cloud.LoginProxyRequestMessage(),
|
||||
on_response=ba.WeakCall(
|
||||
self._on_proxy_request_response))
|
||||
|
||||
def _on_proxy_request_response(
|
||||
self, response: Union[bacommon.cloud.LoginProxyRequestResponse,
|
||||
Exception]
|
||||
) -> None:
|
||||
from ba.internal import is_browser_likely_available
|
||||
|
||||
# Something went wrong. Show an error message and that's it.
|
||||
if isinstance(response, Exception):
|
||||
ba.textwidget(
|
||||
edit=self._loading_text,
|
||||
text=ba.Lstr(resource='internal.unavailableNoConnectionText'),
|
||||
color=(1, 0, 0))
|
||||
return
|
||||
|
||||
# Show link(s) the user can use to log in.
|
||||
address = _ba.get_master_server_address(version=2) + response.url
|
||||
ba.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(self._width * 0.5, self._height - 85),
|
||||
@ -65,22 +117,6 @@ class V2SignInWindow(ba.Window):
|
||||
v_align='center')
|
||||
qroffs = 20.0
|
||||
|
||||
self._cancel_button = ba.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
position=(30, self._height - 55),
|
||||
size=(130, 50),
|
||||
scale=0.8,
|
||||
label=ba.Lstr(resource='cancelText'),
|
||||
# color=(0.6, 0.5, 0.6),
|
||||
on_activate_call=self._done,
|
||||
autoselect=True,
|
||||
textcolor=(0.75, 0.7, 0.8),
|
||||
# icon=ba.gettexture('crossOut'),
|
||||
# iconscale=1.2
|
||||
)
|
||||
ba.containerwidget(edit=self._root_widget,
|
||||
cancel_button=self._cancel_button)
|
||||
|
||||
qr_size = 270
|
||||
ba.imagewidget(parent=self._root_widget,
|
||||
position=(self._width * 0.5 - qr_size * 0.5,
|
||||
@ -88,5 +124,68 @@ class V2SignInWindow(ba.Window):
|
||||
size=(qr_size, qr_size),
|
||||
texture=_ba.get_qrcode_texture(address))
|
||||
|
||||
# Start querying for results.
|
||||
self._proxyid = response.proxyid
|
||||
self._proxykey = response.proxykey
|
||||
ba.timer(STATUS_CHECK_INTERVAL_SECONDS,
|
||||
ba.WeakCall(self._ask_for_status))
|
||||
|
||||
def _ask_for_status(self) -> None:
|
||||
assert self._proxyid is not None
|
||||
assert self._proxykey is not None
|
||||
ba.app.cloud.send_message(bacommon.cloud.LoginProxyStateQueryMessage(
|
||||
proxyid=self._proxyid, proxykey=self._proxykey),
|
||||
on_response=ba.WeakCall(self._got_status))
|
||||
|
||||
def _got_status(
|
||||
self, response: Union[bacommon.cloud.LoginProxyStateQueryResponse,
|
||||
Exception]
|
||||
) -> None:
|
||||
|
||||
# For now, if anything goes wrong on the server-side, just abort
|
||||
# with a vague error message. Can be more verbose later if need be.
|
||||
if (isinstance(response, bacommon.cloud.LoginProxyStateQueryResponse)
|
||||
and response.state is response.State.FAIL):
|
||||
ba.playsound(ba.getsound('error'))
|
||||
ba.screenmessage(ba.Lstr(resource='errorText'), color=(1, 0, 0))
|
||||
self._done()
|
||||
return
|
||||
|
||||
# If we got a token, set ourself as signed in. Hooray!
|
||||
if (isinstance(response, bacommon.cloud.LoginProxyStateQueryResponse)
|
||||
and response.state is response.State.SUCCESS):
|
||||
assert response.credentials is not None
|
||||
ba.app.accounts_v2.set_primary_credentials(response.credentials)
|
||||
|
||||
# As a courtesy, tell the server we're done with this proxy
|
||||
# so it can clean up (not a huge deal if this fails)
|
||||
assert self._proxyid is not None
|
||||
try:
|
||||
ba.app.cloud.send_message(
|
||||
bacommon.cloud.LoginProxyCompleteMessage(
|
||||
proxyid=self._proxyid),
|
||||
on_response=ba.WeakCall(self._proxy_complete_response))
|
||||
except CommunicationError:
|
||||
pass
|
||||
except Exception:
|
||||
logging.warning(
|
||||
'Unexpected error sending login-proxy-complete message',
|
||||
exc_info=True)
|
||||
|
||||
self._done()
|
||||
return
|
||||
|
||||
# If we're still waiting, ask again soon.
|
||||
if (isinstance(response, Exception)
|
||||
or response.state is response.State.WAITING):
|
||||
ba.timer(STATUS_CHECK_INTERVAL_SECONDS,
|
||||
ba.WeakCall(self._ask_for_status))
|
||||
|
||||
def _proxy_complete_response(self, response: Union[None,
|
||||
Exception]) -> None:
|
||||
del response # Not used.
|
||||
# We could do something smart like retry on exceptions here, but
|
||||
# this isn't critical so we'll just let anything slide.
|
||||
|
||||
def _done(self) -> None:
|
||||
ba.containerwidget(edit=self._root_widget, transition='out_scale')
|
||||
|
||||
@ -91,7 +91,7 @@ class AccountViewerWindow(popup.PopupWindow):
|
||||
|
||||
# In cases where the user most likely has a browser/email, lets
|
||||
# offer a 'report this user' button.
|
||||
if (is_browser_likely_available() and _ba.get_account_misc_read_val(
|
||||
if (is_browser_likely_available() and _ba.get_v1_account_misc_read_val(
|
||||
'showAccountExtrasMenu', False)):
|
||||
|
||||
self._extras_menu_button = ba.buttonwidget(
|
||||
|
||||
@ -60,15 +60,14 @@ class AppInviteWindow(ba.Window):
|
||||
resource='gatherWindow.earnTicketsForRecommendingAmountText',
|
||||
fallback_resource=(
|
||||
'gatherWindow.earnTicketsForRecommendingText'),
|
||||
subs=[
|
||||
('${COUNT}',
|
||||
str(_ba.get_account_misc_read_val('friendTryTickets',
|
||||
300))),
|
||||
('${YOU_COUNT}',
|
||||
str(
|
||||
_ba.get_account_misc_read_val('friendTryAwardTickets',
|
||||
100)))
|
||||
]))
|
||||
subs=[('${COUNT}',
|
||||
str(
|
||||
_ba.get_v1_account_misc_read_val(
|
||||
'friendTryTickets', 300))),
|
||||
('${YOU_COUNT}',
|
||||
str(
|
||||
_ba.get_v1_account_misc_read_val(
|
||||
'friendTryAwardTickets', 100)))]))
|
||||
|
||||
or_text = ba.Lstr(resource='orText',
|
||||
subs=[('${A}', ''),
|
||||
@ -129,17 +128,18 @@ class AppInviteWindow(ba.Window):
|
||||
ba.playsound(ba.getsound('error'))
|
||||
return
|
||||
|
||||
if _ba.get_account_state() == 'signed_in':
|
||||
if _ba.get_v1_account_state() == 'signed_in':
|
||||
ba.set_analytics_screen('App Invite UI')
|
||||
_ba.show_app_invite(
|
||||
ba.Lstr(resource='gatherWindow.appInviteTitleText',
|
||||
subs=[('${APP_NAME}', ba.Lstr(resource='titleText'))
|
||||
]).evaluate(),
|
||||
ba.Lstr(resource='gatherWindow.appInviteMessageText',
|
||||
subs=[('${COUNT}', str(self._data['tickets'])),
|
||||
('${NAME}', _ba.get_account_name().split()[0]),
|
||||
('${APP_NAME}', ba.Lstr(resource='titleText'))
|
||||
]).evaluate(), self._data['code'])
|
||||
subs=[
|
||||
('${COUNT}', str(self._data['tickets'])),
|
||||
('${NAME}', _ba.get_v1_account_name().split()[0]),
|
||||
('${APP_NAME}', ba.Lstr(resource='titleText'))
|
||||
]).evaluate(), self._data['code'])
|
||||
else:
|
||||
ba.playsound(ba.getsound('error'))
|
||||
|
||||
@ -256,7 +256,7 @@ class ShowFriendCodeWindow(ba.Window):
|
||||
]).evaluate(),
|
||||
ba.Lstr(resource='gatherWindow.appInviteMessageText',
|
||||
subs=[('${COUNT}', str(self._data['tickets'])),
|
||||
('${NAME}', _ba.get_account_name().split()[0]),
|
||||
('${NAME}', _ba.get_v1_account_name().split()[0]),
|
||||
('${APP_NAME}', ba.Lstr(resource='titleText'))
|
||||
]).evaluate(), self._data['code'])
|
||||
|
||||
@ -264,7 +264,7 @@ class ShowFriendCodeWindow(ba.Window):
|
||||
import urllib.parse
|
||||
|
||||
# If somehow we got signed out.
|
||||
if _ba.get_account_state() != 'signed_in':
|
||||
if _ba.get_v1_account_state() != 'signed_in':
|
||||
ba.screenmessage(ba.Lstr(resource='notSignedInText'),
|
||||
color=(1, 0, 0))
|
||||
ba.playsound(ba.getsound('error'))
|
||||
@ -273,7 +273,7 @@ class ShowFriendCodeWindow(ba.Window):
|
||||
ba.set_analytics_screen('Email Friend Code')
|
||||
subject = (ba.Lstr(resource='gatherWindow.friendHasSentPromoCodeText').
|
||||
evaluate().replace(
|
||||
'${NAME}', _ba.get_account_name()).replace(
|
||||
'${NAME}', _ba.get_v1_account_name()).replace(
|
||||
'${APP_NAME}',
|
||||
ba.Lstr(resource='titleText').evaluate()).replace(
|
||||
'${COUNT}', str(self._data['tickets'])))
|
||||
@ -304,7 +304,7 @@ def handle_app_invites_press(force_code: bool = False) -> None:
|
||||
"""(internal)"""
|
||||
app = ba.app
|
||||
do_app_invites = (app.platform == 'android' and app.subplatform == 'google'
|
||||
and _ba.get_account_misc_read_val(
|
||||
and _ba.get_v1_account_misc_read_val(
|
||||
'enableAppInvites', False) and not app.on_tv)
|
||||
if force_code:
|
||||
do_app_invites = False
|
||||
|
||||
@ -156,7 +156,7 @@ class CharacterPicker(popup.PopupWindow):
|
||||
def _on_store_press(self) -> None:
|
||||
from bastd.ui.account import show_sign_in_prompt
|
||||
from bastd.ui.store.browser import StoreBrowserWindow
|
||||
if _ba.get_account_state() != 'signed_in':
|
||||
if _ba.get_v1_account_state() != 'signed_in':
|
||||
show_sign_in_prompt()
|
||||
return
|
||||
self._transition_out()
|
||||
|
||||
@ -94,7 +94,7 @@ class ColorPicker(PopupWindow):
|
||||
on_activate_call=ba.WeakCall(self._select_other))
|
||||
|
||||
# Custom colors are limited to pro currently.
|
||||
if not ba.app.accounts.have_pro():
|
||||
if not ba.app.accounts_v1.have_pro():
|
||||
ba.imagewidget(parent=self.root_widget,
|
||||
position=(50, 12),
|
||||
size=(30, 30),
|
||||
@ -118,7 +118,7 @@ class ColorPicker(PopupWindow):
|
||||
from bastd.ui import purchase
|
||||
|
||||
# Requires pro.
|
||||
if not ba.app.accounts.have_pro():
|
||||
if not ba.app.accounts_v1.have_pro():
|
||||
purchase.PurchaseWindow(items=['pro'])
|
||||
self._transition_out()
|
||||
return
|
||||
|
||||
@ -142,9 +142,9 @@ class ContinuesWindow(ba.Window):
|
||||
self._on_cancel()
|
||||
return
|
||||
|
||||
if _ba.get_account_state() == 'signed_in':
|
||||
if _ba.get_v1_account_state() == 'signed_in':
|
||||
sval = (ba.charstr(ba.SpecialChar.TICKET) +
|
||||
str(_ba.get_account_ticket_count()))
|
||||
str(_ba.get_v1_account_ticket_count()))
|
||||
else:
|
||||
sval = '?'
|
||||
if self._tickets_text is not None:
|
||||
@ -176,14 +176,14 @@ class ContinuesWindow(ba.Window):
|
||||
ba.playsound(ba.getsound('error'))
|
||||
else:
|
||||
# If somehow we got signed out...
|
||||
if _ba.get_account_state() != 'signed_in':
|
||||
if _ba.get_v1_account_state() != 'signed_in':
|
||||
ba.screenmessage(ba.Lstr(resource='notSignedInText'),
|
||||
color=(1, 0, 0))
|
||||
ba.playsound(ba.getsound('error'))
|
||||
return
|
||||
|
||||
# If it appears we don't have enough tickets, offer to buy more.
|
||||
tickets = _ba.get_account_ticket_count()
|
||||
tickets = _ba.get_v1_account_ticket_count()
|
||||
if tickets < self._cost:
|
||||
# FIXME: Should we start the timer back up again after?
|
||||
self._counting_down = False
|
||||
|
||||
@ -92,7 +92,7 @@ class CoopBrowserWindow(ba.Window):
|
||||
|
||||
self._tourney_data_up_to_date = False
|
||||
|
||||
self._campaign_difficulty = _ba.get_account_misc_val(
|
||||
self._campaign_difficulty = _ba.get_v1_account_misc_val(
|
||||
'campaignDifficulty', 'easy')
|
||||
|
||||
super().__init__(root_widget=ba.containerwidget(
|
||||
@ -235,7 +235,7 @@ class CoopBrowserWindow(ba.Window):
|
||||
self._subcontainer: Optional[ba.Widget] = None
|
||||
|
||||
# Take note of our account state; we'll refresh later if this changes.
|
||||
self._account_state_num = _ba.get_account_state_num()
|
||||
self._account_state_num = _ba.get_v1_account_state_num()
|
||||
|
||||
# Same for fg/bg state.
|
||||
self._fg_state = app.fg_state
|
||||
@ -251,14 +251,14 @@ class CoopBrowserWindow(ba.Window):
|
||||
# If we've got a cached tournament list for our account and info for
|
||||
# each one of those tournaments, go ahead and display it as a
|
||||
# starting point.
|
||||
if (app.accounts.account_tournament_list is not None
|
||||
and app.accounts.account_tournament_list[0]
|
||||
== _ba.get_account_state_num()
|
||||
and all(t_id in app.accounts.tournament_info
|
||||
for t_id in app.accounts.account_tournament_list[1])):
|
||||
if (app.accounts_v1.account_tournament_list is not None
|
||||
and app.accounts_v1.account_tournament_list[0]
|
||||
== _ba.get_v1_account_state_num() and all(
|
||||
t_id in app.accounts_v1.tournament_info
|
||||
for t_id in app.accounts_v1.account_tournament_list[1])):
|
||||
tourney_data = [
|
||||
app.accounts.tournament_info[t_id]
|
||||
for t_id in app.accounts.account_tournament_list[1]
|
||||
app.accounts_v1.tournament_info[t_id]
|
||||
for t_id in app.accounts_v1.account_tournament_list[1]
|
||||
]
|
||||
self._update_for_data(tourney_data)
|
||||
|
||||
@ -300,7 +300,7 @@ class CoopBrowserWindow(ba.Window):
|
||||
self._tourney_data_up_to_date = False
|
||||
|
||||
# If our account state has changed, do a full request.
|
||||
account_state_num = _ba.get_account_state_num()
|
||||
account_state_num = _ba.get_v1_account_state_num()
|
||||
if account_state_num != self._account_state_num:
|
||||
self._account_state_num = account_state_num
|
||||
self._save_state()
|
||||
@ -358,7 +358,7 @@ class CoopBrowserWindow(ba.Window):
|
||||
try:
|
||||
ba.imagewidget(
|
||||
edit=self._hard_button_lock_image,
|
||||
opacity=0.0 if ba.app.accounts.have_pro_options() else 1.0)
|
||||
opacity=0.0 if ba.app.accounts_v1.have_pro_options() else 1.0)
|
||||
except Exception:
|
||||
ba.print_exception('Error updating campaign lock.')
|
||||
|
||||
@ -480,7 +480,7 @@ class CoopBrowserWindow(ba.Window):
|
||||
tbtn['required_league'] = (None if 'requiredLeague' not in entry
|
||||
else entry['requiredLeague'])
|
||||
|
||||
game = ba.app.accounts.tournament_info[
|
||||
game = ba.app.accounts_v1.tournament_info[
|
||||
tbtn['tournament_id']]['game']
|
||||
|
||||
if game is None:
|
||||
@ -491,7 +491,7 @@ class CoopBrowserWindow(ba.Window):
|
||||
else:
|
||||
campaignname, levelname = game.split(':')
|
||||
campaign = getcampaign(campaignname)
|
||||
max_players = ba.app.accounts.tournament_info[
|
||||
max_players = ba.app.accounts_v1.tournament_info[
|
||||
tbtn['tournament_id']]['maxPlayers']
|
||||
txt = ba.Lstr(
|
||||
value='${A} ${B}',
|
||||
@ -525,7 +525,7 @@ class CoopBrowserWindow(ba.Window):
|
||||
tbtn['allow_ads'] = allow_ads = entry['allowAds']
|
||||
|
||||
final_fee: Optional[int] = (None if fee_var is None else
|
||||
_ba.get_account_misc_read_val(
|
||||
_ba.get_v1_account_misc_read_val(
|
||||
fee_var, '?'))
|
||||
|
||||
final_fee_str: Union[str, ba.Lstr]
|
||||
@ -540,9 +540,9 @@ class CoopBrowserWindow(ba.Window):
|
||||
ba.charstr(ba.SpecialChar.TICKET_BACKING) +
|
||||
str(final_fee))
|
||||
|
||||
ad_tries_remaining = ba.app.accounts.tournament_info[
|
||||
ad_tries_remaining = ba.app.accounts_v1.tournament_info[
|
||||
tbtn['tournament_id']]['adTriesRemaining']
|
||||
free_tries_remaining = ba.app.accounts.tournament_info[
|
||||
free_tries_remaining = ba.app.accounts_v1.tournament_info[
|
||||
tbtn['tournament_id']]['freeTriesRemaining']
|
||||
|
||||
# Now, if this fee allows ads and we support video ads, show
|
||||
@ -592,7 +592,7 @@ class CoopBrowserWindow(ba.Window):
|
||||
|
||||
def _on_tournament_query_response(self, data: Optional[dict[str,
|
||||
Any]]) -> None:
|
||||
accounts = ba.app.accounts
|
||||
accounts = ba.app.accounts_v1
|
||||
if data is not None:
|
||||
tournament_data = data['t'] # This used to be the whole payload.
|
||||
self._last_tournament_query_response_time = ba.time(
|
||||
@ -606,9 +606,11 @@ class CoopBrowserWindow(ba.Window):
|
||||
accounts.cache_tournament_info(tournament_data)
|
||||
|
||||
# Also cache the current tourney list/order for this account.
|
||||
accounts.account_tournament_list = (_ba.get_account_state_num(), [
|
||||
e['tournamentID'] for e in tournament_data
|
||||
])
|
||||
accounts.account_tournament_list = (_ba.get_v1_account_state_num(),
|
||||
[
|
||||
e['tournamentID']
|
||||
for e in tournament_data
|
||||
])
|
||||
|
||||
self._doing_tournament_query = False
|
||||
self._update_for_data(tournament_data)
|
||||
@ -617,7 +619,8 @@ class CoopBrowserWindow(ba.Window):
|
||||
# pylint: disable=cyclic-import
|
||||
from bastd.ui.purchase import PurchaseWindow
|
||||
if difficulty != self._campaign_difficulty:
|
||||
if difficulty == 'hard' and not ba.app.accounts.have_pro_options():
|
||||
if (difficulty == 'hard'
|
||||
and not ba.app.accounts_v1.have_pro_options()):
|
||||
PurchaseWindow(items=['pro'])
|
||||
return
|
||||
ba.playsound(ba.getsound('gunCocking'))
|
||||
@ -872,7 +875,7 @@ class CoopBrowserWindow(ba.Window):
|
||||
# no tournaments).
|
||||
if self._tournament_button_count == 0:
|
||||
unavailable_text = ba.Lstr(resource='unavailableText')
|
||||
if _ba.get_account_state() != 'signed_in':
|
||||
if _ba.get_v1_account_state() != 'signed_in':
|
||||
unavailable_text = ba.Lstr(
|
||||
value='${A} (${B})',
|
||||
subs=[('${A}', unavailable_text),
|
||||
@ -943,7 +946,7 @@ class CoopBrowserWindow(ba.Window):
|
||||
]
|
||||
|
||||
# Show easter-egg-hunt either if its easter or we own it.
|
||||
if _ba.get_account_misc_read_val(
|
||||
if _ba.get_v1_account_misc_read_val(
|
||||
'easter', False) or _ba.get_purchased('games.easter_egg_hunt'):
|
||||
items = [
|
||||
'Challenges:Easter Egg Hunt', 'Challenges:Pro Easter Egg Hunt'
|
||||
@ -1346,7 +1349,7 @@ class CoopBrowserWindow(ba.Window):
|
||||
# pylint: disable=cyclic-import
|
||||
from bastd.ui.account import show_sign_in_prompt
|
||||
from bastd.ui.league.rankwindow import LeagueRankWindow
|
||||
if _ba.get_account_state() != 'signed_in':
|
||||
if _ba.get_v1_account_state() != 'signed_in':
|
||||
show_sign_in_prompt()
|
||||
return
|
||||
self._save_state()
|
||||
@ -1363,7 +1366,7 @@ class CoopBrowserWindow(ba.Window):
|
||||
) -> None:
|
||||
# pylint: disable=cyclic-import
|
||||
from bastd.ui.account import show_sign_in_prompt
|
||||
if _ba.get_account_state() != 'signed_in':
|
||||
if _ba.get_v1_account_state() != 'signed_in':
|
||||
show_sign_in_prompt()
|
||||
return
|
||||
self._save_state()
|
||||
@ -1427,7 +1430,7 @@ class CoopBrowserWindow(ba.Window):
|
||||
# Do a bit of pre-flight for tournament options.
|
||||
if tournament_button is not None:
|
||||
|
||||
if _ba.get_account_state() != 'signed_in':
|
||||
if _ba.get_v1_account_state() != 'signed_in':
|
||||
show_sign_in_prompt()
|
||||
return
|
||||
|
||||
@ -1465,7 +1468,7 @@ class CoopBrowserWindow(ba.Window):
|
||||
return
|
||||
|
||||
# Game is whatever the tournament tells us it is.
|
||||
game = ba.app.accounts.tournament_info[
|
||||
game = ba.app.accounts_v1.tournament_info[
|
||||
tournament_button['tournament_id']]['game']
|
||||
|
||||
if tournament_button is None and game == 'Easy:The Last Stand':
|
||||
@ -1481,8 +1484,8 @@ class CoopBrowserWindow(ba.Window):
|
||||
if tournament_button is None and game in (
|
||||
'Challenges:Infinite Runaround',
|
||||
'Challenges:Infinite Onslaught'
|
||||
) and not ba.app.accounts.have_pro():
|
||||
if _ba.get_account_state() != 'signed_in':
|
||||
) and not ba.app.accounts_v1.have_pro():
|
||||
if _ba.get_v1_account_state() != 'signed_in':
|
||||
show_sign_in_prompt()
|
||||
else:
|
||||
PurchaseWindow(items=['pro'])
|
||||
@ -1508,7 +1511,7 @@ class CoopBrowserWindow(ba.Window):
|
||||
|
||||
if (tournament_button is None and required_purchase is not None
|
||||
and not _ba.get_purchased(required_purchase)):
|
||||
if _ba.get_account_state() != 'signed_in':
|
||||
if _ba.get_v1_account_state() != 'signed_in':
|
||||
show_sign_in_prompt()
|
||||
else:
|
||||
PurchaseWindow(items=[required_purchase])
|
||||
|
||||
@ -199,7 +199,7 @@ class GameButton:
|
||||
# Hard-code games we haven't unlocked.
|
||||
if ((game in ('Challenges:Infinite Runaround',
|
||||
'Challenges:Infinite Onslaught')
|
||||
and not ba.app.accounts.have_pro())
|
||||
and not ba.app.accounts_v1.have_pro())
|
||||
or (game in ('Challenges:Meteor Shower', )
|
||||
and not _ba.get_purchased('games.meteor_shower'))
|
||||
or (game in ('Challenges:Target Practice',
|
||||
|
||||
@ -151,7 +151,7 @@ class GatherWindow(ba.Window):
|
||||
tabdefs: list[tuple[GatherWindow.TabID, ba.Lstr]] = [
|
||||
(self.TabID.ABOUT, ba.Lstr(resource=self._r + '.aboutText'))
|
||||
]
|
||||
if _ba.get_account_misc_read_val('enablePublicParties', True):
|
||||
if _ba.get_v1_account_misc_read_val('enablePublicParties', True):
|
||||
tabdefs.append((self.TabID.INTERNET,
|
||||
ba.Lstr(resource=self._r + '.publicText')))
|
||||
tabdefs.append(
|
||||
|
||||
@ -52,7 +52,8 @@ class AboutGatherTab(GatherTab):
|
||||
include_invite = True
|
||||
msc_scale = 1.1
|
||||
c_height_2 = min(region_height, string_height * msc_scale + 100)
|
||||
try_tickets = _ba.get_account_misc_read_val('friendTryTickets', None)
|
||||
try_tickets = _ba.get_v1_account_misc_read_val('friendTryTickets',
|
||||
None)
|
||||
if try_tickets is None:
|
||||
include_invite = False
|
||||
self._container = ba.containerwidget(
|
||||
@ -106,7 +107,7 @@ class AboutGatherTab(GatherTab):
|
||||
def _invite_to_try_press(self) -> None:
|
||||
from bastd.ui.account import show_sign_in_prompt
|
||||
from bastd.ui.appinvite import handle_app_invites_press
|
||||
if _ba.get_account_state() != 'signed_in':
|
||||
if _ba.get_v1_account_state() != 'signed_in':
|
||||
show_sign_in_prompt()
|
||||
return
|
||||
handle_app_invites_press()
|
||||
|
||||
@ -225,7 +225,7 @@ class PrivateGatherTab(GatherTab):
|
||||
def _update_currency_ui(self) -> None:
|
||||
# Keep currency count up to date if applicable.
|
||||
try:
|
||||
t_str = str(_ba.get_account_ticket_count())
|
||||
t_str = str(_ba.get_v1_account_ticket_count())
|
||||
except Exception:
|
||||
t_str = '?'
|
||||
if self._get_tickets_button:
|
||||
@ -245,7 +245,7 @@ class PrivateGatherTab(GatherTab):
|
||||
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'
|
||||
if (_ba.get_v1_account_state() != 'signed_in'
|
||||
and self._showing_not_signed_in_screen):
|
||||
self._refresh_sub_tab()
|
||||
else:
|
||||
@ -254,7 +254,7 @@ class PrivateGatherTab(GatherTab):
|
||||
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':
|
||||
if _ba.get_v1_account_state() == 'signed_in':
|
||||
_ba.add_transaction(
|
||||
{
|
||||
'type': 'PRIVATE_PARTY_QUERY',
|
||||
@ -437,7 +437,7 @@ class PrivateGatherTab(GatherTab):
|
||||
# pylint: disable=too-many-branches
|
||||
# pylint: disable=too-many-statements
|
||||
|
||||
if _ba.get_account_state() != 'signed_in':
|
||||
if _ba.get_v1_account_state() != 'signed_in':
|
||||
ba.textwidget(parent=self._container,
|
||||
size=(0, 0),
|
||||
h_align='center',
|
||||
@ -776,7 +776,7 @@ class PrivateGatherTab(GatherTab):
|
||||
or self._waiting_for_initial_state):
|
||||
return
|
||||
|
||||
if _ba.get_account_state() != 'signed_in':
|
||||
if _ba.get_v1_account_state() != 'signed_in':
|
||||
ba.screenmessage(ba.Lstr(resource='notSignedInErrorText'))
|
||||
ba.playsound(ba.getsound('error'))
|
||||
self._refresh_sub_tab()
|
||||
@ -795,7 +795,7 @@ class PrivateGatherTab(GatherTab):
|
||||
if self._hostingstate.tickets_to_host_now > 0:
|
||||
ticket_count: Optional[int]
|
||||
try:
|
||||
ticket_count = _ba.get_account_ticket_count()
|
||||
ticket_count = _ba.get_v1_account_ticket_count()
|
||||
except Exception:
|
||||
# FIXME: should add a ba.NotSignedInError we can use here.
|
||||
ticket_count = None
|
||||
|
||||
@ -88,8 +88,8 @@ class UIRow:
|
||||
if party.clean_display_index == index:
|
||||
return
|
||||
|
||||
ping_good = _ba.get_account_misc_read_val('pingGood', 100)
|
||||
ping_med = _ba.get_account_misc_read_val('pingMed', 500)
|
||||
ping_good = _ba.get_v1_account_misc_read_val('pingGood', 100)
|
||||
ping_med = _ba.get_v1_account_misc_read_val('pingMed', 500)
|
||||
|
||||
self._clear()
|
||||
hpos = 20
|
||||
@ -122,8 +122,8 @@ class UIRow:
|
||||
if party.stats_addr:
|
||||
url = party.stats_addr.replace(
|
||||
'${ACCOUNT}',
|
||||
_ba.get_account_misc_read_val_2('resolvedAccountID',
|
||||
'UNKNOWN'))
|
||||
_ba.get_v1_account_misc_read_val_2('resolvedAccountID',
|
||||
'UNKNOWN'))
|
||||
self._stats_button = ba.buttonwidget(
|
||||
color=(0.3, 0.6, 0.94),
|
||||
textcolor=(1.0, 1.0, 1.0),
|
||||
@ -793,7 +793,7 @@ class PublicGatherTab(GatherTab):
|
||||
self._process_pending_party_infos()
|
||||
|
||||
# Anytime we sign in/out, make sure we refresh our list.
|
||||
signed_in = _ba.get_account_state() == 'signed_in'
|
||||
signed_in = _ba.get_v1_account_state() == 'signed_in'
|
||||
if self._signed_in != signed_in:
|
||||
self._signed_in = signed_in
|
||||
self._party_lists_dirty = True
|
||||
@ -986,7 +986,7 @@ class PublicGatherTab(GatherTab):
|
||||
p[1].index))
|
||||
|
||||
# If signed out or errored, show no parties.
|
||||
if (_ba.get_account_state() != 'signed_in'
|
||||
if (_ba.get_v1_account_state() != 'signed_in'
|
||||
or not self._have_valid_server_list):
|
||||
self._parties_displayed = {}
|
||||
else:
|
||||
@ -1023,11 +1023,11 @@ class PublicGatherTab(GatherTab):
|
||||
# Fire off a new public-party query periodically.
|
||||
if (self._last_server_list_query_time is None
|
||||
or now - self._last_server_list_query_time > 0.001 *
|
||||
_ba.get_account_misc_read_val('pubPartyRefreshMS', 10000)):
|
||||
_ba.get_v1_account_misc_read_val('pubPartyRefreshMS', 10000)):
|
||||
self._last_server_list_query_time = now
|
||||
if DEBUG_SERVER_COMMUNICATION:
|
||||
print('REQUESTING SERVER LIST')
|
||||
if _ba.get_account_state() == 'signed_in':
|
||||
if _ba.get_v1_account_state() == 'signed_in':
|
||||
_ba.add_transaction(
|
||||
{
|
||||
'type': 'PUBLIC_PARTY_QUERY',
|
||||
@ -1156,7 +1156,7 @@ class PublicGatherTab(GatherTab):
|
||||
|
||||
def _on_start_advertizing_press(self) -> None:
|
||||
from bastd.ui.account import show_sign_in_prompt
|
||||
if _ba.get_account_state() != 'signed_in':
|
||||
if _ba.get_v1_account_state() != 'signed_in':
|
||||
show_sign_in_prompt()
|
||||
return
|
||||
|
||||
|
||||
@ -179,22 +179,27 @@ class GetCurrencyWindow(ba.Window):
|
||||
c2txt = ba.Lstr(
|
||||
resource=rsrc,
|
||||
subs=[('${COUNT}',
|
||||
str(_ba.get_account_misc_read_val('tickets2Amount', 500)))])
|
||||
str(_ba.get_v1_account_misc_read_val('tickets2Amount',
|
||||
500)))])
|
||||
c3txt = ba.Lstr(
|
||||
resource=rsrc,
|
||||
subs=[('${COUNT}',
|
||||
str(_ba.get_account_misc_read_val('tickets3Amount',
|
||||
1500)))])
|
||||
subs=[
|
||||
('${COUNT}',
|
||||
str(_ba.get_v1_account_misc_read_val('tickets3Amount', 1500)))
|
||||
])
|
||||
c4txt = ba.Lstr(
|
||||
resource=rsrc,
|
||||
subs=[('${COUNT}',
|
||||
str(_ba.get_account_misc_read_val('tickets4Amount',
|
||||
5000)))])
|
||||
subs=[
|
||||
('${COUNT}',
|
||||
str(_ba.get_v1_account_misc_read_val('tickets4Amount', 5000)))
|
||||
])
|
||||
c5txt = ba.Lstr(
|
||||
resource=rsrc,
|
||||
subs=[('${COUNT}',
|
||||
str(_ba.get_account_misc_read_val('tickets5Amount',
|
||||
15000)))])
|
||||
subs=[
|
||||
('${COUNT}',
|
||||
str(_ba.get_v1_account_misc_read_val('tickets5Amount',
|
||||
15000)))
|
||||
])
|
||||
|
||||
h = 110.0
|
||||
|
||||
@ -261,7 +266,7 @@ class GetCurrencyWindow(ba.Window):
|
||||
label=ba.Lstr(resource=self._r + '.ticketsFromASponsorText',
|
||||
subs=[('${COUNT}',
|
||||
str(
|
||||
_ba.get_account_misc_read_val(
|
||||
_ba.get_v1_account_misc_read_val(
|
||||
'sponsorTickets', 5)))]),
|
||||
tex_name='ticketsMore',
|
||||
enabled=self._enable_ad_button,
|
||||
@ -301,11 +306,10 @@ class GetCurrencyWindow(ba.Window):
|
||||
size=b_size_3,
|
||||
label=ba.Lstr(
|
||||
resource='gatherWindow.earnTicketsForRecommendingText',
|
||||
subs=[
|
||||
('${COUNT}',
|
||||
str(_ba.get_account_misc_read_val(
|
||||
'sponsorTickets', 5)))
|
||||
]),
|
||||
subs=[('${COUNT}',
|
||||
str(
|
||||
_ba.get_v1_account_misc_read_val(
|
||||
'sponsorTickets', 5)))]),
|
||||
tex_name='ticketsMore',
|
||||
enabled=True,
|
||||
tex_opacity=0.6,
|
||||
@ -427,16 +431,16 @@ class GetCurrencyWindow(ba.Window):
|
||||
import datetime
|
||||
|
||||
# if we somehow get signed out, just die..
|
||||
if _ba.get_account_state() != 'signed_in':
|
||||
if _ba.get_v1_account_state() != 'signed_in':
|
||||
self._back()
|
||||
return
|
||||
|
||||
self._ticket_count = _ba.get_account_ticket_count()
|
||||
self._ticket_count = _ba.get_v1_account_ticket_count()
|
||||
|
||||
# update our incentivized ad button depending on whether ads are
|
||||
# available
|
||||
if self._ad_button is not None:
|
||||
next_reward_ad_time = _ba.get_account_misc_read_val_2(
|
||||
next_reward_ad_time = _ba.get_v1_account_misc_read_val_2(
|
||||
'nextRewardAdTime', None)
|
||||
if next_reward_ad_time is not None:
|
||||
next_reward_ad_time = datetime.datetime.utcfromtimestamp(
|
||||
@ -494,8 +498,9 @@ class GetCurrencyWindow(ba.Window):
|
||||
app = ba.app
|
||||
if ((app.test_build or
|
||||
(app.platform == 'android'
|
||||
and app.subplatform in ['oculus', 'cardboard'])) and
|
||||
_ba.get_account_misc_read_val('allowAccountLinking2', False)):
|
||||
and app.subplatform in ['oculus', 'cardboard']))
|
||||
and _ba.get_v1_account_misc_read_val('allowAccountLinking2',
|
||||
False)):
|
||||
ba.screenmessage(ba.Lstr(resource=self._r +
|
||||
'.unavailableLinkAccountText'),
|
||||
color=(1, 0.5, 0))
|
||||
@ -509,7 +514,7 @@ class GetCurrencyWindow(ba.Window):
|
||||
from bastd.ui import appinvite
|
||||
from ba.internal import master_server_get
|
||||
if item == 'app_invite':
|
||||
if _ba.get_account_state() != 'signed_in':
|
||||
if _ba.get_v1_account_state() != 'signed_in':
|
||||
account.show_sign_in_prompt()
|
||||
return
|
||||
appinvite.handle_app_invites_press()
|
||||
@ -554,7 +559,7 @@ class GetCurrencyWindow(ba.Window):
|
||||
if item == 'ad':
|
||||
import datetime
|
||||
# if ads are disabled until some time, error..
|
||||
next_reward_ad_time = _ba.get_account_misc_read_val_2(
|
||||
next_reward_ad_time = _ba.get_v1_account_misc_read_val_2(
|
||||
'nextRewardAdTime', None)
|
||||
if next_reward_ad_time is not None:
|
||||
next_reward_ad_time = datetime.datetime.utcfromtimestamp(
|
||||
|
||||
@ -40,7 +40,7 @@ class IconPicker(popup.PopupWindow):
|
||||
self._transitioning_out = False
|
||||
|
||||
self._icons = [ba.charstr(ba.SpecialChar.LOGO)
|
||||
] + ba.app.accounts.get_purchased_icons()
|
||||
] + ba.app.accounts_v1.get_purchased_icons()
|
||||
count = len(self._icons)
|
||||
columns = 4
|
||||
rows = int(math.ceil(float(count) / columns))
|
||||
@ -137,7 +137,7 @@ class IconPicker(popup.PopupWindow):
|
||||
def _on_store_press(self) -> None:
|
||||
from bastd.ui.account import show_sign_in_prompt
|
||||
from bastd.ui.store.browser import StoreBrowserWindow
|
||||
if _ba.get_account_state() != 'signed_in':
|
||||
if _ba.get_v1_account_state() != 'signed_in':
|
||||
show_sign_in_prompt()
|
||||
return
|
||||
self._transition_out()
|
||||
|
||||
@ -360,7 +360,7 @@ class KioskWindow(ba.Window):
|
||||
def _update(self) -> None:
|
||||
# Kiosk-mode is designed to be used signed-out... try for force
|
||||
# the issue.
|
||||
if _ba.get_account_state() == 'signed_in':
|
||||
if _ba.get_v1_account_state() == 'signed_in':
|
||||
# _bs.sign_out()
|
||||
# FIXME: Try to delete player profiles here too.
|
||||
pass
|
||||
|
||||
@ -94,7 +94,7 @@ class LeagueRankButton:
|
||||
self._smooth_update_timer: Optional[ba.Timer] = None
|
||||
|
||||
# Take note of our account state; we'll refresh later if this changes.
|
||||
self._account_state_num = _ba.get_account_state_num()
|
||||
self._account_state_num = _ba.get_v1_account_state_num()
|
||||
self._last_power_ranking_query_time: Optional[float] = None
|
||||
self._doing_power_ranking_query = False
|
||||
self.set_position(position)
|
||||
@ -106,7 +106,7 @@ class LeagueRankButton:
|
||||
self._update()
|
||||
|
||||
# If we've got cached power-ranking data already, apply it.
|
||||
data = ba.app.accounts.get_cached_league_rank_data()
|
||||
data = ba.app.accounts_v1.get_cached_league_rank_data()
|
||||
if data is not None:
|
||||
self._update_for_league_rank_data(data)
|
||||
|
||||
@ -224,7 +224,7 @@ class LeagueRankButton:
|
||||
|
||||
in_top = data is not None and data['rank'] is not None
|
||||
do_percent = False
|
||||
if data is None or _ba.get_account_state() != 'signed_in':
|
||||
if data is None or _ba.get_v1_account_state() != 'signed_in':
|
||||
self._percent = self._rank = None
|
||||
status_text = '-'
|
||||
elif in_top:
|
||||
@ -248,7 +248,8 @@ class LeagueRankButton:
|
||||
self._percent = self._rank = None
|
||||
status_text = '-'
|
||||
else:
|
||||
our_points = ba.app.accounts.get_league_rank_points(data)
|
||||
our_points = ba.app.accounts_v1.get_league_rank_points(
|
||||
data)
|
||||
progress = float(our_points) / data['scores'][-1][1]
|
||||
self._percent = int(progress * 100.0)
|
||||
self._rank = None
|
||||
@ -327,14 +328,14 @@ class LeagueRankButton:
|
||||
def _on_power_ranking_query_response(
|
||||
self, data: Optional[dict[str, Any]]) -> None:
|
||||
self._doing_power_ranking_query = False
|
||||
ba.app.accounts.cache_league_rank_data(data)
|
||||
ba.app.accounts_v1.cache_league_rank_data(data)
|
||||
self._update_for_league_rank_data(data)
|
||||
|
||||
def _update(self) -> None:
|
||||
cur_time = ba.time(ba.TimeType.REAL)
|
||||
|
||||
# If our account state has changed, refresh our UI.
|
||||
account_state_num = _ba.get_account_state_num()
|
||||
account_state_num = _ba.get_v1_account_state_num()
|
||||
if account_state_num != self._account_state_num:
|
||||
self._account_state_num = account_state_num
|
||||
|
||||
|
||||
@ -118,13 +118,13 @@ class LeagueRankWindow(ba.Window):
|
||||
self._season: Optional[str] = None
|
||||
|
||||
# take note of our account state; we'll refresh later if this changes
|
||||
self._account_state = _ba.get_account_state()
|
||||
self._account_state = _ba.get_v1_account_state()
|
||||
|
||||
self._refresh()
|
||||
self._restore_state()
|
||||
|
||||
# if we've got cached power-ranking data already, display it
|
||||
info = ba.app.accounts.get_cached_league_rank_data()
|
||||
info = ba.app.accounts_v1.get_cached_league_rank_data()
|
||||
if info is not None:
|
||||
self._update_for_league_rank_data(info)
|
||||
|
||||
@ -155,7 +155,8 @@ class LeagueRankWindow(ba.Window):
|
||||
resource='coopSelectWindow.activenessAllTimeInfoText'
|
||||
if self._season == 'a' else 'coopSelectWindow.activenessInfoText',
|
||||
subs=[('${MAX}',
|
||||
str(_ba.get_account_misc_read_val('activenessMax', 1.0)))])
|
||||
str(_ba.get_v1_account_misc_read_val('activenessMax',
|
||||
1.0)))])
|
||||
confirm.ConfirmWindow(txt,
|
||||
cancel_button=False,
|
||||
width=460,
|
||||
@ -164,17 +165,15 @@ class LeagueRankWindow(ba.Window):
|
||||
|
||||
def _on_pro_mult_press(self) -> None:
|
||||
from bastd.ui import confirm
|
||||
txt = ba.Lstr(
|
||||
resource='coopSelectWindow.proMultInfoText',
|
||||
subs=[
|
||||
('${PERCENT}',
|
||||
str(_ba.get_account_misc_read_val('proPowerRankingBoost',
|
||||
10))),
|
||||
('${PRO}',
|
||||
ba.Lstr(resource='store.bombSquadProNameText',
|
||||
subs=[('${APP_NAME}', ba.Lstr(resource='titleText'))
|
||||
]))
|
||||
])
|
||||
txt = ba.Lstr(resource='coopSelectWindow.proMultInfoText',
|
||||
subs=[('${PERCENT}',
|
||||
str(
|
||||
_ba.get_v1_account_misc_read_val(
|
||||
'proPowerRankingBoost', 10))),
|
||||
('${PRO}',
|
||||
ba.Lstr(resource='store.bombSquadProNameText',
|
||||
subs=[('${APP_NAME}',
|
||||
ba.Lstr(resource='titleText'))]))])
|
||||
confirm.ConfirmWindow(txt,
|
||||
cancel_button=False,
|
||||
width=460,
|
||||
@ -196,7 +195,7 @@ class LeagueRankWindow(ba.Window):
|
||||
self._doing_power_ranking_query = False
|
||||
# important: *only* cache this if we requested the current season..
|
||||
if data is not None and data.get('s', None) is None:
|
||||
ba.app.accounts.cache_league_rank_data(data)
|
||||
ba.app.accounts_v1.cache_league_rank_data(data)
|
||||
# always store a copy locally though (even for other seasons)
|
||||
self._league_rank_data = copy.deepcopy(data)
|
||||
self._update_for_league_rank_data(data)
|
||||
@ -209,7 +208,7 @@ class LeagueRankWindow(ba.Window):
|
||||
cur_time = ba.time(ba.TimeType.REAL)
|
||||
|
||||
# if our account state has changed, refresh our UI
|
||||
account_state = _ba.get_account_state()
|
||||
account_state = _ba.get_v1_account_state()
|
||||
if account_state != self._account_state:
|
||||
self._account_state = account_state
|
||||
self._save_state()
|
||||
@ -353,7 +352,7 @@ class LeagueRankWindow(ba.Window):
|
||||
maxwidth=200)
|
||||
|
||||
self._activity_mult_button: Optional[ba.Widget]
|
||||
if _ba.get_account_misc_read_val('act', False):
|
||||
if _ba.get_v1_account_misc_read_val('act', False):
|
||||
self._activity_mult_button = ba.buttonwidget(
|
||||
parent=w_parent,
|
||||
position=(h2 - 60, v2 + 10),
|
||||
@ -594,7 +593,7 @@ class LeagueRankWindow(ba.Window):
|
||||
# pylint: disable=too-many-locals
|
||||
if not self._root_widget:
|
||||
return
|
||||
accounts = ba.app.accounts
|
||||
accounts = ba.app.accounts_v1
|
||||
in_top = (data is not None and data['rank'] is not None)
|
||||
eq_text = self._rdict.powerRankingPointsEqualsText
|
||||
pts_txt = self._rdict.powerRankingPointsText
|
||||
@ -603,7 +602,7 @@ class LeagueRankWindow(ba.Window):
|
||||
finished_season_unranked = False
|
||||
self._can_do_more_button = True
|
||||
extra_text = ''
|
||||
if _ba.get_account_state() != 'signed_in':
|
||||
if _ba.get_v1_account_state() != 'signed_in':
|
||||
status_text = '(' + ba.Lstr(
|
||||
resource='notSignedInText').evaluate() + ')'
|
||||
elif in_top:
|
||||
@ -790,7 +789,8 @@ class LeagueRankWindow(ba.Window):
|
||||
|
||||
have_pro = False if data is None else data['p']
|
||||
pro_mult = 1.0 + float(
|
||||
_ba.get_account_misc_read_val('proPowerRankingBoost', 0.0)) * 0.01
|
||||
_ba.get_v1_account_misc_read_val('proPowerRankingBoost',
|
||||
0.0)) * 0.01
|
||||
# pylint: disable=consider-using-f-string
|
||||
ba.textwidget(edit=self._pro_mult_text,
|
||||
text=' -' if
|
||||
|
||||
@ -67,9 +67,9 @@ class MainMenuWindow(ba.Window):
|
||||
self._restore_state()
|
||||
|
||||
# Keep an eye on a few things and refresh if they change.
|
||||
self._account_state = _ba.get_account_state()
|
||||
self._account_state_num = _ba.get_account_state_num()
|
||||
self._account_type = (_ba.get_account_type()
|
||||
self._account_state = _ba.get_v1_account_state()
|
||||
self._account_state_num = _ba.get_v1_account_state_num()
|
||||
self._account_type = (_ba.get_v1_account_type()
|
||||
if self._account_state == 'signed_in' else None)
|
||||
self._refresh_timer = ba.Timer(1.0,
|
||||
ba.WeakCall(self._check_refresh),
|
||||
@ -122,9 +122,9 @@ class MainMenuWindow(ba.Window):
|
||||
ba.print_exception('Error showing get-remote-app info')
|
||||
|
||||
def _get_store_char_tex(self) -> str:
|
||||
return ('storeCharacterXmas' if _ba.get_account_misc_read_val(
|
||||
return ('storeCharacterXmas' if _ba.get_v1_account_misc_read_val(
|
||||
'xmas', False) else
|
||||
'storeCharacterEaster' if _ba.get_account_misc_read_val(
|
||||
'storeCharacterEaster' if _ba.get_v1_account_misc_read_val(
|
||||
'easter', False) else 'storeCharacter')
|
||||
|
||||
def _check_refresh(self) -> None:
|
||||
@ -138,13 +138,13 @@ class MainMenuWindow(ba.Window):
|
||||
return
|
||||
|
||||
store_char_tex = self._get_store_char_tex()
|
||||
account_state_num = _ba.get_account_state_num()
|
||||
account_state_num = _ba.get_v1_account_state_num()
|
||||
if (account_state_num != self._account_state_num
|
||||
or store_char_tex != self._store_char_tex):
|
||||
self._store_char_tex = store_char_tex
|
||||
self._account_state_num = account_state_num
|
||||
account_state = self._account_state = (_ba.get_account_state())
|
||||
self._account_type = (_ba.get_account_type()
|
||||
account_state = self._account_state = (_ba.get_v1_account_state())
|
||||
self._account_type = (_ba.get_v1_account_type()
|
||||
if account_state == 'signed_in' else None)
|
||||
self._save_state()
|
||||
self._refresh()
|
||||
@ -213,8 +213,8 @@ class MainMenuWindow(ba.Window):
|
||||
on_activate_call=self._settings)
|
||||
|
||||
# Scattered eggs on easter.
|
||||
if _ba.get_account_misc_read_val('easter',
|
||||
False) and not self._in_game:
|
||||
if _ba.get_v1_account_misc_read_val('easter',
|
||||
False) and not self._in_game:
|
||||
icon_size = 34
|
||||
ba.imagewidget(parent=self._root_widget,
|
||||
position=(h - icon_size * 0.5 - 15,
|
||||
@ -310,7 +310,7 @@ class MainMenuWindow(ba.Window):
|
||||
transition_delay=self._tdelay)
|
||||
|
||||
# Scattered eggs on easter.
|
||||
if _ba.get_account_misc_read_val('easter', False):
|
||||
if _ba.get_v1_account_misc_read_val('easter', False):
|
||||
icon_size = 30
|
||||
ba.imagewidget(parent=self._root_widget,
|
||||
position=(h - icon_size * 0.5 + 25,
|
||||
@ -427,8 +427,8 @@ class MainMenuWindow(ba.Window):
|
||||
self._height = 200.0
|
||||
enable_account_button = True
|
||||
account_type_name: Union[str, ba.Lstr]
|
||||
if _ba.get_account_state() == 'signed_in':
|
||||
account_type_name = _ba.get_account_display_string()
|
||||
if _ba.get_v1_account_state() == 'signed_in':
|
||||
account_type_name = _ba.get_v1_account_display_string()
|
||||
account_type_icon = None
|
||||
account_textcolor = (1.0, 1.0, 1.0)
|
||||
else:
|
||||
@ -618,8 +618,8 @@ class MainMenuWindow(ba.Window):
|
||||
enable_sound=account_type_enable_button_sound)
|
||||
|
||||
# Scattered eggs on easter.
|
||||
if _ba.get_account_misc_read_val('easter',
|
||||
False) and not self._in_game:
|
||||
if _ba.get_v1_account_misc_read_val('easter',
|
||||
False) and not self._in_game:
|
||||
icon_size = 32
|
||||
ba.imagewidget(parent=self._root_widget,
|
||||
position=(h - icon_size * 0.5 + 35,
|
||||
@ -648,8 +648,8 @@ class MainMenuWindow(ba.Window):
|
||||
self._how_to_play_button = btn
|
||||
|
||||
# Scattered eggs on easter.
|
||||
if _ba.get_account_misc_read_val('easter',
|
||||
False) and not self._in_game:
|
||||
if _ba.get_v1_account_misc_read_val('easter',
|
||||
False) and not self._in_game:
|
||||
icon_size = 28
|
||||
ba.imagewidget(parent=self._root_widget,
|
||||
position=(h - icon_size * 0.5 + 30,
|
||||
@ -851,7 +851,7 @@ class MainMenuWindow(ba.Window):
|
||||
# pylint: disable=cyclic-import
|
||||
from bastd.ui.store.browser import StoreBrowserWindow
|
||||
from bastd.ui.account import show_sign_in_prompt
|
||||
if _ba.get_account_state() != 'signed_in':
|
||||
if _ba.get_v1_account_state() != 'signed_in':
|
||||
show_sign_in_prompt()
|
||||
return
|
||||
self._save_state()
|
||||
|
||||
@ -320,8 +320,8 @@ class PartyQueueWindow(ba.Window):
|
||||
if -1 not in self._dudes_by_id:
|
||||
dude = self.Dude(
|
||||
self, response['d'], self._initial_offset, True,
|
||||
_ba.get_account_misc_read_val_2('resolvedAccountID', None),
|
||||
_ba.get_account_display_string())
|
||||
_ba.get_v1_account_misc_read_val_2('resolvedAccountID', None),
|
||||
_ba.get_v1_account_display_string())
|
||||
self._dudes_by_id[-1] = dude
|
||||
self._dudes.append(dude)
|
||||
else:
|
||||
@ -457,11 +457,11 @@ class PartyQueueWindow(ba.Window):
|
||||
"""Boost was pressed."""
|
||||
from bastd.ui import account
|
||||
from bastd.ui import getcurrency
|
||||
if _ba.get_account_state() != 'signed_in':
|
||||
if _ba.get_v1_account_state() != 'signed_in':
|
||||
account.show_sign_in_prompt()
|
||||
return
|
||||
|
||||
if _ba.get_account_ticket_count() < self._boost_tickets:
|
||||
if _ba.get_v1_account_ticket_count() < self._boost_tickets:
|
||||
ba.playsound(ba.getsound('error'))
|
||||
getcurrency.show_get_tickets_prompt()
|
||||
return
|
||||
@ -498,17 +498,17 @@ class PartyQueueWindow(ba.Window):
|
||||
# Update boost button color based on if we have enough moola.
|
||||
if self._boost_button is not None:
|
||||
can_boost = (
|
||||
(_ba.get_account_state() == 'signed_in'
|
||||
and _ba.get_account_ticket_count() >= self._boost_tickets))
|
||||
(_ba.get_v1_account_state() == 'signed_in'
|
||||
and _ba.get_v1_account_ticket_count() >= self._boost_tickets))
|
||||
ba.buttonwidget(edit=self._boost_button,
|
||||
color=(0, 1, 0) if can_boost else (0.7, 0.7, 0.7))
|
||||
|
||||
# Update ticket-count.
|
||||
if self._tickets_text is not None:
|
||||
if self._boost_button is not None:
|
||||
if _ba.get_account_state() == 'signed_in':
|
||||
if _ba.get_v1_account_state() == 'signed_in':
|
||||
val = ba.charstr(ba.SpecialChar.TICKET) + str(
|
||||
_ba.get_account_ticket_count())
|
||||
_ba.get_v1_account_ticket_count())
|
||||
else:
|
||||
val = ba.charstr(ba.SpecialChar.TICKET) + '???'
|
||||
ba.textwidget(edit=self._tickets_text, text=val)
|
||||
@ -518,7 +518,7 @@ class PartyQueueWindow(ba.Window):
|
||||
current_time = ba.time(ba.TimeType.REAL)
|
||||
if (self._last_transaction_time is None
|
||||
or current_time - self._last_transaction_time >
|
||||
0.001 * _ba.get_account_misc_read_val('pqInt', 5000)):
|
||||
0.001 * _ba.get_v1_account_misc_read_val('pqInt', 5000)):
|
||||
self._last_transaction_time = current_time
|
||||
_ba.add_transaction(
|
||||
{
|
||||
|
||||
@ -447,7 +447,7 @@ class PlayWindow(ba.Window):
|
||||
# pylint: disable=cyclic-import
|
||||
from bastd.ui.account import show_sign_in_prompt
|
||||
from bastd.ui.coop.browser import CoopBrowserWindow
|
||||
if _ba.get_account_state() != 'signed_in':
|
||||
if _ba.get_v1_account_state() != 'signed_in':
|
||||
show_sign_in_prompt()
|
||||
return
|
||||
self._save_state()
|
||||
|
||||
@ -176,7 +176,7 @@ class PlaylistAddGameWindow(ba.Window):
|
||||
def _on_get_more_games_press(self) -> None:
|
||||
from bastd.ui.account import show_sign_in_prompt
|
||||
from bastd.ui.store.browser import StoreBrowserWindow
|
||||
if _ba.get_account_state() != 'signed_in':
|
||||
if _ba.get_v1_account_state() != 'signed_in':
|
||||
show_sign_in_prompt()
|
||||
return
|
||||
StoreBrowserWindow(modal=True,
|
||||
|
||||
@ -140,7 +140,7 @@ class PlaylistBrowserWindow(ba.Window):
|
||||
def _ensure_standard_playlists_exist(self) -> None:
|
||||
# On new installations, go ahead and create a few playlists
|
||||
# besides the hard-coded default one:
|
||||
if not _ba.get_account_misc_val('madeStandardPlaylists', False):
|
||||
if not _ba.get_v1_account_misc_val('madeStandardPlaylists', False):
|
||||
_ba.add_transaction({
|
||||
'type':
|
||||
'ADD_PLAYLIST',
|
||||
|
||||
@ -253,7 +253,7 @@ class PlaylistCustomizeBrowserWindow(ba.Window):
|
||||
self._update()
|
||||
|
||||
def _update(self) -> None:
|
||||
have = ba.app.accounts.have_pro_options()
|
||||
have = ba.app.accounts_v1.have_pro_options()
|
||||
for lock in self._lock_images:
|
||||
ba.imagewidget(edit=lock, opacity=0.0 if have else 1.0)
|
||||
|
||||
@ -383,7 +383,7 @@ class PlaylistCustomizeBrowserWindow(ba.Window):
|
||||
# pylint: disable=cyclic-import
|
||||
from bastd.ui.playlist.editcontroller import PlaylistEditController
|
||||
from bastd.ui.purchase import PurchaseWindow
|
||||
if not ba.app.accounts.have_pro_options():
|
||||
if not ba.app.accounts_v1.have_pro_options():
|
||||
PurchaseWindow(items=['pro'])
|
||||
return
|
||||
|
||||
@ -407,7 +407,7 @@ class PlaylistCustomizeBrowserWindow(ba.Window):
|
||||
# pylint: disable=cyclic-import
|
||||
from bastd.ui.playlist.editcontroller import PlaylistEditController
|
||||
from bastd.ui.purchase import PurchaseWindow
|
||||
if not ba.app.accounts.have_pro_options():
|
||||
if not ba.app.accounts_v1.have_pro_options():
|
||||
PurchaseWindow(items=['pro'])
|
||||
return
|
||||
if self._selected_playlist_name is None:
|
||||
@ -445,7 +445,7 @@ class PlaylistCustomizeBrowserWindow(ba.Window):
|
||||
from bastd.ui.playlist import share
|
||||
|
||||
# Gotta be signed in for this to work.
|
||||
if _ba.get_account_state() != 'signed_in':
|
||||
if _ba.get_v1_account_state() != 'signed_in':
|
||||
ba.screenmessage(ba.Lstr(resource='notSignedInErrorText'),
|
||||
color=(1, 0, 0))
|
||||
ba.playsound(ba.getsound('error'))
|
||||
@ -472,12 +472,12 @@ class PlaylistCustomizeBrowserWindow(ba.Window):
|
||||
def _share_playlist(self) -> None:
|
||||
# pylint: disable=cyclic-import
|
||||
from bastd.ui.purchase import PurchaseWindow
|
||||
if not ba.app.accounts.have_pro_options():
|
||||
if not ba.app.accounts_v1.have_pro_options():
|
||||
PurchaseWindow(items=['pro'])
|
||||
return
|
||||
|
||||
# Gotta be signed in for this to work.
|
||||
if _ba.get_account_state() != 'signed_in':
|
||||
if _ba.get_v1_account_state() != 'signed_in':
|
||||
ba.screenmessage(ba.Lstr(resource='notSignedInErrorText'),
|
||||
color=(1, 0, 0))
|
||||
ba.playsound(ba.getsound('error'))
|
||||
@ -508,7 +508,7 @@ class PlaylistCustomizeBrowserWindow(ba.Window):
|
||||
# pylint: disable=cyclic-import
|
||||
from bastd.ui.purchase import PurchaseWindow
|
||||
from bastd.ui.confirm import ConfirmWindow
|
||||
if not ba.app.accounts.have_pro_options():
|
||||
if not ba.app.accounts_v1.have_pro_options():
|
||||
PurchaseWindow(items=['pro'])
|
||||
return
|
||||
|
||||
@ -534,7 +534,7 @@ class PlaylistCustomizeBrowserWindow(ba.Window):
|
||||
# pylint: disable=too-many-branches
|
||||
# pylint: disable=cyclic-import
|
||||
from bastd.ui.purchase import PurchaseWindow
|
||||
if not ba.app.accounts.have_pro_options():
|
||||
if not ba.app.accounts_v1.have_pro_options():
|
||||
PurchaseWindow(items=['pro'])
|
||||
return
|
||||
if self._selected_playlist_name is None:
|
||||
|
||||
@ -210,7 +210,7 @@ class PlaylistMapSelectWindow(ba.Window):
|
||||
def _on_store_press(self) -> None:
|
||||
from bastd.ui import account
|
||||
from bastd.ui.store.browser import StoreBrowserWindow
|
||||
if _ba.get_account_state() != 'signed_in':
|
||||
if _ba.get_v1_account_state() != 'signed_in':
|
||||
account.show_sign_in_prompt()
|
||||
return
|
||||
StoreBrowserWindow(modal=True,
|
||||
|
||||
@ -250,7 +250,7 @@ class PlayOptionsWindow(popup.PopupWindow):
|
||||
autoselect=True,
|
||||
textcolor=(0.8, 0.8, 0.8),
|
||||
label=ba.Lstr(resource='teamNamesColorText'))
|
||||
if not ba.app.accounts.have_pro():
|
||||
if not ba.app.accounts_v1.have_pro():
|
||||
ba.imagewidget(
|
||||
parent=self.root_widget,
|
||||
size=(30, 30),
|
||||
@ -348,8 +348,8 @@ class PlayOptionsWindow(popup.PopupWindow):
|
||||
from bastd.ui.account import show_sign_in_prompt
|
||||
from bastd.ui.teamnamescolors import TeamNamesColorsWindow
|
||||
from bastd.ui.purchase import PurchaseWindow
|
||||
if not ba.app.accounts.have_pro():
|
||||
if _ba.get_account_state() != 'signed_in':
|
||||
if not ba.app.accounts_v1.have_pro():
|
||||
if _ba.get_v1_account_state() != 'signed_in':
|
||||
show_sign_in_prompt()
|
||||
else:
|
||||
PurchaseWindow(items=['pro'])
|
||||
|
||||
@ -51,7 +51,7 @@ class ProfileBrowserWindow(ba.Window):
|
||||
self._r = 'playerProfilesWindow'
|
||||
|
||||
# Ensure we've got an account-profile in cases where we're signed in.
|
||||
ba.app.accounts.ensure_have_account_player_profile()
|
||||
ba.app.accounts_v1.ensure_have_account_player_profile()
|
||||
|
||||
top_extra = 20 if uiscale is ba.UIScale.SMALL else 0
|
||||
|
||||
@ -174,9 +174,9 @@ class ProfileBrowserWindow(ba.Window):
|
||||
from bastd.ui.purchase import PurchaseWindow
|
||||
|
||||
# Limit to a handful profiles if they don't have pro-options.
|
||||
max_non_pro_profiles = _ba.get_account_misc_read_val('mnpp', 5)
|
||||
max_non_pro_profiles = _ba.get_v1_account_misc_read_val('mnpp', 5)
|
||||
assert self._profiles is not None
|
||||
if (not ba.app.accounts.have_pro_options()
|
||||
if (not ba.app.accounts_v1.have_pro_options()
|
||||
and len(self._profiles) >= max_non_pro_profiles):
|
||||
PurchaseWindow(items=['pro'],
|
||||
header_text=ba.Lstr(
|
||||
@ -283,8 +283,8 @@ class ProfileBrowserWindow(ba.Window):
|
||||
items.sort(key=lambda x: asserttype(x[0], str).lower())
|
||||
index = 0
|
||||
account_name: Optional[str]
|
||||
if _ba.get_account_state() == 'signed_in':
|
||||
account_name = _ba.get_account_display_string()
|
||||
if _ba.get_v1_account_state() == 'signed_in':
|
||||
account_name = _ba.get_v1_account_display_string()
|
||||
else:
|
||||
account_name = None
|
||||
widget_to_select = None
|
||||
|
||||
@ -173,8 +173,8 @@ class EditProfileWindow(ba.Window):
|
||||
|
||||
self._upgrade_button = None
|
||||
if self._is_account_profile:
|
||||
if _ba.get_account_state() == 'signed_in':
|
||||
sval = _ba.get_account_display_string()
|
||||
if _ba.get_v1_account_state() == 'signed_in':
|
||||
sval = _ba.get_v1_account_display_string()
|
||||
else:
|
||||
sval = '??'
|
||||
ba.textwidget(parent=self._root_widget,
|
||||
@ -427,7 +427,7 @@ class EditProfileWindow(ba.Window):
|
||||
"""Attempt to ugrade the profile to global."""
|
||||
from bastd.ui import account
|
||||
from bastd.ui.profile import upgrade as pupgrade
|
||||
if _ba.get_account_state() != 'signed_in':
|
||||
if _ba.get_v1_account_state() != 'signed_in':
|
||||
account.show_sign_in_prompt()
|
||||
return
|
||||
|
||||
@ -593,8 +593,8 @@ class EditProfileWindow(ba.Window):
|
||||
return
|
||||
name = self.getname()
|
||||
if name == '__account__':
|
||||
name = (_ba.get_account_name()
|
||||
if _ba.get_account_state() == 'signed_in' else '???')
|
||||
name = (_ba.get_v1_account_name()
|
||||
if _ba.get_v1_account_state() == 'signed_in' else '???')
|
||||
if len(name) > 10 and not (self._global or self._is_account_profile):
|
||||
ba.textwidget(edit=self._clipped_name_text,
|
||||
text=ba.Lstr(resource='inGameClippedNameText',
|
||||
|
||||
@ -126,7 +126,8 @@ class ProfileUpgradeWindow(ba.Window):
|
||||
'b': ba.app.build_number
|
||||
},
|
||||
callback=ba.WeakCall(self._profile_check_result))
|
||||
self._cost = _ba.get_account_misc_read_val('price.global_profile', 500)
|
||||
self._cost = _ba.get_v1_account_misc_read_val('price.global_profile',
|
||||
500)
|
||||
self._status: Optional[str] = 'waiting'
|
||||
self._update_timer = ba.Timer(1.0,
|
||||
ba.WeakCall(self._update),
|
||||
@ -169,7 +170,7 @@ class ProfileUpgradeWindow(ba.Window):
|
||||
from bastd.ui import getcurrency
|
||||
if self._status is None:
|
||||
# If it appears we don't have enough tickets, offer to buy more.
|
||||
tickets = _ba.get_account_ticket_count()
|
||||
tickets = _ba.get_v1_account_ticket_count()
|
||||
if tickets < self._cost:
|
||||
ba.playsound(ba.getsound('error'))
|
||||
getcurrency.show_get_tickets_prompt()
|
||||
@ -204,7 +205,7 @@ class ProfileUpgradeWindow(ba.Window):
|
||||
|
||||
def _update(self) -> None:
|
||||
try:
|
||||
t_str = str(_ba.get_account_ticket_count())
|
||||
t_str = str(_ba.get_v1_account_ticket_count())
|
||||
except Exception:
|
||||
t_str = '?'
|
||||
if self._tickets_text is not None:
|
||||
|
||||
@ -72,7 +72,7 @@ class PurchaseWindow(ba.Window):
|
||||
pyoffs = -15
|
||||
else:
|
||||
pyoffs = 0
|
||||
price = self._price = _ba.get_account_misc_read_val(
|
||||
price = self._price = _ba.get_v1_account_misc_read_val(
|
||||
'price.' + str(items[0]), -1)
|
||||
price_str = ba.charstr(ba.SpecialChar.TICKET) + str(price)
|
||||
self._price_text = ba.textwidget(parent=self._root_widget,
|
||||
@ -118,7 +118,7 @@ class PurchaseWindow(ba.Window):
|
||||
|
||||
# We go away if we see that our target item is owned.
|
||||
if self._items == ['pro']:
|
||||
if ba.app.accounts.have_pro():
|
||||
if ba.app.accounts_v1.have_pro():
|
||||
can_die = True
|
||||
else:
|
||||
if _ba.get_purchased(self._items[0]):
|
||||
@ -134,7 +134,7 @@ class PurchaseWindow(ba.Window):
|
||||
else:
|
||||
ticket_count: Optional[int]
|
||||
try:
|
||||
ticket_count = _ba.get_account_ticket_count()
|
||||
ticket_count = _ba.get_v1_account_ticket_count()
|
||||
except Exception:
|
||||
ticket_count = None
|
||||
if ticket_count is not None and ticket_count < self._price:
|
||||
|
||||
@ -339,7 +339,7 @@ class AdvancedSettingsWindow(ba.Window):
|
||||
self._update_lang_status()
|
||||
v -= 40
|
||||
|
||||
lang_inform = _ba.get_account_misc_val('langInform', False)
|
||||
lang_inform = _ba.get_v1_account_misc_val('langInform', False)
|
||||
|
||||
self._language_inform_checkbox = cbw = ba.checkboxwidget(
|
||||
parent=self._subcontainer,
|
||||
@ -550,7 +550,7 @@ class AdvancedSettingsWindow(ba.Window):
|
||||
def _on_friend_promo_code_press(self) -> None:
|
||||
from bastd.ui import appinvite
|
||||
from bastd.ui import account
|
||||
if _ba.get_account_state() != 'signed_in':
|
||||
if _ba.get_v1_account_state() != 'signed_in':
|
||||
account.show_sign_in_prompt()
|
||||
return
|
||||
appinvite.handle_app_invites_press()
|
||||
@ -568,7 +568,7 @@ class AdvancedSettingsWindow(ba.Window):
|
||||
from bastd.ui.account import show_sign_in_prompt
|
||||
|
||||
# We have to be logged in for promo-codes to work.
|
||||
if _ba.get_account_state() != 'signed_in':
|
||||
if _ba.get_v1_account_state() != 'signed_in':
|
||||
show_sign_in_prompt()
|
||||
return
|
||||
self._save_state()
|
||||
|
||||
@ -232,7 +232,7 @@ def _dummy_fail() -> None:
|
||||
|
||||
def _test_v1_transaction() -> None:
|
||||
"""Dummy fail test case."""
|
||||
if _ba.get_account_state() != 'signed_in':
|
||||
if _ba.get_v1_account_state() != 'signed_in':
|
||||
raise RuntimeError('Not signed in.')
|
||||
|
||||
starttime = time.monotonic()
|
||||
|
||||
@ -211,7 +211,7 @@ class SoundtrackBrowserWindow(ba.Window):
|
||||
on_cancel_call=self._back)
|
||||
|
||||
def _update(self) -> None:
|
||||
have = ba.app.accounts.have_pro_options()
|
||||
have = ba.app.accounts_v1.have_pro_options()
|
||||
for lock in self._lock_images:
|
||||
ba.imagewidget(edit=lock, opacity=0.0 if have else 1.0)
|
||||
|
||||
@ -232,7 +232,7 @@ class SoundtrackBrowserWindow(ba.Window):
|
||||
# pylint: disable=cyclic-import
|
||||
from bastd.ui.purchase import PurchaseWindow
|
||||
from bastd.ui.confirm import ConfirmWindow
|
||||
if not ba.app.accounts.have_pro_options():
|
||||
if not ba.app.accounts_v1.have_pro_options():
|
||||
PurchaseWindow(items=['pro'])
|
||||
return
|
||||
if self._selected_soundtrack is None:
|
||||
@ -251,7 +251,7 @@ class SoundtrackBrowserWindow(ba.Window):
|
||||
def _duplicate_soundtrack(self) -> None:
|
||||
# pylint: disable=cyclic-import
|
||||
from bastd.ui.purchase import PurchaseWindow
|
||||
if not ba.app.accounts.have_pro_options():
|
||||
if not ba.app.accounts_v1.have_pro_options():
|
||||
PurchaseWindow(items=['pro'])
|
||||
return
|
||||
cfg = ba.app.config
|
||||
@ -322,7 +322,7 @@ class SoundtrackBrowserWindow(ba.Window):
|
||||
def _edit_soundtrack_with_sound(self) -> None:
|
||||
# pylint: disable=cyclic-import
|
||||
from bastd.ui.purchase import PurchaseWindow
|
||||
if not ba.app.accounts.have_pro_options():
|
||||
if not ba.app.accounts_v1.have_pro_options():
|
||||
PurchaseWindow(items=['pro'])
|
||||
return
|
||||
ba.playsound(ba.getsound('swish'))
|
||||
@ -332,7 +332,7 @@ class SoundtrackBrowserWindow(ba.Window):
|
||||
# pylint: disable=cyclic-import
|
||||
from bastd.ui.purchase import PurchaseWindow
|
||||
from bastd.ui.soundtrack.edit import SoundtrackEditWindow
|
||||
if not ba.app.accounts.have_pro_options():
|
||||
if not ba.app.accounts_v1.have_pro_options():
|
||||
PurchaseWindow(items=['pro'])
|
||||
return
|
||||
if self._selected_soundtrack is None:
|
||||
@ -434,7 +434,7 @@ class SoundtrackBrowserWindow(ba.Window):
|
||||
# pylint: disable=cyclic-import
|
||||
from bastd.ui.purchase import PurchaseWindow
|
||||
from bastd.ui.soundtrack.edit import SoundtrackEditWindow
|
||||
if not ba.app.accounts.have_pro_options():
|
||||
if not ba.app.accounts_v1.have_pro_options():
|
||||
PurchaseWindow(items=['pro'])
|
||||
return
|
||||
self._save_state()
|
||||
|
||||
@ -95,7 +95,7 @@ class SpecialOfferWindow(ba.Window):
|
||||
if ('bonusTickets' in offer
|
||||
and offer['bonusTickets'] is not None):
|
||||
self._is_bundle_sale = True
|
||||
original_price = _ba.get_account_misc_read_val(
|
||||
original_price = _ba.get_v1_account_misc_read_val(
|
||||
'price.' + self._offer_item, 9999)
|
||||
|
||||
# For pure ticket prices we can show a percent-off.
|
||||
@ -341,7 +341,7 @@ class SpecialOfferWindow(ba.Window):
|
||||
|
||||
# We go away if we see that our target item is owned.
|
||||
if self._offer_item == 'pro':
|
||||
if _ba.app.accounts.have_pro():
|
||||
if _ba.app.accounts_v1.have_pro():
|
||||
can_die = True
|
||||
else:
|
||||
if _ba.get_purchased(self._offer_item):
|
||||
@ -364,9 +364,9 @@ class SpecialOfferWindow(ba.Window):
|
||||
if not self._root_widget:
|
||||
return
|
||||
sval: Union[str, ba.Lstr]
|
||||
if _ba.get_account_state() == 'signed_in':
|
||||
if _ba.get_v1_account_state() == 'signed_in':
|
||||
sval = (ba.charstr(SpecialChar.TICKET) +
|
||||
str(_ba.get_account_ticket_count()))
|
||||
str(_ba.get_v1_account_ticket_count()))
|
||||
else:
|
||||
sval = ba.Lstr(resource='getTicketsWindow.titleText')
|
||||
ba.buttonwidget(edit=self._get_tickets_button, label=sval)
|
||||
@ -374,7 +374,7 @@ class SpecialOfferWindow(ba.Window):
|
||||
def _on_get_more_tickets_press(self) -> None:
|
||||
from bastd.ui import account
|
||||
from bastd.ui import getcurrency
|
||||
if _ba.get_account_state() != 'signed_in':
|
||||
if _ba.get_v1_account_state() != 'signed_in':
|
||||
account.show_sign_in_prompt()
|
||||
return
|
||||
getcurrency.GetCurrencyWindow(modal=True).get_root_widget()
|
||||
@ -393,7 +393,7 @@ class SpecialOfferWindow(ba.Window):
|
||||
else:
|
||||
ticket_count: Optional[int]
|
||||
try:
|
||||
ticket_count = _ba.get_account_ticket_count()
|
||||
ticket_count = _ba.get_v1_account_ticket_count()
|
||||
except Exception:
|
||||
ticket_count = None
|
||||
if (ticket_count is not None
|
||||
|
||||
@ -282,7 +282,7 @@ class StoreBrowserWindow(ba.Window):
|
||||
|
||||
def _restore_purchases(self) -> None:
|
||||
from bastd.ui import account
|
||||
if _ba.get_account_state() != 'signed_in':
|
||||
if _ba.get_v1_account_state() != 'signed_in':
|
||||
account.show_sign_in_prompt()
|
||||
else:
|
||||
_ba.restore_purchases()
|
||||
@ -323,9 +323,9 @@ class StoreBrowserWindow(ba.Window):
|
||||
if not self._root_widget:
|
||||
return
|
||||
sval: Union[str, ba.Lstr]
|
||||
if _ba.get_account_state() == 'signed_in':
|
||||
if _ba.get_v1_account_state() == 'signed_in':
|
||||
sval = ba.charstr(SpecialChar.TICKET) + str(
|
||||
_ba.get_account_ticket_count())
|
||||
_ba.get_v1_account_ticket_count())
|
||||
else:
|
||||
sval = ba.Lstr(resource='getTicketsWindow.titleText')
|
||||
if self._get_tickets_button:
|
||||
@ -410,7 +410,7 @@ class StoreBrowserWindow(ba.Window):
|
||||
else:
|
||||
if is_ticket_purchase:
|
||||
if result['allow']:
|
||||
price = _ba.get_account_misc_read_val(
|
||||
price = _ba.get_v1_account_misc_read_val(
|
||||
'price.' + item, None)
|
||||
if (price is None or not isinstance(price, int)
|
||||
or price <= 0):
|
||||
@ -485,7 +485,7 @@ class StoreBrowserWindow(ba.Window):
|
||||
self._last_buy_time) < 2.0:
|
||||
ba.playsound(ba.getsound('error'))
|
||||
else:
|
||||
if _ba.get_account_state() != 'signed_in':
|
||||
if _ba.get_v1_account_state() != 'signed_in':
|
||||
account.show_sign_in_prompt()
|
||||
else:
|
||||
self._last_buy_time = curtime
|
||||
@ -499,9 +499,9 @@ class StoreBrowserWindow(ba.Window):
|
||||
self._do_purchase_check('pro' if get_available_sale_time(
|
||||
'extras') is None else 'pro_sale')
|
||||
else:
|
||||
price = _ba.get_account_misc_read_val(
|
||||
price = _ba.get_v1_account_misc_read_val(
|
||||
'price.' + item, None)
|
||||
our_tickets = _ba.get_account_ticket_count()
|
||||
our_tickets = _ba.get_v1_account_ticket_count()
|
||||
if price is not None and our_tickets < price:
|
||||
ba.playsound(ba.getsound('error'))
|
||||
getcurrency.show_get_tickets_prompt()
|
||||
@ -540,7 +540,7 @@ class StoreBrowserWindow(ba.Window):
|
||||
if not self._root_widget:
|
||||
return
|
||||
import datetime
|
||||
sales_raw = _ba.get_account_misc_read_val('sales', {})
|
||||
sales_raw = _ba.get_v1_account_misc_read_val('sales', {})
|
||||
sales = {}
|
||||
try:
|
||||
# Look at the current set of sales; filter any with time remaining.
|
||||
@ -559,7 +559,7 @@ class StoreBrowserWindow(ba.Window):
|
||||
for b_type, b_info in self.button_infos.items():
|
||||
|
||||
if b_type in ['upgrades.pro', 'pro']:
|
||||
purchased = _ba.app.accounts.have_pro()
|
||||
purchased = _ba.app.accounts_v1.have_pro()
|
||||
else:
|
||||
purchased = _ba.get_purchased(b_type)
|
||||
|
||||
@ -606,14 +606,16 @@ class StoreBrowserWindow(ba.Window):
|
||||
price_text_left = ''
|
||||
price_text_right = ''
|
||||
else:
|
||||
price = _ba.get_account_misc_read_val('price.' + b_type, 0)
|
||||
price = _ba.get_v1_account_misc_read_val(
|
||||
'price.' + b_type, 0)
|
||||
|
||||
# Color the button differently if we cant afford this.
|
||||
if _ba.get_account_state() == 'signed_in':
|
||||
if _ba.get_account_ticket_count() < price:
|
||||
if _ba.get_v1_account_state() == 'signed_in':
|
||||
if _ba.get_v1_account_ticket_count() < price:
|
||||
color = (0.6, 0.61, 0.6)
|
||||
price_text = ba.charstr(ba.SpecialChar.TICKET) + str(
|
||||
_ba.get_account_misc_read_val('price.' + b_type, '?'))
|
||||
_ba.get_v1_account_misc_read_val(
|
||||
'price.' + b_type, '?'))
|
||||
price_text_left = ''
|
||||
price_text_right = ''
|
||||
|
||||
@ -1062,7 +1064,7 @@ class StoreBrowserWindow(ba.Window):
|
||||
# pylint: disable=cyclic-import
|
||||
from bastd.ui.account import show_sign_in_prompt
|
||||
from bastd.ui.getcurrency import GetCurrencyWindow
|
||||
if _ba.get_account_state() != 'signed_in':
|
||||
if _ba.get_v1_account_state() != 'signed_in':
|
||||
show_sign_in_prompt()
|
||||
return
|
||||
self._save_state()
|
||||
|
||||
@ -197,7 +197,7 @@ class StoreButton:
|
||||
# pylint: disable=cyclic-import
|
||||
from bastd.ui.account import show_sign_in_prompt
|
||||
from bastd.ui.store.browser import StoreBrowserWindow
|
||||
if _ba.get_account_state() != 'signed_in':
|
||||
if _ba.get_v1_account_state() != 'signed_in':
|
||||
show_sign_in_prompt()
|
||||
return
|
||||
StoreBrowserWindow(modal=True, origin_widget=self._button)
|
||||
@ -216,9 +216,9 @@ class StoreButton:
|
||||
return # Our instance may outlive our UI objects.
|
||||
|
||||
if self._ticket_text is not None:
|
||||
if _ba.get_account_state() == 'signed_in':
|
||||
if _ba.get_v1_account_state() == 'signed_in':
|
||||
sval = ba.charstr(SpecialChar.TICKET) + str(
|
||||
_ba.get_account_ticket_count())
|
||||
_ba.get_v1_account_ticket_count())
|
||||
else:
|
||||
sval = '-'
|
||||
ba.textwidget(edit=self._ticket_text, text=sval)
|
||||
@ -230,7 +230,7 @@ class StoreButton:
|
||||
# ..also look for new style sales.
|
||||
if sale_time is None:
|
||||
import datetime
|
||||
sales_raw = _ba.get_account_misc_read_val('sales', {})
|
||||
sales_raw = _ba.get_v1_account_misc_read_val('sales', {})
|
||||
sale_times = []
|
||||
try:
|
||||
# Look at the current set of sales; filter any with time
|
||||
|
||||
@ -202,7 +202,7 @@ def instantiate_store_item_display(item_name: str,
|
||||
color=(1, 1, 1),
|
||||
texture=ba.gettexture('ticketsMore')))
|
||||
bonus_tickets = str(
|
||||
_ba.get_account_misc_read_val('proBonusTickets', 100))
|
||||
_ba.get_v1_account_misc_read_val('proBonusTickets', 100))
|
||||
extra_texts.append(
|
||||
ba.textwidget(parent=parent_widget,
|
||||
draw_controller=btn,
|
||||
@ -270,8 +270,8 @@ def instantiate_store_item_display(item_name: str,
|
||||
|
||||
# If we have a 'total-worth' item-id for this id, show that price so
|
||||
# the user knows how much this is worth.
|
||||
total_worth_item = _ba.get_account_misc_read_val('twrths',
|
||||
{}).get(item_name)
|
||||
total_worth_item = _ba.get_v1_account_misc_read_val('twrths',
|
||||
{}).get(item_name)
|
||||
total_worth_price: Optional[str]
|
||||
if total_worth_item is not None:
|
||||
price = _ba.get_price(total_worth_item)
|
||||
|
||||
@ -33,7 +33,7 @@ class TournamentEntryWindow(popup.PopupWindow):
|
||||
|
||||
self._tournament_id = tournament_id
|
||||
self._tournament_info = (
|
||||
ba.app.accounts.tournament_info[self._tournament_id])
|
||||
ba.app.accounts_v1.tournament_info[self._tournament_id])
|
||||
|
||||
# Set a few vars depending on the tourney fee.
|
||||
self._fee = self._tournament_info['fee']
|
||||
@ -274,13 +274,14 @@ class TournamentEntryWindow(popup.PopupWindow):
|
||||
|
||||
# If there seems to be a relatively-recent valid cached info for this
|
||||
# tournament, use it. Otherwise we'll kick off a query ourselves.
|
||||
if (self._tournament_id in ba.app.accounts.tournament_info and
|
||||
ba.app.accounts.tournament_info[self._tournament_id]['valid']
|
||||
if (self._tournament_id in ba.app.accounts_v1.tournament_info
|
||||
and ba.app.accounts_v1.tournament_info[
|
||||
self._tournament_id]['valid']
|
||||
and (ba.time(ba.TimeType.REAL, ba.TimeFormat.MILLISECONDS) -
|
||||
ba.app.accounts.tournament_info[self._tournament_id]
|
||||
ba.app.accounts_v1.tournament_info[self._tournament_id]
|
||||
['timeReceived'] < 1000 * 60 * 5)):
|
||||
try:
|
||||
info = ba.app.accounts.tournament_info[self._tournament_id]
|
||||
info = ba.app.accounts_v1.tournament_info[self._tournament_id]
|
||||
self._seconds_remaining = max(
|
||||
0, info['timeRemaining'] - int(
|
||||
(ba.time(ba.TimeType.REAL, ba.TimeFormat.MILLISECONDS)
|
||||
@ -304,7 +305,7 @@ class TournamentEntryWindow(popup.PopupWindow):
|
||||
|
||||
def _on_tournament_query_response(self, data: Optional[dict[str,
|
||||
Any]]) -> None:
|
||||
accounts = ba.app.accounts
|
||||
accounts = ba.app.accounts_v1
|
||||
self._running_query = False
|
||||
if data is not None:
|
||||
data = data['t'] # This used to be the whole payload.
|
||||
@ -358,7 +359,7 @@ class TournamentEntryWindow(popup.PopupWindow):
|
||||
self._running_query = True
|
||||
|
||||
# Grab the latest info on our tourney.
|
||||
self._tournament_info = ba.app.accounts.tournament_info[
|
||||
self._tournament_info = ba.app.accounts_v1.tournament_info[
|
||||
self._tournament_id]
|
||||
|
||||
# If we don't have valid data always show a '-' for time.
|
||||
@ -374,7 +375,7 @@ class TournamentEntryWindow(popup.PopupWindow):
|
||||
timeformat=ba.TimeFormat.MILLISECONDS))
|
||||
|
||||
# Keep price up-to-date and update the button with it.
|
||||
self._purchase_price = _ba.get_account_misc_read_val(
|
||||
self._purchase_price = _ba.get_v1_account_misc_read_val(
|
||||
self._purchase_price_name, None)
|
||||
|
||||
ba.textwidget(
|
||||
@ -422,7 +423,7 @@ class TournamentEntryWindow(popup.PopupWindow):
|
||||
color=(0, 0.8, 0) if enabled else (0.4, 0.4, 0.4))
|
||||
|
||||
try:
|
||||
t_str = str(_ba.get_account_ticket_count())
|
||||
t_str = str(_ba.get_v1_account_ticket_count())
|
||||
except Exception:
|
||||
t_str = '?'
|
||||
if self._get_tickets_button:
|
||||
@ -512,7 +513,7 @@ class TournamentEntryWindow(popup.PopupWindow):
|
||||
# Deny if we don't have enough tickets.
|
||||
ticket_count: Optional[int]
|
||||
try:
|
||||
ticket_count = _ba.get_account_ticket_count()
|
||||
ticket_count = _ba.get_v1_account_ticket_count()
|
||||
except Exception:
|
||||
# FIXME: should add a ba.NotSignedInError we can use here.
|
||||
ticket_count = None
|
||||
|
||||
53
ballisticacore-cmake/.idea/dictionaries/ericf.xml
generated
53
ballisticacore-cmake/.idea/dictionaries/ericf.xml
generated
@ -8,7 +8,10 @@
|
||||
<w>abouttab</w>
|
||||
<w>absval</w>
|
||||
<w>accel</w>
|
||||
<w>accountclientv</w>
|
||||
<w>accountid</w>
|
||||
<w>accountv</w>
|
||||
<w>accountvalues</w>
|
||||
<w>achs</w>
|
||||
<w>acinstance</w>
|
||||
<w>ack'ed</w>
|
||||
@ -17,6 +20,7 @@
|
||||
<w>aclass</w>
|
||||
<w>aclass's</w>
|
||||
<w>activityplayer</w>
|
||||
<w>actool</w>
|
||||
<w>addcall</w>
|
||||
<w>addchars</w>
|
||||
<w>addr</w>
|
||||
@ -62,6 +66,7 @@
|
||||
<w>appspot</w>
|
||||
<w>appstate</w>
|
||||
<w>argsjoined</w>
|
||||
<w>argstr</w>
|
||||
<w>asci</w>
|
||||
<w>assetsmakefile</w>
|
||||
<w>assigninput</w>
|
||||
@ -99,6 +104,7 @@
|
||||
<w>basntoclient</w>
|
||||
<w>bastd</w>
|
||||
<w>batoolsinternal</w>
|
||||
<w>baworker</w>
|
||||
<w>bbbb</w>
|
||||
<w>bbbbb</w>
|
||||
<w>bbbbbb</w>
|
||||
@ -150,6 +156,7 @@
|
||||
<w>bsmhi</w>
|
||||
<w>bsstd</w>
|
||||
<w>bstat</w>
|
||||
<w>bstr</w>
|
||||
<w>bsuuid</w>
|
||||
<w>btnlabel</w>
|
||||
<w>bucketnum</w>
|
||||
@ -197,6 +204,7 @@
|
||||
<w>checkarglist</w>
|
||||
<w>checkboxwidget</w>
|
||||
<w>checkchisel</w>
|
||||
<w>checksummed</w>
|
||||
<w>childanntype</w>
|
||||
<w>childanntypes</w>
|
||||
<w>childtype</w>
|
||||
@ -225,11 +233,20 @@
|
||||
<w>collider</w>
|
||||
<w>columnwidget</w>
|
||||
<w>comms</w>
|
||||
<w>compileassetcatalog</w>
|
||||
<w>compilec</w>
|
||||
<w>compilemetalfile</w>
|
||||
<w>compilestoryboard</w>
|
||||
<w>compileswift</w>
|
||||
<w>compileswiftsources</w>
|
||||
<w>connectattr</w>
|
||||
<w>containerwidget</w>
|
||||
<w>controlfp</w>
|
||||
<w>cooldown</w>
|
||||
<w>coopscore</w>
|
||||
<w>copypng</w>
|
||||
<w>copystringsfile</w>
|
||||
<w>copyswiftlibs</w>
|
||||
<w>coreaudio</w>
|
||||
<w>coulda</w>
|
||||
<w>cout</w>
|
||||
@ -239,6 +256,7 @@
|
||||
<w>cpuid</w>
|
||||
<w>crashenv</w>
|
||||
<w>crashlytics</w>
|
||||
<w>createbuilddirectory</w>
|
||||
<w>createtime</w>
|
||||
<w>cresult</w>
|
||||
<w>crom</w>
|
||||
@ -252,6 +270,7 @@
|
||||
<w>cstdint</w>
|
||||
<w>cstdlib</w>
|
||||
<w>cstring</w>
|
||||
<w>csval</w>
|
||||
<w>ctargetref</w>
|
||||
<w>ctracker</w>
|
||||
<w>cubemap</w>
|
||||
@ -260,6 +279,7 @@
|
||||
<w>cutef</w>
|
||||
<w>cvar</w>
|
||||
<w>data</w>
|
||||
<w>databytes</w>
|
||||
<w>dataclassio</w>
|
||||
<w>datadata</w>
|
||||
<w>dataout</w>
|
||||
@ -546,9 +566,13 @@
|
||||
<w>htonf</w>
|
||||
<w>htonl</w>
|
||||
<w>htons</w>
|
||||
<w>ibtool</w>
|
||||
<w>ibtoold</w>
|
||||
<w>ibuf</w>
|
||||
<w>icloud</w>
|
||||
<w>iconscale</w>
|
||||
<w>iconset</w>
|
||||
<w>iconutil</w>
|
||||
<w>ieeefp</w>
|
||||
<w>ifaddr</w>
|
||||
<w>ifaddrs</w>
|
||||
@ -615,6 +639,7 @@
|
||||
<w>keycode</w>
|
||||
<w>keyfilt</w>
|
||||
<w>keyint</w>
|
||||
<w>keylen</w>
|
||||
<w>keysyms</w>
|
||||
<w>keywds</w>
|
||||
<w>khronos</w>
|
||||
@ -648,6 +673,8 @@
|
||||
<w>lightshad</w>
|
||||
<w>linearsize</w>
|
||||
<w>linearstep</w>
|
||||
<w>linemax</w>
|
||||
<w>linkstoryboards</w>
|
||||
<w>listobj</w>
|
||||
<w>llock</w>
|
||||
<w>localns</w>
|
||||
@ -667,6 +694,7 @@
|
||||
<w>lrintf</w>
|
||||
<w>lscope</w>
|
||||
<w>lshort</w>
|
||||
<w>lsregister</w>
|
||||
<w>lstr</w>
|
||||
<w>lsync</w>
|
||||
<w>ltypes</w>
|
||||
@ -709,6 +737,7 @@
|
||||
<w>meshdata</w>
|
||||
<w>messagebox</w>
|
||||
<w>messagetype</w>
|
||||
<w>metallink</w>
|
||||
<w>metamakefile</w>
|
||||
<w>meth</w>
|
||||
<w>mhbegin</w>
|
||||
@ -722,6 +751,7 @@
|
||||
<w>mipmaps</w>
|
||||
<w>mkflags</w>
|
||||
<w>mlen</w>
|
||||
<w>mmacosx</w>
|
||||
<w>mmask</w>
|
||||
<w>mmdevapi</w>
|
||||
<w>modder</w>
|
||||
@ -782,6 +812,7 @@
|
||||
<w>newitem</w>
|
||||
<w>newname</w>
|
||||
<w>newnode</w>
|
||||
<w>newtoken</w>
|
||||
<w>nextchar</w>
|
||||
<w>nitpicky</w>
|
||||
<w>nlpos</w>
|
||||
@ -828,6 +859,7 @@
|
||||
<w>okbtn</w>
|
||||
<w>oldbook</w>
|
||||
<w>oldname</w>
|
||||
<w>oldtoken</w>
|
||||
<w>oooo</w>
|
||||
<w>ooooooo</w>
|
||||
<w>ooooooooo</w>
|
||||
@ -859,6 +891,8 @@
|
||||
<w>ourname</w>
|
||||
<w>ourself</w>
|
||||
<w>ourstanding</w>
|
||||
<w>outdict</w>
|
||||
<w>outmsg</w>
|
||||
<w>outpath</w>
|
||||
<w>outputter</w>
|
||||
<w>outval</w>
|
||||
@ -880,6 +914,7 @@
|
||||
<w>pflag</w>
|
||||
<w>pflags</w>
|
||||
<w>pgmout</w>
|
||||
<w>phasescriptexecution</w>
|
||||
<w>piplist</w>
|
||||
<w>pipvers</w>
|
||||
<w>pixelformat</w>
|
||||
@ -918,6 +953,9 @@
|
||||
<w>printobjects</w>
|
||||
<w>priv</w>
|
||||
<w>privatetab</w>
|
||||
<w>processinfoplistfile</w>
|
||||
<w>processpch</w>
|
||||
<w>processpchplusplus</w>
|
||||
<w>profilers</w>
|
||||
<w>prog</w>
|
||||
<w>proj</w>
|
||||
@ -925,6 +963,7 @@
|
||||
<w>projpath</w>
|
||||
<w>projprefix</w>
|
||||
<w>prolly</w>
|
||||
<w>proxykey</w>
|
||||
<w>psmx</w>
|
||||
<w>pspec</w>
|
||||
<w>psps</w>
|
||||
@ -992,6 +1031,8 @@
|
||||
<w>refcounted</w>
|
||||
<w>refl</w>
|
||||
<w>regionid</w>
|
||||
<w>registerexecutionpolicyexception</w>
|
||||
<w>registerwithlaunchservices</w>
|
||||
<w>regtp</w>
|
||||
<w>rehel</w>
|
||||
<w>reimported</w>
|
||||
@ -1042,6 +1083,7 @@
|
||||
<w>sapspace</w>
|
||||
<w>savebtn</w>
|
||||
<w>savebutton</w>
|
||||
<w>sbytes</w>
|
||||
<w>scancode</w>
|
||||
<w>scenetime</w>
|
||||
<w>screenmessage</w>
|
||||
@ -1050,6 +1092,7 @@
|
||||
<w>sdkcheck</w>
|
||||
<w>sdl's</w>
|
||||
<w>sdlk</w>
|
||||
<w>sectionchanged</w>
|
||||
<w>selchild</w>
|
||||
<w>selindex</w>
|
||||
<w>selwidget</w>
|
||||
@ -1137,6 +1180,7 @@
|
||||
<w>standin</w>
|
||||
<w>startedptr</w>
|
||||
<w>startpos</w>
|
||||
<w>startsplits</w>
|
||||
<w>starttime</w>
|
||||
<w>startx</w>
|
||||
<w>starty</w>
|
||||
@ -1171,6 +1215,7 @@
|
||||
<w>subscr</w>
|
||||
<w>subtypestr</w>
|
||||
<w>sval</w>
|
||||
<w>swiftc</w>
|
||||
<w>symbolification</w>
|
||||
<w>syscalls</w>
|
||||
<w>tabdefs</w>
|
||||
@ -1194,8 +1239,10 @@
|
||||
<w>textcolor</w>
|
||||
<w>textwidget</w>
|
||||
<w>thang</w>
|
||||
<w>thats</w>
|
||||
<w>thecommand</w>
|
||||
<w>theres</w>
|
||||
<w>thislinelen</w>
|
||||
<w>thismodule</w>
|
||||
<w>threadname</w>
|
||||
<w>threadpool</w>
|
||||
@ -1330,10 +1377,14 @@
|
||||
<w>worldspace</w>
|
||||
<w>woutdir</w>
|
||||
<w>wprjp</w>
|
||||
<w>writeauxiliaryfile</w>
|
||||
<w>wsroot</w>
|
||||
<w>wunused</w>
|
||||
<w>wvmpth</w>
|
||||
<w>xcframework</w>
|
||||
<w>xclamped</w>
|
||||
<w>xcodebuildverbose</w>
|
||||
<w>xcoderun</w>
|
||||
<w>xcrun</w>
|
||||
<w>xdiff</w>
|
||||
<w>xdist</w>
|
||||
@ -1342,6 +1393,7 @@
|
||||
<w>xmin</w>
|
||||
<w>xmmintrin</w>
|
||||
<w>xoffset</w>
|
||||
<w>xors</w>
|
||||
<w>xtweak</w>
|
||||
<w>xxlimited</w>
|
||||
<w>xxsubinterpreters</w>
|
||||
@ -1354,6 +1406,7 @@
|
||||
<w>yoffs</w>
|
||||
<w>yooooooo</w>
|
||||
<w>ytweak</w>
|
||||
<w>zipdata</w>
|
||||
<w>zmax</w>
|
||||
<w>zmin</w>
|
||||
<w>zoffset</w>
|
||||
|
||||
@ -21,8 +21,8 @@
|
||||
namespace ballistica {
|
||||
|
||||
// These are set automatically via script; don't modify them here.
|
||||
const int kAppBuildNumber = 20567;
|
||||
const char* kAppVersion = "1.6.12";
|
||||
const int kAppBuildNumber = 20577;
|
||||
const char* kAppVersion = "1.7.0";
|
||||
|
||||
// Our standalone globals.
|
||||
// These are separated out for easy access.
|
||||
|
||||
@ -1018,7 +1018,8 @@ enum class V1AccountType {
|
||||
kServer,
|
||||
kOculus,
|
||||
kSteam,
|
||||
kNvidiaChina
|
||||
kNvidiaChina,
|
||||
kV2
|
||||
};
|
||||
|
||||
enum class GraphicsQuality {
|
||||
|
||||
@ -16,7 +16,7 @@ namespace ballistica {
|
||||
class PlatformApple : public Platform {
|
||||
public:
|
||||
PlatformApple();
|
||||
auto GetDeviceAccountUUIDPrefix() -> std::string override;
|
||||
auto GetDeviceV1AccountUUIDPrefix() -> std::string override;
|
||||
auto GetRealLegacyDeviceUUID(std::string* uuid) -> bool override;
|
||||
auto GenerateUUID() -> std::string override;
|
||||
auto GetDefaultConfigDir() -> std::string override;
|
||||
|
||||
@ -13,7 +13,7 @@ namespace ballistica {
|
||||
class PlatformLinux : public Platform {
|
||||
public:
|
||||
PlatformLinux();
|
||||
auto GetDeviceAccountUUIDPrefix() -> std::string override { return "l"; }
|
||||
auto GetDeviceV1AccountUUIDPrefix() -> std::string override { return "l"; }
|
||||
auto GenerateUUID() -> std::string override;
|
||||
auto DoHasTouchScreen() -> bool override;
|
||||
auto DoOpenURL(const std::string& url) -> void override;
|
||||
|
||||
@ -118,7 +118,7 @@ Platform::~Platform() = default;
|
||||
|
||||
auto Platform::GetLegacyDeviceUUID() -> const std::string& {
|
||||
if (!have_device_uuid_) {
|
||||
legacy_device_uuid_ = GetDeviceAccountUUIDPrefix();
|
||||
legacy_device_uuid_ = GetDeviceV1AccountUUIDPrefix();
|
||||
|
||||
std::string real_unique_uuid;
|
||||
bool have_real_unique_uuid = GetRealLegacyDeviceUUID(&real_unique_uuid);
|
||||
@ -168,8 +168,8 @@ auto Platform::GetLegacyDeviceUUID() -> const std::string& {
|
||||
return legacy_device_uuid_;
|
||||
}
|
||||
|
||||
auto Platform::GetDeviceAccountUUIDPrefix() -> std::string {
|
||||
Log("GetDeviceAccountUUIDPrefix() unimplemented");
|
||||
auto Platform::GetDeviceV1AccountUUIDPrefix() -> std::string {
|
||||
Log("GetDeviceV1AccountUUIDPrefix() unimplemented");
|
||||
return "u";
|
||||
}
|
||||
|
||||
@ -804,7 +804,7 @@ auto Platform::IsStdinATerminal() -> bool {
|
||||
#endif
|
||||
}
|
||||
|
||||
auto Platform::GetOSVersionString() -> std::string { return "?"; }
|
||||
auto Platform::GetOSVersionString() -> std::string { return ""; }
|
||||
|
||||
auto Platform::GetUserAgentString() -> std::string {
|
||||
std::string device = GetDeviceName();
|
||||
@ -949,7 +949,7 @@ void Platform::AndroidQuitActivity() {
|
||||
Log("AndroidQuitActivity() unimplemented");
|
||||
}
|
||||
|
||||
auto Platform::GetDeviceAccountID() -> std::string {
|
||||
auto Platform::GetDeviceV1AccountID() -> std::string {
|
||||
if (HeadlessMode()) {
|
||||
return "S-" + GetLegacyDeviceUUID();
|
||||
}
|
||||
@ -1073,15 +1073,15 @@ auto Platform::GetHasVideoAds() -> bool {
|
||||
return GetHasAds();
|
||||
}
|
||||
|
||||
void Platform::SignIn(const std::string& account_type) {
|
||||
Log("SignIn() unimplemented");
|
||||
void Platform::SignInV1(const std::string& account_type) {
|
||||
Log("SignInV1() unimplemented");
|
||||
}
|
||||
|
||||
void Platform::LoginDidChange() {
|
||||
// Default is no-op.
|
||||
}
|
||||
|
||||
void Platform::SignOut() { Log("SignOut() unimplemented"); }
|
||||
void Platform::SignOutV1() { Log("SignOutV1() unimplemented"); }
|
||||
|
||||
void Platform::AndroidShowWifiSettings() {
|
||||
Log("AndroidShowWifiSettings() unimplemented");
|
||||
|
||||
@ -304,17 +304,17 @@ class Platform {
|
||||
|
||||
#pragma mark ACCOUNTS ----------------------------------------------------------
|
||||
|
||||
virtual auto SignIn(const std::string& account_type) -> void;
|
||||
virtual auto SignOut() -> void;
|
||||
virtual auto SignInV1(const std::string& account_type) -> void;
|
||||
virtual auto SignOutV1() -> void;
|
||||
|
||||
virtual auto GameCenterLogin() -> void;
|
||||
virtual auto LoginDidChange() -> void;
|
||||
|
||||
/// Returns the ID to use for the device account.
|
||||
auto GetDeviceAccountID() -> std::string;
|
||||
auto GetDeviceV1AccountID() -> std::string;
|
||||
|
||||
/// Return the prefix to use for device-account ids on this platform.
|
||||
virtual auto GetDeviceAccountUUIDPrefix() -> std::string;
|
||||
virtual auto GetDeviceV1AccountUUIDPrefix() -> std::string;
|
||||
|
||||
#pragma mark MUSIC PLAYBACK ----------------------------------------------------
|
||||
|
||||
|
||||
@ -15,7 +15,7 @@ class PlatformWindows : public Platform {
|
||||
public:
|
||||
PlatformWindows();
|
||||
void SetupInterruptHandling() override;
|
||||
auto GetDeviceAccountUUIDPrefix() -> std::string override { return "w"; }
|
||||
auto GetDeviceV1AccountUUIDPrefix() -> std::string override { return "w"; }
|
||||
auto GetDeviceUUIDInputs() -> std::list<std::string> override;
|
||||
auto GenerateUUID() -> std::string override;
|
||||
auto GetDefaultConfigDir() -> std::string override;
|
||||
|
||||
@ -300,8 +300,8 @@ auto PythonClassInputDevice::GetPlayerProfiles(PythonClassInputDevice* self)
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
auto PythonClassInputDevice::GetAccountName(PythonClassInputDevice* self,
|
||||
PyObject* args, PyObject* keywds)
|
||||
auto PythonClassInputDevice::GetV1AccountName(PythonClassInputDevice* self,
|
||||
PyObject* args, PyObject* keywds)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
int full;
|
||||
@ -440,9 +440,9 @@ PyMethodDef PythonClassInputDevice::tp_methods[] = {
|
||||
"\n"
|
||||
"Returns the default player name for this device. (used for the 'random'\n"
|
||||
"profile)"},
|
||||
{"get_account_name", (PyCFunction)GetAccountName,
|
||||
{"get_v1_account_name", (PyCFunction)GetV1AccountName,
|
||||
METH_VARARGS | METH_KEYWORDS, // NOLINT (signed bitwise ops)
|
||||
"get_account_name(full: bool) -> str\n"
|
||||
"get_v1_account_name(full: bool) -> str\n"
|
||||
"\n"
|
||||
"Returns the account name associated with this device.\n"
|
||||
"\n"
|
||||
|
||||
@ -34,8 +34,8 @@ class PythonClassInputDevice : public PythonClass {
|
||||
-> PyObject*;
|
||||
static auto GetDefaultPlayerName(PythonClassInputDevice* self) -> PyObject*;
|
||||
static auto GetPlayerProfiles(PythonClassInputDevice* self) -> PyObject*;
|
||||
static auto GetAccountName(PythonClassInputDevice* self, PyObject* args,
|
||||
PyObject* keywds) -> PyObject*;
|
||||
static auto GetV1AccountName(PythonClassInputDevice* self, PyObject* args,
|
||||
PyObject* keywds) -> PyObject*;
|
||||
static auto IsConnectedToRemotePlayer(PythonClassInputDevice* self)
|
||||
-> PyObject*;
|
||||
static auto Exists(PythonClassInputDevice* self) -> PyObject*;
|
||||
|
||||
@ -481,7 +481,7 @@ auto PythonClassSessionPlayer::GetTeam(PythonClassSessionPlayer* self)
|
||||
|
||||
// NOTE: this returns their PUBLIC account-id; we want to keep
|
||||
// actual account-ids as hidden as possible for now.
|
||||
auto PythonClassSessionPlayer::GetAccountID(PythonClassSessionPlayer* self)
|
||||
auto PythonClassSessionPlayer::GetV1AccountID(PythonClassSessionPlayer* self)
|
||||
-> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
assert(InGameThread());
|
||||
@ -703,10 +703,11 @@ PyMethodDef PythonClassSessionPlayer::tp_methods[] = {
|
||||
"remove_from_game() -> None\n"
|
||||
"\n"
|
||||
"Removes the player from the game."},
|
||||
{"get_account_id", (PyCFunction)GetAccountID, METH_VARARGS | METH_KEYWORDS,
|
||||
"get_account_id() -> str\n"
|
||||
{"get_v1_account_id", (PyCFunction)GetV1AccountID,
|
||||
METH_VARARGS | METH_KEYWORDS,
|
||||
"get_v1_account_id() -> str\n"
|
||||
"\n"
|
||||
"Return the Account ID this player is signed in under, if\n"
|
||||
"Return the V1 Account ID this player is signed in under, if\n"
|
||||
"there is one and it can be determined with relative certainty.\n"
|
||||
"Returns None otherwise. Note that this may require an active\n"
|
||||
"internet connection (especially for network-connected players)\n"
|
||||
|
||||
@ -40,7 +40,7 @@ class PythonClassSessionPlayer : public PythonClass {
|
||||
PyObject* keywds) -> PyObject*;
|
||||
static auto RemoveFromGame(PythonClassSessionPlayer* self) -> PyObject*;
|
||||
static auto GetTeam(PythonClassSessionPlayer* self) -> PyObject*;
|
||||
static auto GetAccountID(PythonClassSessionPlayer* self) -> PyObject*;
|
||||
static auto GetV1AccountID(PythonClassSessionPlayer* self) -> PyObject*;
|
||||
static auto SetData(PythonClassSessionPlayer* self, PyObject* args,
|
||||
PyObject* keywds) -> PyObject*;
|
||||
static auto GetIconInfo(PythonClassSessionPlayer* self) -> PyObject*;
|
||||
|
||||
@ -350,6 +350,7 @@ class Python {
|
||||
kLstrFromJsonCall,
|
||||
kUUIDStrCall,
|
||||
kHashStringsCall,
|
||||
kHaveAccountV2CredentialsCall,
|
||||
kLast // Sentinel; must be at end.
|
||||
};
|
||||
|
||||
|
||||
@ -83,7 +83,7 @@ def get_binding_values() -> tuple[Any, ...]:
|
||||
_hooks.do_quit, # kQuitCall
|
||||
_hooks.shutdown, # kShutdownCall
|
||||
_hooks.gc_disable, # kGCDisableCall
|
||||
ba.app.accounts.show_post_purchase_message, # kShowPostPurchaseMessageCall
|
||||
ba.app.accounts_v1.show_post_purchase_message, # kShowPostPurchaseMessageCall
|
||||
_hooks.device_menu_press, # kDeviceMenuPressCall
|
||||
_hooks.show_url_window, # kShowURLWindowCall
|
||||
_hooks.party_invite_revoke, # kHandlePartyInviteRevokeCall
|
||||
@ -134,4 +134,5 @@ def get_binding_values() -> tuple[Any, ...]:
|
||||
_language.Lstr.from_json, # kLstrFromJsonCall
|
||||
_hooks.uuid_str, # kUUIDStrCall
|
||||
_hooks.hash_strings, # kHashStringsCall
|
||||
_hooks.have_account_v2_credentials, # kHaveAccountV2CredentialsCall
|
||||
) # yapf: disable
|
||||
|
||||
@ -751,6 +751,7 @@ def test_full_pipeline() -> None:
|
||||
|
||||
def __init__(self, target: Union[TestClassRSync,
|
||||
TestClassRAsync]) -> None:
|
||||
self.test_sidecar = False
|
||||
self._target = target
|
||||
|
||||
@msg.send_method
|
||||
@ -766,7 +767,9 @@ def test_full_pipeline() -> None:
|
||||
if self.test_handling_unregistered:
|
||||
# Emulate forwarding unregistered messages on to some
|
||||
# other handler...
|
||||
return self.msg.protocol.encode_response(EmptyResponse())
|
||||
response_dict = self.msg.protocol.response_to_dict(
|
||||
EmptyResponse())
|
||||
return self.msg.protocol.encode_dict(response_dict)
|
||||
raise
|
||||
|
||||
@msg.send_async_method
|
||||
@ -778,11 +781,26 @@ def test_full_pipeline() -> None:
|
||||
return self._target.receiver.handle_raw_message(data)
|
||||
return await self._target.receiver.handle_raw_message(data)
|
||||
|
||||
@msg.encode_filter_method
|
||||
def _encode_filter(self, msg: Message, outdict: dict) -> None:
|
||||
"""Filter our outgoing messages."""
|
||||
if self.test_sidecar:
|
||||
outdict['_sidecar_data'] = getattr(msg, '_sidecar_data')
|
||||
|
||||
@msg.decode_filter_method
|
||||
def _decode_filter(self, indata: dict, response: Response) -> None:
|
||||
"""Filter our incoming responses."""
|
||||
if self.test_sidecar:
|
||||
setattr(response, '_sidecar_data', indata['_sidecar_data'])
|
||||
|
||||
class TestClassRSync:
|
||||
"""Test class incorporating synchronous receive functionality."""
|
||||
|
||||
receiver = _TestSyncMessageReceiver()
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.test_sidecar = False
|
||||
|
||||
@receiver.handler
|
||||
def handle_test_message_1(self, msg: _TMsg1) -> _TResp1:
|
||||
"""Test."""
|
||||
@ -790,7 +808,10 @@ def test_full_pipeline() -> None:
|
||||
raise CleanError('Testing Clean Error')
|
||||
if msg.ival == 2:
|
||||
raise RuntimeError('Testing Runtime Error')
|
||||
return _TResp1(bval=True)
|
||||
out = _TResp1(bval=True)
|
||||
if self.test_sidecar:
|
||||
setattr(out, '_sidecar_data', getattr(msg, '_sidecar_data'))
|
||||
return out
|
||||
|
||||
@receiver.handler
|
||||
def handle_test_message_2(self,
|
||||
@ -804,6 +825,18 @@ def test_full_pipeline() -> None:
|
||||
"""Test."""
|
||||
del msg # Unused
|
||||
|
||||
@receiver.decode_filter_method
|
||||
def _decode_filter(self, indata: dict, message: Message) -> None:
|
||||
"""Filter our incoming messages."""
|
||||
if self.test_sidecar:
|
||||
setattr(message, '_sidecar_data', indata['_sidecar_data'])
|
||||
|
||||
@receiver.encode_filter_method
|
||||
def _encode_filter(self, response: Response, outdict: dict) -> None:
|
||||
"""Filter our outgoing responses."""
|
||||
if self.test_sidecar:
|
||||
outdict['_sidecar_data'] = getattr(response, '_sidecar_data')
|
||||
|
||||
receiver.validate()
|
||||
|
||||
class TestClassRAsync:
|
||||
@ -885,3 +918,15 @@ def test_full_pipeline() -> None:
|
||||
# Make sure static typing lines up with what we expect.
|
||||
if os.environ.get('EFRO_TEST_MESSAGE_FAST') != '1':
|
||||
assert static_type_equals(response6, _TResp1)
|
||||
|
||||
# Now test adding extra data to messages. This should be transferred
|
||||
# into the encoded message, copied to the response, and again back
|
||||
# through the encoded response using the filter functions we defined.
|
||||
obj.test_sidecar = True
|
||||
obj_r_sync.test_sidecar = True
|
||||
outmsg = _TMsg1(ival=0)
|
||||
setattr(outmsg, '_sidecar_data', 198) # Our test payload.
|
||||
response1 = obj.msg.send(outmsg)
|
||||
assert getattr(response1, '_sidecar_data') == 198
|
||||
obj.test_sidecar = False
|
||||
obj_r_sync.test_sidecar = False
|
||||
|
||||
@ -182,6 +182,7 @@ class App:
|
||||
|
||||
response_raw_2 = requests.post(
|
||||
(MASTER_SERVER_URL + '/bacloudcmd'),
|
||||
headers={'User-Agent': f'bacloud/{VERSION}'},
|
||||
data={
|
||||
'c': cmd,
|
||||
'v': VERSION,
|
||||
|
||||
103
tools/bacommon/cloud.py
Normal file
103
tools/bacommon/cloud.py
Normal file
@ -0,0 +1,103 @@
|
||||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Functionality related to cloud functionality."""
|
||||
|
||||
from __future__ import annotations
|
||||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING, Annotated, Optional
|
||||
from enum import Enum
|
||||
|
||||
from efro.message import Message, Response
|
||||
from efro.dataclassio import ioprepped, IOAttrs
|
||||
|
||||
if TYPE_CHECKING:
|
||||
pass
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class LoginProxyRequestMessage(Message):
|
||||
"""Request send to the cloud to ask for a login-proxy."""
|
||||
|
||||
@classmethod
|
||||
def get_response_types(cls) -> list[type[Response]]:
|
||||
return [LoginProxyRequestResponse]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class LoginProxyRequestResponse(Response):
|
||||
"""Response to a request for a login proxy."""
|
||||
|
||||
# URL to direct the user to for login.
|
||||
url: Annotated[str, IOAttrs('u')]
|
||||
|
||||
# Proxy-Login id for querying results.
|
||||
proxyid: Annotated[str, IOAttrs('p')]
|
||||
|
||||
# Proxy-Login key for querying results.
|
||||
proxykey: Annotated[str, IOAttrs('k')]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class LoginProxyStateQueryMessage(Message):
|
||||
"""Soo.. how is that login proxy going?"""
|
||||
proxyid: Annotated[str, IOAttrs('p')]
|
||||
proxykey: Annotated[str, IOAttrs('k')]
|
||||
|
||||
@classmethod
|
||||
def get_response_types(cls) -> list[type[Response]]:
|
||||
return [LoginProxyStateQueryResponse]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class LoginProxyStateQueryResponse(Response):
|
||||
"""Here's the info on that login-proxy you asked about, boss."""
|
||||
|
||||
class State(Enum):
|
||||
"""States a login-proxy can be in."""
|
||||
WAITING = 'waiting'
|
||||
SUCCESS = 'success'
|
||||
FAIL = 'fail'
|
||||
|
||||
state: Annotated[State, IOAttrs('s')]
|
||||
|
||||
# On success, these will be filled out.
|
||||
credentials: Annotated[Optional[str], IOAttrs('tk')]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class LoginProxyCompleteMessage(Message):
|
||||
"""Just so you know, we're done with this proxy."""
|
||||
proxyid: Annotated[str, IOAttrs('p')]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class AccountSessionReleaseMessage(Message):
|
||||
"""We're done using this particular session."""
|
||||
token: Annotated[str, IOAttrs('tk')]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class CredentialsCheckMessage(Message):
|
||||
"""Are our current credentials valid?"""
|
||||
|
||||
@classmethod
|
||||
def get_response_types(cls) -> list[type[Response]]:
|
||||
return [CredentialsCheckResponse]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class CredentialsCheckResponse(Response):
|
||||
"""Info returned when checking credentials."""
|
||||
|
||||
verified: Annotated[bool, IOAttrs('v')]
|
||||
|
||||
# Current account tag (good time to check if it has changed).
|
||||
tag: Annotated[str, IOAttrs('t')]
|
||||
@ -416,7 +416,7 @@ def stage_server_file(projroot: str, mode: str, infilename: str,
|
||||
outfilename: str) -> None:
|
||||
"""Stage files for the server environment with some filtering."""
|
||||
import batools.build
|
||||
from efrotools import replace_one
|
||||
from efrotools import replace_exact
|
||||
if mode not in ('debug', 'release'):
|
||||
raise RuntimeError(f"Invalid server-file-staging mode '{mode}';"
|
||||
f" expected 'debug' or 'release'.")
|
||||
@ -437,9 +437,9 @@ def stage_server_file(projroot: str, mode: str, infilename: str,
|
||||
with open(infilename, encoding='utf-8') as infile:
|
||||
lines = infile.read().splitlines()
|
||||
if mode == 'release':
|
||||
lines[0] = replace_one(lines[0],
|
||||
f'#!/usr/bin/env python{PYVER}',
|
||||
f'#!/usr/bin/env -S python{PYVER} -O')
|
||||
lines[0] = replace_exact(
|
||||
lines[0], f'#!/usr/bin/env python{PYVER}',
|
||||
f'#!/usr/bin/env -S python{PYVER} -O')
|
||||
_write_if_changed(outfilename,
|
||||
'\n'.join(lines) + '\n',
|
||||
make_executable=True)
|
||||
@ -452,15 +452,15 @@ def stage_server_file(projroot: str, mode: str, infilename: str,
|
||||
with open(infilename, encoding='utf-8') as infile:
|
||||
lines = infile.read().splitlines()
|
||||
if mode == 'release':
|
||||
lines[1] = replace_one(
|
||||
lines[1] = replace_exact(
|
||||
lines[1], ':: Python interpreter.', ':: Python interpreter.'
|
||||
' (in opt mode so we use bundled .opt-1.pyc files)')
|
||||
lines[2] = replace_one(
|
||||
lines[2] = replace_exact(
|
||||
lines[2], 'dist\\\\python.exe ballisticacore_server.py',
|
||||
'dist\\\\python.exe -O ballisticacore_server.py')
|
||||
else:
|
||||
# In debug mode we use the bundled debug interpreter.
|
||||
lines[2] = replace_one(
|
||||
lines[2] = replace_exact(
|
||||
lines[2], 'dist\\\\python.exe ballisticacore_server.py',
|
||||
'dist\\\\python_d.exe ballisticacore_server.py')
|
||||
|
||||
|
||||
@ -38,23 +38,23 @@ class PipRequirement:
|
||||
# installing it. And as far as manually-installed bits, pip itself must
|
||||
# have some way to allow for that, right?...
|
||||
PIP_REQUIREMENTS = [
|
||||
PipRequirement(modulename='pylint', minversion=[2, 12, 2]),
|
||||
PipRequirement(modulename='mypy', minversion=[0, 931]),
|
||||
PipRequirement(modulename='pylint', minversion=[2, 13, 9]),
|
||||
PipRequirement(modulename='mypy', minversion=[0, 960]),
|
||||
PipRequirement(modulename='yapf', minversion=[0, 32, 0]),
|
||||
PipRequirement(modulename='cpplint', minversion=[1, 5, 5]),
|
||||
PipRequirement(modulename='pytest', minversion=[6, 2, 5]),
|
||||
PipRequirement(modulename='cpplint', minversion=[1, 6, 0]),
|
||||
PipRequirement(modulename='pytest', minversion=[7, 1, 2]),
|
||||
PipRequirement(modulename='pytz'),
|
||||
PipRequirement(modulename='ansiwrap'),
|
||||
PipRequirement(modulename='yaml', pipname='PyYAML'),
|
||||
PipRequirement(modulename='requests'),
|
||||
PipRequirement(modulename='pdoc'),
|
||||
PipRequirement(pipname='typing_extensions', minversion=[4, 0, 1]),
|
||||
PipRequirement(pipname='types-filelock', minversion=[3, 2, 5]),
|
||||
PipRequirement(pipname='types-requests', minversion=[2, 27, 7]),
|
||||
PipRequirement(pipname='types-pytz', minversion=[2021, 3, 4]),
|
||||
PipRequirement(pipname='types-PyYAML', minversion=[6, 0, 3]),
|
||||
PipRequirement(pipname='certifi', minversion=[2021, 10, 8]),
|
||||
PipRequirement(pipname='types-certifi', minversion=[2021, 10, 8, 1]),
|
||||
PipRequirement(pipname='typing_extensions', minversion=[4, 2, 0]),
|
||||
PipRequirement(pipname='types-filelock', minversion=[3, 2, 6]),
|
||||
PipRequirement(pipname='types-requests', minversion=[2, 27, 29]),
|
||||
PipRequirement(pipname='types-pytz', minversion=[2021, 3, 8]),
|
||||
PipRequirement(pipname='types-PyYAML', minversion=[6, 0, 7]),
|
||||
PipRequirement(pipname='certifi', minversion=[2022, 5, 18, 1]),
|
||||
PipRequirement(pipname='types-certifi', minversion=[2021, 10, 8, 2]),
|
||||
]
|
||||
|
||||
# Parts of full-tests suite we only run on particular days.
|
||||
@ -566,7 +566,8 @@ def checkenv() -> None:
|
||||
f' will update all pip requirements.')
|
||||
if minver is not None:
|
||||
vnums = pipvers[pipname]
|
||||
assert len(vnums) == len(minver)
|
||||
assert len(vnums) == len(minver), (
|
||||
f'unexpected version format for {pipname}: {vnums}')
|
||||
if vnums < minver:
|
||||
raise CleanError(
|
||||
f'{pipname} ver. {_vstr(minver)} or newer'
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
# pylint: disable=too-many-lines
|
||||
"""A nice collection of ready-to-use pcommands for this package."""
|
||||
from __future__ import annotations
|
||||
|
||||
@ -387,7 +386,10 @@ def python_apple_patch() -> None:
|
||||
"""Patches Python to prep for building for Apple platforms."""
|
||||
from efrotools import pybuild
|
||||
arch = sys.argv[2]
|
||||
pybuild.apple_patch(arch)
|
||||
slc = sys.argv[3]
|
||||
assert slc
|
||||
assert ' ' not in slc
|
||||
pybuild.apple_patch(arch, slc)
|
||||
|
||||
|
||||
def python_gather() -> None:
|
||||
@ -954,21 +956,6 @@ def update_meta_makefile() -> None:
|
||||
update(projroot=str(PROJROOT), check='--check' in sys.argv)
|
||||
|
||||
|
||||
def xcode_build_path() -> None:
|
||||
"""Get the build path for an xcode project."""
|
||||
import os
|
||||
from batools.xcode import project_build_path
|
||||
if len(sys.argv) != 4:
|
||||
raise Exception(
|
||||
'Expected 2 args: <xcode project path> <configuration name>')
|
||||
project_path = os.path.abspath(sys.argv[2])
|
||||
configuration = sys.argv[3]
|
||||
path = project_build_path(projroot=str(PROJROOT),
|
||||
project_path=project_path,
|
||||
configuration=configuration)
|
||||
print(path)
|
||||
|
||||
|
||||
def gen_python_enums_module() -> None:
|
||||
"""Update our procedurally generated python enums."""
|
||||
from batools.pythonenumsmodule import generate
|
||||
|
||||
@ -1,91 +0,0 @@
|
||||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Fetch and cache xcode project build paths.
|
||||
|
||||
This saves the few seconds it normally would take to fire up xcodebuild
|
||||
and filter its output.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Any, Optional
|
||||
|
||||
|
||||
def project_build_path(projroot: str, project_path: str,
|
||||
configuration: str) -> str:
|
||||
"""Main script entry point."""
|
||||
# pylint: disable=too-many-locals
|
||||
|
||||
config_path = os.path.join(projroot, '.cache', 'xcode_build_path')
|
||||
out_path = None
|
||||
config: dict[str, dict[str, Any]] = {}
|
||||
|
||||
build_dir: Optional[str] = None
|
||||
|
||||
try:
|
||||
if os.path.exists(config_path):
|
||||
with open(config_path, encoding='utf-8') as infile:
|
||||
config = json.loads(infile.read())
|
||||
if (project_path in config
|
||||
and configuration in config[project_path]):
|
||||
|
||||
# Ok we've found a build-dir entry for this project; now if it
|
||||
# exists on disk and all timestamps within it are decently
|
||||
# close to the one we've got recorded, lets use it.
|
||||
# (Anything using this script should also be building
|
||||
# stuff there so mod times should be pretty recent; if not
|
||||
# then its worth re-caching to be sure.)
|
||||
build_dir = config[project_path][configuration]['build_dir']
|
||||
timestamp = config[project_path][configuration]['timestamp']
|
||||
assert build_dir is not None
|
||||
if os.path.isdir(build_dir):
|
||||
use_cached = True
|
||||
|
||||
# if its been over a day since we cached this, renew it
|
||||
now = time.time()
|
||||
if abs(now - timestamp) > 60 * 60 * 24:
|
||||
use_cached = False
|
||||
|
||||
if use_cached:
|
||||
out_path = build_dir
|
||||
except Exception:
|
||||
import traceback
|
||||
print('EXCEPTION checking cached build path', file=sys.stderr)
|
||||
traceback.print_exc()
|
||||
out_path = None
|
||||
|
||||
# If we don't have a path at this point we look it up and cache it.
|
||||
if out_path is None:
|
||||
print('Caching xcode build path...', file=sys.stderr)
|
||||
output = subprocess.check_output([
|
||||
'xcodebuild', '-project', project_path, '-showBuildSettings',
|
||||
'-configuration', configuration
|
||||
]).decode('utf-8')
|
||||
prefix = 'TARGET_BUILD_DIR = '
|
||||
lines = [
|
||||
l for l in output.splitlines() if l.strip().startswith(prefix)
|
||||
]
|
||||
if len(lines) != 1:
|
||||
raise Exception(
|
||||
'TARGET_BUILD_DIR not found in xcodebuild settings output')
|
||||
build_dir = lines[0].replace(prefix, '').strip()
|
||||
if project_path not in config:
|
||||
config[project_path] = {}
|
||||
config[project_path][configuration] = {
|
||||
'build_dir': build_dir,
|
||||
'timestamp': time.time()
|
||||
}
|
||||
os.makedirs(os.path.dirname(config_path), exist_ok=True)
|
||||
with open(config_path, 'w', encoding='utf-8') as outfile:
|
||||
outfile.write(json.dumps(config))
|
||||
|
||||
assert build_dir is not None
|
||||
return build_dir
|
||||
@ -10,7 +10,7 @@ if TYPE_CHECKING:
|
||||
|
||||
|
||||
class CleanError(Exception):
|
||||
"""An error that should be presented to the user as a simple message.
|
||||
"""An error that can be presented to the user as a simple message.
|
||||
|
||||
These errors should be completely self-explanatory, to the point where
|
||||
a traceback or other context would not be useful.
|
||||
@ -41,7 +41,7 @@ class CommunicationError(Exception):
|
||||
This covers anything network-related going wrong in the sending
|
||||
of data or receiving of a response. This error does not imply
|
||||
that data was not received on the other end; only that a full
|
||||
response round trip was not completed.
|
||||
acknowledgement round trip was not completed.
|
||||
|
||||
These errors should be gracefully handled whenever possible, as
|
||||
occasional network outages are generally unavoidable.
|
||||
@ -55,9 +55,9 @@ class RemoteError(Exception):
|
||||
occurs remotely. The error string can consist of a remote stack
|
||||
trace or a simple message depending on the context.
|
||||
|
||||
Depending on the situation, more specific error types such as CleanError
|
||||
may be raised due to the remote error, so this one is considered somewhat
|
||||
of a catch-all.
|
||||
Communication systems should raise more specific error types when
|
||||
more introspection/control is needed; this is intended somewhat as
|
||||
a catch-all.
|
||||
"""
|
||||
|
||||
def __str__(self) -> str:
|
||||
@ -65,6 +65,10 @@ class RemoteError(Exception):
|
||||
return f'Remote Exception Follows:\n{s}'
|
||||
|
||||
|
||||
class IntegrityError(ValueError):
|
||||
"""Data has been tampered with or corrupted in some form."""
|
||||
|
||||
|
||||
def is_urllib_network_error(exc: BaseException) -> bool:
|
||||
"""Is the provided exception from urllib a network-related error?
|
||||
|
||||
|
||||
@ -11,7 +11,7 @@ import traceback
|
||||
import logging
|
||||
import json
|
||||
|
||||
from efro.error import CleanError, RemoteError
|
||||
from efro.error import CleanError
|
||||
from efro.dataclassio import (is_ioprepped_dataclass, dataclass_to_dict,
|
||||
dataclass_from_dict)
|
||||
from efro.message._message import (Message, Response, ErrorResponse,
|
||||
@ -33,7 +33,6 @@ class MessageProtocol:
|
||||
def __init__(self,
|
||||
message_types: dict[int, type[Message]],
|
||||
response_types: dict[int, type[Response]],
|
||||
type_key: Optional[str] = None,
|
||||
preserve_clean_errors: bool = True,
|
||||
log_remote_exceptions: bool = True,
|
||||
trusted_sender: bool = False) -> None:
|
||||
@ -43,11 +42,6 @@ class MessageProtocol:
|
||||
with (unchanging negative ids) so they don't need to be passed
|
||||
explicitly (but can be if a different id is desired).
|
||||
|
||||
If 'type_key' is provided, the message type ID is stored as the
|
||||
provided key in the message dict; otherwise it will be stored as
|
||||
part of a top level dict with the message payload appearing as a
|
||||
child dict. This is mainly for backwards compatibility.
|
||||
|
||||
If 'preserve_clean_errors' is True, efro.error.CleanError errors
|
||||
on the remote end will result in the same error raised locally.
|
||||
All other Exception types come across as efro.error.RemoteError.
|
||||
@ -55,7 +49,6 @@ class MessageProtocol:
|
||||
If 'trusted_sender' is True, stringified remote stack traces will
|
||||
be included in the responses if errors occur.
|
||||
"""
|
||||
# pylint: disable=too-many-locals
|
||||
self.message_types_by_id: dict[int, type[Message]] = {}
|
||||
self.message_ids_by_type: dict[type[Message], int] = {}
|
||||
self.response_types_by_id: dict[int, type[Response]] = {}
|
||||
@ -93,7 +86,6 @@ class MessageProtocol:
|
||||
|
||||
_reg_if_not(ErrorResponse, -1)
|
||||
_reg_if_not(EmptyResponse, -2)
|
||||
# _reg_if_not(BoolResponse, -3)
|
||||
|
||||
# Some extra-thorough validation in debug mode.
|
||||
if __debug__:
|
||||
@ -124,84 +116,78 @@ class MessageProtocol:
|
||||
'message_types contains duplicate __name__s;'
|
||||
' all types are required to have unique names.')
|
||||
|
||||
self._type_key = type_key
|
||||
self.preserve_clean_errors = preserve_clean_errors
|
||||
self.log_remote_exceptions = log_remote_exceptions
|
||||
self.trusted_sender = trusted_sender
|
||||
|
||||
def encode_message(self, message: Message) -> str:
|
||||
"""Encode a message to a json string for transport."""
|
||||
return self._encode(message, self.message_ids_by_type, 'message')
|
||||
@staticmethod
|
||||
def encode_dict(obj: dict) -> str:
|
||||
"""Json-encode a provided dict."""
|
||||
return json.dumps(obj, separators=(',', ':'))
|
||||
|
||||
def encode_response(self, response: Response) -> str:
|
||||
"""Encode a response to a json string for transport."""
|
||||
return self._encode(response, self.response_ids_by_type, 'response')
|
||||
def message_to_dict(self, message: Message) -> dict:
|
||||
"""Encode a message to a json ready dict."""
|
||||
return self._to_dict(message, self.message_ids_by_type, 'message')
|
||||
|
||||
def encode_error_response(self, exc: Exception) -> str:
|
||||
"""Return a raw response for an error that occurred during handling."""
|
||||
def response_to_dict(self, response: Response) -> dict:
|
||||
"""Encode a response to a json ready dict."""
|
||||
return self._to_dict(response, self.response_ids_by_type, 'response')
|
||||
|
||||
def error_to_response(self, exc: Exception) -> Response:
|
||||
"""Translate an error to a response."""
|
||||
if self.log_remote_exceptions:
|
||||
logging.exception('Error handling message.')
|
||||
|
||||
# If anything goes wrong, return a ErrorResponse instead.
|
||||
if isinstance(exc, CleanError) and self.preserve_clean_errors:
|
||||
err_response = ErrorResponse(error_message=str(exc),
|
||||
error_type=ErrorType.CLEAN)
|
||||
else:
|
||||
err_response = ErrorResponse(
|
||||
error_message=(traceback.format_exc() if self.trusted_sender
|
||||
else 'An unknown error has occurred.'),
|
||||
error_type=ErrorType.OTHER)
|
||||
return self.encode_response(err_response)
|
||||
return ErrorResponse(error_message=str(exc),
|
||||
error_type=ErrorType.CLEAN)
|
||||
return ErrorResponse(
|
||||
error_message=(traceback.format_exc() if self.trusted_sender else
|
||||
'An unknown error has occurred.'),
|
||||
error_type=ErrorType.OTHER)
|
||||
|
||||
def _encode(self, message: Any, ids_by_type: dict[type, int],
|
||||
opname: str) -> str:
|
||||
def _to_dict(self, message: Any, ids_by_type: dict[type, int],
|
||||
opname: str) -> dict:
|
||||
"""Encode a message to a json string for transport."""
|
||||
|
||||
m_id: Optional[int] = ids_by_type.get(type(message))
|
||||
if m_id is None:
|
||||
raise TypeError(f'{opname} type is not registered in protocol:'
|
||||
f' {type(message)}')
|
||||
msgdict = dataclass_to_dict(message)
|
||||
out = {'t': m_id, 'm': dataclass_to_dict(message)}
|
||||
return out
|
||||
|
||||
# Encode type as part of the message/response dict if desired
|
||||
# (for legacy compatibility).
|
||||
if self._type_key is not None:
|
||||
if self._type_key in msgdict:
|
||||
raise RuntimeError(f'Type-key {self._type_key}'
|
||||
f' found in msg of type {type(message)}')
|
||||
msgdict[self._type_key] = m_id
|
||||
out = msgdict
|
||||
else:
|
||||
out = {'m': msgdict, 't': m_id}
|
||||
return json.dumps(out, separators=(',', ':'))
|
||||
@staticmethod
|
||||
def decode_dict(data: str) -> dict:
|
||||
"""Decode data to a dict."""
|
||||
out = json.loads(data)
|
||||
assert isinstance(out, dict)
|
||||
return out
|
||||
|
||||
def decode_message(self, data: str) -> Message:
|
||||
def message_from_dict(self, data: dict) -> Message:
|
||||
"""Decode a message from a json string."""
|
||||
out = self._decode(data, self.message_types_by_id, 'message')
|
||||
out = self._from_dict(data, self.message_types_by_id, 'message')
|
||||
assert isinstance(out, Message)
|
||||
return out
|
||||
|
||||
def decode_response(self, data: str) -> Optional[Response]:
|
||||
def response_from_dict(self, data: dict) -> Response:
|
||||
"""Decode a response from a json string."""
|
||||
out = self._decode(data, self.response_types_by_id, 'response')
|
||||
assert isinstance(out, (Response, type(None)))
|
||||
out = self._from_dict(data, self.response_types_by_id, 'response')
|
||||
assert isinstance(out, Response)
|
||||
return out
|
||||
|
||||
# Weeeird; we get mypy errors returning dict[int, type] but
|
||||
# dict[int, typing.Type] or dict[int, type[Any]] works..
|
||||
def _decode(self, data: str, types_by_id: dict[int, type[Any]],
|
||||
opname: str) -> Any:
|
||||
def _from_dict(self, data: dict, types_by_id: dict[int, type[Any]],
|
||||
opname: str) -> Any:
|
||||
"""Decode a message from a json string."""
|
||||
msgfull = json.loads(data)
|
||||
assert isinstance(msgfull, dict)
|
||||
msgdict: Optional[dict]
|
||||
if self._type_key is not None:
|
||||
m_id = msgfull.pop(self._type_key)
|
||||
msgdict = msgfull
|
||||
assert isinstance(m_id, int)
|
||||
else:
|
||||
m_id = msgfull.get('t')
|
||||
msgdict = msgfull.get('m')
|
||||
|
||||
m_id = data.get('t')
|
||||
# Allow omitting 'm' dict if its empty.
|
||||
msgdict = data.get('m', {})
|
||||
|
||||
assert isinstance(m_id, int)
|
||||
assert isinstance(msgdict, dict)
|
||||
|
||||
@ -210,22 +196,7 @@ class MessageProtocol:
|
||||
if msgtype is None:
|
||||
raise UnregisteredMessageIDError(
|
||||
f'Got unregistered {opname} id of {m_id}.')
|
||||
out = dataclass_from_dict(msgtype, msgdict)
|
||||
|
||||
# Special case: if we get EmptyResponse, we simply return None.
|
||||
if isinstance(out, EmptyResponse):
|
||||
return None
|
||||
|
||||
# Special case: a remote error occurred. Raise a local Exception
|
||||
# instead of returning the message.
|
||||
if isinstance(out, ErrorResponse):
|
||||
assert opname == 'response'
|
||||
if (self.preserve_clean_errors
|
||||
and out.error_type is ErrorType.CLEAN):
|
||||
raise CleanError(out.error_message)
|
||||
raise RemoteError(out.error_message)
|
||||
|
||||
return out
|
||||
return dataclass_from_dict(msgtype, msgdict)
|
||||
|
||||
def _get_module_header(self,
|
||||
part: Literal['sender', 'receiver'],
|
||||
|
||||
@ -49,6 +49,10 @@ class MessageReceiver:
|
||||
def __init__(self, protocol: MessageProtocol) -> None:
|
||||
self.protocol = protocol
|
||||
self._handlers: dict[type[Message], Callable] = {}
|
||||
self._decode_filter_call: Optional[Callable[[Any, dict, Message],
|
||||
None]] = None
|
||||
self._encode_filter_call: Optional[Callable[[Any, Response, dict],
|
||||
None]] = None
|
||||
|
||||
# noinspection PyProtectedMember
|
||||
def register_handler(
|
||||
@ -59,6 +63,9 @@ class MessageReceiver:
|
||||
type annotation.
|
||||
"""
|
||||
# TODO: can use types.GenericAlias in 3.9.
|
||||
# (hmm though now that we're there, it seems a drop-in
|
||||
# replace gives us errors. Should re-test in 3.10 as it seems
|
||||
# that typing_extensions handles it differently in that case)
|
||||
from typing import _GenericAlias # type: ignore
|
||||
from typing import get_type_hints, get_args
|
||||
|
||||
@ -136,6 +143,30 @@ class MessageReceiver:
|
||||
# Ok; we're good!
|
||||
self._handlers[msgtype] = call
|
||||
|
||||
def decode_filter_method(
|
||||
self, call: Callable[[Any, dict, Message], None]
|
||||
) -> Callable[[Any, dict, Message], None]:
|
||||
"""Function decorator for defining a decode filter.
|
||||
|
||||
Decode filters can be used to extract extra data from incoming
|
||||
message dicts.
|
||||
"""
|
||||
assert self._decode_filter_call is None
|
||||
self._decode_filter_call = call
|
||||
return call
|
||||
|
||||
def encode_filter_method(
|
||||
self, call: Callable[[Any, Response, dict], None]
|
||||
) -> Callable[[Any, Response, dict], None]:
|
||||
"""Function decorator for defining an encode filter.
|
||||
|
||||
Encode filters can be used to add extra data to the message
|
||||
dict before is is encoded to a string and sent out.
|
||||
"""
|
||||
assert self._encode_filter_call is None
|
||||
self._encode_filter_call = call
|
||||
return call
|
||||
|
||||
def validate(self, log_only: bool = False) -> None:
|
||||
"""Check for handler completeness, valid types, etc."""
|
||||
for msgtype in self.protocol.message_ids_by_type.keys():
|
||||
@ -149,16 +180,22 @@ class MessageReceiver:
|
||||
else:
|
||||
raise TypeError(msg)
|
||||
|
||||
def _decode_incoming_message(self,
|
||||
def _decode_incoming_message(self, bound_obj: Any,
|
||||
msg: str) -> tuple[Message, type[Message]]:
|
||||
# Decode the incoming message.
|
||||
msg_decoded = self.protocol.decode_message(msg)
|
||||
msg_dict = self.protocol.decode_dict(msg)
|
||||
msg_decoded = self.protocol.message_from_dict(msg_dict)
|
||||
msgtype = type(msg_decoded)
|
||||
assert issubclass(msgtype, Message)
|
||||
if self._decode_filter_call is not None:
|
||||
self._decode_filter_call(bound_obj, msg_dict, msg_decoded)
|
||||
|
||||
return msg_decoded, msgtype
|
||||
|
||||
def _encode_response(self, response: Optional[Response],
|
||||
msgtype: type[Message]) -> str:
|
||||
def encode_user_response(self, bound_obj: Any,
|
||||
response: Optional[Response],
|
||||
msgtype: type[Message]) -> str:
|
||||
"""Encode a response provided by the user for sending."""
|
||||
|
||||
# A return value of None equals EmptyResponse.
|
||||
if response is None:
|
||||
@ -168,7 +205,18 @@ class MessageReceiver:
|
||||
# (user should never explicitly return error-responses)
|
||||
assert not isinstance(response, ErrorResponse)
|
||||
assert type(response) in msgtype.get_response_types()
|
||||
return self.protocol.encode_response(response)
|
||||
response_dict = self.protocol.response_to_dict(response)
|
||||
if self._encode_filter_call is not None:
|
||||
self._encode_filter_call(bound_obj, response, response_dict)
|
||||
return self.protocol.encode_dict(response_dict)
|
||||
|
||||
def encode_error_response(self, bound_obj: Any, exc: Exception) -> str:
|
||||
"""Given an error, return a response ready for sending."""
|
||||
response = self.protocol.error_to_response(exc)
|
||||
response_dict = self.protocol.response_to_dict(response)
|
||||
if self._encode_filter_call is not None:
|
||||
self._encode_filter_call(bound_obj, response, response_dict)
|
||||
return self.protocol.encode_dict(response_dict)
|
||||
|
||||
def handle_raw_message(self,
|
||||
bound_obj: Any,
|
||||
@ -183,18 +231,20 @@ class MessageReceiver:
|
||||
"""
|
||||
assert not self.is_async, "can't call sync handler on async receiver"
|
||||
try:
|
||||
msg_decoded, msgtype = self._decode_incoming_message(msg)
|
||||
msg_decoded, msgtype = self._decode_incoming_message(
|
||||
bound_obj, msg)
|
||||
handler = self._handlers.get(msgtype)
|
||||
if handler is None:
|
||||
raise RuntimeError(f'Got unhandled message type: {msgtype}.')
|
||||
result = handler(bound_obj, msg_decoded)
|
||||
return self._encode_response(result, msgtype)
|
||||
response = handler(bound_obj, msg_decoded)
|
||||
assert isinstance(response, (Response, type(None)))
|
||||
return self.encode_user_response(bound_obj, response, msgtype)
|
||||
|
||||
except Exception as exc:
|
||||
if (raise_unregistered
|
||||
and isinstance(exc, UnregisteredMessageIDError)):
|
||||
raise
|
||||
return self.protocol.encode_error_response(exc)
|
||||
return self.encode_error_response(bound_obj, exc)
|
||||
|
||||
async def handle_raw_message_async(
|
||||
self,
|
||||
@ -207,18 +257,20 @@ class MessageReceiver:
|
||||
"""
|
||||
assert self.is_async, "can't call async handler on sync receiver"
|
||||
try:
|
||||
msg_decoded, msgtype = self._decode_incoming_message(msg)
|
||||
msg_decoded, msgtype = self._decode_incoming_message(
|
||||
bound_obj, msg)
|
||||
handler = self._handlers.get(msgtype)
|
||||
if handler is None:
|
||||
raise RuntimeError(f'Got unhandled message type: {msgtype}.')
|
||||
result = await handler(bound_obj, msg_decoded)
|
||||
return self._encode_response(result, msgtype)
|
||||
response = await handler(bound_obj, msg_decoded)
|
||||
assert isinstance(response, (Response, type(None)))
|
||||
return self.encode_user_response(bound_obj, response, msgtype)
|
||||
|
||||
except Exception as exc:
|
||||
if (raise_unregistered
|
||||
and isinstance(exc, UnregisteredMessageIDError)):
|
||||
raise
|
||||
return self.protocol.encode_error_response(exc)
|
||||
return self.encode_error_response(bound_obj, exc)
|
||||
|
||||
|
||||
class BoundMessageReceiver:
|
||||
@ -237,3 +289,7 @@ class BoundMessageReceiver:
|
||||
def protocol(self) -> MessageProtocol:
|
||||
"""Protocol associated with this receiver."""
|
||||
return self._receiver.protocol
|
||||
|
||||
def encode_error_response(self, exc: Exception) -> str:
|
||||
"""Given an error, return a response ready to send."""
|
||||
return self._receiver.encode_error_response(self._obj, exc)
|
||||
|
||||
@ -8,12 +8,13 @@ from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, TypeVar
|
||||
|
||||
from efro.message._message import Response
|
||||
from efro.error import CleanError, RemoteError
|
||||
from efro.message._message import (EmptyResponse, ErrorResponse, ErrorType)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Any, Callable, Optional, Awaitable
|
||||
|
||||
from efro.message._message import Message
|
||||
from efro.message._message import Message, Response
|
||||
from efro.message._protocol import MessageProtocol
|
||||
|
||||
TM = TypeVar('TM', bound='MessageSender')
|
||||
@ -44,6 +45,10 @@ class MessageSender:
|
||||
self._send_raw_message_call: Optional[Callable[[Any, str], str]] = None
|
||||
self._send_async_raw_message_call: Optional[Callable[
|
||||
[Any, str], Awaitable[str]]] = None
|
||||
self._encode_filter_call: Optional[Callable[[Any, Message, dict],
|
||||
None]] = None
|
||||
self._decode_filter_call: Optional[Callable[[Any, dict, Response],
|
||||
None]] = None
|
||||
|
||||
def send_method(
|
||||
self, call: Callable[[Any, str],
|
||||
@ -61,6 +66,30 @@ class MessageSender:
|
||||
self._send_async_raw_message_call = call
|
||||
return call
|
||||
|
||||
def encode_filter_method(
|
||||
self, call: Callable[[Any, Message, dict], None]
|
||||
) -> Callable[[Any, Message, dict], None]:
|
||||
"""Function decorator for defining an encode filter.
|
||||
|
||||
Encode filters can be used to add extra data to the message
|
||||
dict before is is encoded to a string and sent out.
|
||||
"""
|
||||
assert self._encode_filter_call is None
|
||||
self._encode_filter_call = call
|
||||
return call
|
||||
|
||||
def decode_filter_method(
|
||||
self, call: Callable[[Any, dict, Response], None]
|
||||
) -> Callable[[Any, dict, Response], None]:
|
||||
"""Function decorator for defining a decode filter.
|
||||
|
||||
Decode filters can be used to extract extra data from incoming
|
||||
message dicts.
|
||||
"""
|
||||
assert self._decode_filter_call is None
|
||||
self._decode_filter_call = call
|
||||
return call
|
||||
|
||||
def send(self, bound_obj: Any, message: Message) -> Optional[Response]:
|
||||
"""Send a message and receive a response.
|
||||
|
||||
@ -69,14 +98,44 @@ class MessageSender:
|
||||
if self._send_raw_message_call is None:
|
||||
raise RuntimeError('send() is unimplemented for this type.')
|
||||
|
||||
msg_encoded = self.protocol.encode_message(message)
|
||||
msg_encoded = self.encode_message(bound_obj, message)
|
||||
|
||||
response_encoded = self._send_raw_message_call(bound_obj, msg_encoded)
|
||||
response = self.protocol.decode_response(response_encoded)
|
||||
assert isinstance(response, (Response, type(None)))
|
||||
|
||||
response = self.decode_response(bound_obj, response_encoded)
|
||||
assert (response is None
|
||||
or type(response) in type(message).get_response_types())
|
||||
return response
|
||||
|
||||
def encode_message(self, bound_obj: Any, message: Message) -> str:
|
||||
"""Encode a message for sending."""
|
||||
msg_dict = self.protocol.message_to_dict(message)
|
||||
if self._encode_filter_call is not None:
|
||||
self._encode_filter_call(bound_obj, message, msg_dict)
|
||||
return self.protocol.encode_dict(msg_dict)
|
||||
|
||||
def decode_response(self, bound_obj: Any,
|
||||
response_encoded: str) -> Optional[Response]:
|
||||
"""Decode, filter, and possibly act on raw response data."""
|
||||
response_dict = self.protocol.decode_dict(response_encoded)
|
||||
response = self.protocol.response_from_dict(response_dict)
|
||||
if self._decode_filter_call is not None:
|
||||
self._decode_filter_call(bound_obj, response_dict, response)
|
||||
|
||||
# Special case: if we get EmptyResponse, we simply return None.
|
||||
if isinstance(response, EmptyResponse):
|
||||
return None
|
||||
|
||||
# Special case: a remote error occurred. Raise a local Exception
|
||||
# instead of returning the message.
|
||||
if isinstance(response, ErrorResponse):
|
||||
if (self.protocol.preserve_clean_errors
|
||||
and response.error_type is ErrorType.CLEAN):
|
||||
raise CleanError(response.error_message)
|
||||
raise RemoteError(response.error_message)
|
||||
|
||||
return response
|
||||
|
||||
async def send_async(self, bound_obj: Any,
|
||||
message: Message) -> Optional[Response]:
|
||||
"""Send a message asynchronously using asyncio.
|
||||
@ -87,11 +146,12 @@ class MessageSender:
|
||||
if self._send_async_raw_message_call is None:
|
||||
raise RuntimeError('send_async() is unimplemented for this type.')
|
||||
|
||||
msg_encoded = self.protocol.encode_message(message)
|
||||
msg_encoded = self.encode_message(bound_obj, message)
|
||||
|
||||
response_encoded = await self._send_async_raw_message_call(
|
||||
bound_obj, msg_encoded)
|
||||
response = self.protocol.decode_response(response_encoded)
|
||||
assert isinstance(response, (Response, type(None)))
|
||||
|
||||
response = self.decode_response(bound_obj, response_encoded)
|
||||
assert (response is None
|
||||
or type(response) in type(message).get_response_types())
|
||||
return response
|
||||
|
||||
@ -4,8 +4,9 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import datetime
|
||||
import os
|
||||
import time
|
||||
import datetime
|
||||
import weakref
|
||||
import functools
|
||||
from enum import Enum
|
||||
@ -652,7 +653,6 @@ def unchanging_hostname() -> str:
|
||||
network conditions. (A Mac will tend to go from Foo to Foo.local,
|
||||
Foo.lan etc. throughout its various adventures)
|
||||
"""
|
||||
import os
|
||||
import platform
|
||||
import subprocess
|
||||
|
||||
|
||||
@ -96,12 +96,16 @@ def writefile(path: Union[str, Path], txt: str) -> None:
|
||||
outfile.write(txt)
|
||||
|
||||
|
||||
def replace_one(opstr: str, old: str, new: str) -> str:
|
||||
"""Replace text ensuring that exactly one occurrence is replaced."""
|
||||
count = opstr.count(old)
|
||||
if count != 1:
|
||||
raise Exception(
|
||||
f'expected 1 string occurrence; found {count}. String = {old}')
|
||||
def replace_exact(opstr: str, old: str, new: str, count: int = 1) -> str:
|
||||
"""Replace text ensuring that exactly x occurrences are replaced.
|
||||
|
||||
Useful when filtering data in some predefined way to ensure the original
|
||||
has not changed.
|
||||
"""
|
||||
found = opstr.count(old)
|
||||
if found != count:
|
||||
raise Exception(f'expected {count} string occurrence(s);'
|
||||
f' found {found}. String = {old}')
|
||||
return opstr.replace(old, new)
|
||||
|
||||
|
||||
|
||||
@ -177,6 +177,31 @@ def spelling() -> None:
|
||||
_spelling(sys.argv[2:])
|
||||
|
||||
|
||||
def xcodebuild() -> None:
|
||||
"""Run xcodebuild with added smarts."""
|
||||
from efrotools.xcode import XCodeBuild
|
||||
XCodeBuild(projroot=str(PROJROOT), args=sys.argv[2:]).run()
|
||||
|
||||
|
||||
def xcoderun() -> None:
|
||||
"""Run an xcode build in the terminal."""
|
||||
import os
|
||||
import subprocess
|
||||
from efro.error import CleanError
|
||||
from efrotools.xcode import project_build_path
|
||||
if len(sys.argv) != 5:
|
||||
raise CleanError(
|
||||
'Expected 3 args: <xcode project path> <configuration name>')
|
||||
project_path = os.path.abspath(sys.argv[2])
|
||||
scheme = sys.argv[3]
|
||||
configuration = sys.argv[4]
|
||||
path = project_build_path(projroot=str(PROJROOT),
|
||||
project_path=project_path,
|
||||
scheme=scheme,
|
||||
configuration=configuration)
|
||||
subprocess.run(path, check=True)
|
||||
|
||||
|
||||
def pyver() -> None:
|
||||
"""Prints the Python version used by this project."""
|
||||
from efrotools import PYVER
|
||||
@ -241,8 +266,9 @@ def gen_empty_py_init() -> None:
|
||||
Used as part of meta builds.
|
||||
"""
|
||||
from efro.terminal import Clr
|
||||
from efro.error import CleanError
|
||||
if len(sys.argv) != 3:
|
||||
raise Exception('Expected a single path arg.')
|
||||
raise CleanError('Expected a single path arg.')
|
||||
outpath = Path(sys.argv[2])
|
||||
outpath.parent.mkdir(parents=True, exist_ok=True)
|
||||
print(f'Meta-building {Clr.BLD}{outpath}{Clr.RST}')
|
||||
@ -374,8 +400,9 @@ def androidstudiocode() -> None:
|
||||
def tool_config_install() -> None:
|
||||
"""Install a tool config file (with some filtering)."""
|
||||
from efro.terminal import Clr
|
||||
from efro.error import CleanError
|
||||
if len(sys.argv) != 4:
|
||||
raise Exception('expected 2 args')
|
||||
raise CleanError('expected 2 args')
|
||||
src = Path(sys.argv[2])
|
||||
dst = Path(sys.argv[3])
|
||||
|
||||
@ -590,6 +617,7 @@ def makefile_target_list() -> None:
|
||||
Takes a single argument: a path to a Makefile.
|
||||
"""
|
||||
from dataclasses import dataclass
|
||||
from efro.error import CleanError
|
||||
from efro.terminal import Clr
|
||||
|
||||
@dataclass
|
||||
@ -599,7 +627,7 @@ def makefile_target_list() -> None:
|
||||
title: str
|
||||
|
||||
if len(sys.argv) != 3:
|
||||
raise RuntimeError('Expected exactly one filename arg.')
|
||||
raise CleanError('Expected exactly one filename arg.')
|
||||
|
||||
with open(sys.argv[2], encoding='utf-8') as infile:
|
||||
lines = infile.readlines()
|
||||
@ -663,3 +691,34 @@ def echo() -> None:
|
||||
out.append(arg)
|
||||
out.append(Clr.RST)
|
||||
print(''.join(out))
|
||||
|
||||
|
||||
def urandom_pretty() -> None:
|
||||
"""Spits out urandom bytes formatted for source files."""
|
||||
# Note; this is not especially efficient. It should probably be rewritten
|
||||
# if ever needed in a performance-sensitive context.
|
||||
import os
|
||||
from efro.error import CleanError
|
||||
|
||||
if len(sys.argv) not in (3, 4):
|
||||
raise CleanError(
|
||||
'Expected one arg (count) and possibly two (line len).')
|
||||
size = int(sys.argv[2])
|
||||
linemax = 72 if len(sys.argv) < 4 else int(sys.argv[3])
|
||||
|
||||
val = os.urandom(size)
|
||||
lines: list[str] = []
|
||||
line = b''
|
||||
|
||||
for i in range(len(val)):
|
||||
char = val[i:i + 1]
|
||||
thislinelen = len(repr(line + char))
|
||||
if thislinelen > linemax:
|
||||
lines.append(repr(line))
|
||||
line = b''
|
||||
line += char
|
||||
if line:
|
||||
lines.append(repr(line))
|
||||
|
||||
bstr = '\n'.join(str(l) for l in lines)
|
||||
print(f'({bstr})')
|
||||
|
||||
@ -8,16 +8,17 @@ import os
|
||||
import subprocess
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from efrotools import PYVER, readfile, writefile, replace_one
|
||||
from efrotools import readfile, writefile, replace_exact
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Any
|
||||
|
||||
ENABLE_OPENSSL = True
|
||||
NEWER_PY_TEST = True
|
||||
PY_VER = '3.10'
|
||||
PY_VER_EXACT_ANDROID = '3.10.4'
|
||||
PY_VER_EXACT_APPLE = '3.10.4'
|
||||
|
||||
PY_VER_EXACT_ANDROID = '3.9.10'
|
||||
PY_VER_EXACT_APPLE = '3.9.6'
|
||||
# ANDROID_PYTHON_REPO = 'https://github.com/yan12125/python3-android.git'
|
||||
ANDROID_PYTHON_REPO = 'https://github.com/GRRedWings/python3-android'
|
||||
|
||||
# Filenames we prune from Python lib dirs in source repo to cut down on size.
|
||||
PRUNE_LIB_NAMES = [
|
||||
@ -66,66 +67,80 @@ def build_apple(arch: str, debug: bool = False) -> None:
|
||||
# broke in the underlying build even on old commits so keeping it
|
||||
# locked for now...
|
||||
# run('git checkout bf1ed73d0d5ff46862ba69dd5eb2ffaeff6f19b6')
|
||||
subprocess.run(['git', 'checkout', PYVER], check=True)
|
||||
subprocess.run(['git', 'checkout', PY_VER], check=True)
|
||||
|
||||
txt = readfile('Makefile')
|
||||
|
||||
# Fix a bug where spaces in PATH cause errors (darn you vmware fusion!)
|
||||
txt = replace_one(
|
||||
txt, '&& PATH=$(PROJECT_DIR)/$(PYTHON_DIR-macOS)/dist/bin:$(PATH) .',
|
||||
'&& PATH="$(PROJECT_DIR)/$(PYTHON_DIR-macOS)/dist/bin:$(PATH)" .')
|
||||
txt = replace_exact(
|
||||
txt,
|
||||
'\t\tPATH=$(PROJECT_DIR)/$(PYTHON_DIR-macOS)/_install/bin:$(PATH)',
|
||||
'\t\tPATH="$(PROJECT_DIR)/$(PYTHON_DIR-macOS)/_install/bin:$(PATH)"')
|
||||
|
||||
# Turn doc strings on; looks like it only adds a few hundred k.
|
||||
txt = txt.replace('--without-doc-strings', '--with-doc-strings')
|
||||
txt = replace_exact(txt,
|
||||
'--without-doc-strings',
|
||||
'--with-doc-strings',
|
||||
count=2)
|
||||
|
||||
# Set mac/ios version reqs
|
||||
# (see issue with utimensat and futimens).
|
||||
txt = replace_one(txt, 'MACOSX_DEPLOYMENT_TARGET=10.8',
|
||||
'MACOSX_DEPLOYMENT_TARGET=10.15')
|
||||
# And equivalent iOS (11+).
|
||||
txt = replace_one(txt, 'CFLAGS-iOS=-mios-version-min=8.0',
|
||||
'CFLAGS-iOS=-mios-version-min=13.0')
|
||||
# Ditto for tvOS.
|
||||
txt = replace_one(txt, 'CFLAGS-tvOS=-mtvos-version-min=9.0',
|
||||
'CFLAGS-tvOS=-mtvos-version-min=13.0')
|
||||
# Customize our minimum version requirements
|
||||
txt = replace_exact(
|
||||
txt,
|
||||
'CFLAGS-macOS=-mmacosx-version-min=10.15\n',
|
||||
'CFLAGS-macOS=-mmacosx-version-min=10.15\n',
|
||||
)
|
||||
txt = replace_exact(
|
||||
txt,
|
||||
'CFLAGS-iOS=-mios-version-min=12.0 ',
|
||||
'CFLAGS-iOS=-mios-version-min=12.0 ',
|
||||
)
|
||||
txt = replace_exact(
|
||||
txt,
|
||||
'CFLAGS-tvOS=-mtvos-version-min=9.0 ',
|
||||
'CFLAGS-tvOS=-mtvos-version-min=9.0 ',
|
||||
)
|
||||
|
||||
assert '--with-pydebug' not in txt
|
||||
if debug:
|
||||
|
||||
# Add debug build flag
|
||||
# (Currently expect to find 2 instances of this).
|
||||
dline = '--with-doc-strings --enable-ipv6 --without-ensurepip'
|
||||
splitlen = len(txt.split(dline))
|
||||
if splitlen != 3:
|
||||
raise Exception('unexpected configure lines')
|
||||
txt = txt.replace(dline, '--with-pydebug ' + dline)
|
||||
txt = replace_exact(
|
||||
txt,
|
||||
'--enable-ipv6 --without-ensurepip ',
|
||||
'--enable-ipv6 --with-pydebug --without-ensurepip ',
|
||||
count=2,
|
||||
)
|
||||
|
||||
# Debug has a different name.
|
||||
# (Currently expect to replace 12 instances of this).
|
||||
dline = ('python$(PYTHON_VER)'
|
||||
if NEWER_PY_TEST else 'python$(PYTHON_VER)m')
|
||||
splitlen = len(txt.split(dline))
|
||||
if splitlen != 13:
|
||||
raise RuntimeError(f'Unexpected configure line count {splitlen}.')
|
||||
txt = txt.replace(
|
||||
dline, 'python$(PYTHON_VER)d'
|
||||
if NEWER_PY_TEST else 'python$(PYTHON_VER)dm')
|
||||
# Debug lib has a different name.
|
||||
txt = replace_exact(txt,
|
||||
'python$(PYTHON_VER).a',
|
||||
'python$(PYTHON_VER)d.a',
|
||||
count=2)
|
||||
|
||||
# Inject our custom modifications to fire before building.
|
||||
txt = txt.replace(
|
||||
' # Configure target Python\n',
|
||||
' cd $$(PYTHON_DIR-$1) && '
|
||||
f'../../../../../tools/pcommand python_apple_patch {arch}\n'
|
||||
' # Configure target Python\n',
|
||||
)
|
||||
txt = replace_exact(txt,
|
||||
'/include/python$(PYTHON_VER)',
|
||||
'/include/python$(PYTHON_VER)d',
|
||||
count=4)
|
||||
|
||||
# Use python3 instead of python for libffi setup script
|
||||
txt = replace_one(
|
||||
txt,
|
||||
'cd $$(LIBFFI_DIR-$1) && python generate-darwin-source-and-headers.py'
|
||||
" --only-$(shell echo $1 | tr '[:upper:]' '[:lower:]')",
|
||||
'cd $$(LIBFFI_DIR-$1) && python3 generate-darwin-source-and-headers.py'
|
||||
" --only-$(shell echo $1 | tr '[:upper:]' '[:lower:]')",
|
||||
)
|
||||
# Inject our custom modifications to fire right after their normal
|
||||
# Setup.local filtering and right before building (and pass the same
|
||||
# 'slice' value they use so we can use it too).
|
||||
txt = replace_exact(
|
||||
txt, '\t\t\tsed -e "s/{{slice}}/$$(SLICE-$$(SDK-$(target)))/g" \\\n'
|
||||
'\t\t\t> $$(PYTHON_DIR-$(target))/Modules/Setup.local\n',
|
||||
'\t\t\tsed -e "s/{{slice}}/$$(SLICE-$$(SDK-$(target)))/g" \\\n'
|
||||
'\t\t\t> $$(PYTHON_DIR-$(target))/Modules/Setup.local\n'
|
||||
'\tcd $$(PYTHON_DIR-$(target)) && '
|
||||
f'../../../../../tools/pcommand python_apple_patch {arch} '
|
||||
'"$$(SLICE-$$(SDK-$(target)))"\n')
|
||||
txt = replace_exact(
|
||||
txt, '\t\t\tsed -e "s/{{slice}}/$$(SLICE-macosx)/g" \\\n'
|
||||
'\t\t\t> $$(PYTHON_DIR-$(os))/Modules/Setup.local\n',
|
||||
'\t\t\tsed -e "s/{{slice}}/$$(SLICE-macosx)/g" \\\n'
|
||||
'\t\t\t> $$(PYTHON_DIR-$(os))/Modules/Setup.local\n'
|
||||
'\tcd $$(PYTHON_DIR-$(os)) && '
|
||||
f'../../../../../tools/pcommand python_apple_patch {arch} '
|
||||
'"$$(SLICE-macosx)"\n')
|
||||
|
||||
writefile('Makefile', txt)
|
||||
|
||||
@ -146,20 +161,6 @@ def build_apple(arch: str, debug: bool = False) -> None:
|
||||
print('python build complete! (apple/' + arch + ')')
|
||||
|
||||
|
||||
def apple_patch(arch: str) -> None:
|
||||
"""Run necessary patches on an apple archive before building."""
|
||||
|
||||
# Here's the deal: we want our custom static python libraries to
|
||||
# be as similar as possible on apple platforms and android, so let's
|
||||
# blow away all the tweaks that this setup does to Setup.local and
|
||||
# instead apply our very similar ones directly to Setup, just as we
|
||||
# do for android.
|
||||
with open('Modules/Setup.local', 'w', encoding='utf-8') as outfile:
|
||||
outfile.write('# cleared by efrotools build\n')
|
||||
|
||||
_patch_setup_file('apple', arch)
|
||||
|
||||
|
||||
def build_android(rootdir: str, arch: str, debug: bool = False) -> None:
|
||||
"""Run a build for android with the given architecture.
|
||||
|
||||
@ -170,14 +171,14 @@ def build_android(rootdir: str, arch: str, debug: bool = False) -> None:
|
||||
subprocess.run(['rm', '-rf', builddir], check=True)
|
||||
subprocess.run(['mkdir', '-p', 'build'], check=True)
|
||||
subprocess.run(
|
||||
[
|
||||
'git', 'clone', 'https://github.com/yan12125/python3-android.git',
|
||||
builddir
|
||||
],
|
||||
['git', 'clone', ANDROID_PYTHON_REPO, builddir],
|
||||
check=True,
|
||||
)
|
||||
os.chdir(builddir)
|
||||
|
||||
# TEMP - use 3.9.6 branch
|
||||
# subprocess.run(['git', 'checkout', PY_VER_EXACT_ANDROID], check=True)
|
||||
|
||||
# These builds require ANDROID_NDK to be set; make sure that's the case.
|
||||
os.environ['ANDROID_NDK'] = subprocess.check_output(
|
||||
[f'{rootdir}/tools/pcommand', 'android_sdk_utils',
|
||||
@ -185,9 +186,9 @@ def build_android(rootdir: str, arch: str, debug: bool = False) -> None:
|
||||
|
||||
# Disable builds for dependencies we don't use.
|
||||
ftxt = readfile('Android/build_deps.py')
|
||||
# ftxt = replace_one(ftxt, ' NCurses,\n',
|
||||
# ftxt = replace_exact(ftxt, ' NCurses,\n',
|
||||
# '# NCurses,\n',)
|
||||
ftxt = replace_one(
|
||||
ftxt = replace_exact(
|
||||
ftxt,
|
||||
' '
|
||||
'BZip2, GDBM, LibFFI, LibUUID, OpenSSL, Readline, SQLite, XZ, ZLib,\n',
|
||||
@ -196,13 +197,14 @@ def build_android(rootdir: str, arch: str, debug: bool = False) -> None:
|
||||
)
|
||||
|
||||
# Older ssl seems to choke on newer ndk layouts.
|
||||
ftxt = replace_one(
|
||||
ftxt,
|
||||
"source = 'https://www.openssl.org/source/openssl-1.1.1h.tar.gz'",
|
||||
"source = 'https://www.openssl.org/source/openssl-1.1.1l.tar.gz'")
|
||||
if bool(False):
|
||||
ftxt = replace_exact(
|
||||
ftxt,
|
||||
"source = 'https://www.openssl.org/source/openssl-1.1.1h.tar.gz'",
|
||||
"source = 'https://www.openssl.org/source/openssl-1.1.1l.tar.gz'")
|
||||
|
||||
# Give ourselves a handle to patch the OpenSSL build.
|
||||
ftxt = replace_one(
|
||||
ftxt = replace_exact(
|
||||
ftxt,
|
||||
' # OpenSSL handles NDK internal paths by itself',
|
||||
' # Ericf addition: do some patching:\n'
|
||||
@ -217,8 +219,9 @@ def build_android(rootdir: str, arch: str, debug: bool = False) -> None:
|
||||
# of Python and also inject some code to modify bits of python
|
||||
# after it is extracted.
|
||||
ftxt = readfile('build.sh')
|
||||
ftxt = replace_one(ftxt, 'PYVER=3.9.0', f'PYVER={PY_VER_EXACT_ANDROID}')
|
||||
ftxt = replace_one(
|
||||
|
||||
ftxt = replace_exact(ftxt, 'PYVER=3.10.4', f'PYVER={PY_VER_EXACT_ANDROID}')
|
||||
ftxt = replace_exact(
|
||||
ftxt, ' popd\n', f' ../../../tools/pcommand'
|
||||
f' python_android_patch Python-{PY_VER_EXACT_ANDROID}\n popd\n')
|
||||
writefile('build.sh', ftxt)
|
||||
@ -231,14 +234,29 @@ def build_android(rootdir: str, arch: str, debug: bool = False) -> None:
|
||||
print('python build complete! (android/' + arch + ')')
|
||||
|
||||
|
||||
def apple_patch(arch: str, slc: str) -> None:
|
||||
"""Run necessary patches on an apple archive before building."""
|
||||
|
||||
# Here's the deal: we want our custom static python libraries to
|
||||
# be as similar as possible on apple platforms and android, so let's
|
||||
# blow away all the tweaks that this setup does to Setup.local and
|
||||
# instead apply our very similar ones directly to Setup, just as we
|
||||
# do for android.
|
||||
with open('Modules/Setup.local', 'w', encoding='utf-8') as outfile:
|
||||
outfile.write('# cleared by efrotools build\n')
|
||||
|
||||
_patch_setup_file('apple', arch, slc)
|
||||
|
||||
|
||||
def android_patch() -> None:
|
||||
"""Run necessary patches on an android archive before building."""
|
||||
_patch_setup_file('android', '?')
|
||||
_patch_setup_file('android', '?', '?')
|
||||
|
||||
|
||||
def _patch_setup_file(platform: str, arch: str) -> None:
|
||||
def _patch_setup_file(platform: str, arch: str, slc: str) -> None:
|
||||
# pylint: disable=too-many-locals
|
||||
# pylint: disable=too-many-statements
|
||||
|
||||
fname = 'Modules/Setup'
|
||||
ftxt = readfile(fname)
|
||||
|
||||
@ -252,18 +270,33 @@ def _patch_setup_file(platform: str, arch: str) -> None:
|
||||
hash_ex = ' -DUSE_SSL -lssl -lcrypto'
|
||||
lzma_ex = ' -llzma'
|
||||
elif platform == 'apple':
|
||||
prefix = '$(srcdir)/Android/sysroot/usr'
|
||||
# These should basically match what the Python-Apple-support dist
|
||||
# does in its patch/Python/Setup.embedded.
|
||||
# (We do our own thing and ignore the Setup.local it generates
|
||||
# for the sake of cross-platform consistency, but still need to
|
||||
# do what we do the same way they do what they do)
|
||||
|
||||
def _slrp(val: str) -> str:
|
||||
# In the distro, the Makefile does this filtering to Setup.local
|
||||
return val.replace('{{slice}}', slc)
|
||||
|
||||
uuid_ex = ''
|
||||
zlib_ex = ' -I$(prefix)/include -lz'
|
||||
bz2_ex = (' -I$(srcdir)/../Support/BZip2/Headers'
|
||||
' -L$(srcdir)/../Support/BZip2 -lbzip2')
|
||||
ssl_ex = (' -I$(srcdir)/../Support/OpenSSL/Headers'
|
||||
' -L$(srcdir)/../Support/OpenSSL -lOpenSSL -DUSE_SSL')
|
||||
bz2_ex = _slrp(
|
||||
' -I$(srcdir)/../Support/BZip2.xcframework/{{slice}}/Headers'
|
||||
' -L$(srcdir)/../Support/BZip2.xcframework/{{slice}} -lbzip2')
|
||||
ssl_ex = _slrp(
|
||||
' -I$(srcdir)/../Support/OpenSSL.xcframework/{{slice}}/Headers'
|
||||
' -L$(srcdir)/../Support/OpenSSL.xcframework/{{slice}}'
|
||||
' -lOpenSSL -DUSE_SSL')
|
||||
sqlite_ex = ' -I$(srcdir)/Modules/_sqlite'
|
||||
hash_ex = (' -I$(srcdir)/../Support/OpenSSL/Headers'
|
||||
' -L$(srcdir)/../Support/OpenSSL -lOpenSSL -DUSE_SSL')
|
||||
lzma_ex = (' -I$(srcdir)/../Support/XZ/Headers'
|
||||
' -L$(srcdir)/../Support/XZ/ -lxz')
|
||||
hash_ex = _slrp(
|
||||
' -I$(srcdir)/../Support/OpenSSL.xcframework/{{slice}}/Headers'
|
||||
' -L$(srcdir)/../Support/OpenSSL.xcframework/{{slice}}'
|
||||
' -lOpenSSL -DUSE_SSL')
|
||||
lzma_ex = _slrp(
|
||||
' -I$(srcdir)/../Support/XZ.xcframework/{{slice}}/Headers'
|
||||
' -L$(srcdir)/../Support/XZ.xcframework/{{slice}} -lxz')
|
||||
else:
|
||||
raise RuntimeError(f'Unknown platform {platform}')
|
||||
|
||||
@ -301,13 +334,13 @@ def _patch_setup_file(platform: str, arch: str) -> None:
|
||||
enables += ['_md5']
|
||||
|
||||
for enable in enables:
|
||||
ftxt = replace_one(ftxt, f'#{enable} ', f'{enable} ')
|
||||
ftxt = replace_exact(ftxt, f'#{enable} ', f'{enable} ')
|
||||
cmodules.remove(enable)
|
||||
|
||||
# Disable ones that were enabled:
|
||||
disables = ['xxsubtype']
|
||||
for disable in disables:
|
||||
ftxt = replace_one(ftxt, f'\n{disable} ', f'\n#{disable} ')
|
||||
ftxt = replace_exact(ftxt, f'\n{disable} ', f'\n#{disable} ')
|
||||
|
||||
# Additions:
|
||||
ftxt += '\n# Additions by efrotools:\n'
|
||||
@ -374,10 +407,10 @@ def _patch_setup_file(platform: str, arch: str) -> None:
|
||||
fname = 'Modules/makesetup'
|
||||
txt = readfile(fname)
|
||||
if platform == 'android':
|
||||
txt = replace_one(txt, ' *=*)'
|
||||
' DEFS="$line$NL$DEFS"; continue;;',
|
||||
' [A-Z]*=*) DEFS="$line$NL$DEFS";'
|
||||
' continue;;')
|
||||
txt = replace_exact(txt, ' *=*)'
|
||||
' DEFS="$line$NL$DEFS"; continue;;',
|
||||
' [A-Z]*=*) DEFS="$line$NL$DEFS";'
|
||||
' continue;;')
|
||||
assert txt.count('[A-Z]*=*') == 1
|
||||
writefile(fname, txt)
|
||||
|
||||
@ -394,7 +427,7 @@ def android_patch_ssl() -> None:
|
||||
# but it seems cleaner to just have things work by default.
|
||||
fname = 'crypto/getenv.c'
|
||||
txt = readfile(fname)
|
||||
txt = replace_one(
|
||||
txt = replace_exact(
|
||||
txt,
|
||||
('char *ossl_safe_getenv(const char *name)\n'
|
||||
'{\n'),
|
||||
@ -455,7 +488,7 @@ def gather() -> None:
|
||||
debug = buildtype == 'debug'
|
||||
bsuffix = '_debug' if buildtype == 'debug' else ''
|
||||
bsuffix2 = '-debug' if buildtype == 'debug' else ''
|
||||
libname = 'python' + PYVER + ('d' if debug else '')
|
||||
libname = 'python' + PY_VER + ('d' if debug else '')
|
||||
|
||||
bases = {
|
||||
'mac': f'build/python_apple_mac{bsuffix}/build/macOS',
|
||||
@ -529,7 +562,7 @@ def gather() -> None:
|
||||
bases2['android_arm'] + '/usr/lib/libuuid.a',
|
||||
],
|
||||
'libinst': 'android_armeabi-v7a',
|
||||
'pylib': (bases['android_arm'] + '/usr/lib/python' + PYVER),
|
||||
'pylib': (bases['android_arm'] + '/usr/lib/python' + PY_VER),
|
||||
}, {
|
||||
'name': 'android_arm64',
|
||||
'group': 'android',
|
||||
@ -616,7 +649,7 @@ def gather() -> None:
|
||||
# so let's skip that.
|
||||
fname = f'{assets_src_dst}/site.py'
|
||||
txt = readfile(fname)
|
||||
txt = replace_one(
|
||||
txt = replace_exact(
|
||||
txt,
|
||||
' known_paths = addusersitepackages(known_paths)',
|
||||
' # efro tweak: this craps out on ios/tvos.\n'
|
||||
|
||||
570
tools/efrotools/xcode.py
Normal file
570
tools/efrotools/xcode.py
Normal file
@ -0,0 +1,570 @@
|
||||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Functionality related to Xcode on Apple platforms."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
import shlex
|
||||
from enum import Enum
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from efro.terminal import Clr
|
||||
from efro.error import CleanError
|
||||
from efro.util import assert_never
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Any, Optional
|
||||
|
||||
|
||||
class _Section(Enum):
|
||||
COMPILEC = 'CompileC'
|
||||
MKDIR = 'MkDir'
|
||||
LD = 'Ld'
|
||||
COMPILEASSETCATALOG = 'CompileAssetCatalog'
|
||||
CODESIGN = 'CodeSign'
|
||||
COMPILESTORYBOARD = 'CompileStoryboard'
|
||||
LINKSTORYBOARDS = 'LinkStoryboards'
|
||||
PROCESSINFOPLISTFILE = 'ProcessInfoPlistFile'
|
||||
COPYSWIFTLIBS = 'CopySwiftLibs'
|
||||
REGISTEREXECUTIONPOLICYEXCEPTION = 'RegisterExecutionPolicyException'
|
||||
VALIDATE = 'Validate'
|
||||
TOUCH = 'Touch'
|
||||
REGISTERWITHLAUNCHSERVICES = 'RegisterWithLaunchServices'
|
||||
METALLINK = 'MetalLink'
|
||||
COMPILESWIFT = 'CompileSwift'
|
||||
CREATEBUILDDIRECTORY = 'CreateBuildDirectory'
|
||||
COMPILEMETALFILE = 'CompileMetalFile'
|
||||
COPY = 'Copy'
|
||||
COPYSTRINGSFILE = 'CopyStringsFile'
|
||||
WRITEAUXILIARYFILE = 'WriteAuxiliaryFile'
|
||||
COMPILESWIFTSOURCES = 'CompileSwiftSources'
|
||||
PROCESSPCH = 'ProcessPCH'
|
||||
PROCESSPCHPLUSPLUS = 'ProcessPCH++'
|
||||
PHASESCRIPTEXECUTION = 'PhaseScriptExecution'
|
||||
|
||||
|
||||
class XCodeBuild:
|
||||
"""xcodebuild wrapper with extra bells and whistles."""
|
||||
|
||||
def __init__(self, projroot: str, args: list[str]):
|
||||
self._projroot = projroot
|
||||
self._args = args
|
||||
self._output: list[str] = []
|
||||
self._verbose = os.environ.get('XCODEBUILDVERBOSE', '0') == '1'
|
||||
self._section: Optional[_Section] = None
|
||||
self._section_line_count = 0
|
||||
self._returncode: Optional[int] = None
|
||||
self._project: str = self._argstr(args, '-project')
|
||||
self._scheme: str = self._argstr(args, '-scheme')
|
||||
self._configuration: str = self._argstr(args, '-configuration')
|
||||
|
||||
def run(self) -> None:
|
||||
"""Do the thing."""
|
||||
self._run_cmd(self._build_cmd_args())
|
||||
assert self._returncode is not None
|
||||
|
||||
# In some failure cases we may want to run a clean and try again.
|
||||
if self._returncode != 0:
|
||||
|
||||
# Getting this error sometimes after xcode updates.
|
||||
if 'error: PCH file built from a different branch' in '\n'.join(
|
||||
self._output):
|
||||
print(f'{Clr.MAG}WILL CLEAN AND'
|
||||
f' RE-ATTEMPT XCODE BUILD{Clr.RST}')
|
||||
self._run_cmd([
|
||||
'xcodebuild', '-project', self._project, '-scheme',
|
||||
self._scheme, '-configuration', self._configuration,
|
||||
'clean'
|
||||
])
|
||||
# Now re-run the original build.
|
||||
print(f'{Clr.MAG}RE-ATTEMPTING XCODE BUILD'
|
||||
f' AFTER CLEAN{Clr.RST}')
|
||||
self._run_cmd(self._build_cmd_args())
|
||||
|
||||
if self._returncode != 0:
|
||||
raise CleanError(f'Command failed with code {self._returncode}.')
|
||||
|
||||
@staticmethod
|
||||
def _argstr(args: list[str], flag: str) -> str:
|
||||
try:
|
||||
return args[args.index(flag) + 1]
|
||||
except (ValueError, IndexError) as exc:
|
||||
raise RuntimeError(f'{flag} value not found') from exc
|
||||
|
||||
def _build_cmd_args(self) -> list[str]:
|
||||
return ['xcodebuild'] + self._args
|
||||
|
||||
def _run_cmd(self, cmd: list[str]) -> None:
|
||||
# reset some state
|
||||
self._output = []
|
||||
self._section = None
|
||||
self._returncode = 0
|
||||
print(f'{Clr.BLU}Running build: {Clr.BLD}{cmd}{Clr.RST}')
|
||||
with subprocess.Popen(cmd,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT) as proc:
|
||||
if proc.stdout is None:
|
||||
raise RuntimeError('Error running command')
|
||||
while True:
|
||||
line = proc.stdout.readline().decode()
|
||||
if len(line) == 0:
|
||||
break
|
||||
self._output.append(line)
|
||||
self._print_filtered_line(line)
|
||||
proc.wait()
|
||||
self._returncode = proc.returncode
|
||||
|
||||
def _print_filtered_line(self, line: str) -> None:
|
||||
|
||||
# pylint: disable=too-many-branches
|
||||
# pylint: disable=too-many-statements
|
||||
|
||||
# NOTE: xcodebuild output can be coming from multiple tasks and
|
||||
# intermingled, so lets try to be as conservative as possible when
|
||||
# hiding lines. When we're not 100% sure we know what a line is,
|
||||
# we should print it to be sure.
|
||||
|
||||
if self._verbose:
|
||||
sys.stdout.write(line)
|
||||
return
|
||||
|
||||
# Look for a few special cases regardless of the section we're in:
|
||||
if line == '** BUILD SUCCEEDED **\n':
|
||||
sys.stdout.write(
|
||||
f'{Clr.GRN}{Clr.BLD}XCODE BUILD SUCCEEDED{Clr.RST}\n')
|
||||
return
|
||||
|
||||
if line == '** CLEAN SUCCEEDED **\n':
|
||||
sys.stdout.write(
|
||||
f'{Clr.GRN}{Clr.BLD}XCODE CLEAN SUCCEEDED{Clr.RST}\n')
|
||||
return
|
||||
|
||||
if 'warning: OpenGL is deprecated.' in line:
|
||||
return # yes Apple, I know.
|
||||
|
||||
# xcodebuild output generally consists of some high level command
|
||||
# ('CompileC blah blah blah') followed by a number of related lines.
|
||||
# Look for particular high level commands to switch us into different
|
||||
# modes.
|
||||
sectionchanged = False
|
||||
for section in _Section:
|
||||
if line.startswith(f'{section.value} '):
|
||||
self._section = section
|
||||
sectionchanged = True
|
||||
|
||||
if sectionchanged:
|
||||
self._section_line_count = 0
|
||||
else:
|
||||
self._section_line_count += 1
|
||||
|
||||
# There's a lot of random chatter at the start of builds,
|
||||
# so let's go ahead and ignore everything before we've got a
|
||||
# line-mode set.
|
||||
if self._section is None:
|
||||
return
|
||||
if self._section is _Section.COMPILEC:
|
||||
self._print_compilec_line(line)
|
||||
elif self._section is _Section.MKDIR:
|
||||
self._print_mkdir_line(line)
|
||||
elif self._section is _Section.LD:
|
||||
self._print_ld_line(line)
|
||||
elif self._section is _Section.COMPILEASSETCATALOG:
|
||||
self._print_compile_asset_catalog_line(line)
|
||||
elif self._section is _Section.CODESIGN:
|
||||
self._print_code_sign_line(line)
|
||||
elif self._section is _Section.COMPILESTORYBOARD:
|
||||
self._print_compile_storyboard_line(line)
|
||||
elif self._section is _Section.LINKSTORYBOARDS:
|
||||
self._print_simple_section_line(
|
||||
line, ignore_line_start_tails=['/ibtool'])
|
||||
elif self._section is _Section.PROCESSINFOPLISTFILE:
|
||||
self._print_process_info_plist_file_line(line)
|
||||
elif self._section is _Section.COPYSWIFTLIBS:
|
||||
self._print_simple_section_line(
|
||||
line, ignore_line_starts=['builtin-swiftStdLibTool'])
|
||||
elif self._section is _Section.REGISTEREXECUTIONPOLICYEXCEPTION:
|
||||
self._print_simple_section_line(
|
||||
line,
|
||||
ignore_line_starts=[
|
||||
'builtin-RegisterExecutionPolicyException'
|
||||
])
|
||||
elif self._section is _Section.VALIDATE:
|
||||
self._print_simple_section_line(
|
||||
line, ignore_line_starts=['builtin-validationUtility'])
|
||||
elif self._section is _Section.TOUCH:
|
||||
self._print_simple_section_line(
|
||||
line, ignore_line_starts=['/usr/bin/touch'])
|
||||
elif self._section is _Section.REGISTERWITHLAUNCHSERVICES:
|
||||
self._print_simple_section_line(
|
||||
line, ignore_line_start_tails=['lsregister'])
|
||||
elif self._section is _Section.METALLINK:
|
||||
self._print_simple_section_line(line,
|
||||
prefix='Linking',
|
||||
ignore_line_start_tails=['/metal'])
|
||||
elif self._section is _Section.COMPILESWIFT:
|
||||
self._print_simple_section_line(
|
||||
line,
|
||||
prefix='Compiling',
|
||||
prefix_index=3,
|
||||
ignore_line_start_tails=['/swift-frontend', 'EmitSwiftModule'])
|
||||
elif self._section is _Section.CREATEBUILDDIRECTORY:
|
||||
self._print_simple_section_line(
|
||||
line, ignore_line_starts=['builtin-create-build-directory'])
|
||||
elif self._section is _Section.COMPILEMETALFILE:
|
||||
self._print_simple_section_line(line,
|
||||
prefix='Metal-Compiling',
|
||||
ignore_line_start_tails=['/metal'])
|
||||
elif self._section is _Section.COPY:
|
||||
self._print_simple_section_line(
|
||||
line, ignore_line_starts=['builtin-copy'])
|
||||
elif self._section is _Section.COPYSTRINGSFILE:
|
||||
self._print_simple_section_line(line,
|
||||
ignore_line_starts=[
|
||||
'builtin-copyStrings',
|
||||
'CopyPNGFile',
|
||||
'ConvertIconsetFile'
|
||||
],
|
||||
ignore_line_start_tails=[
|
||||
'/InfoPlist.strings:1:1:',
|
||||
'/copypng', '/iconutil'
|
||||
])
|
||||
elif self._section is _Section.WRITEAUXILIARYFILE:
|
||||
# EW: this spits out our full list of entitlements line by line.
|
||||
# We should make this smart enough to ignore that whole section
|
||||
# but just ignoring specific exact lines for now.
|
||||
self._print_simple_section_line(
|
||||
line,
|
||||
ignore_line_starts=[
|
||||
'PhaseScriptExecution',
|
||||
'/bin/sh -c',
|
||||
'write-file',
|
||||
'builtin-productPackagingUtility',
|
||||
'ProcessProductPackaging',
|
||||
'Entitlements:',
|
||||
'{',
|
||||
'}',
|
||||
');',
|
||||
'};',
|
||||
'"com.apple.security.get-task-allow"'
|
||||
'"com.apple.security.app-sandbox"',
|
||||
'"com.apple.Music"',
|
||||
'"com.apple.Music.library.read"',
|
||||
'"com.apple.Music.playback"',
|
||||
'"com.apple.security.app-sandbox"',
|
||||
'"com.apple.security.automation.apple-events"',
|
||||
'"com.apple.security.device.bluetooth"',
|
||||
'"com.apple.security.device.usb"',
|
||||
'"com.apple.security.get-task-allow"',
|
||||
'"com.apple.security.network.client"',
|
||||
'"com.apple.security.network.server"',
|
||||
'"com.apple.security.scripting-targets"',
|
||||
'"com.apple.Music.library.read",',
|
||||
])
|
||||
elif self._section is _Section.COMPILESWIFTSOURCES:
|
||||
self._print_simple_section_line(
|
||||
line,
|
||||
prefix='Compiling Swift Sources',
|
||||
prefix_index=None,
|
||||
ignore_line_starts=['PrecompileSwiftBridgingHeader'],
|
||||
ignore_line_start_tails=['/swiftc', '/swift-frontend'])
|
||||
elif self._section is _Section.PROCESSPCH:
|
||||
self._print_simple_section_line(
|
||||
line,
|
||||
ignore_line_starts=['Precompile of'],
|
||||
ignore_line_start_tails=['/clang'])
|
||||
elif self._section is _Section.PROCESSPCHPLUSPLUS:
|
||||
self._print_simple_section_line(
|
||||
line,
|
||||
ignore_line_starts=['Precompile of'],
|
||||
ignore_line_start_tails=['/clang'])
|
||||
elif self._section is _Section.PHASESCRIPTEXECUTION:
|
||||
self._print_simple_section_line(line,
|
||||
prefix='Running Script',
|
||||
ignore_line_starts=['/bin/sh'])
|
||||
else:
|
||||
assert_never(self._section)
|
||||
|
||||
def _print_compilec_line(self, line: str) -> None:
|
||||
|
||||
# First line of the section.
|
||||
if self._section_line_count == 0:
|
||||
fname = os.path.basename(shlex.split(line)[2])
|
||||
sys.stdout.write(f'{Clr.BLU}Compiling {Clr.BLD}{fname}{Clr.RST}\n')
|
||||
return
|
||||
|
||||
# Ignore empty lines or things we expect to be there.
|
||||
splits = line.split()
|
||||
if not splits:
|
||||
return
|
||||
if splits[0] in ['cd', 'export']:
|
||||
return
|
||||
if splits[0].endswith('/clang'):
|
||||
return
|
||||
|
||||
# Fall back on printing anything we don't recognize.
|
||||
sys.stdout.write(line)
|
||||
|
||||
def _print_mkdir_line(self, line: str) -> None:
|
||||
|
||||
# First line of the section.
|
||||
if self._section_line_count == 0:
|
||||
return
|
||||
|
||||
# Ignore empty lines or things we expect to be there.
|
||||
splits = line.split()
|
||||
if not splits:
|
||||
return
|
||||
if splits[0] in ['cd', '/bin/mkdir']:
|
||||
return
|
||||
|
||||
# Fall back on printing anything we don't recognize.
|
||||
sys.stdout.write(line)
|
||||
|
||||
def _print_ld_line(self, line: str) -> None:
|
||||
|
||||
# First line of the section.
|
||||
if self._section_line_count == 0:
|
||||
name = os.path.basename(shlex.split(line)[1])
|
||||
sys.stdout.write(f'{Clr.BLU}Linking {Clr.BLD}{name}{Clr.RST}\n')
|
||||
return
|
||||
|
||||
# Ignore empty lines or things we expect to be there.
|
||||
splits = line.split()
|
||||
if not splits:
|
||||
return
|
||||
if splits[0] in ['cd']:
|
||||
return
|
||||
if splits[0].endswith('/clang++'):
|
||||
return
|
||||
|
||||
# Fall back on printing anything we don't recognize.
|
||||
sys.stdout.write(line)
|
||||
|
||||
def _print_compile_asset_catalog_line(self, line: str) -> None:
|
||||
# pylint: disable=too-many-return-statements
|
||||
|
||||
# First line of the section.
|
||||
if self._section_line_count == 0:
|
||||
name = os.path.basename(shlex.split(line)[1])
|
||||
sys.stdout.write(
|
||||
f'{Clr.BLU}Compiling Asset Catalog {Clr.BLD}{name}{Clr.RST}\n')
|
||||
return
|
||||
|
||||
# Ignore empty lines or things we expect to be there.
|
||||
line_s = line.strip()
|
||||
splits = line.split()
|
||||
if not splits:
|
||||
return
|
||||
if splits[0] in ['cd']:
|
||||
return
|
||||
if splits[0].endswith('/actool'):
|
||||
return
|
||||
if line_s == '/* com.apple.actool.compilation-results */':
|
||||
return
|
||||
if (' ibtoold[' in line_s
|
||||
and 'NSFileCoordinator is doing nothing' in line_s):
|
||||
return
|
||||
if any(line_s.endswith(x) for x in ('.plist', '.icns', '.car')):
|
||||
return
|
||||
|
||||
# Fall back on printing anything we don't recognize.
|
||||
sys.stdout.write(line)
|
||||
|
||||
def _print_compile_storyboard_line(self, line: str) -> None:
|
||||
|
||||
# First line of the section.
|
||||
if self._section_line_count == 0:
|
||||
name = os.path.basename(shlex.split(line)[1])
|
||||
sys.stdout.write(
|
||||
f'{Clr.BLU}Compiling Storyboard {Clr.BLD}{name}{Clr.RST}\n')
|
||||
return
|
||||
|
||||
# Ignore empty lines or things we expect to be there.
|
||||
splits = line.split()
|
||||
if not splits:
|
||||
return
|
||||
if splits[0] in ['cd', 'export']:
|
||||
return
|
||||
if splits[0].endswith('/ibtool'):
|
||||
return
|
||||
|
||||
# Fall back on printing anything we don't recognize.
|
||||
sys.stdout.write(line)
|
||||
|
||||
def _print_code_sign_line(self, line: str) -> None:
|
||||
|
||||
# First line of the section.
|
||||
if self._section_line_count == 0:
|
||||
name = os.path.basename(shlex.split(line)[1])
|
||||
sys.stdout.write(f'{Clr.BLU}Signing'
|
||||
f' {Clr.BLD}{name}{Clr.RST}\n')
|
||||
return
|
||||
|
||||
# Ignore empty lines or things we expect to be there.
|
||||
splits = line.split()
|
||||
if not splits:
|
||||
return
|
||||
if splits[0] in ['cd', 'export', '/usr/bin/codesign']:
|
||||
return
|
||||
if line.strip().startswith('Signing Identity:'):
|
||||
return
|
||||
if ': replacing existing signature' in line:
|
||||
return
|
||||
|
||||
# Fall back on printing anything we don't recognize.
|
||||
sys.stdout.write(line)
|
||||
|
||||
def _print_process_info_plist_file_line(self, line: str) -> None:
|
||||
|
||||
# First line of the section.
|
||||
if self._section_line_count == 0:
|
||||
name = os.path.basename(shlex.split(line)[1])
|
||||
sys.stdout.write(f'{Clr.BLU}Processing {Clr.BLD}{name}{Clr.RST}\n')
|
||||
return
|
||||
|
||||
# Ignore empty lines or things we expect to be there.
|
||||
splits = line.split()
|
||||
if not splits:
|
||||
return
|
||||
if splits[0] in ['cd', 'export', 'builtin-infoPlistUtility']:
|
||||
return
|
||||
|
||||
# Fall back on printing anything we don't recognize.
|
||||
sys.stdout.write(line)
|
||||
|
||||
def _print_simple_section_line(
|
||||
self,
|
||||
line: str,
|
||||
prefix: str = None,
|
||||
prefix_index: Optional[int] = 1,
|
||||
ignore_line_starts: list[str] = None,
|
||||
ignore_line_start_tails: list[str] = None) -> None:
|
||||
|
||||
if ignore_line_starts is None:
|
||||
ignore_line_starts = []
|
||||
if ignore_line_start_tails is None:
|
||||
ignore_line_start_tails = []
|
||||
|
||||
# First line of the section.
|
||||
if self._section_line_count == 0:
|
||||
if prefix is not None:
|
||||
if prefix_index is None:
|
||||
sys.stdout.write(f'{Clr.BLU}{prefix}{Clr.RST}\n')
|
||||
else:
|
||||
name = os.path.basename(shlex.split(line)[prefix_index])
|
||||
sys.stdout.write(f'{Clr.BLU}{prefix}'
|
||||
f' {Clr.BLD}{name}{Clr.RST}\n')
|
||||
return
|
||||
|
||||
# Ignore empty lines or things we expect to be there.
|
||||
splits = line.split()
|
||||
if not splits:
|
||||
return
|
||||
for start in ['cd', 'export'] + ignore_line_starts:
|
||||
# The start strings they pass may themselves be splittable so
|
||||
# we may need to compare more than one string.
|
||||
startsplits = start.split()
|
||||
if splits[:len(startsplits)] == startsplits:
|
||||
return
|
||||
if any(splits[0].endswith(tail) for tail in ignore_line_start_tails):
|
||||
return
|
||||
|
||||
# Fall back on printing anything we don't recognize.
|
||||
if prefix is None:
|
||||
# If a prefix was not supplied for this section, the user will
|
||||
# have no way to know what this output relates to. Tack a bit
|
||||
# on to clarify in that case.
|
||||
assert self._section is not None
|
||||
sys.stdout.write(f'{Clr.YLW}Unexpected {self._section.value}'
|
||||
f' Output:{Clr.RST} {line}')
|
||||
else:
|
||||
sys.stdout.write(line)
|
||||
|
||||
|
||||
def project_build_path(projroot: str, project_path: str, scheme: str,
|
||||
configuration: str) -> str:
|
||||
"""Get build paths for an xcode project (cached for efficiency)."""
|
||||
# pylint: disable=too-many-locals
|
||||
|
||||
config_path = os.path.join(projroot, '.cache', 'xcode_build_path')
|
||||
config: dict[str, dict[str, Any]] = {}
|
||||
|
||||
build_dir: Optional[str] = None
|
||||
executable_path: Optional[str] = None
|
||||
|
||||
if os.path.exists(config_path):
|
||||
with open(config_path, encoding='utf-8') as infile:
|
||||
config = json.loads(infile.read())
|
||||
if (project_path in config and configuration in config[project_path]
|
||||
and scheme in config[project_path][configuration]):
|
||||
|
||||
# Ok we've found a build-dir entry for this project; now if it
|
||||
# exists on disk and all timestamps within it are decently
|
||||
# close to the one we've got recorded, lets use it.
|
||||
# (Anything using this script should also be building
|
||||
# stuff there so mod times should be pretty recent; if not
|
||||
# then its worth re-caching to be sure.)
|
||||
cached_build_dir = config[project_path][configuration][scheme][
|
||||
'build_dir']
|
||||
cached_timestamp = config[project_path][configuration][scheme][
|
||||
'timestamp']
|
||||
cached_executable_path = config[project_path][configuration][
|
||||
scheme]['executable_path']
|
||||
assert isinstance(cached_build_dir, str)
|
||||
assert isinstance(cached_timestamp, float)
|
||||
assert isinstance(cached_executable_path, str)
|
||||
now = time.time()
|
||||
if (os.path.isdir(cached_build_dir)
|
||||
and abs(now - cached_timestamp) < 60 * 60 * 24):
|
||||
build_dir = cached_build_dir
|
||||
executable_path = cached_executable_path
|
||||
|
||||
# If we don't have a path at this point we look it up and cache it.
|
||||
if build_dir is None:
|
||||
print('Caching xcode build path...', file=sys.stderr)
|
||||
cmd = [
|
||||
'xcodebuild', '-project', project_path, '-showBuildSettings',
|
||||
'-configuration', configuration, '-scheme', scheme
|
||||
]
|
||||
output = subprocess.run(cmd, check=True,
|
||||
capture_output=True).stdout.decode()
|
||||
|
||||
prefix = 'TARGET_BUILD_DIR = '
|
||||
lines = [
|
||||
l for l in output.splitlines() if l.strip().startswith(prefix)
|
||||
]
|
||||
if len(lines) != 1:
|
||||
raise Exception(
|
||||
'TARGET_BUILD_DIR not found in xcodebuild settings output')
|
||||
build_dir = lines[0].replace(prefix, '').strip()
|
||||
|
||||
prefix = 'EXECUTABLE_PATH = '
|
||||
lines = [
|
||||
l for l in output.splitlines() if l.strip().startswith(prefix)
|
||||
]
|
||||
if len(lines) != 1:
|
||||
raise Exception(
|
||||
'EXECUTABLE_PATH not found in xcodebuild settings output')
|
||||
executable_path = lines[0].replace(prefix, '').strip()
|
||||
|
||||
if project_path not in config:
|
||||
config[project_path] = {}
|
||||
if configuration not in config[project_path]:
|
||||
config[project_path][configuration] = {}
|
||||
config[project_path][configuration][scheme] = {
|
||||
'build_dir': build_dir,
|
||||
'executable_path': executable_path,
|
||||
'timestamp': time.time()
|
||||
}
|
||||
os.makedirs(os.path.dirname(config_path), exist_ok=True)
|
||||
with open(config_path, 'w', encoding='utf-8') as outfile:
|
||||
outfile.write(json.dumps(config))
|
||||
|
||||
assert build_dir is not None
|
||||
assert executable_path is not None
|
||||
return os.path.join(build_dir, executable_path)
|
||||
@ -23,7 +23,7 @@ from efrotools.pcommand import (
|
||||
cpplint, pylint, pylint_files, mypy, runmypy, dmypy, tool_config_install,
|
||||
sync, sync_all, scriptfiles, pycharm, clioncode, androidstudiocode,
|
||||
makefile_target_list, spelling, spelling_all, pytest, echo,
|
||||
compile_python_files, pyver, try_repeat)
|
||||
compile_python_files, pyver, try_repeat, xcodebuild, xcoderun)
|
||||
from batools.pcommand import (
|
||||
stage_server_file, py_examine, resize_image, check_clean_safety,
|
||||
clean_orphaned_assets, archive_old_builds, lazy_increment_build,
|
||||
@ -41,9 +41,8 @@ from batools.pcommand import (
|
||||
update_cmake_prefab_lib, cmake_prep_dir, gen_binding_code,
|
||||
gen_flat_data_code, wsl_path_to_win, wsl_build_check_win_drive,
|
||||
win_ci_binary_build, genchangelog, android_sdk_utils,
|
||||
update_resources_makefile, update_meta_makefile, xcode_build_path,
|
||||
gen_python_enums_module, gen_python_init_module, update_dummy_module,
|
||||
win_ci_install_prereqs)
|
||||
update_resources_makefile, update_meta_makefile, gen_python_enums_module,
|
||||
gen_python_init_module, update_dummy_module, win_ci_install_prereqs)
|
||||
# pylint: enable=unused-import
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user