diff --git a/.efrocachemap b/.efrocachemap index 205b29dd..abb26d0d 100644 --- a/.efrocachemap +++ b/.efrocachemap @@ -421,9 +421,9 @@ "build/assets/ba_data/audio/zoeOw.ogg": "https://files.ballistica.net/cache/ba1/74/be/fe45a8417e95b6a2233c51992a26", "build/assets/ba_data/audio/zoePickup01.ogg": "https://files.ballistica.net/cache/ba1/48/ab/8cddfcde36a750856f3f81dd20c8", "build/assets/ba_data/audio/zoeScream01.ogg": "https://files.ballistica.net/cache/ba1/2b/46/8aedfa8741090247f04eb9e6df55", - "build/assets/ba_data/data/langdata.json": "https://files.ballistica.net/cache/ba1/b8/5e/c8766634397fb77ae3a407c05d63", - "build/assets/ba_data/data/languages/arabic.json": "https://files.ballistica.net/cache/ba1/6f/38/958616d8cb85916aa8b2bcd84f63", - "build/assets/ba_data/data/languages/belarussian.json": "https://files.ballistica.net/cache/ba1/57/68/d03a19b9035cfae7cdc5377d889a", + "build/assets/ba_data/data/langdata.json": "https://files.ballistica.net/cache/ba1/85/81/9425f358eafcd41e91fb999612ba", + "build/assets/ba_data/data/languages/arabic.json": "https://files.ballistica.net/cache/ba1/db/96/1f7fe0541a31880929e1c17ea957", + "build/assets/ba_data/data/languages/belarussian.json": "https://files.ballistica.net/cache/ba1/5e/37/3ddcfa6e1f771b74c02298a6599a", "build/assets/ba_data/data/languages/chinese.json": "https://files.ballistica.net/cache/ba1/b6/00/924583b899165757f412eef0dd01", "build/assets/ba_data/data/languages/chinesetraditional.json": "https://files.ballistica.net/cache/ba1/3f/e9/60a8f0ca529aa57b4f9cb7385abc", "build/assets/ba_data/data/languages/croatian.json": "https://files.ballistica.net/cache/ba1/76/65/32c67af5bd0144c2d63cab0516fa", @@ -432,30 +432,30 @@ "build/assets/ba_data/data/languages/dutch.json": "https://files.ballistica.net/cache/ba1/22/b4/4a33bf81142ba2befad14eb5746e", "build/assets/ba_data/data/languages/english.json": "https://files.ballistica.net/cache/ba1/d1/6e/8899211693c20d3b00fc198f58c6", "build/assets/ba_data/data/languages/esperanto.json": "https://files.ballistica.net/cache/ba1/0e/39/7cfa5f3fb8cef5f4a64f21cda880", - "build/assets/ba_data/data/languages/filipino.json": "https://files.ballistica.net/cache/ba1/cb/49/1739273c68c82cebca0aee16d6c9", + "build/assets/ba_data/data/languages/filipino.json": "https://files.ballistica.net/cache/ba1/58/f3/63cfd8a3ccf0c904ab753d95789b", "build/assets/ba_data/data/languages/french.json": "https://files.ballistica.net/cache/ba1/51/89/e01389f8153497b56fbf0fa069c2", "build/assets/ba_data/data/languages/german.json": "https://files.ballistica.net/cache/ba1/54/97/54d2a530d825200c6126be56df5c", "build/assets/ba_data/data/languages/gibberish.json": "https://files.ballistica.net/cache/ba1/23/6f/8547ba09722b7c7f5b8333986984", "build/assets/ba_data/data/languages/greek.json": "https://files.ballistica.net/cache/ba1/a6/5d/78f912e9a89f98de004405167a6a", "build/assets/ba_data/data/languages/hindi.json": "https://files.ballistica.net/cache/ba1/88/ee/0cda537bab9ac827def5e236fe1a", - "build/assets/ba_data/data/languages/hungarian.json": "https://files.ballistica.net/cache/ba1/a9/b5/10de2f3928d8c1f4887e0975743f", + "build/assets/ba_data/data/languages/hungarian.json": "https://files.ballistica.net/cache/ba1/79/6a/290a8c44a1e7635208c2ff5fdc6e", "build/assets/ba_data/data/languages/indonesian.json": "https://files.ballistica.net/cache/ba1/58/3b/ae1ecc04375cee089a82359110b7", - "build/assets/ba_data/data/languages/italian.json": "https://files.ballistica.net/cache/ba1/67/44/40ada7b8e76adceb2129d7668df6", + "build/assets/ba_data/data/languages/italian.json": "https://files.ballistica.net/cache/ba1/ce/99/027a5d1af17689b4311eafa5ff24", "build/assets/ba_data/data/languages/korean.json": "https://files.ballistica.net/cache/ba1/bd/c1/3f8632adda5517059323d928f192", "build/assets/ba_data/data/languages/malay.json": "https://files.ballistica.net/cache/ba1/83/25/62ce997fc70704b9234c95fb2e38", - "build/assets/ba_data/data/languages/persian.json": "https://files.ballistica.net/cache/ba1/0e/46/0cb71876e02d361e11db64640831", + "build/assets/ba_data/data/languages/persian.json": "https://files.ballistica.net/cache/ba1/51/19/aec9cbb2f8d00f2afaccf5fd5410", "build/assets/ba_data/data/languages/polish.json": "https://files.ballistica.net/cache/ba1/a6/a7/4a9a289fa1b97847c9a2578c112b", "build/assets/ba_data/data/languages/portuguese.json": "https://files.ballistica.net/cache/ba1/99/b2/7c598c90fd522132af3536aef0ee", "build/assets/ba_data/data/languages/romanian.json": "https://files.ballistica.net/cache/ba1/ae/eb/dd54f65939c2facc6ac50c117826", "build/assets/ba_data/data/languages/russian.json": "https://files.ballistica.net/cache/ba1/aa/99/f9f597787fe4e09c8ab53fe2e081", "build/assets/ba_data/data/languages/serbian.json": "https://files.ballistica.net/cache/ba1/d7/45/2dd72ac0e51680cb39b5ebaa1c69", "build/assets/ba_data/data/languages/slovak.json": "https://files.ballistica.net/cache/ba1/27/96/2d53dc3f7dd4e877cd40faafeeef", - "build/assets/ba_data/data/languages/spanish.json": "https://files.ballistica.net/cache/ba1/45/dd/ce6d9dd446293f5e0ae541f36943", + "build/assets/ba_data/data/languages/spanish.json": "https://files.ballistica.net/cache/ba1/87/76/884442afd08b31364dc6a8b0cc16", "build/assets/ba_data/data/languages/swedish.json": "https://files.ballistica.net/cache/ba1/77/d6/71f10613291ebf9c71da66f18a18", - "build/assets/ba_data/data/languages/tamil.json": "https://files.ballistica.net/cache/ba1/c7/fc/5ed7bd686839ec1a867763248cf9", + "build/assets/ba_data/data/languages/tamil.json": "https://files.ballistica.net/cache/ba1/b9/d4/b4e107456ea6420ee0f9d9d7a03e", "build/assets/ba_data/data/languages/thai.json": "https://files.ballistica.net/cache/ba1/33/f6/3753c9af9a5b238d229a0bf23fbc", "build/assets/ba_data/data/languages/turkish.json": "https://files.ballistica.net/cache/ba1/0a/97/f1f948f6587ea7d40b639aba67ce", - "build/assets/ba_data/data/languages/ukrainian.json": "https://files.ballistica.net/cache/ba1/97/4a/399422e3061fdd82f66591283397", + "build/assets/ba_data/data/languages/ukrainian.json": "https://files.ballistica.net/cache/ba1/f7/2e/b51abfbbb56e27866895d7e947d2", "build/assets/ba_data/data/languages/venetian.json": "https://files.ballistica.net/cache/ba1/b0/e3/d73ccf96c5fa490a54f090ee77a5", "build/assets/ba_data/data/languages/vietnamese.json": "https://files.ballistica.net/cache/ba1/92/1c/d1e50f60fe3e101f246e172750ba", "build/assets/ba_data/data/maps/big_g.json": "https://files.ballistica.net/cache/ba1/1d/d3/01d490643088a435ce75df971054", @@ -4068,50 +4068,50 @@ "build/assets/windows/Win32/ucrtbased.dll": "https://files.ballistica.net/cache/ba1/2d/ef/5335207d41b21b9823f6805997f1", "build/assets/windows/Win32/vc_redist.x86.exe": "https://files.ballistica.net/cache/ba1/b0/8a/55e2e77623fe657bea24f223a3ae", "build/assets/windows/Win32/vcruntime140d.dll": "https://files.ballistica.net/cache/ba1/86/5b/2af4d1e26a1a8073c89acb06e599", - "build/prefab/full/linux_arm64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/6b/a1/5f560a97ab8641091343c6ee688b", - "build/prefab/full/linux_arm64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/50/dd/86cbb96aca3a339318b00574b2db", - "build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/6b/b4/65070558df0a917c9a1aac8bb280", - "build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/93/04/19410cb96b5c12fc2cd20dd9c099", - "build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/36/69/25b4f3e931ff0add15a975383491", - "build/prefab/full/linux_x86_64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/16/68/6011835e4db7927b26761847950b", - "build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/11/bf/aa9df1fd5ae51e9b076a324d8e7a", - "build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/8a/76/54da9b7ff4d79164d3f4dea2782b", - "build/prefab/full/mac_arm64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/4a/08/bf75de3244efe6fc342139a6da32", - "build/prefab/full/mac_arm64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/33/c7/d3534c1d605b5bcc4a541457cde9", - "build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/0e/58/c7da77e4c0d031073e4db047e4f3", - "build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/f5/be/f80777972954ebe6fd91b52a6533", - "build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/e4/20/0f1e3a2e343e48dbe3c3ae8c6ab7", - "build/prefab/full/mac_x86_64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/23/46/72f453ea380bd5f04957886c2b57", - "build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/f8/e3/b7bf2bdd4fe4879e8f95bc56073c", - "build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/b5/e6/451f3cc73b5b79d21b19c2416d61", - "build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "https://files.ballistica.net/cache/ba1/b2/0e/778420dfd1a6f81ed457a94f8f1d", - "build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "https://files.ballistica.net/cache/ba1/00/76/0f4dd3bbf7a98f00221307535b4f", - "build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "https://files.ballistica.net/cache/ba1/4d/fe/1d4e9c927e74f900766cb3d3c55f", - "build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "https://files.ballistica.net/cache/ba1/82/99/e0a873f37e95674f2151ea99bf3d", - "build/prefab/lib/linux_arm64_gui/debug/libballisticaplus.a": "https://files.ballistica.net/cache/ba1/b3/a2/5da0c4dc65f469e4a476e0395eb5", - "build/prefab/lib/linux_arm64_gui/release/libballisticaplus.a": "https://files.ballistica.net/cache/ba1/a9/9a/adb83188f9c7d7b51dafd0f8b8a8", - "build/prefab/lib/linux_arm64_server/debug/libballisticaplus.a": "https://files.ballistica.net/cache/ba1/b3/a2/5da0c4dc65f469e4a476e0395eb5", - "build/prefab/lib/linux_arm64_server/release/libballisticaplus.a": "https://files.ballistica.net/cache/ba1/a9/9a/adb83188f9c7d7b51dafd0f8b8a8", - "build/prefab/lib/linux_x86_64_gui/debug/libballisticaplus.a": "https://files.ballistica.net/cache/ba1/27/f4/269f5d37a8e3938c0acdab299833", - "build/prefab/lib/linux_x86_64_gui/release/libballisticaplus.a": "https://files.ballistica.net/cache/ba1/7b/f3/f98278c9654a972baf65d5f04c12", - "build/prefab/lib/linux_x86_64_server/debug/libballisticaplus.a": "https://files.ballistica.net/cache/ba1/27/f4/269f5d37a8e3938c0acdab299833", - "build/prefab/lib/linux_x86_64_server/release/libballisticaplus.a": "https://files.ballistica.net/cache/ba1/7b/f3/f98278c9654a972baf65d5f04c12", - "build/prefab/lib/mac_arm64_gui/debug/libballisticaplus.a": "https://files.ballistica.net/cache/ba1/c7/a2/40728a3ebfb3006c7a47b698214f", - "build/prefab/lib/mac_arm64_gui/release/libballisticaplus.a": "https://files.ballistica.net/cache/ba1/84/19/a1bbbf42c50329f0cd1377d103bb", - "build/prefab/lib/mac_arm64_server/debug/libballisticaplus.a": "https://files.ballistica.net/cache/ba1/c7/a2/40728a3ebfb3006c7a47b698214f", - "build/prefab/lib/mac_arm64_server/release/libballisticaplus.a": "https://files.ballistica.net/cache/ba1/84/19/a1bbbf42c50329f0cd1377d103bb", - "build/prefab/lib/mac_x86_64_gui/debug/libballisticaplus.a": "https://files.ballistica.net/cache/ba1/53/e4/455c68ee50813fe89e3002cf1fe8", - "build/prefab/lib/mac_x86_64_gui/release/libballisticaplus.a": "https://files.ballistica.net/cache/ba1/59/fe/a3e369f2db87a305641e74ae70ab", - "build/prefab/lib/mac_x86_64_server/debug/libballisticaplus.a": "https://files.ballistica.net/cache/ba1/be/df/60063a6845e8654958f1a3e37867", - "build/prefab/lib/mac_x86_64_server/release/libballisticaplus.a": "https://files.ballistica.net/cache/ba1/59/fe/a3e369f2db87a305641e74ae70ab", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "https://files.ballistica.net/cache/ba1/91/2f/362a643d543963de549d830fe604", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "https://files.ballistica.net/cache/ba1/2e/d4/67d6c0f9b372eb5cd92c9def6fc8", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "https://files.ballistica.net/cache/ba1/68/8b/d6049425f1069d256abdaf90004c", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "https://files.ballistica.net/cache/ba1/3e/82/f70e75696765ac05875cb5dd778c", - "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "https://files.ballistica.net/cache/ba1/38/ee/56658557aa2ecabd0d30eb01b68e", - "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "https://files.ballistica.net/cache/ba1/6b/4c/568766d02bbca174752488850737", - "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "https://files.ballistica.net/cache/ba1/3e/5b/1aa2252706188de69075eb7b3656", - "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "https://files.ballistica.net/cache/ba1/ab/a7/21bc5acc8a823bd7c7ec940e97bc", + "build/prefab/full/linux_arm64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/3a/79/8c3f90efa237a22e105a984ddc69", + "build/prefab/full/linux_arm64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/3f/fe/4c9f0926b60c6a4f5253fde8bb35", + "build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/98/07/9edc3796de01efe4f2fc30d3fcc9", + "build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/37/ba/ee706254c5a21f08b8d4c47bb6e2", + "build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/1f/9a/33ed6f8e2b78af7b00b2727c6630", + "build/prefab/full/linux_x86_64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/11/f3/0feed20f5d34c23d96b39e5702ae", + "build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/4d/f8/c574339560c3f324d258d73dbe70", + "build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/bc/b1/66680fead22bdecfcd4c8d825f8b", + "build/prefab/full/mac_arm64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/79/44/6cddf6fe194de66ab9576d1d958d", + "build/prefab/full/mac_arm64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/ef/1c/ddcdbedb47affd72d2f4c5164fd9", + "build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/4a/ed/e7b293c3d484257e9c4a6c4b5ce1", + "build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/c1/ed/4691901b692a59e598bd982f6d5b", + "build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/02/74/f6091bf03a2de2690ac9de36391a", + "build/prefab/full/mac_x86_64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/fa/8c/79e39ee8297c9946f3dfed5d47c4", + "build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/f0/c4/0554dbc45078ce12e3ab1ab00667", + "build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/2a/c3/4378d0290b122a345787b46ef23c", + "build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "https://files.ballistica.net/cache/ba1/f1/6a/dcbae2252ce071fd07a350615351", + "build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "https://files.ballistica.net/cache/ba1/c2/4b/04f9d9a58fa1d1cefc096cf84e57", + "build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "https://files.ballistica.net/cache/ba1/61/8c/ec3c377382be6503836613fe0ae0", + "build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "https://files.ballistica.net/cache/ba1/65/8f/25f37b8f6175fbe9274576e2eb4a", + "build/prefab/lib/linux_arm64_gui/debug/libballisticaplus.a": "https://files.ballistica.net/cache/ba1/50/4e/39e4ff62ab5c2c3e72862c2c0881", + "build/prefab/lib/linux_arm64_gui/release/libballisticaplus.a": "https://files.ballistica.net/cache/ba1/9d/2b/44367bfb114e7b1148e4e100acd1", + "build/prefab/lib/linux_arm64_server/debug/libballisticaplus.a": "https://files.ballistica.net/cache/ba1/50/4e/39e4ff62ab5c2c3e72862c2c0881", + "build/prefab/lib/linux_arm64_server/release/libballisticaplus.a": "https://files.ballistica.net/cache/ba1/9d/2b/44367bfb114e7b1148e4e100acd1", + "build/prefab/lib/linux_x86_64_gui/debug/libballisticaplus.a": "https://files.ballistica.net/cache/ba1/eb/1a/21761c11e160525903190cfe4ed3", + "build/prefab/lib/linux_x86_64_gui/release/libballisticaplus.a": "https://files.ballistica.net/cache/ba1/a3/38/2c4abbc88434720311048185fdd1", + "build/prefab/lib/linux_x86_64_server/debug/libballisticaplus.a": "https://files.ballistica.net/cache/ba1/eb/1a/21761c11e160525903190cfe4ed3", + "build/prefab/lib/linux_x86_64_server/release/libballisticaplus.a": "https://files.ballistica.net/cache/ba1/a3/38/2c4abbc88434720311048185fdd1", + "build/prefab/lib/mac_arm64_gui/debug/libballisticaplus.a": "https://files.ballistica.net/cache/ba1/5f/47/5e5c2e910c783b15c6dcf0d78715", + "build/prefab/lib/mac_arm64_gui/release/libballisticaplus.a": "https://files.ballistica.net/cache/ba1/75/af/1ca3bec27e72101585254739189d", + "build/prefab/lib/mac_arm64_server/debug/libballisticaplus.a": "https://files.ballistica.net/cache/ba1/5f/47/5e5c2e910c783b15c6dcf0d78715", + "build/prefab/lib/mac_arm64_server/release/libballisticaplus.a": "https://files.ballistica.net/cache/ba1/75/af/1ca3bec27e72101585254739189d", + "build/prefab/lib/mac_x86_64_gui/debug/libballisticaplus.a": "https://files.ballistica.net/cache/ba1/0b/6e/67ffc264f249cde0044637982bab", + "build/prefab/lib/mac_x86_64_gui/release/libballisticaplus.a": "https://files.ballistica.net/cache/ba1/06/ab/699f575fabf3819dcf40b4c3125e", + "build/prefab/lib/mac_x86_64_server/debug/libballisticaplus.a": "https://files.ballistica.net/cache/ba1/b3/27/a8e1a856695454d8b37a7d4f3604", + "build/prefab/lib/mac_x86_64_server/release/libballisticaplus.a": "https://files.ballistica.net/cache/ba1/06/ab/699f575fabf3819dcf40b4c3125e", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "https://files.ballistica.net/cache/ba1/b0/37/db1f330cc6d7d6cf987ff55b9ed4", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "https://files.ballistica.net/cache/ba1/b0/55/536be0beef09a49ecca957ca7318", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "https://files.ballistica.net/cache/ba1/d1/06/f341df353f2df7dbcbc80e2f3987", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "https://files.ballistica.net/cache/ba1/bd/f3/6f99da945c68fae7f860e69f86c6", + "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "https://files.ballistica.net/cache/ba1/ea/2d/ca8db3623be87b383486f905f41c", + "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "https://files.ballistica.net/cache/ba1/1e/fa/09a99039de3bf810e3ed13959416", + "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "https://files.ballistica.net/cache/ba1/cb/b0/f146f9d033d82a1d03496360001c", + "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "https://files.ballistica.net/cache/ba1/e1/5d/453846d79912b5a9a82cb5f8e2f5", "src/assets/ba_data/python/babase/_mgen/__init__.py": "https://files.ballistica.net/cache/ba1/f8/85/fed7f2ed98ff2ba271f9dbe3391c", "src/assets/ba_data/python/babase/_mgen/enums.py": "https://files.ballistica.net/cache/ba1/f8/cd/3af311ac63147882590123b78318", "src/ballistica/base/mgen/pyembed/binding_base.inc": "https://files.ballistica.net/cache/ba1/ee/dd/ad968b176000e31c65be6206a2bc", diff --git a/.idea/dictionaries/ericf.xml b/.idea/dictionaries/ericf.xml index 6304dd3b..3e3ab804 100644 --- a/.idea/dictionaries/ericf.xml +++ b/.idea/dictionaries/ericf.xml @@ -125,6 +125,7 @@ appletvsimulator appmode appmodeselector + appmodule appname appnameupper appnow @@ -904,6 +905,7 @@ enumvalue enval envcfg + envconfig envglobals envhash envname @@ -1892,6 +1894,7 @@ namedarg namel namepre + namepretty nametext nameu nameval @@ -2432,6 +2435,7 @@ redist redistributables reenabled + reexpose regionid registerexecutionpolicyexception registerwithlaunchservices diff --git a/.idea/inspectionProfiles/Default.xml b/.idea/inspectionProfiles/Default.xml index ff8542f6..f2590100 100644 --- a/.idea/inspectionProfiles/Default.xml +++ b/.idea/inspectionProfiles/Default.xml @@ -113,6 +113,8 @@ diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b044686..a2e55376 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ -### 1.7.24 (build 21196, api 8, 2023-07-25) +### 1.7.24 (build 21199, api 8, 2023-07-27) +- Fixed an issue where respawn icons could disappear in epic mode (Thanks for + the heads-up Rikko!) +- The `BA_ENABLE_IRONY_BUILD_DB` optional build env-var is now + `BA_ENABLE_COMPILE_COMMANDS_DB` since this same functionality can be used by + clangd or other tools. Originally I was using it for Irony for Emacs; hence + the old name. - Due to the cleanup done in 1.7.20, it is now possible to build and run Ballistica as a 'pure' Python app consisting of binary Python modules loaded by a standard Python interpreter. This new build style is referred to as diff --git a/Makefile b/Makefile index 18de0bc9..cd5af619 100644 --- a/Makefile +++ b/Makefile @@ -28,11 +28,11 @@ help: @tools/pcommand makefile_target_list Makefile -# Set env-var BA_ENABLE_IRONY_BUILD_DB=1 to enable creating/updating a cmake -# compile-commands database for use with irony for emacs (and possibly other -# tools). -ifeq ($(BA_ENABLE_IRONY_BUILD_DB),1) - PREREQ_IRONY_BUILD_DB = .cache/irony/compile_commands.json +# Set env-var BA_ENABLE_COMPILE_COMMANDS_DB=1 to enable creating/updating a +# cmake compile-commands database for use with irony for emacs (and possibly +# other tools). +ifeq ($(BA_ENABLE_COMPILE_COMMANDS_DB),1) + PREREQ_COMPILE_COMMANDS_DB = .cache/compile_commands_db/compile_commands.json endif # Prereq targets that should be safe to run anytime; even if project-files @@ -45,7 +45,7 @@ PREREQS_SAFE = .cache/checkenv .dir-locals.el .mypy.ini .pycheckers .pylintrc \ # fail if the CMakeList files don't match what's on disk. If such a target was # included in PREREQS_SAFE it would try to build *before* project updates # which would leave us stuck in a broken state. -PREREQS_POST_UPDATE_ONLY = $(PREREQ_IRONY_BUILD_DB) +PREREQS_POST_UPDATE_ONLY = $(PREREQ_COMPILE_COMMANDS_DB) # Target that should be built before running most any other build. # This installs tool config files, runs environment checks, etc. @@ -1010,20 +1010,20 @@ CMAKE_BUILD_TYPE ?= Debug # Build and run the cmake build. cmake: cmake-build - @cd build/cmake/$(CM_BT_LC)/staged && ./ballisticakit + cd build/cmake/$(CM_BT_LC)/staged && ./ballisticakit # Build and run the cmake build under the gdb debugger. # Sets up the ballistica environment to do things like abort() out to the # debugger on errors instead of trying to cleanly exit. cmake-gdb: cmake-build - @cd build/cmake/$(CM_BT_LC)/staged && \ + cd build/cmake/$(CM_BT_LC)/staged && \ BA_DEBUGGER_ATTACHED=1 gdb ./ballisticakit # Build and run the cmake build under the lldb debugger. # Sets up the ballistica environment to do things like abort() out to the # debugger on errors instead of trying to cleanly exit. cmake-lldb: cmake-build - @cd build/cmake/$(CM_BT_LC)/staged && \ + cd build/cmake/$(CM_BT_LC)/staged && \ BA_DEBUGGER_ATTACHED=1 lldb ./ballisticakit # Build but don't run it. @@ -1045,7 +1045,7 @@ cmake-clean: rm -rf build/cmake/$(CM_BT_LC) cmake-server: cmake-server-build - @cd build/cmake/server-$(CM_BT_LC)/staged && ./ballisticakit_server + cd build/cmake/server-$(CM_BT_LC)/staged && ./ballisticakit_server cmake-server-build: assets-server meta cmake-server-binary @$(STAGE_BUILD) -cmakeserver -$(CM_BT_LC) \ @@ -1074,7 +1074,7 @@ cmake-modular-build: assets-cmake meta cmake-modular-binary Modular build complete: BLU build/cmake/modular-$(CM_BT_LC)/staged cmake-modular: cmake-modular-build - @cd build/cmake/modular-$(CM_BT_LC)/staged && ./ballisticakit + cd build/cmake/modular-$(CM_BT_LC)/staged && ./ballisticakit cmake-modular-binary: meta @tools/pcommand cmake_prep_dir build/cmake/modular-$(CM_BT_LC) @@ -1089,7 +1089,7 @@ cmake-modular-clean: rm -rf build/cmake/modular-$(CM_BT_LC) cmake-modular-server: cmake-modular-server-build - @cd build/cmake/modular-server-$(CM_BT_LC)/staged && ./ballisticakit_server + cd build/cmake/modular-server-$(CM_BT_LC)/staged && ./ballisticakit_server cmake-modular-server-build: assets-server meta cmake-modular-server-binary @$(STAGE_BUILD) -cmakemodularserver -$(CM_BT_LC) \ @@ -1221,21 +1221,21 @@ ballisticakit-cmake/.clang-format: .clang-format @mkdir -p ballisticakit-cmake @cd ballisticakit-cmake && ln -sf ../.clang-format . -# Irony in emacs requires us to use cmake to generate a full -# list of compile commands for all files; lets try to keep it up to date +# Various tools such as Irony for Emacs or clangd make use of a list of +# compile commands for all files; lets try to keep it up to date # whenever CMakeLists changes. -.cache/irony/compile_commands.json: ballisticakit-cmake/CMakeLists.txt - @tools/pcommand echo BLU Updating Irony build commands db... - @echo Generating Irony compile-commands-list... - @mkdir -p .cache/irony - @cd .cache/irony \ +.cache/compile_commands_db/compile_commands.json: \ + ballisticakit-cmake/CMakeLists.txt + @tools/pcommand echo BLU Updating compile commands db... + @mkdir -p .cache/compile_commands_db + @cd .cache/compile_commands_db \ && cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_BUILD_TYPE=Debug \ $(shell pwd)/ballisticakit-cmake - @mv .cache/irony/compile_commands.json . \ - && rm -rf .cache/irony \ - && mkdir .cache/irony \ - && mv compile_commands.json .cache/irony - @tools/pcommand echo BLU Created Irony build db at $@ + @mv .cache/compile_commands_db/compile_commands.json . \ + && rm -rf .cache/compile_commands_db \ + && mkdir .cache/compile_commands_db \ + && mv compile_commands.json .cache/compile_commands_db + @tools/pcommand echo BLU Created compile commands db at $@ _windows-wsl-build: @tools/pcommand wsl_build_check_win_drive diff --git a/ballisticakit-cmake/.idea/dictionaries/ericf.xml b/ballisticakit-cmake/.idea/dictionaries/ericf.xml index ed693e85..965b454f 100644 --- a/ballisticakit-cmake/.idea/dictionaries/ericf.xml +++ b/ballisticakit-cmake/.idea/dictionaries/ericf.xml @@ -83,6 +83,7 @@ appletvsimulator appmode appmodeselector + appmodule appname appnameupper appnow @@ -549,6 +550,7 @@ enumvalue enval envcfg + envconfig envglobals envs envval @@ -1102,6 +1104,7 @@ namecap namel namepre + namepretty nameu nameval nbuffer @@ -1430,6 +1433,7 @@ recvfrom redundants reenabled + reexpose refcounted refl regionid diff --git a/config/toolconfigsrc/dir-locals.el b/config/toolconfigsrc/dir-locals.el index 30c7cf5a..a1859328 100644 --- a/config/toolconfigsrc/dir-locals.el +++ b/config/toolconfigsrc/dir-locals.el @@ -2,7 +2,8 @@ ;;; For more information see (info "(emacs) Directory Variables") ;;; Turn flycheck mode on for our c++ stuff and tell jedi where to look for our python stuff. -((c++-mode (eval . (flycheck-mode))) +( + ;; (c++-mode (eval . (flycheck-mode))) (python-ts-mode (jedi:server-args . ("--sys-path" "__EFRO_PROJECT_ROOT__/tools" @@ -22,8 +23,18 @@ (nil . ((projectile-globally-ignored-directories . ("docs" "submodules" "src/external" + "src/assets/ba_data/python-site-packages" "src/assets/pylib-android" "src/assets/pylib-apple" "src/assets/windows")))) + ;; Trying to get project.el to work the same as projectile since its built in. + (nil . ((project-vc-ignores . ("docs" + "submodules" + "src/external" + "src/assets/ba_data/python-site-packages" + "src/assets/pylib-android" + "src/assets/pylib-apple" + "src/assets/windows")))) + ) diff --git a/src/assets/ba_data/python/babase/__init__.py b/src/assets/ba_data/python/babase/__init__.py index 52bf7cb5..7388a683 100644 --- a/src/assets/ba_data/python/babase/__init__.py +++ b/src/assets/ba_data/python/babase/__init__.py @@ -41,6 +41,7 @@ from _babase import ( fade_screen, fatal_error, get_display_resolution, + get_immediate_return_code, get_low_level_config_value, get_max_graphics_quality, get_replays_dir, @@ -204,6 +205,7 @@ __all__ = [ 'fatal_error', 'garbage_collect', 'get_display_resolution', + 'get_immediate_return_code', 'get_ip_address_type', 'get_low_level_config_value', 'get_max_graphics_quality', diff --git a/src/assets/ba_data/python/babase/_env.py b/src/assets/ba_data/python/babase/_env.py index 08b13d3b..fd426fcd 100644 --- a/src/assets/ba_data/python/babase/_env.py +++ b/src/assets/ba_data/python/babase/_env.py @@ -156,11 +156,11 @@ def on_app_launching() -> None: assert _babase.in_logic_thread() - # Let the user know if the app Python dir is a custom one. - user_sys_scripts_dir = baenv.get_user_system_scripts_dir() - if user_sys_scripts_dir is not None: + # Let the user know if the app Python dir is a 'user' one. + envconfig = baenv.get_config() + if envconfig.is_user_app_python_dir: _babase.screenmessage( - f"Using user system scripts: '{user_sys_scripts_dir}'", + f"Using user system scripts: '{envconfig.app_python_dir}'", color=(0.6, 0.6, 1.0), ) diff --git a/src/assets/ba_data/python/baenv.py b/src/assets/ba_data/python/baenv.py index 9ae293a2..0e613c7a 100644 --- a/src/assets/ba_data/python/baenv.py +++ b/src/assets/ba_data/python/baenv.py @@ -24,20 +24,19 @@ from dataclasses import dataclass from typing import TYPE_CHECKING import __main__ -from efro.log import setup_logging, LogLevel - if TYPE_CHECKING: from efro.log import LogHandler # IMPORTANT - It is likely (and in some cases expected) that this # module's code will be exec'ed multiple times. This is because it is -# the job of this module to set up paths for an engine run, and that may -# involve modifying sys.path in such a way that this module resolves to -# a different path afterwards (for example from +# the job of this module to set up Python paths for an engine run, and +# that may involve modifying sys.path in such a way that this module +# resolves to a different path afterwards (for example from # /abs/path/to/ba_data/scripts/babase.py to ba_data/scripts/babase.py). # This can result in the next import of baenv loading us from our 'new' # location, which may or may not actually be the same file on disk as -# the old. Either way, however, multiple execs will happen in some form. +# the last load. Either way, however, multiple execs will happen in some +# form. # # So we need to do a few things to handle that situation gracefully. # @@ -51,13 +50,13 @@ if TYPE_CHECKING: # Build number and version of the ballistica binary we expect to be # using. -TARGET_BALLISTICA_BUILD = 21196 +TARGET_BALLISTICA_BUILD = 21199 TARGET_BALLISTICA_VERSION = '1.7.24' @dataclass class EnvConfig: - """Environment put together by the configure call.""" + """Environment values put together by the configure call.""" config_dir: str data_dir: str @@ -66,49 +65,45 @@ class EnvConfig: standard_app_python_dir: str site_python_dir: str | None log_handler: LogHandler | None + is_user_app_python_dir: bool @dataclass -class EnvGlobals: +class _EnvGlobals: """Our globals we store in the main module.""" config: EnvConfig | None = None - config_called: bool = False + called_configure: bool = False paths_set_failed: bool = False - user_system_scripts_dir: str | None = None + modular_main_called: bool = False @classmethod - def get(cls) -> EnvGlobals: + def get(cls) -> _EnvGlobals: """Create/return our singleton.""" name = '_baenv_globals' - envglobals: EnvGlobals | None = getattr(__main__, name, None) + envglobals: _EnvGlobals | None = getattr(__main__, name, None) if envglobals is None: - envglobals = EnvGlobals() + envglobals = _EnvGlobals() setattr(__main__, name, envglobals) return envglobals +def did_paths_set_fail() -> bool: + """Did we try to set paths and failed?""" + return _EnvGlobals.get().paths_set_failed + + def config_exists() -> bool: """Has a config been created?""" - return EnvGlobals.get().config is not None - - -def did_paths_set_fail() -> bool: - """Did we try to set paths and failed?""" - return EnvGlobals.get().paths_set_failed - - -def get_user_system_scripts_dir() -> str | None: - """If there's a custom user system scripts dir in play, return it.""" - return EnvGlobals.get().user_system_scripts_dir + return _EnvGlobals.get().config is not None def get_config() -> EnvConfig: """Return the active config, creating a default if none exists.""" - envglobals = EnvGlobals.get() + envglobals = _EnvGlobals.get() - if not envglobals.config_called: + if not envglobals.called_configure: configure() config = envglobals.config @@ -128,7 +123,7 @@ def configure( site_python_dir: str | None = None, contains_python_dist: bool = False, ) -> None: - """Set up the Python environment for running a ballistica app. + """Set up the Python environment for running a Ballistica app. This includes things such as Python path wrangling and app directory creation. This should be called before any other ballistica modules @@ -136,14 +131,14 @@ def configure( where those modules get loaded from. """ - envglobals = EnvGlobals.get() + envglobals = _EnvGlobals.get() - if envglobals.config_called: + if envglobals.called_configure: raise RuntimeError( 'baenv.configure() has already been called;' ' it can only be called once.' ) - envglobals.config_called = True + envglobals.called_configure = True # The very first thing we do is set up our logging system and pipe # Python's stdout/stderr into it. Then we can at least debug @@ -167,6 +162,7 @@ def configure( data_dir, config_dir, standard_app_python_dir, + is_user_app_python_dir, ) = _setup_paths( user_python_dir, app_python_dir, @@ -190,6 +186,7 @@ def configure( standard_app_python_dir=standard_app_python_dir, site_python_dir=site_python_dir, log_handler=log_handler, + is_user_app_python_dir=is_user_app_python_dir, ) @@ -215,6 +212,8 @@ def _calc_data_dir(data_dir: str | None) -> str: def _setup_logging() -> LogHandler: + from efro.log import setup_logging, LogLevel + log_handler = setup_logging( log_path=None, level=LogLevel.DEBUG, @@ -249,11 +248,11 @@ def _setup_paths( site_python_dir: str | None, data_dir: str | None, config_dir: str | None, -) -> tuple[str | None, str | None, str | None, str, str, str]: +) -> tuple[str | None, str | None, str | None, str, str, str, bool]: # First a few paths we can ALWAYS calculate since they don't affect # Python imports: - envglobals = EnvGlobals.get() + envglobals = _EnvGlobals.get() data_dir = _calc_data_dir(data_dir) @@ -264,6 +263,9 @@ def _setup_paths( # Standard app-python-dir is simply ba_data/python under data-dir. standard_app_python_dir = str(Path(data_dir, 'ba_data', 'python')) + # Whether the final app-dir we're returning is a custom user-owned one. + is_user_app_python_dir = False + # If _babase has already been imported, there's not much we can do # at this point aside from complain and inform for next time. if '_babase' in sys.modules: @@ -299,7 +301,8 @@ def _setup_paths( # stuff. check_dir = Path(user_python_dir, 'sys', TARGET_BALLISTICA_VERSION) if check_dir.is_dir(): - envglobals.user_system_scripts_dir = app_python_dir = str(check_dir) + app_python_dir = str(check_dir) + is_user_app_python_dir = True # Ok, now apply these to sys.path. @@ -342,6 +345,7 @@ def _setup_paths( data_dir, config_dir, standard_app_python_dir, + is_user_app_python_dir, ) @@ -361,16 +365,96 @@ def _setup_dirs(config_dir: str | None, user_python_dir: str | None) -> None: ) -def _main() -> None: - # Run a default configure BEFORE importing babase. - # (may affect where babase comes from). - configure() +def extract_arg(args: list[str], names: list[str], is_dir: bool) -> str | None: + """Given a list of args and an arg name, returns a value. - import babase + The arg flag and value are removed from the arg list. We also check + to make sure the path exists. - babase.app.run() + raises CleanErrors on any problems. + """ + from efro.error import CleanError + + count = sum(args.count(n) for n in names) + if not count: + return None + + if count > 1: + raise CleanError(f'Arg {names} passed multiple times.') + + for name in names: + if name not in args: + continue + argindex = args.index(name) + if argindex + 1 >= len(args): + raise CleanError(f'No value passed after {name} arg.') + + val = args[argindex + 1] + del args[argindex : argindex + 2] + + if is_dir and not os.path.isdir(val): + namepretty = names[0].removeprefix('--') + raise CleanError( + f"Provided {namepretty} path '{val}' is not a directory." + ) + return val + + raise RuntimeError(f'Expected arg name not found from {names}') -# Allow exec'ing this module directly to do a standard app run. +def _modular_main() -> None: + from efro.error import CleanError + + try: + # Take note that we're running via modular-main. + # The native layer can key off this to know whether it + # should apply sys.argv or not. + _EnvGlobals.get().modular_main_called = True + + # We run configure() BEFORE importing babase. (part of its job + # is to set up where babase and everything else gets loaded + # from). + + # We deal with a few key ours here ourself before spinning up + # any engine stuff. + + # NOTE: Need to keep these arg long/short versions synced to + # those in core_config.cc since they parse the same values (they + # just don't handle them). + + args = sys.argv.copy() + + # Our -c arg basically mirrors Python's -c arg. If we get that, + # simply exec it and return; no engine stuff. + command = extract_arg(args, ['--command', '-c'], is_dir=False) + if command is not None: + exec(command) # pylint: disable=exec-used + return + + config_dir = extract_arg(args, ['--config-dir', '-C'], is_dir=True) + data_dir = extract_arg(args, ['--data-dir', '-d'], is_dir=True) + mods_dir = extract_arg(args, ['--mods-dir', '-m'], is_dir=True) + configure( + config_dir=config_dir, + data_dir=data_dir, + user_python_dir=mods_dir, + ) + + import babase + + # The engine will have parsed and processed all other args as + # part of the above import. If there were errors or args such as + # --help which should lead to immediate returns, do so. + code = babase.get_immediate_return_code() + if code is not None: + sys.exit(code) + + babase.app.run() + except CleanError as clean_exc: + clean_exc.pretty_print() + sys.exit(1) + + +# Exec'ing this module directly will do a standard app run. if __name__ == '__main__': - _main() + _modular_main() diff --git a/src/assets/ba_data/python/bascenev1/__init__.py b/src/assets/ba_data/python/bascenev1/__init__.py index 7b44deeb..77cd93e6 100644 --- a/src/assets/ba_data/python/bascenev1/__init__.py +++ b/src/assets/ba_data/python/bascenev1/__init__.py @@ -45,6 +45,7 @@ from babase import ( NodeNotFoundError, normalized_color, NotFoundError, + PlayerNotFoundError, Plugin, pushcall, safecolor, @@ -374,6 +375,7 @@ __all__ = [ 'PlayerDiedMessage', 'PlayerProfilesChangedMessage', 'PlayerInfo', + 'PlayerNotFoundError', 'PlayerRecord', 'PlayerScoredMessage', 'Plugin', diff --git a/src/assets/ba_data/python/bascenev1lib/activity/coopjoin.py b/src/assets/ba_data/python/bascenev1lib/activity/coopjoin.py index 7e301cc6..64675338 100644 --- a/src/assets/ba_data/python/bascenev1lib/activity/coopjoin.py +++ b/src/assets/ba_data/python/bascenev1lib/activity/coopjoin.py @@ -4,14 +4,8 @@ from __future__ import annotations -from typing import TYPE_CHECKING - -import babase import bascenev1 as bs -if TYPE_CHECKING: - pass - class CoopJoinActivity(bs.JoinActivity): """Join-screen for co-op mode.""" @@ -46,7 +40,7 @@ class CoopJoinActivity(bs.JoinActivity): ).autoretain() ControlsGuide(delay=1.0).autoretain() - babase.pushcall(self._show_remaining_achievements) + bs.pushcall(self._show_remaining_achievements) def _show_remaining_achievements(self) -> None: from bascenev1lib.actor.text import Text @@ -70,22 +64,22 @@ class CoopJoinActivity(bs.JoinActivity): ts_h_offs = 60 # Show remaining achievements in some cases. - if babase.app.classic is not None and not ( - babase.app.demo_mode or babase.app.arcade_mode + if bs.app.classic is not None and not ( + bs.app.demo_mode or bs.app.arcade_mode ): achievements = [ a - for a in babase.app.classic.ach.achievements_for_coop_level( + for a in bs.app.classic.ach.achievements_for_coop_level( levelname ) if not a.complete ] have_achievements = bool(achievements) achievements = [a for a in achievements if not a.complete] - vrmode = babase.app.vr_mode + vrmode = bs.app.vr_mode if have_achievements: Text( - babase.Lstr(resource='achievementsRemainingText'), + bs.Lstr(resource='achievementsRemainingText'), host_only=True, position=(ts_h_offs - 10, vpos), transition=Text.Transition.FADE_IN, @@ -105,7 +99,7 @@ class CoopJoinActivity(bs.JoinActivity): vpos -= 55 if not achievements: Text( - babase.Lstr(resource='noAchievementsRemainingText'), + bs.Lstr(resource='noAchievementsRemainingText'), host_only=True, position=(ts_h_offs + 15, vpos + 10), transition=Text.Transition.FADE_IN, diff --git a/src/assets/ba_data/python/bascenev1lib/activity/drawscore.py b/src/assets/ba_data/python/bascenev1lib/activity/drawscore.py index 991e5289..2d260dfd 100644 --- a/src/assets/ba_data/python/bascenev1lib/activity/drawscore.py +++ b/src/assets/ba_data/python/bascenev1lib/activity/drawscore.py @@ -4,15 +4,9 @@ from __future__ import annotations -from typing import TYPE_CHECKING - -import babase +import bascenev1 as bs from bascenev1lib.activity.multiteamscore import MultiTeamScoreScreenActivity from bascenev1lib.actor.zoomtext import ZoomText -import bascenev1 as bs - -if TYPE_CHECKING: - pass class DrawScoreScreenActivity(MultiTeamScoreScreenActivity): @@ -21,10 +15,10 @@ class DrawScoreScreenActivity(MultiTeamScoreScreenActivity): default_music = None # Awkward silence... def on_begin(self) -> None: - babase.set_analytics_screen('Draw Score Screen') + bs.set_analytics_screen('Draw Score Screen') super().on_begin() ZoomText( - babase.Lstr(resource='drawText'), + bs.Lstr(resource='drawText'), position=(0, 0), maxwidth=400, shiftposition=(-220, 0), diff --git a/src/assets/ba_data/python/bascenev1lib/activity/dualteamscore.py b/src/assets/ba_data/python/bascenev1lib/activity/dualteamscore.py index 8d665031..c369e17b 100644 --- a/src/assets/ba_data/python/bascenev1lib/activity/dualteamscore.py +++ b/src/assets/ba_data/python/bascenev1lib/activity/dualteamscore.py @@ -4,16 +4,10 @@ from __future__ import annotations -from typing import TYPE_CHECKING - -import babase import bascenev1 as bs from bascenev1lib.activity.multiteamscore import MultiTeamScoreScreenActivity from bascenev1lib.actor.zoomtext import ZoomText -if TYPE_CHECKING: - pass - class TeamVictoryScoreScreenActivity(MultiTeamScoreScreenActivity): """Scorescreen between rounds of a dual-team session.""" @@ -24,7 +18,7 @@ class TeamVictoryScoreScreenActivity(MultiTeamScoreScreenActivity): assert isinstance(self._winner, bs.SessionTeam) def on_begin(self) -> None: - babase.set_analytics_screen('Teams Score Screen') + bs.set_analytics_screen('Teams Score Screen') super().on_begin() height = 130 @@ -37,13 +31,13 @@ class TeamVictoryScoreScreenActivity(MultiTeamScoreScreenActivity): # 'First to 4'. session = self.session assert isinstance(session, bs.MultiTeamSession) - if babase.app.lang.get_resource('bestOfUseFirstToInstead'): - best_txt = babase.Lstr( + if bs.app.lang.get_resource('bestOfUseFirstToInstead'): + best_txt = bs.Lstr( resource='firstToSeriesText', subs=[('${COUNT}', str(session.get_series_length() / 2 + 1))], ) else: - best_txt = babase.Lstr( + best_txt = bs.Lstr( resource='bestOfSeriesText', subs=[('${COUNT}', str(session.get_series_length()))], ) @@ -63,7 +57,7 @@ class TeamVictoryScoreScreenActivity(MultiTeamScoreScreenActivity): for team in self.session.sessionteams: bs.timer( i * 0.15 + 0.15, - babase.WeakCall( + bs.WeakCall( self._show_team_name, vval - i * height, team, @@ -78,7 +72,7 @@ class TeamVictoryScoreScreenActivity(MultiTeamScoreScreenActivity): delay = 1.2 bs.timer( i * 0.150 + 0.2, - babase.WeakCall( + bs.WeakCall( self._show_team_old_score, vval - i * height, team, @@ -89,7 +83,7 @@ class TeamVictoryScoreScreenActivity(MultiTeamScoreScreenActivity): bs.timer( i * 0.150 + delay, - babase.WeakCall( + bs.WeakCall( self._show_team_score, vval - i * height, team, @@ -110,7 +104,7 @@ class TeamVictoryScoreScreenActivity(MultiTeamScoreScreenActivity): ) -> None: del kill_delay # Unused arg. ZoomText( - babase.Lstr(value='${A}:', subs=[('${A}', team.name)]), + bs.Lstr(value='${A}:', subs=[('${A}', team.name)]), position=(100, pos_v), shiftposition=(-150, pos_v), shiftdelay=shiftdelay, diff --git a/src/assets/ba_data/python/bascenev1lib/activity/multiteamjoin.py b/src/assets/ba_data/python/bascenev1lib/activity/multiteamjoin.py index 28f4f978..2870c1fd 100644 --- a/src/assets/ba_data/python/bascenev1lib/activity/multiteamjoin.py +++ b/src/assets/ba_data/python/bascenev1lib/activity/multiteamjoin.py @@ -4,15 +4,9 @@ from __future__ import annotations -from typing import TYPE_CHECKING - -import babase import bascenev1 as bs from bascenev1lib.actor.text import Text -if TYPE_CHECKING: - pass - class MultiTeamJoinActivity(bs.JoinActivity): """Join screen for teams sessions.""" @@ -32,10 +26,10 @@ class MultiTeamJoinActivity(bs.JoinActivity): # Show info about the next up game. self._next_up_text = Text( - babase.Lstr( + bs.Lstr( value='${1} ${2}', subs=[ - ('${1}', babase.Lstr(resource='upFirstText')), + ('${1}', bs.Lstr(resource='upFirstText')), ('${2}', session.get_next_game_description()), ], ), @@ -72,12 +66,12 @@ class MultiTeamJoinActivity(bs.JoinActivity): ).autoretain() Text( - babase.Lstr( + bs.Lstr( resource='mustInviteFriendsText', subs=[ ( '${GATHER}', - babase.Lstr(resource='gatherWindow.titleText'), + bs.Lstr(resource='gatherWindow.titleText'), ) ], ), diff --git a/src/assets/ba_data/python/bascenev1lib/activity/multiteamscore.py b/src/assets/ba_data/python/bascenev1lib/activity/multiteamscore.py index dfd498a1..ef2ba012 100644 --- a/src/assets/ba_data/python/bascenev1lib/activity/multiteamscore.py +++ b/src/assets/ba_data/python/bascenev1lib/activity/multiteamscore.py @@ -3,16 +3,10 @@ """Functionality related to teams mode score screen.""" from __future__ import annotations -from typing import TYPE_CHECKING - -import babase import bascenev1 as bs from bascenev1lib.actor.text import Text from bascenev1lib.actor.image import Image -if TYPE_CHECKING: - pass - class MultiTeamScoreScreenActivity(bs.ScoreScreenActivity): """Base class for score screens.""" @@ -28,12 +22,12 @@ class MultiTeamScoreScreenActivity(bs.ScoreScreenActivity): super().on_begin() session = self.session if self._show_up_next and isinstance(session, bs.MultiTeamSession): - txt = babase.Lstr( + txt = bs.Lstr( value='${A} ${B}', subs=[ ( '${A}', - babase.Lstr( + bs.Lstr( resource='upNextText', subs=[ ('${COUNT}', str(session.get_game_number() + 1)) @@ -84,7 +78,7 @@ class MultiTeamScoreScreenActivity(bs.ScoreScreenActivity): return val return p_rec.accumscore - def _get_prec_score_str(p_rec: bs.PlayerRecord) -> str | babase.Lstr: + def _get_prec_score_str(p_rec: bs.PlayerRecord) -> str | bs.Lstr: if is_free_for_all and results is not None: assert isinstance(results, bs.GameResults) assert p_rec.team.activityteam is not None @@ -135,7 +129,7 @@ class MultiTeamScoreScreenActivity(bs.ScoreScreenActivity): def _txt( xoffs: float, yoffs: float, - text: babase.Lstr, + text: bs.Lstr, h_align: Text.HAlign = Text.HAlign.RIGHT, extrascale: float = 1.0, maxwidth: float | None = 120.0, @@ -157,7 +151,7 @@ class MultiTeamScoreScreenActivity(bs.ScoreScreenActivity): session = self.session assert isinstance(session, bs.MultiTeamSession) - tval = babase.Lstr( + tval = bs.Lstr( resource='gameLeadersText', subs=[('${COUNT}', str(session.get_game_number()))], ) @@ -169,14 +163,12 @@ class MultiTeamScoreScreenActivity(bs.ScoreScreenActivity): extrascale=1.4, maxwidth=None, ) - _txt( - -15, 4, babase.Lstr(resource='playerText'), h_align=Text.HAlign.LEFT - ) - _txt(180, 4, babase.Lstr(resource='killsText')) - _txt(280, 4, babase.Lstr(resource='deathsText'), maxwidth=100) + _txt(-15, 4, bs.Lstr(resource='playerText'), h_align=Text.HAlign.LEFT) + _txt(180, 4, bs.Lstr(resource='killsText')) + _txt(280, 4, bs.Lstr(resource='deathsText'), maxwidth=100) score_label = 'Score' if results is None else results.score_label - translated = babase.Lstr(translate=('scoreNames', score_label)) + translated = bs.Lstr(translate=('scoreNames', score_label)) _txt(390, 0, translated) @@ -191,7 +183,7 @@ class MultiTeamScoreScreenActivity(bs.ScoreScreenActivity): topkilledcount = min(topkilledcount, prec.accum_killed_count) def _scoretxt( - text: str | babase.Lstr, + text: str | bs.Lstr, x_offs: float, highlight: bool, delay2: float, @@ -228,7 +220,7 @@ class MultiTeamScoreScreenActivity(bs.ScoreScreenActivity): transition_delay=tdelay, ).autoretain() Text( - babase.Lstr(value=playerrec.getname(full=True)), + bs.Lstr(value=playerrec.getname(full=True)), maxwidth=160, scale=0.75 * scale, position=( @@ -237,7 +229,7 @@ class MultiTeamScoreScreenActivity(bs.ScoreScreenActivity): ), h_align=Text.HAlign.LEFT, v_align=Text.VAlign.CENTER, - color=babase.safecolor(playerrec.team.color + (1,)), + color=bs.safecolor(playerrec.team.color + (1,)), transition=Text.Transition.IN_LEFT, transition_delay=tdelay, ).autoretain() diff --git a/src/assets/ba_data/python/bascenev1lib/activity/multiteamvictory.py b/src/assets/ba_data/python/bascenev1lib/activity/multiteamvictory.py index 64493e77..203f0516 100644 --- a/src/assets/ba_data/python/bascenev1lib/activity/multiteamvictory.py +++ b/src/assets/ba_data/python/bascenev1lib/activity/multiteamvictory.py @@ -4,15 +4,9 @@ from __future__ import annotations -from typing import TYPE_CHECKING - -import babase import bascenev1 as bs from bascenev1lib.activity.multiteamscore import MultiTeamScoreScreenActivity -if TYPE_CHECKING: - pass - class TeamSeriesVictoryScoreScreenActivity(MultiTeamScoreScreenActivity): """Final score screen for a team series.""" @@ -35,25 +29,25 @@ class TeamSeriesVictoryScoreScreenActivity(MultiTeamScoreScreenActivity): from bascenev1lib.actor.text import Text from bascenev1lib.actor.image import Image - babase.set_analytics_screen( + bs.set_analytics_screen( 'FreeForAll Series Victory Screen' if self._is_ffa else 'Teams Series Victory Screen' ) - assert babase.app.classic is not None - if babase.app.ui_v1.uiscale is babase.UIScale.LARGE: - sval = babase.Lstr(resource='pressAnyKeyButtonPlayAgainText') + assert bs.app.classic is not None + if bs.app.ui_v1.uiscale is bs.UIScale.LARGE: + sval = bs.Lstr(resource='pressAnyKeyButtonPlayAgainText') else: - sval = babase.Lstr(resource='pressAnyButtonPlayAgainText') + sval = bs.Lstr(resource='pressAnyButtonPlayAgainText') self._show_up_next = False self._custom_continue_message = sval super().on_begin() winning_sessionteam = self.settings_raw['winner'] # Pause a moment before playing victory music. - bs.timer(0.6, babase.WeakCall(self._play_victory_music)) + bs.timer(0.6, bs.WeakCall(self._play_victory_music)) bs.timer( - 4.4, babase.WeakCall(self._show_winner, self.settings_raw['winner']) + 4.4, bs.WeakCall(self._show_winner, self.settings_raw['winner']) ) bs.timer(4.6, self._score_display_sound.play) @@ -82,19 +76,19 @@ class TeamSeriesVictoryScoreScreenActivity(MultiTeamScoreScreenActivity): tval = 6.4 t_incr = 0.12 - always_use_first_to = babase.app.lang.get_resource( + always_use_first_to = bs.app.lang.get_resource( 'bestOfUseFirstToInstead' ) session = self.session if self._is_ffa: assert isinstance(session, bs.FreeForAllSession) - txt = babase.Lstr( + txt = bs.Lstr( value='${A}:', subs=[ ( '${A}', - babase.Lstr( + bs.Lstr( resource='firstToFinalText', subs=[ ( @@ -115,12 +109,12 @@ class TeamSeriesVictoryScoreScreenActivity(MultiTeamScoreScreenActivity): # they're not using this language. Should try to come up # with a wording that works everywhere. if always_use_first_to: - txt = babase.Lstr( + txt = bs.Lstr( value='${A}:', subs=[ ( '${A}', - babase.Lstr( + bs.Lstr( resource='firstToFinalText', subs=[ ( @@ -135,12 +129,12 @@ class TeamSeriesVictoryScoreScreenActivity(MultiTeamScoreScreenActivity): ], ) else: - txt = babase.Lstr( + txt = bs.Lstr( value='${A}:', subs=[ ( '${A}', - babase.Lstr( + bs.Lstr( resource='bestOfFinalText', subs=[ ( @@ -173,7 +167,7 @@ class TeamSeriesVictoryScoreScreenActivity(MultiTeamScoreScreenActivity): if not self._is_ffa: Text( - babase.Lstr( + bs.Lstr( resource='gamesToText', subs=[ ('${WINCOUNT}', str(win_score)), @@ -208,7 +202,7 @@ class TeamSeriesVictoryScoreScreenActivity(MultiTeamScoreScreenActivity): break if mvp is not None: Text( - babase.Lstr(resource='mostValuablePlayerText'), + bs.Lstr(resource='mostValuablePlayerText'), color=(0.5, 0.5, 0.5, 1.0), v_align=Text.VAlign.CENTER, maxwidth=300, @@ -228,13 +222,13 @@ class TeamSeriesVictoryScoreScreenActivity(MultiTeamScoreScreenActivity): ).autoretain() assert mvp_name is not None Text( - babase.Lstr(value=mvp_name), + bs.Lstr(value=mvp_name), position=(280, ts_height / 2 - 55 + 15 - 5), h_align=Text.HAlign.LEFT, v_align=Text.VAlign.CENTER, maxwidth=170, scale=1.3, - color=babase.safecolor(mvp.team.color + (1,)), + color=bs.safecolor(mvp.team.color + (1,)), transition=Text.Transition.IN_LEFT, transition_delay=tval, ).autoretain() @@ -249,7 +243,7 @@ class TeamSeriesVictoryScoreScreenActivity(MultiTeamScoreScreenActivity): most_kills = entry[2].kill_count if mvp is not None: Text( - babase.Lstr(resource='mostViolentPlayerText'), + bs.Lstr(resource='mostViolentPlayerText'), color=(0.5, 0.5, 0.5, 1.0), v_align=Text.VAlign.CENTER, maxwidth=300, @@ -259,12 +253,12 @@ class TeamSeriesVictoryScoreScreenActivity(MultiTeamScoreScreenActivity): transition_delay=tval, ).autoretain() Text( - babase.Lstr( + bs.Lstr( value='(${A})', subs=[ ( '${A}', - babase.Lstr( + bs.Lstr( resource='killsTallyText', subs=[('${COUNT}', str(most_kills))], ), @@ -289,12 +283,12 @@ class TeamSeriesVictoryScoreScreenActivity(MultiTeamScoreScreenActivity): ).autoretain() assert mvp_name is not None Text( - babase.Lstr(value=mvp_name), + bs.Lstr(value=mvp_name), position=(270, ts_height / 2 - 150 - 30 - 36 + v_extra + 15), h_align=Text.HAlign.LEFT, v_align=Text.VAlign.CENTER, maxwidth=180, - color=babase.safecolor(mvp.team.color + (1,)), + color=bs.safecolor(mvp.team.color + (1,)), transition=Text.Transition.IN_LEFT, transition_delay=tval, ).autoretain() @@ -310,7 +304,7 @@ class TeamSeriesVictoryScoreScreenActivity(MultiTeamScoreScreenActivity): most_killed = entry[2].killed_count if mkp is not None: Text( - babase.Lstr(resource='mostViolatedPlayerText'), + bs.Lstr(resource='mostViolatedPlayerText'), color=(0.5, 0.5, 0.5, 1.0), v_align=Text.VAlign.CENTER, maxwidth=300, @@ -320,12 +314,12 @@ class TeamSeriesVictoryScoreScreenActivity(MultiTeamScoreScreenActivity): transition_delay=tval, ).autoretain() Text( - babase.Lstr( + bs.Lstr( value='(${A})', subs=[ ( '${A}', - babase.Lstr( + bs.Lstr( resource='deathsTallyText', subs=[('${COUNT}', str(most_killed))], ), @@ -349,11 +343,11 @@ class TeamSeriesVictoryScoreScreenActivity(MultiTeamScoreScreenActivity): ).autoretain() assert mkp_name is not None Text( - babase.Lstr(value=mkp_name), + bs.Lstr(value=mkp_name), position=(270, ts_height / 2 - 300 - 30 - 36 + v_extra + 15), h_align=Text.HAlign.LEFT, v_align=Text.VAlign.CENTER, - color=babase.safecolor(mkp.team.color + (1,)), + color=bs.safecolor(mkp.team.color + (1,)), maxwidth=180, transition=Text.Transition.IN_LEFT, transition_delay=tval, @@ -363,7 +357,7 @@ class TeamSeriesVictoryScoreScreenActivity(MultiTeamScoreScreenActivity): # Now show individual scores. tdelay = tval Text( - babase.Lstr(resource='finalScoresText'), + bs.Lstr(resource='finalScoresText'), color=(0.5, 0.5, 0.5, 1.0), position=(ts_h_offs, ts_height / 2), transition=Text.Transition.IN_RIGHT, @@ -396,17 +390,17 @@ class TeamSeriesVictoryScoreScreenActivity(MultiTeamScoreScreenActivity): transition_delay=tdelay, ).autoretain() Text( - babase.Lstr(value=name), + bs.Lstr(value=name), position=(ts_h_offs - 50, ts_height / 2 + v_offs + 15), h_align=Text.HAlign.LEFT, v_align=Text.VAlign.CENTER, maxwidth=180, - color=babase.safecolor(prec.team.color + (1,)), + color=bs.safecolor(prec.team.color + (1,)), transition=Text.Transition.IN_RIGHT, transition_delay=tdelay, ).autoretain() - bs.timer(15.0, babase.WeakCall(self._show_tips)) + bs.timer(15.0, bs.WeakCall(self._show_tips)) def _show_tips(self) -> None: from bascenev1lib.actor.tipstext import TipsText @@ -443,7 +437,7 @@ class TeamSeriesVictoryScoreScreenActivity(MultiTeamScoreScreenActivity): assert i.node bs.animate(i.node, 'opacity', {0.0: 0.0, 0.25: 1.0}) ZoomText( - babase.Lstr( + bs.Lstr( value=team.players[0].getname(full=True, icon=False) ), position=(0, 97 + offs_v), @@ -460,7 +454,7 @@ class TeamSeriesVictoryScoreScreenActivity(MultiTeamScoreScreenActivity): wins_resource = 'seriesWinLine1PlayerText' else: wins_resource = 'seriesWinLine1TeamText' - wins_text = babase.Lstr(resource=wins_resource) + wins_text = bs.Lstr(resource=wins_resource) # Temp - if these come up as the english default, fall-back to the # unified old form which is more likely to be translated. @@ -473,7 +467,7 @@ class TeamSeriesVictoryScoreScreenActivity(MultiTeamScoreScreenActivity): maxwidth=250, ).autoretain() ZoomText( - babase.Lstr(resource='seriesWinLine2Text'), + bs.Lstr(resource='seriesWinLine2Text'), position=(0, -110 + offs_v), scale=1.0 * s_extra, color=team.color, diff --git a/src/assets/ba_data/python/bascenev1lib/actor/playerspaz.py b/src/assets/ba_data/python/bascenev1lib/actor/playerspaz.py index 928d5ecb..57c1db0f 100644 --- a/src/assets/ba_data/python/bascenev1lib/actor/playerspaz.py +++ b/src/assets/ba_data/python/bascenev1lib/actor/playerspaz.py @@ -6,7 +6,6 @@ from __future__ import annotations from typing import TYPE_CHECKING, TypeVar, overload -import babase import bascenev1 as bs from bascenev1lib.actor.spaz import Spaz @@ -99,7 +98,7 @@ class PlayerSpaz(Spaz): player: Any = self._player assert isinstance(player, playertype) if not player.exists() and doraise: - raise babase.PlayerNotFoundError() + raise bs.PlayerNotFoundError() return player if player.exists() else None def connect_controls_to_player( @@ -129,16 +128,16 @@ class PlayerSpaz(Spaz): else: player.resetinput() - player.assigninput(babase.InputType.UP_DOWN, self.on_move_up_down) - player.assigninput(babase.InputType.LEFT_RIGHT, self.on_move_left_right) + player.assigninput(bs.InputType.UP_DOWN, self.on_move_up_down) + player.assigninput(bs.InputType.LEFT_RIGHT, self.on_move_left_right) player.assigninput( - babase.InputType.HOLD_POSITION_PRESS, self.on_hold_position_press + bs.InputType.HOLD_POSITION_PRESS, self.on_hold_position_press ) player.assigninput( - babase.InputType.HOLD_POSITION_RELEASE, + bs.InputType.HOLD_POSITION_RELEASE, self.on_hold_position_release, ) - intp = babase.InputType + intp = bs.InputType if enable_jump: player.assigninput(intp.JUMP_PRESS, self.on_jump_press) player.assigninput(intp.JUMP_RELEASE, self.on_jump_release) @@ -210,7 +209,7 @@ class PlayerSpaz(Spaz): picked_up_by = msg.node.source_player if picked_up_by: self.last_player_attacked_by = picked_up_by - self.last_attacked_time = babase.apptime() + self.last_attacked_time = bs.apptime() self.last_attacked_type = ('picked_up', 'default') elif isinstance(msg, bs.StandMessage): super().handlemessage(msg) # Augment standard behavior. @@ -248,7 +247,7 @@ class PlayerSpaz(Spaz): # something like last_actor_attacked_by to fix that. if ( self.last_player_attacked_by - and babase.apptime() - self.last_attacked_time < 4.0 + and bs.apptime() - self.last_attacked_time < 4.0 ): killerplayer = self.last_player_attacked_by else: @@ -279,7 +278,7 @@ class PlayerSpaz(Spaz): source_player = msg.get_source_player(type(self._player)) if source_player: self.last_player_attacked_by = source_player - self.last_attacked_time = babase.apptime() + self.last_attacked_time = bs.apptime() self.last_attacked_type = (msg.hit_type, msg.hit_subtype) super().handlemessage(msg) # Augment standard behavior. activity = self._activity() diff --git a/src/assets/ba_data/python/bascenev1lib/actor/popuptext.py b/src/assets/ba_data/python/bascenev1lib/actor/popuptext.py index e08de510..c5c1dccd 100644 --- a/src/assets/ba_data/python/bascenev1lib/actor/popuptext.py +++ b/src/assets/ba_data/python/bascenev1lib/actor/popuptext.py @@ -7,7 +7,6 @@ from __future__ import annotations import random from typing import TYPE_CHECKING -import babase import bascenev1 as bs if TYPE_CHECKING: @@ -22,7 +21,7 @@ class PopupText(bs.Actor): def __init__( self, - text: str | babase.Lstr, + text: str | bs.Lstr, position: Sequence[float] = (0.0, 0.0, 0.0), color: Sequence[float] = (1.0, 1.0, 1.0, 1.0), random_offset: float = 0.5, @@ -116,7 +115,7 @@ class PopupText(bs.Actor): # kill ourself self._die_timer = bs.Timer( - lifespan, babase.WeakCall(self.handlemessage, bs.DieMessage()) + lifespan, bs.WeakCall(self.handlemessage, bs.DieMessage()) ) def handlemessage(self, msg: Any) -> Any: diff --git a/src/assets/ba_data/python/bascenev1lib/actor/respawnicon.py b/src/assets/ba_data/python/bascenev1lib/actor/respawnicon.py index 1b5da40c..72a14c93 100644 --- a/src/assets/ba_data/python/bascenev1lib/actor/respawnicon.py +++ b/src/assets/ba_data/python/bascenev1lib/actor/respawnicon.py @@ -5,14 +5,9 @@ from __future__ import annotations import weakref -from typing import TYPE_CHECKING -import babase import bascenev1 as bs -if TYPE_CHECKING: - pass - class RespawnIcon: """An icon with a countdown that appears alongside the screen. @@ -22,11 +17,11 @@ class RespawnIcon: This is used to indicate that a bascenev1.Player is waiting to respawn. """ - _MASKTEXSTORENAME = babase.storagename('masktex') - _ICONSSTORENAME = babase.storagename('icons') + _MASKTEXSTORENAME = bs.storagename('masktex') + _ICONSSTORENAME = bs.storagename('icons') def __init__(self, player: bs.Player, respawn_time: float): - """Instantiate with a bascenev1.Player and respawn_time (in seconds).""" + """Instantiate with a Player and respawn_time (in seconds).""" self._visible = True on_right, offs_extra, respawn_icons = self._get_context(player) @@ -81,13 +76,13 @@ class RespawnIcon: attrs={ 'v_attach': 'top', 'h_attach': 'right' if on_right else 'left', - 'text': babase.Lstr(value=player.getname()), + 'text': bs.Lstr(value=player.getname()), 'maxwidth': 100, 'h_align': 'center', 'v_align': 'center', 'shadow': 1.0, 'flatness': 1.0, - 'color': babase.safecolor(icon['tint_color']), + 'color': bs.safecolor(icon['tint_color']), 'scale': 0.5, 'position': npos, }, @@ -109,7 +104,7 @@ class RespawnIcon: 'shadow': 0.5, 'flatness': 0.5, 'v_attach': 'top', - 'color': babase.safecolor(icon['tint_color']), + 'color': bs.safecolor(icon['tint_color']), 'text': '', }, ) @@ -118,10 +113,10 @@ class RespawnIcon: assert self._text.node bs.animate(self._text.node, 'scale', {0: 0, 0.1: 0.9}) - self._respawn_time = babase.apptime() + respawn_time + self._respawn_time = bs.time() + respawn_time self._update() self._timer: bs.Timer | None = bs.Timer( - 1.0, babase.WeakCall(self._update), repeat=True + 1.0, bs.WeakCall(self._update), repeat=True ) @property @@ -159,7 +154,7 @@ class RespawnIcon: return on_right, offs_extra, icons def _update(self) -> None: - remaining = int(round(self._respawn_time - babase.apptime())) + remaining = int(round(self._respawn_time - bs.time())) if remaining > 0: assert self._text is not None if self._text.node: diff --git a/src/assets/ba_data/python/bascenev1lib/actor/text.py b/src/assets/ba_data/python/bascenev1lib/actor/text.py index b69edd5f..1a274d64 100644 --- a/src/assets/ba_data/python/bascenev1lib/actor/text.py +++ b/src/assets/ba_data/python/bascenev1lib/actor/text.py @@ -7,7 +7,6 @@ from __future__ import annotations from enum import Enum from typing import TYPE_CHECKING -import babase import bascenev1 as bs if TYPE_CHECKING: @@ -56,7 +55,7 @@ class Text(bs.Actor): def __init__( self, - text: str | babase.Lstr, + text: str | bs.Lstr, position: tuple[float, float] = (0.0, 0.0), h_align: HAlign = HAlign.LEFT, v_align: VAlign = VAlign.NONE, @@ -219,7 +218,7 @@ class Text(bs.Actor): if transition_out_delay is not None: bs.timer( transition_delay + transition_out_delay + 1.0, - babase.WeakCall(self.handlemessage, bs.DieMessage()), + bs.WeakCall(self.handlemessage, bs.DieMessage()), ) def handlemessage(self, msg: Any) -> Any: diff --git a/src/assets/ba_data/python/bascenev1lib/actor/tipstext.py b/src/assets/ba_data/python/bascenev1lib/actor/tipstext.py index 83c4f2d7..ede00802 100644 --- a/src/assets/ba_data/python/bascenev1lib/actor/tipstext.py +++ b/src/assets/ba_data/python/bascenev1lib/actor/tipstext.py @@ -6,7 +6,6 @@ from __future__ import annotations from typing import TYPE_CHECKING -import babase import bascenev1 as bs if TYPE_CHECKING: @@ -34,8 +33,8 @@ class TipsText(bs.Actor): 'v_attach': 'bottom', }, ) - tval = babase.Lstr( - value='${A}:', subs=[('${A}', babase.Lstr(resource='tipText'))] + tval = bs.Lstr( + value='${A}:', subs=[('${A}', bs.Lstr(resource='tipText'))] ) self.title_node = bs.newnode( 'text', @@ -54,7 +53,7 @@ class TipsText(bs.Actor): self._message_spacing = 3000 self._change_timer = bs.Timer( 0.001 * (self._message_duration + self._message_spacing), - babase.WeakCall(self.change_phrase), + bs.WeakCall(self.change_phrase), repeat=True, ) self._combine = bs.newnode( @@ -70,11 +69,11 @@ class TipsText(bs.Actor): """Switch the visible tip phrase.""" from babase import get_remote_app_name - next_tip = babase.Lstr( + next_tip = bs.Lstr( translate=( 'tips', - babase.app.classic.get_next_tip() - if babase.app.classic is not None + bs.app.classic.get_next_tip() + if bs.app.classic is not None else '', ), subs=[('${REMOTE_APP_NAME}', get_remote_app_name())], diff --git a/src/assets/ba_data/python/bascenev1lib/actor/zoomtext.py b/src/assets/ba_data/python/bascenev1lib/actor/zoomtext.py index 0a63dae2..ad6276cd 100644 --- a/src/assets/ba_data/python/bascenev1lib/actor/zoomtext.py +++ b/src/assets/ba_data/python/bascenev1lib/actor/zoomtext.py @@ -5,9 +5,9 @@ from __future__ import annotations import random +import logging from typing import TYPE_CHECKING -import babase import bascenev1 as bs if TYPE_CHECKING: @@ -24,7 +24,7 @@ class ZoomText(bs.Actor): def __init__( self, - text: str | babase.Lstr, + text: str | bs.Lstr, position: tuple[float, float] = (0.0, 0.0), shiftposition: tuple[float, float] | None = None, shiftdelay: float | None = None, @@ -47,7 +47,7 @@ class ZoomText(bs.Actor): if shiftdelay is None: shiftdelay = 2.500 if shiftdelay < 0.0: - babase.print_error('got shiftdelay < 0') + logging.error('got shiftdelay < 0') shiftdelay = 0.0 self._project_scale = project_scale self.node = bs.newnode( @@ -69,7 +69,7 @@ class ZoomText(bs.Actor): ) # we never jitter in vr mode.. - if babase.app.vr_mode: + if bs.app.vr_mode: jitter = 0.0 # if they want jitter, animate its position slightly... @@ -82,14 +82,12 @@ class ZoomText(bs.Actor): positionadjusted2 = (shiftposition[0], shiftposition[1] - 100) bs.timer( shiftdelay, - babase.WeakCall( - self._shift, positionadjusted, positionadjusted2 - ), + bs.WeakCall(self._shift, positionadjusted, positionadjusted2), ) if jitter > 0.0: bs.timer( shiftdelay + 0.25, - babase.WeakCall( + bs.WeakCall( self._jitter, positionadjusted2, jitter * scale ), ) @@ -158,9 +156,7 @@ class ZoomText(bs.Actor): # if they give us a lifespan, kill ourself down the line if lifespan is not None: - bs.timer( - lifespan, babase.WeakCall(self.handlemessage, bs.DieMessage()) - ) + bs.timer(lifespan, bs.WeakCall(self.handlemessage, bs.DieMessage())) def handlemessage(self, msg: Any) -> Any: assert not self.expired diff --git a/src/assets/ba_data/python/bascenev1lib/gameutils.py b/src/assets/ba_data/python/bascenev1lib/gameutils.py index ca8d7ab1..35810d06 100644 --- a/src/assets/ba_data/python/bascenev1lib/gameutils.py +++ b/src/assets/ba_data/python/bascenev1lib/gameutils.py @@ -6,7 +6,6 @@ from __future__ import annotations from typing import TYPE_CHECKING -import babase import bascenev1 as bs if TYPE_CHECKING: @@ -23,7 +22,7 @@ class SharedObjects: standard materials. """ - _STORENAME = babase.storagename() + _STORENAME = bs.storagename() def __init__(self) -> None: activity = bs.getactivity() diff --git a/src/assets/ba_data/python/bauiv1lib/playlist/__init__.py b/src/assets/ba_data/python/bauiv1lib/playlist/__init__.py index 3aa24bc6..dbe143f7 100644 --- a/src/assets/ba_data/python/bauiv1lib/playlist/__init__.py +++ b/src/assets/ba_data/python/bauiv1lib/playlist/__init__.py @@ -4,14 +4,8 @@ from __future__ import annotations -from typing import TYPE_CHECKING - -import babase import bascenev1 as bs -if TYPE_CHECKING: - pass - # FIXME: Could change this to be a classmethod of session types? class PlaylistTypeVars: @@ -26,26 +20,26 @@ class PlaylistTypeVars: self.sessiontype: type[bs.Session] if issubclass(sessiontype, bs.DualTeamSession): - play_mode_name = babase.Lstr( + play_mode_name = bs.Lstr( resource='playModes.teamsText', fallback_resource='teamsText' ) self.get_default_list_call = get_default_teams_playlist self.session_type_name = 'bascenev1.DualTeamSession' self.config_name = 'Team Tournament' - self.window_title_name = babase.Lstr( + self.window_title_name = bs.Lstr( resource='playModes.teamsText', fallback_resource='teamsText' ) self.sessiontype = bs.DualTeamSession elif issubclass(sessiontype, bs.FreeForAllSession): - play_mode_name = babase.Lstr( + play_mode_name = bs.Lstr( resource='playModes.freeForAllText', fallback_resource='freeForAllText', ) self.get_default_list_call = get_default_free_for_all_playlist self.session_type_name = 'bascenev1.FreeForAllSession' self.config_name = 'Free-for-All' - self.window_title_name = babase.Lstr( + self.window_title_name = bs.Lstr( resource='playModes.freeForAllText', fallback_resource='freeForAllText', ) @@ -55,11 +49,11 @@ class PlaylistTypeVars: raise RuntimeError( f'Playlist type vars undefined for sessiontype: {sessiontype}' ) - self.default_list_name = babase.Lstr( + self.default_list_name = bs.Lstr( resource='defaultGameListNameText', subs=[('${PLAYMODE}', play_mode_name)], ) - self.default_new_list_name = babase.Lstr( + self.default_new_list_name = bs.Lstr( resource='defaultNewGameListNameText', subs=[('${PLAYMODE}', play_mode_name)], ) diff --git a/src/assets/ba_data/python/bauiv1lib/settings/vrtesting.py b/src/assets/ba_data/python/bauiv1lib/settings/vrtesting.py index d4916759..19432834 100644 --- a/src/assets/ba_data/python/bauiv1lib/settings/vrtesting.py +++ b/src/assets/ba_data/python/bauiv1lib/settings/vrtesting.py @@ -6,7 +6,7 @@ from __future__ import annotations from typing import TYPE_CHECKING -import babase +import bauiv1 as bui from bauiv1lib.settings.testing import TestingWindow if TYPE_CHECKING: @@ -18,10 +18,10 @@ class VRTestingWindow(TestingWindow): def __init__(self, transition: str = 'in_right'): entries: list[dict[str, Any]] = [] - app = babase.app + app = bui.app assert app.classic is not None - # these are gear-vr only + # These are gear-vr only. if ( app.classic.platform == 'android' and app.classic.subplatform == 'oculus' @@ -44,16 +44,19 @@ class VRTestingWindow(TestingWindow): }, # {'name':'eyeOffsX','label':'Eye IPD','increment':0.001} ] - # cardboard/gearvr get eye offset controls.. + + # Cardboard/gearvr get eye offset controls. # if app.platform == 'android': # entries += [ # {'name':'eyeOffsY','label':'Eye Offset Y','increment':0.01}, # {'name':'eyeOffsZ','label':'Eye Offset Z','increment':0.005}] - # everyone gets head-scale + + # Everyone gets head-scale. entries += [ {'name': 'headScale', 'label': 'Head Scale', 'increment': 1.0} ] - # and everyone gets all these.. + + # And everyone gets all these. entries += [ { 'name': 'vrCamOffsetY', @@ -88,7 +91,7 @@ class VRTestingWindow(TestingWindow): ] super().__init__( - babase.Lstr(resource='settingsWindowAdvanced.vrTestingText'), + bui.Lstr(resource='settingsWindowAdvanced.vrTestingText'), entries, transition, ) diff --git a/src/assets/ba_data/python/bauiv1lib/tabs.py b/src/assets/ba_data/python/bauiv1lib/tabs.py index 2803e634..ffb0cff4 100644 --- a/src/assets/ba_data/python/bauiv1lib/tabs.py +++ b/src/assets/ba_data/python/bauiv1lib/tabs.py @@ -7,7 +7,6 @@ from __future__ import annotations from dataclasses import dataclass from typing import TYPE_CHECKING, TypeVar, Generic -import babase import bauiv1 as bui if TYPE_CHECKING: @@ -35,7 +34,7 @@ class TabRow(Generic[T]): def __init__( self, parent: bui.Widget, - tabdefs: list[tuple[T, babase.Lstr]], + tabdefs: list[tuple[T, bui.Lstr]], pos: tuple[float, float], size: tuple[float, float], on_select_call: Callable[[T], None] | None = None, @@ -58,7 +57,7 @@ class TabRow(Generic[T]): size=size, label=tab_label, enable_sound=False, - on_activate_call=babase.Call( + on_activate_call=bui.Call( self._tick_and_call, on_select_call, tab_id ), ) diff --git a/src/ballistica/base/app/stress_test.cc b/src/ballistica/base/app/stress_test.cc index 88a21428..e35da034 100644 --- a/src/ballistica/base/app/stress_test.cc +++ b/src/ballistica/base/app/stress_test.cc @@ -45,7 +45,7 @@ void StressTest::Update() { if (t - last_stress_test_update_time_ >= 10000) { if (stress_test_stats_file_ == nullptr) { assert(g_core); - auto user_python_dir = g_core->platform->GetUserPythonDirectory(); + auto user_python_dir = g_core->GetUserPythonDirectory(); if (user_python_dir) { std::string f_name = *user_python_dir + "/stress_test_stats.csv"; stress_test_stats_file_ = diff --git a/src/ballistica/base/assets/assets.cc b/src/ballistica/base/assets/assets.cc index 1b57aedf..2f603a7b 100644 --- a/src/ballistica/base/assets/assets.cc +++ b/src/ballistica/base/assets/assets.cc @@ -39,7 +39,7 @@ static const bool kShowPruningInfo = false; #define PENDING_LOAD_PROCESS_TIME 5 Assets::Assets() { - asset_paths_.emplace_back(g_core->platform->GetDataDirectory() + BA_DIRSLASH + asset_paths_.emplace_back(g_core->GetDataDirectory() + BA_DIRSLASH + "ba_data"); for (bool& have_pending_load : have_pending_loads_) { have_pending_load = false; diff --git a/src/ballistica/base/base.cc b/src/ballistica/base/base.cc index fbb6773e..3192c6a1 100644 --- a/src/ballistica/base/base.cc +++ b/src/ballistica/base/base.cc @@ -86,7 +86,7 @@ void BaseFeatureSet::OnModuleExec(PyObject* module) { // Want to run this at the last possible moment before spinning up our // BaseFeatureSet. This locks in baenv customizations. - g_core->python->ApplyBaEnvConfig(); + g_core->ApplyBaEnvConfig(); // Create our feature-set's C++ front-end. assert(g_base == nullptr); diff --git a/src/ballistica/base/graphics/text/text_graphics.cc b/src/ballistica/base/graphics/text/text_graphics.cc index 09aaba4f..f9e5a8d5 100644 --- a/src/ballistica/base/graphics/text/text_graphics.cc +++ b/src/ballistica/base/graphics/text/text_graphics.cc @@ -826,8 +826,8 @@ void TextGraphics::LoadGlyphPage(uint32_t index) { if (g_glyph_pages[index] == nullptr) { char buffer[256]; snprintf(buffer, sizeof(buffer), "%s%sba_data%sfonts%sfontSmall%d.fdata", - g_core->platform->GetDataDirectory().c_str(), BA_DIRSLASH, - BA_DIRSLASH, BA_DIRSLASH, index); + g_core->GetDataDirectory().c_str(), BA_DIRSLASH, BA_DIRSLASH, + BA_DIRSLASH, index); FILE* f = g_core->platform->FOpen(buffer, "rb"); BA_PRECONDITION(f); BA_PRECONDITION(sizeof(TextGraphics::Glyph[2]) == sizeof(float[18])); diff --git a/src/ballistica/base/python/methods/python_methods_app.cc b/src/ballistica/base/python/methods/python_methods_app.cc index 62c9a69e..6e8be733 100644 --- a/src/ballistica/base/python/methods/python_methods_app.cc +++ b/src/ballistica/base/python/methods/python_methods_app.cc @@ -711,12 +711,9 @@ static auto PyEnv(PyObject* self) -> PyObject* { default: throw Exception(); } - std::optional user_py_dir = - g_core->platform->GetUserPythonDirectory(); - std::optional app_py_dir = - g_core->platform->GetAppPythonDirectory(); - std::optional site_py_dir = - g_core->platform->GetSitePythonDirectory(); + std::optional user_py_dir = g_core->GetUserPythonDirectory(); + std::optional app_py_dir = g_core->GetAppPythonDirectory(); + std::optional site_py_dir = g_core->GetSitePythonDirectory(); // clang-format off PyObject* env = Py_BuildValue( @@ -772,7 +769,7 @@ static auto PyEnv(PyObject* self) -> PyObject* { "device_name", g_core->platform->GetDeviceName().c_str(), "data_directory", - g_core->platform->GetDataDirectory().c_str()); + g_core->GetDataDirectory().c_str()); // clang-format on g_base->python->StoreEnv(env); } @@ -1449,6 +1446,29 @@ static PyMethodDef PyEmptyAppModeHandleIntentExecDef = { "(internal)", }; +// ---------------------- get_immediate_return_code ---------------------------- + +static auto PyGetImmediateReturnCode(PyObject* self) -> PyObject* { + BA_PYTHON_TRY; + BA_PRECONDITION(g_core); + auto val = g_core->core_config().immediate_return_code; + if (!val.has_value()) { + Py_RETURN_NONE; + } + return PyLong_FromLong(*val); + BA_PYTHON_CATCH; +} + +static PyMethodDef PyGetImmediateReturnCodeDef = { + "get_immediate_return_code", // name + (PyCFunction)PyGetImmediateReturnCode, // method + METH_NOARGS, // flags + + "get_immediate_return_code() -> int | None\n" + "\n" + "(internal)\n", +}; + // ----------------------------------------------------------------------------- auto PythonMethodsApp::GetMethods() -> std::vector { @@ -1496,6 +1516,7 @@ auto PythonMethodsApp::GetMethods() -> std::vector { PyEmptyAppModeDeactivateDef, PyEmptyAppModeHandleIntentDefaultDef, PyEmptyAppModeHandleIntentExecDef, + PyGetImmediateReturnCodeDef, }; } diff --git a/src/ballistica/base/python/methods/python_methods_misc.cc b/src/ballistica/base/python/methods/python_methods_misc.cc index 8dbc6b6f..07a6a0f8 100644 --- a/src/ballistica/base/python/methods/python_methods_misc.cc +++ b/src/ballistica/base/python/methods/python_methods_misc.cc @@ -854,7 +854,7 @@ static PyMethodDef PySetPlatformMiscReadValsDef = { static auto PyGetLogFilePath(PyObject* self, PyObject* args) -> PyObject* { BA_PYTHON_TRY; - std::string config_dir = g_core->platform->GetConfigDirectory(); + std::string config_dir = g_core->GetConfigDirectory(); std::string logpath = config_dir + BA_DIRSLASH + "log.json"; return PyUnicode_FromString(logpath.c_str()); BA_PYTHON_CATCH; diff --git a/src/ballistica/core/core.cc b/src/ballistica/core/core.cc index a515a8c2..c2d1461b 100644 --- a/src/ballistica/core/core.cc +++ b/src/ballistica/core/core.cc @@ -15,21 +15,46 @@ CoreFeatureSet* g_core{}; BaseSoftInterface* g_base_soft{}; auto CoreFeatureSet::Import(const CoreConfig* config) -> CoreFeatureSet* { - // Only accept a config in monolithic builds if this is the first import. - if (config != nullptr) { - if (!g_buildconfig.monolithic_build()) { - FatalError("CoreConfig can only be passed in monolithic builds."); - } - if (g_core != nullptr) { - FatalError("CoreConfig can only be passed on the first import call."); - } - - if (g_core == nullptr) { - DoImport(*config); + // In monolithic builds we can accept an explicit core-config the first + // time we're imported. + if (g_buildconfig.monolithic_build()) { + if (config != nullptr) { + if (g_core != nullptr) { + FatalError( + "CoreConfig can only be passed on the first CoreFeatureSet::Import " + "call."); + } + if (g_core == nullptr) { + DoImport(*config); + } + } else { + // No config passed; use a default. + if (g_core == nullptr) { + DoImport({}); + } } } else { + // In modular builds we autogenerate a CoreConfig that takes into + // account only env-vars (or env-vars plus Python args if we're being + // run via the baenv script). + if (config != nullptr) { + FatalError("CoreConfig can't be explicitly passed in modular builds."); + } if (g_core == nullptr) { - DoImport({}); + bool can_do_args = CorePython::WasModularMainCalled(); + if (can_do_args) { + // Wrangle Python's sys.argv into a standard C-style argc/argv so we + // can pass to the same handler as the monolithic C route. Note that + // a few of the values we parse here (--command, etc) have already + // been handled at the Python layer, but we parse them here just the + // same so that we have uniform records and invalid-value handling + // between monolithic and modular. + std::vector argbuffer; + std::vector argv = CorePython::FetchPythonArgs(&argbuffer); + DoImport(CoreConfig::ForArgsAndEnvVars(argv.size(), argv.data())); + } else { + DoImport(CoreConfig::ForEnvVars()); + } } } return g_core; @@ -90,14 +115,88 @@ void CoreFeatureSet::PostInit() { // (so that our log handling system is fully bootstrapped), but // technically we can push our log calls out to Python any time now since // we grabbed the logging calls above. Do so immediately here if asked. - if (!g_core->core_config().hold_early_logs) { + if (!core_config_.hold_early_logs) { python->EnablePythonLoggingCalls(); } +} - // FIXME: MOVE THIS TO A RUN_APP_TO_COMPLETION() SORT OF PLACE. - // For now it does the right thing here since all we have is monolithic - // builds but this will need to account for more situations later. - // python->ReleaseMainThreadGIL(); +auto CoreFeatureSet::core_config() const -> const CoreConfig& { + // Try to make a bit of noise if we're accessed in modular builds before + // baenv values are set, since in that case we won't yet have our final + // core-config values. Though we want to keep this to a minimal printf so + // we don't interfere with low-level stuff like FatalError handling that + // might need core_config access at any time. + if (!g_buildconfig.monolithic_build()) { + if (!HaveBaEnvVals()) { + static bool did_warn = false; + if (!did_warn) { + did_warn = true; + printf( + "WARNING: accessing core_config() before baenv values have been " + "applied to it.\n"); + } + } + } + return core_config_; +} + +void CoreFeatureSet::ApplyBaEnvConfig() { + auto envcfg = + python->objs().Get(core::CorePython::ObjID::kBaEnvGetConfigCall).Call(); + BA_PRECONDITION_FATAL(envcfg.Exists()); + + assert(!have_ba_env_vals_); + have_ba_env_vals_ = true; + + // Grab everything baenv shipped us. + ba_env_config_dir_ = envcfg.GetAttr("config_dir").ValueAsString(); + ba_env_data_dir_ = envcfg.GetAttr("data_dir").ValueAsString(); + ba_env_app_python_dir_ = + envcfg.GetAttr("app_python_dir").ValueAsOptionalString(); + ba_env_user_python_dir_ = + envcfg.GetAttr("user_python_dir").ValueAsOptionalString(); + ba_env_site_python_dir_ = + envcfg.GetAttr("site_python_dir").ValueAsOptionalString(); + + // Consider app-python-dir to be 'custom' if baenv provided a value for it + // AND that value differs from baenv's default. + auto standard_app_python_dir = + envcfg.GetAttr("standard_app_python_dir").ValueAsString(); + using_custom_app_python_dir_ = + ba_env_app_python_dir_.has_value() + && *ba_env_app_python_dir_ != standard_app_python_dir; + + // Ok, now look for the existence of ba_data in the dir we've got. + auto fullpath = ba_env_data_dir_ + BA_DIRSLASH + "ba_data"; + if (!platform->FilePathExists(fullpath)) { + FatalError("ba_data directory not found at '" + fullpath + "'."); + } +} + +auto CoreFeatureSet::GetAppPythonDirectory() -> std::optional { + BA_PRECONDITION(have_ba_env_vals_); + return ba_env_app_python_dir_; +} + +auto CoreFeatureSet::GetUserPythonDirectory() -> std::optional { + BA_PRECONDITION(have_ba_env_vals_); + return ba_env_user_python_dir_; +} + +// Return the ballisticakit config dir. This does not vary across versions. +auto CoreFeatureSet::GetConfigDirectory() -> std::string { + BA_PRECONDITION(have_ba_env_vals_); + return ba_env_config_dir_; +} + +auto CoreFeatureSet::GetDataDirectory() -> std::string { + BA_PRECONDITION(have_ba_env_vals_); + return ba_env_data_dir_; +} + +auto CoreFeatureSet::GetSitePythonDirectory() -> std::optional { + BA_PRECONDITION(have_ba_env_vals_); + return ba_env_site_python_dir_; } auto CoreFeatureSet::CalcBuildSrcDir() -> std::string { diff --git a/src/ballistica/core/core.h b/src/ballistica/core/core.h index bedfdb6d..cc9e90a0 100644 --- a/src/ballistica/core/core.h +++ b/src/ballistica/core/core.h @@ -3,6 +3,7 @@ #ifndef BALLISTICA_CORE_CORE_H_ #define BALLISTICA_CORE_CORE_H_ +#include #include #include #include @@ -54,13 +55,15 @@ class CoreFeatureSet { auto SoftImportBase() -> BaseSoftInterface*; /// The core-config we were inited with. - const auto& core_config() const { return core_config_; } + auto core_config() const -> const CoreConfig&; /// Start a timer to force-kill our process after the set length of time. /// Can be used during shutdown or when trying to send a crash-report to /// ensure we don't hang indefinitely. void StartSuicideTimer(const std::string& action, millisecs_t delay); + void ApplyBaEnvConfig(); + // Call this if the main thread changes. // Fixme: Should come up with something less hacky feeling. void UpdateMainThreadID(); @@ -108,6 +111,38 @@ class CoreFeatureSet { legacy_user_agent_string_ = val; } + /// Return true if baenv values have been locked in: python paths, log + /// handling, etc. Early-running code may wish to explicitly avoid making log + /// calls until this condition is met to ensure predictable behavior. + auto HaveBaEnvVals() const { return have_ba_env_vals_; } + + /// Return the directory where the app expects to find its bundled Python + /// files. + auto GetAppPythonDirectory() -> std::optional; + + /// Return a directory where the local user can manually place Python + /// files where they will be accessible by the app. When possible, this + /// directory should be in a place easily accessible to the user. + auto GetUserPythonDirectory() -> std::optional; + + /// Get the root config directory. This dir contains the app config file + /// and other data considered essential to the app install. This directory + /// should be included in OS backups. + auto GetConfigDirectory() -> std::string; + + /// Get the data directory. This dir contains ba_data and possibly other + /// platform-specific bits needed for the app to function. + auto GetDataDirectory() -> std::string; + + /// Return the directory where bundled 3rd party Python files live. + auto GetSitePythonDirectory() -> std::optional; + + // Are we using a non-standard app python dir (such as a 'sys' dir within a + // user-python-dir). + auto using_custom_app_python_dir() const { + return using_custom_app_python_dir_; + } + // Subsystems. CorePython* const python; CorePlatform* const platform; @@ -159,6 +194,13 @@ class CoreFeatureSet { std::mutex app_time_mutex_; std::string legacy_user_agent_string_{ "BA_USER_AGENT_UNSET (" BA_PLATFORM_STRING ")"}; + bool have_ba_env_vals_{}; + std::optional ba_env_app_python_dir_; + std::string ba_env_config_dir_; + std::optional ba_env_user_python_dir_; + std::optional ba_env_site_python_dir_; + std::string ba_env_data_dir_; + bool using_custom_app_python_dir_{}; }; } // namespace ballistica::core diff --git a/src/ballistica/core/platform/core_platform.cc b/src/ballistica/core/platform/core_platform.cc index 7244b2f1..daa92f0d 100644 --- a/src/ballistica/core/platform/core_platform.cc +++ b/src/ballistica/core/platform/core_platform.cc @@ -144,7 +144,7 @@ auto CorePlatform::GetLegacyDeviceUUID() -> const std::string& { // in our config dir. This should be globally-unique, but the downside is // the user can tamper with it. if (!have_real_unique_uuid) { - std::string path = GetConfigDirectory() + BA_DIRSLASH + ".bsuuid"; + std::string path = g_core->GetConfigDirectory() + BA_DIRSLASH + ".bsuuid"; if (FILE* f = FOpen(path.c_str(), "rb")) { // There's an existing one; read it. @@ -201,13 +201,14 @@ auto CorePlatform::DoGetConfigDirectoryMonolithicDefault() } auto CorePlatform::GetConfigFilePath() -> std::string { - return GetConfigDirectory() + BA_DIRSLASH + "config.json"; + return g_core->GetConfigDirectory() + BA_DIRSLASH + "config.json"; } // FIXME: should make this unnecessary. auto CorePlatform::GetLowLevelConfigValue(const char* key, int default_value) -> int { - std::string path = GetConfigDirectory() + BA_DIRSLASH + ".cvar_" + key; + std::string path = + g_core->GetConfigDirectory() + BA_DIRSLASH + ".cvar_" + key; int val = default_value; FILE* f = FOpen(path.c_str(), "r"); if (f) { @@ -225,7 +226,8 @@ auto CorePlatform::GetLowLevelConfigValue(const char* key, int default_value) // FIXME: should make this unnecessary. void CorePlatform::SetLowLevelConfigValue(const char* key, int value) { - std::string path = GetConfigDirectory() + BA_DIRSLASH + ".cvar_" + key; + std::string path = + g_core->GetConfigDirectory() + BA_DIRSLASH + ".cvar_" + key; std::string out = std::to_string(value); FILE* f = FOpen(path.c_str(), "w"); if (f) { @@ -238,11 +240,6 @@ void CorePlatform::SetLowLevelConfigValue(const char* key, int value) { } } -auto CorePlatform::GetUserPythonDirectory() -> std::optional { - BA_PRECONDITION(have_ba_env_vals_); - return ba_env_user_python_dir_; -} - auto CorePlatform::GetVolatileDataDirectory() -> std::string { if (!made_volatile_data_dir_) { volatile_data_dir_ = GetDefaultVolatileDataDirectory(); @@ -254,23 +251,13 @@ auto CorePlatform::GetVolatileDataDirectory() -> std::string { auto CorePlatform::GetDefaultVolatileDataDirectory() -> std::string { // By default, stuff this in a subdir under our config dir. - return GetConfigDirectory() + BA_DIRSLASH + "vdata"; -} - -auto CorePlatform::GetAppPythonDirectory() -> std::optional { - BA_PRECONDITION(have_ba_env_vals_); - return ba_env_app_python_dir_; -} - -auto CorePlatform::GetSitePythonDirectory() -> std::optional { - BA_PRECONDITION(have_ba_env_vals_); - return ba_env_site_python_dir_; + return g_core->GetConfigDirectory() + BA_DIRSLASH + "vdata"; } auto CorePlatform::GetReplaysDir() -> std::string { static bool made_dir = false; if (!made_dir) { - replays_dir_ = GetConfigDirectory() + BA_DIRSLASH + "replays"; + replays_dir_ = g_core->GetConfigDirectory() + BA_DIRSLASH + "replays"; MakeDir(replays_dir_); made_dir = true; } @@ -355,13 +342,6 @@ auto CorePlatform::GetErrnoString() -> std::string { #endif } -// Return the ballisticakit config dir -// This does not vary across versions. -auto CorePlatform::GetConfigDirectory() -> std::string { - BA_PRECONDITION(have_ba_env_vals_); - return ba_env_config_dir_; -} - auto CorePlatform::GetConfigDirectoryMonolithicDefault() -> std::optional { // CoreConfig value trumps all. Otherwise go with platform-specific default. @@ -371,11 +351,6 @@ auto CorePlatform::GetConfigDirectoryMonolithicDefault() return DoGetConfigDirectoryMonolithicDefault(); } -auto CorePlatform::GetDataDirectory() -> std::string { - BA_PRECONDITION(have_ba_env_vals_); - return ba_env_data_dir_; -} - auto CorePlatform::GetDataDirectoryMonolithicDefault() -> std::string { // CoreConfig arg trumps all. Otherwise ask for platform-specific value. if (g_core->core_config().data_dir.has_value()) { @@ -1216,32 +1191,4 @@ auto CorePlatform::System(const char* cmd) -> int { #endif } -void CorePlatform::SetBaEnvVals(const PythonRef& ref) { - assert(!have_ba_env_vals_); - have_ba_env_vals_ = true; - - ba_env_config_dir_ = ref.GetAttr("config_dir").ValueAsString(); - ba_env_data_dir_ = ref.GetAttr("data_dir").ValueAsString(); - ba_env_app_python_dir_ = - ref.GetAttr("app_python_dir").ValueAsOptionalString(); - ba_env_user_python_dir_ = - ref.GetAttr("user_python_dir").ValueAsOptionalString(); - ba_env_site_python_dir_ = - ref.GetAttr("site_python_dir").ValueAsOptionalString(); - - // Consider app-python-dir 'custom' if baenv provided a value - // for it AND that value differs from baenv's default. - auto standard_app_python_dir = - ref.GetAttr("standard_app_python_dir").ValueAsString(); - using_custom_app_python_dir_ = - ba_env_app_python_dir_.has_value() - && *ba_env_app_python_dir_ != standard_app_python_dir; - - // Ok, now look for the existence of ba_data in the dir we've got. - auto fullpath = ba_env_data_dir_ + BA_DIRSLASH + "ba_data"; - if (!FilePathExists(fullpath)) { - FatalError("ba_data directory not found at '" + fullpath + "'."); - } -} - } // namespace ballistica::core diff --git a/src/ballistica/core/platform/core_platform.h b/src/ballistica/core/platform/core_platform.h index 16b08f18..b7efa227 100644 --- a/src/ballistica/core/platform/core_platform.h +++ b/src/ballistica/core/platform/core_platform.h @@ -141,35 +141,16 @@ class CorePlatform { /// etc). virtual auto GetUIScale() -> UIScale; - /// Get the data directory. This dir contains ba_data and possibly other - /// platform-specific bits needed for the app to function. - auto GetDataDirectory() -> std::string; - /// Return default DataDirectory value for monolithic builds. auto GetDataDirectoryMonolithicDefault() -> std::string; - /// Get the root config directory. This dir contains the app config file - /// and other data considered essential to the app install. This directory - /// should be included in OS backups. - auto GetConfigDirectory() -> std::string; auto GetConfigDirectoryMonolithicDefault() -> std::optional; /// Get the path of the app config file. auto GetConfigFilePath() -> std::string; - /// Return a directory where the local user can manually place Python - /// files where they will be accessible by the app. When possible, this - /// directory should be in a place easily accessible to the user. - auto GetUserPythonDirectory() -> std::optional; auto GetUserPythonDirectoryMonolithicDefault() -> std::optional; - /// Return the directory where the app expects to find its bundled Python - /// files. - auto GetAppPythonDirectory() -> std::optional; - - /// Return the directory where bundled 3rd party Python files live. - auto GetSitePythonDirectory() -> std::optional; - /// Get a directory where the app can store internal generated data. This /// directory should not be included in backups and the app should remain /// functional if this directory is completely cleared between runs @@ -470,20 +451,9 @@ class CorePlatform { // return true and set the native full res here. Otherwise return false; virtual auto GetDisplayResolution(int* x, int* y) -> bool; - auto using_custom_app_python_dir() const { - return using_custom_app_python_dir_; - } - /// Are we being run from a terminal? (should we show prompts, etc?). auto is_stdin_a_terminal() const { return is_stdin_a_terminal_; } - void SetBaEnvVals(const PythonRef& ref); - - /// Return true if baenv values have been locked in: python paths, log - /// handling, etc. Early-running code may wish to explicitly avoid making log - /// calls until this condition is met to ensure predictable behavior. - auto HaveBaEnvVals() const { return have_ba_env_vals_; } - protected: /// Are we being run from a terminal? (should we show prompts, etc?). virtual auto GetIsStdinATerminal() -> bool; @@ -544,7 +514,6 @@ class CorePlatform { private: bool is_stdin_a_terminal_{}; - bool using_custom_app_python_dir_{}; bool have_has_touchscreen_value_{}; bool have_touchscreen_{}; bool is_tegra_k1_{}; @@ -553,17 +522,11 @@ class CorePlatform { bool made_volatile_data_dir_{}; bool have_device_uuid_{}; bool ran_base_post_init_{}; - bool have_ba_env_vals_{}; millisecs_t start_time_millisecs_{}; std::string device_name_; std::string legacy_device_uuid_; std::string volatile_data_dir_; std::string replays_dir_; - std::string ba_env_config_dir_; - std::string ba_env_data_dir_; - std::optional ba_env_app_python_dir_; - std::optional ba_env_user_python_dir_; - std::optional ba_env_site_python_dir_; }; } // namespace ballistica::core diff --git a/src/ballistica/core/python/core_python.cc b/src/ballistica/core/python/core_python.cc index 9ff02388..36f27548 100644 --- a/src/ballistica/core/python/core_python.cc +++ b/src/ballistica/core/python/core_python.cc @@ -15,13 +15,6 @@ void LowLevelPythonDebugLog(const char* msg) { g_core->platform->DebugLog(msg); } -void CorePython::ApplyBaEnvConfig() { - // Fetch the env-config (creates it if need be). - auto envcfg = objs().Get(core::CorePython::ObjID::kBaEnvGetConfigCall).Call(); - BA_PRECONDITION(envcfg.Exists()); - g_core->platform->SetBaEnvVals(envcfg); -} - static void CheckPyInitStatus(const char* where, const PyStatus& status) { if (PyStatus_Exception(status)) { FatalError(std::string("Error in ") + where + ": " @@ -337,4 +330,76 @@ void CorePython::LoggingCall(LogLevel loglevel, const std::string& msg) { objs().Get(logcallobj).Call(args); } +auto CorePython::WasModularMainCalled() -> bool { + assert(!g_buildconfig.monolithic_build()); + + // This gets called in modular builds before anything is inited, so we need to + // avoid using anything from g_core or whatnot here; only raw Python stuff. + + PyObject* baenv = PyImport_ImportModule("baenv"); + if (!baenv) { + FatalError("Unable to import baenv module."); + } + PyObject* env_globals_class = PyObject_GetAttrString(baenv, "_EnvGlobals"); + if (!env_globals_class) { + FatalError("_EnvGlobals class not found in baenv."); + } + PyObject* get_call = PyObject_GetAttrString(env_globals_class, "get"); + if (!get_call) { + FatalError("get() call not found on baenv._EnvGlobals."); + } + PyObject* env_globals_instance = PyObject_CallNoArgs(get_call); + if (!get_call) { + FatalError("baenv._EnvGlobals.get() call failed."); + } + PyObject* modular_main_called = + PyObject_GetAttrString(env_globals_instance, "modular_main_called"); + if (!modular_main_called || !PyBool_Check(modular_main_called)) { + FatalError("modular_main_called bool not found on baenv _EnvGlobals."); + } + assert(modular_main_called == Py_True || modular_main_called == Py_False); + bool val = modular_main_called == Py_True; + + Py_DECREF(baenv); + Py_DECREF(env_globals_class); + Py_DECREF(get_call); + Py_DECREF(env_globals_instance); + Py_DECREF(modular_main_called); + + return val; +} + +auto CorePython::FetchPythonArgs(std::vector* buffer) + -> std::vector { + // This gets called in modular builds before anything is inited, so we need to + // avoid using anything from g_core or whatnot here; only raw Python stuff. + + assert(buffer && buffer->empty()); + PyObject* sys = PyImport_ImportModule("sys"); + if (!sys) { + FatalError("Unable to import sys module."); + } + PyObject* argv = PyObject_GetAttrString(sys, "argv"); + if (!argv || !PyList_Check(argv)) { + FatalError("Unable to fetch sys.argv list."); + } + Py_ssize_t listlen = PyList_GET_SIZE(argv); + for (Py_ssize_t i = 0; i < listlen; ++i) { + PyObject* arg = PyList_GET_ITEM(argv, i); + BA_PRECONDITION_FATAL(PyUnicode_Check(arg)); + buffer->push_back(PyUnicode_AsUTF8(arg)); + } + Py_DECREF(sys); + Py_DECREF(argv); + + // Ok, we've filled the buffer so it won't be resizing anymore. Now set up + // argv pointers to it. + std::vector out; + out.reserve(buffer->size()); + for (int i = 0; i < buffer->size(); ++i) { + out.push_back(const_cast((*buffer)[i].c_str())); + } + return out; +} + } // namespace ballistica::core diff --git a/src/ballistica/core/python/core_python.h b/src/ballistica/core/python/core_python.h index 9b9ab74e..2aa49e42 100644 --- a/src/ballistica/core/python/core_python.h +++ b/src/ballistica/core/python/core_python.h @@ -43,10 +43,6 @@ class CorePython { /// pent up ones) to Python. void EnablePythonLoggingCalls(); - /// Should be called just before base feature set import; locks in the - /// baenv environment and runs some checks. - void ApplyBaEnvConfig(); - /// Calls Python logging function (logging.error, logging.warning, etc.) /// Can be called from any thread at any time. If called before Python /// logging is available, logs locally using Logging::DisplayLog() @@ -56,6 +52,13 @@ class CorePython { void VerifyPythonEnvironment(); void SoftImportBase(); + static auto WasModularMainCalled() -> bool; + + /// Builds a vector of strings out of Python's sys.argv. Returns an argv + /// array pointing to them. + static auto FetchPythonArgs(std::vector* buffer) + -> std::vector; + const auto& objs() { return objs_; } private: diff --git a/src/ballistica/core/support/core_config.cc b/src/ballistica/core/support/core_config.cc index 62373cb9..f18b08e4 100644 --- a/src/ballistica/core/support/core_config.cc +++ b/src/ballistica/core/support/core_config.cc @@ -75,45 +75,36 @@ static auto ParseArgValue(int argc, char** argv, int* i, const char* arg_long, return {}; } -auto CoreConfig::FromCommandLineAndEnv(int argc, char** argv) -> CoreConfig { - auto cfg = CoreConfig(); - - // First set any values we allow env-vars for. - // We want explicitly passed values to override these in any cases where both - // forms are accepted. +void CoreConfig::ApplyEnvVars() { if (auto* envval = getenv("BA_LIFECYCLE_LOG")) { if (!strcmp(envval, "1")) { - cfg.lifecycle_log = true; + lifecycle_log = true; } } if (auto* envval = getenv("BA_DEBUGGER_ATTACHED")) { if (!strcmp(envval, "1")) { - cfg.debugger_attached = true; + debugger_attached = true; } } if (auto* envval = getenv("BA_DEBUG_TIMING")) { if (!strcmp(envval, "1")) { - cfg.debug_timing = true; + debug_timing = true; } } +} - // REMOVE ME FOR 1.7.20 FINAL. - if (explicit_bool(false)) { - printf("TEMP: forcing BA_LIFECYCLE_LOG=1 during 1.7.20 development.\n"); - cfg.lifecycle_log = true; - } - +void CoreConfig::ApplyArgs(int argc, char** argv) { try { // First handle single-arg special cases like --help or --version. if (IsSingleArgSpecialCase(argc, argv, "--help", "-h")) { PrintHelp(); - cfg.immediate_return_code = 0; - return cfg; + immediate_return_code = 0; + return; } if (IsSingleArgSpecialCase(argc, argv, "--version", "-v")) { printf("BallisticaKit %s build %d\n", kEngineVersion, kEngineBuildNumber); - cfg.immediate_return_code = 0; - return cfg; + immediate_return_code = 0; + return; } if (IsSingleArgSpecialCase(argc, argv, "--crash")) { int dummyval{}; @@ -126,7 +117,7 @@ auto CoreConfig::FromCommandLineAndEnv(int argc, char** argv) -> CoreConfig { if (explicit_bool(true)) { *invalid_ptr = 1; } - return cfg; + return; } // Ok, all single-arg cases handled; now go through everything else @@ -135,35 +126,35 @@ auto CoreConfig::FromCommandLineAndEnv(int argc, char** argv) -> CoreConfig { std::optional value; while (i < argc) { if ((value = ParseArgValue(argc, argv, &i, "--command", "-c"))) { - cfg.call_command = *value; + call_command = *value; } else if ((value = ParseArgValue(argc, argv, &i, "--exec", "-e"))) { - cfg.exec_command = *value; + exec_command = *value; } else if ((value = ParseArgValue(argc, argv, &i, "--config-dir", "-C"))) { - cfg.config_dir = *value; + config_dir = *value; // Make sure what they passed exists. // Note: Normally baenv will try to create whatever the config dir is; // do we just want to allow that to happen in this case? But perhaps // being more strict is ok when accepting user input. - if (!std::filesystem::exists(*cfg.config_dir)) { - printf("Error: Provided config dir does not exist: '%s'.", - cfg.config_dir->c_str()); + if (!std::filesystem::is_directory(*config_dir)) { + printf("Error: Provided config-dir path '%s' is not a directory.", + config_dir->c_str()); throw BadArgsException(); } } else if ((value = ParseArgValue(argc, argv, &i, "--data-dir", "-d"))) { - cfg.data_dir = *value; + data_dir = *value; // Make sure what they passed exists. - if (!std::filesystem::exists(*cfg.data_dir)) { - printf("Error: Provided data dir does not exist: '%s'.", - cfg.data_dir->c_str()); + if (!std::filesystem::is_directory(*data_dir)) { + printf("Error: Provided data-dir path '%s' is not a directory.", + data_dir->c_str()); throw BadArgsException(); } } else if ((value = ParseArgValue(argc, argv, &i, "--mods-dir", "-m"))) { - cfg.user_python_dir = *value; + user_python_dir = *value; // Make sure what they passed exists. - if (!std::filesystem::exists(*cfg.user_python_dir)) { - printf("Error: Provided mods dir does not exist: '%s'.", - cfg.user_python_dir->c_str()); + if (!std::filesystem::is_directory(*user_python_dir)) { + printf("Error: Provided mods-dir path '%s' is not a directory.", + user_python_dir->c_str()); throw BadArgsException(); } } else { @@ -175,8 +166,25 @@ auto CoreConfig::FromCommandLineAndEnv(int argc, char** argv) -> CoreConfig { } } } catch (const BadArgsException&) { - cfg.immediate_return_code = 1; + immediate_return_code = 1; } +} + +auto CoreConfig::ForEnvVars() -> CoreConfig { + CoreConfig cfg{}; + + cfg.ApplyEnvVars(); + + return cfg; +} + +auto CoreConfig::ForArgsAndEnvVars(int argc, char** argv) -> CoreConfig { + CoreConfig cfg{}; + + // Apply env-vars first. We want explicit args to override these. + cfg.ApplyEnvVars(); + cfg.ApplyArgs(argc, argv); + return cfg; } diff --git a/src/ballistica/core/support/core_config.h b/src/ballistica/core/support/core_config.h index 27e73e65..ddcd5450 100644 --- a/src/ballistica/core/support/core_config.h +++ b/src/ballistica/core/support/core_config.h @@ -14,7 +14,17 @@ namespace ballistica::core { /// when initing the core feature-set. class CoreConfig { public: - static auto FromCommandLineAndEnv(int argc, char** argv) -> CoreConfig; + static auto ForArgsAndEnvVars(int argc, char** argv) -> CoreConfig; + + static auto ForEnvVars() -> CoreConfig; + + /// Build a core-config for a modular app being run from the command-line. + /// In this case, Python has already been inited and Ballistica has + /// already been imported (since that's where this code lives) so there is + /// less that can be affected by a core-config. + + void ApplyEnvVars(); + void ApplyArgs(int argc, char** argv); /// Enable vr mode on supported platforms. bool vr_mode{}; @@ -26,19 +36,19 @@ class CoreConfig { std::optional immediate_return_code{}; /// If set, this single Python command will be run instead of the - /// normal app loop. + /// normal app loop (monolithic builds only). std::optional call_command{}; /// Python command to be run within the normal app loop. std::optional exec_command{}; - /// Explicitly set config dir. + /// Explicitly passed config dir. std::optional config_dir{}; - /// Explicitly set data dir. + /// Explicitly passed data dir. std::optional data_dir{}; - /// Explicitly set user-python (mods) dir. + /// Explicitly passed user-python (mods) dir. std::optional user_python_dir{}; /// Log various stages/times in the bootstrapping process. diff --git a/src/ballistica/shared/ballistica.cc b/src/ballistica/shared/ballistica.cc index e58c14e7..f085a14b 100644 --- a/src/ballistica/shared/ballistica.cc +++ b/src/ballistica/shared/ballistica.cc @@ -25,7 +25,7 @@ #if BA_MONOLITHIC_BUILD && BA_DEFINE_MAIN auto main(int argc, char** argv) -> int { auto core_config = - ballistica::core::CoreConfig::FromCommandLineAndEnv(argc, argv); + ballistica::core::CoreConfig::ForArgsAndEnvVars(argc, argv); // Arg-parsing may have yielded an error or printed simple output for // things such as '--help', in which case we're done. @@ -39,7 +39,7 @@ auto main(int argc, char** argv) -> int { namespace ballistica { // These are set automatically via script; don't modify them here. -const int kEngineBuildNumber = 21196; +const int kEngineBuildNumber = 21199; const char* kEngineVersion = "1.7.24"; #if BA_MONOLITHIC_BUILD @@ -65,9 +65,6 @@ auto MonolithicMain(const core::CoreConfig& core_config) -> int { // import it first thing even if we don't explicitly use it. l_core = core::CoreFeatureSet::Import(&core_config); - // TEMP - bug hunting. - l_core->platform->DebugLog("mm1"); - // If a command was passed, simply run it and exit. We want to act // simply as a Python interpreter in that case; we don't do any // environment setup (aside from the bits core does automatically such @@ -80,9 +77,6 @@ auto MonolithicMain(const core::CoreConfig& core_config) -> int { exit(success ? 0 : 1); } - // TEMP - bug hunting. - l_core->platform->DebugLog("mm2"); - // Ok, looks like we're doing a standard monolithic-mode app run. // ------------------------------------------------------------------------- @@ -95,9 +89,6 @@ auto MonolithicMain(const core::CoreConfig& core_config) -> int { // those modules get loaded from in the first place. l_core->python->MonolithicModeBaEnvConfigure(); - // TEMP - bug hunting. - l_core->platform->DebugLog("mm3"); - // We need the base feature-set to run a full app but we don't have a hard // dependency to it. Let's see if it's available. l_base = l_core->SoftImportBase(); @@ -109,9 +100,6 @@ auto MonolithicMain(const core::CoreConfig& core_config) -> int { // Phase 2: "The pieces are moving." // ------------------------------------------------------------------------- - // TEMP - bug hunting. - l_core->platform->DebugLog("mm4"); - // Spin up all app machinery such as threads and subsystems. This gets // things ready to rock, but there's no actual rocking quite yet. l_base->StartApp(); @@ -120,9 +108,6 @@ auto MonolithicMain(const core::CoreConfig& core_config) -> int { // Phase 3: "We come to it at last; the great battle of our time." // ------------------------------------------------------------------------- - // TEMP - bug hunting. - l_core->platform->DebugLog("mm5"); - // At this point we unleash the beast and then simply process events // until the app exits (or we return from this function and let the // environment do that part). @@ -165,7 +150,6 @@ auto MonolithicMain(const core::CoreConfig& core_config) -> int { } } } - if (l_core) { l_core->platform->WillExitMain(false); return l_core->return_value; diff --git a/src/ballistica/shared/python/python.cc b/src/ballistica/shared/python/python.cc index 139cccb6..c1f30067 100644 --- a/src/ballistica/shared/python/python.cc +++ b/src/ballistica/shared/python/python.cc @@ -105,7 +105,8 @@ auto Python::GetPyString(PyObject* o) -> std::string { return PyUnicode_AsUTF8(o); } throw Exception( - "Can't get string from value: " + Python::ObjToString(o) + ".", exctype); + "Expected a string object; got type " + Python::ObjTypeToString(o) + ".", + exctype); } template @@ -220,6 +221,24 @@ auto Python::GetPyFloats(PyObject* o) -> std::vector { return vals; } +auto Python::GetPyStrings(PyObject* o) -> std::list { + assert(HaveGIL()); + BA_PRECONDITION_FATAL(o != nullptr); + + if (!PySequence_Check(o)) { + throw Exception("Object is not a sequence.", PyExcType::kType); + } + PythonRef sequence(PySequence_Fast(o, "Not a sequence."), PythonRef::kSteal); + assert(sequence.Exists()); + Py_ssize_t size = PySequence_Fast_GET_SIZE(sequence.Get()); + PyObject** py_objects = PySequence_Fast_ITEMS(sequence.Get()); + std::list vals; + for (Py_ssize_t i = 0; i < size; i++) { + vals.emplace_back(Python::GetPyString(py_objects[i])); + } + return vals; +} + template auto GetPyIntsT(PyObject* o) -> std::vector { assert(Python::HaveGIL()); diff --git a/src/ballistica/shared/python/python.h b/src/ballistica/shared/python/python.h index a4245004..de3c04a9 100644 --- a/src/ballistica/shared/python/python.h +++ b/src/ballistica/shared/python/python.h @@ -121,6 +121,7 @@ class Python { static auto GetPyInts(PyObject* o) -> std::vector; static auto GetPyUInts64(PyObject* o) -> std::vector; static auto GetPyPoint2D(PyObject* o) -> Point2D; + static auto GetPyStrings(PyObject* o) -> std::list; /// Set Python exception from C++ Exception. static void SetPythonException(const Exception& exc); diff --git a/src/ballistica/shared/python/python_command.h b/src/ballistica/shared/python/python_command.h index 71e200da..f3a84095 100644 --- a/src/ballistica/shared/python/python_command.h +++ b/src/ballistica/shared/python/python_command.h @@ -12,14 +12,14 @@ namespace ballistica { // String based Python commands. -// Note to self: originally I though I'd be using this in a lot of places, -// so I added the ability to compile once and run repeatedly, quietly capture -// output instead of printing it, etc. Now, however, its usage is pretty -// much limited to a few places such as handling stdin and the in-game console. -// (Most places it is much cleaner to work with proper python modules and just -// interact with PyObject* refs to them) -// I should look and see if python's default high level calls would suffice -// for these purposes and potentially kill this off. +// Note to self: Originally I though I'd be using this in a lot of places, +// so I added the ability to compile once and run repeatedly, quietly +// capture output instead of printing it, etc. Now, however, its usage is +// pretty much limited to a few places such as handling stdin and the +// in-app console. (Most places it is much cleaner to work with proper +// python modules and just interact with PyObject* refs to them) I should +// look and see if python's default high level calls would suffice for these +// purposes and potentially kill this off. class PythonCommand { public: PythonCommand(); diff --git a/src/ballistica/shared/python/python_ref.cc b/src/ballistica/shared/python/python_ref.cc index c05db174..660a3ea4 100644 --- a/src/ballistica/shared/python/python_ref.cc +++ b/src/ballistica/shared/python/python_ref.cc @@ -4,6 +4,7 @@ #include "ballistica/core/python/core_python.h" #include "ballistica/core/support/base_soft.h" +#include "ballistica/shared/foundation/types.h" #include "ballistica/shared/math/vector2f.h" #include "ballistica/shared/python/python.h" #include "ballistica/shared/python/python_sys.h" @@ -13,7 +14,6 @@ namespace ballistica { // Note: implicitly using core globals here; our behavior is undefined // if core has not been imported by anyone yet. using core::g_base_soft; -using core::g_core; // Ignore a few things that python macros do. #pragma clang diagnostic push @@ -179,6 +179,16 @@ auto PythonRef::ValueAsOptionalString() const -> std::optional { return Python::GetPyString(obj_); } +auto PythonRef::ValueAsOptionalStringSequence() const + -> std::optional> { + assert(Python::HaveGIL()); + ThrowIfUnset(); + if (obj_ == Py_None) { + return {}; + } + return Python::GetPyStrings(obj_); +} + auto PythonRef::ValueAsInt() const -> int64_t { assert(Python::HaveGIL()); ThrowIfUnset(); @@ -238,15 +248,8 @@ auto PythonRef::UnicodeCheck() const -> bool { return static_cast(PyUnicode_Check(obj_)); } -auto PythonRef::Call(PyObject* args, PyObject* keywds, bool print_errors) const - -> PythonRef { - assert(obj_); - assert(Python::HaveGIL()); - assert(CallableCheck()); - assert(args); - assert(PyTuple_Check(args)); // NOLINT (signed bitwise stuff) - assert(!keywds || PyDict_Check(keywds)); // NOLINT (signed bitwise) - PyObject* out = PyObject_Call(obj_, args, keywds); +static inline auto _HandleCallResults(PyObject* out, bool print_errors) + -> PyObject* { if (!out) { if (print_errors) { // Save/restore error or it can mess with context print calls. @@ -262,20 +265,36 @@ auto PythonRef::Call(PyObject* args, PyObject* keywds, bool print_errors) const } PyErr_Clear(); } + return out; +} + +auto PythonRef::Call(PyObject* args, PyObject* keywds, bool print_errors) const + -> PythonRef { + assert(obj_); + assert(Python::HaveGIL()); + assert(CallableCheck()); + assert(args); + assert(PyTuple_Check(args)); // NOLINT (signed bitwise stuff) + assert(!keywds || PyDict_Check(keywds)); // NOLINT (signed bitwise) + PyObject* out = PyObject_Call(obj_, args, keywds); + out = _HandleCallResults(out, print_errors); return out ? PythonRef(out, PythonRef::kSteal) : PythonRef(); } -auto PythonRef::Call() const -> PythonRef { - // NOTE: Using core globals directly here; normally don't do this. - assert(g_core); - return Call( - g_core->python->objs().Get(core::CorePython::ObjID::kEmptyTuple).Get()); +auto PythonRef::Call(bool print_errors) const -> PythonRef { + assert(obj_); + assert(Python::HaveGIL()); + assert(CallableCheck()); + PyObject* out = PyObject_CallNoArgs(obj_); + out = _HandleCallResults(out, print_errors); + return out ? PythonRef(out, PythonRef::kSteal) : PythonRef(); } -auto PythonRef::Call(const Vector2f& val) const -> PythonRef { +auto PythonRef::Call(const Vector2f& val, bool print_errors) const + -> PythonRef { assert(Python::HaveGIL()); PythonRef args(Py_BuildValue("((ff))", val.x, val.y), PythonRef::kSteal); - return Call(args); + return Call(args.Get(), nullptr, print_errors); } PythonRef::~PythonRef() { Release(); } diff --git a/src/ballistica/shared/python/python_ref.h b/src/ballistica/shared/python/python_ref.h index 3e16f242..9c5cc0e0 100644 --- a/src/ballistica/shared/python/python_ref.h +++ b/src/ballistica/shared/python/python_ref.h @@ -3,6 +3,7 @@ #ifndef BALLISTICA_SHARED_PYTHON_PYTHON_REF_H_ #define BALLISTICA_SHARED_PYTHON_PYTHON_REF_H_ +#include #include #include @@ -166,6 +167,8 @@ class PythonRef { auto ValueAsString() const -> std::string; auto ValueAsOptionalString() const -> std::optional; + auto ValueAsOptionalStringSequence() const + -> std::optional>; auto ValueAsInt() const -> int64_t; @@ -185,10 +188,10 @@ class PythonRef { bool print_errors = true) const -> PythonRef { return Call(args.Get(), keywds.Get(), print_errors); } - auto Call() const -> PythonRef; + auto Call(bool print_errors = true) const -> PythonRef; /// Call with Vector2f passed as a tuple. - auto Call(const Vector2f& val) const -> PythonRef; + auto Call(const Vector2f& val, bool print_errors = true) const -> PythonRef; private: void ThrowIfUnset() const; diff --git a/tools/batools/dummymodule.py b/tools/batools/dummymodule.py index 0c18cd71..c2ae4d54 100755 --- a/tools/batools/dummymodule.py +++ b/tools/batools/dummymodule.py @@ -271,6 +271,8 @@ def _writefuncs( returnstr = 'return (0.0, 0.0)' elif returns == 'str | None': returnstr = "return ''" + elif returns == 'int | None': + returnstr = 'return 0' elif returns == 'tuple[float, float, float, float]': returnstr = 'return (0.0, 0.0, 0.0, 0.0)' elif returns == 'bauiv1.Widget | None': diff --git a/tools/batools/pruneincludes.py b/tools/batools/pruneincludes.py index 9b24405a..3a282fa0 100755 --- a/tools/batools/pruneincludes.py +++ b/tools/batools/pruneincludes.py @@ -105,7 +105,7 @@ class Pruner: self.paths = [os.path.abspath(p) for p in self.paths] def _get_entries(self) -> list[_CompileCommandsEntry]: - cmdspath = '.cache/irony/compile_commands.json' + cmdspath = '.cache/compile_commands_db/compile_commands.json' if not os.path.isfile(cmdspath): raise CleanError( f'Compile-commands not found at "{cmdspath}".' diff --git a/tools/batools/spinoff/_context.py b/tools/batools/spinoff/_context.py index 807751f5..c7e1306b 100644 --- a/tools/batools/spinoff/_context.py +++ b/tools/batools/spinoff/_context.py @@ -786,34 +786,16 @@ class SpinoffContext: if 'base' in self._src_omit_feature_sets: text = replace_exact( text, - ( - 'def _main() -> None:\n' - ' # Run a default configure BEFORE importing' - ' babase.\n' - ' # (may affect where babase comes from).\n' - ' configure()\n' - '\n' - ' import babase\n' - '\n' - ' babase.app.run()\n' - '\n' - ), - ( - 'def _main() -> None:\n' - ' # DISABLED; REQUIRES BASE FEATURE SET.\n' - ' # Run a default configure BEFORE importing' - ' babase.\n' - ' # (may affect where babase comes from).\n' - ' # configure()\n' - '\n' - ' # import babase\n' - '\n' - ' # babase.app.run()\n' - '\n' - " raise RuntimeError('App-exec requires" - " base feature set.')\n" - '\n' - ), + ' import babase\n', + ' # (Hack; spinoff disabled babase).\n' + ' if TYPE_CHECKING:\n' + ' from typing import Any\n' + '\n' + ' # import babase\n' + '\n' + ' babase: Any = None\n' + ' if bool(True):\n' + " raise CleanError('babase not present')\n", label=src_path, ) @@ -1587,7 +1569,7 @@ class SpinoffContext: os.chmod(dst_path_full, mode) else: raise RuntimeError( - f"Invalid entity type: '{src_entity['type']}'." + f"Invalid entity type: '{src_entity.entity_type}'." ) # NOTE TO SELF - was using lchmod here but it doesn't exist diff --git a/tools/batools/staging.py b/tools/batools/staging.py index 95effb0d..322293da 100755 --- a/tools/batools/staging.py +++ b/tools/batools/staging.py @@ -526,7 +526,7 @@ class AssetStager: '# Basically this will do:\n' '# import baenv; baenv.configure();' ' import babase; babase.app.run().\n' - 'exec python3.11 ba_data/python/baenv.py $@\n' + 'exec python3.11 ba_data/python/baenv.py "$@"\n' ) subprocess.run(['chmod', '+x', path], check=True) diff --git a/tools/efro/error.py b/tools/efro/error.py index ed731ae3..8bd4c8c7 100644 --- a/tools/efro/error.py +++ b/tools/efro/error.py @@ -25,16 +25,18 @@ class CleanError(Exception): more descriptive exception types. """ - def pretty_print(self, flush: bool = False) -> None: + def pretty_print(self, flush: bool = True, prefix: str = 'Error') -> None: """Print the error to stdout, using red colored output if available. If the error has an empty message, prints nothing (not even a newline). """ from efro.terminal import Clr + if prefix: + prefix = f'{prefix}: ' errstr = str(self) if errstr: - print(f'{Clr.SRED}{errstr}{Clr.RST}', flush=flush) + print(f'{Clr.SRED}{prefix}{errstr}{Clr.RST}', flush=flush) class CommunicationError(Exception):