Lots of stuff. Chests now functional for tourneys

This commit is contained in:
Eric Froemling 2025-01-11 14:42:12 -08:00
parent 690da5a3da
commit 2190568baf
No known key found for this signature in database
140 changed files with 5081 additions and 1778 deletions

111
.efrocachemap generated
View File

@ -53,6 +53,7 @@
"build/assets/ba_data/audio/assassinFall.ogg": "201d192debe8bda9e9dead28e9cc6939",
"build/assets/ba_data/audio/assassinHit1.ogg": "caaab755b159e399b121be1aec8f61b9",
"build/assets/ba_data/audio/assassinHit2.ogg": "a08ac94f02040af67bc46eff6a691a84",
"build/assets/ba_data/audio/aww.ogg": "80cf7a35a58ec6633d3c5440764de3f5",
"build/assets/ba_data/audio/bear1.ogg": "acddcf643e9fbf8d92eacf50992c81d0",
"build/assets/ba_data/audio/bear2.ogg": "74f7ce4f64e0fb943ab4ec34fdc83779",
"build/assets/ba_data/audio/bear3.ogg": "142d1f3d021c8639fbbc1a2ed0f3dc93",
@ -95,6 +96,7 @@
"build/assets/ba_data/audio/cheer.ogg": "b9f1cce825ca00295b61b088f353715b",
"build/assets/ba_data/audio/click01.ogg": "204f93a3eb7d82cf8ca9172ee5f01c11",
"build/assets/ba_data/audio/corkPop.ogg": "7b0bdbb0cdaf40ec5adf3311ecfe620a",
"build/assets/ba_data/audio/corkPop2.ogg": "1ad5aff84af244acb2f3eaf95ddb7f6a",
"build/assets/ba_data/audio/cowboy1.ogg": "e12671599d7f6fa7e246b3ad4d83ad7d",
"build/assets/ba_data/audio/cowboy2.ogg": "0cd227e165a76bf829ac0e3ac129929c",
"build/assets/ba_data/audio/cowboy3.ogg": "9ce2c947a9ddcf9c6e793721d5b1eb66",
@ -121,6 +123,7 @@
"build/assets/ba_data/audio/dingSmallHigh.ogg": "73ce9e68ef59847dc7621d38ed019c42",
"build/assets/ba_data/audio/dripity.ogg": "db450cee4e5241fbaa80a2874cb3cf7f",
"build/assets/ba_data/audio/drumRoll.ogg": "8d8234c10e7b9dee277a4e26aec3c9e1",
"build/assets/ba_data/audio/drumRollShort.ogg": "b4273cdd69c0b09bd1da5146a6b1bc76",
"build/assets/ba_data/audio/error.ogg": "a39731636b92282052e15eb5b9413816",
"build/assets/ba_data/audio/explosion01.ogg": "51bba8fc738410a61da8d535d8485a20",
"build/assets/ba_data/audio/explosion02.ogg": "b4ce0ea7abd1c5b52ad7a868f1029a2d",
@ -147,6 +150,7 @@
"build/assets/ba_data/audio/frostyHit02.ogg": "c608e2ce52c872d701110367fa447656",
"build/assets/ba_data/audio/frostyHit03.ogg": "20860a3e0acd4104d2b62ac4a624749f",
"build/assets/ba_data/audio/fuse01.ogg": "f24744d8b4590b5898015dd98b2b9374",
"build/assets/ba_data/audio/gasp.ogg": "91b267abf7015cf36d263e961f83856b",
"build/assets/ba_data/audio/gladiator1.ogg": "528596df10aec80417a4df83325fc09d",
"build/assets/ba_data/audio/gladiator2.ogg": "a0f42843c7a73523e90ed32d766959f1",
"build/assets/ba_data/audio/gladiator3.ogg": "b3fa702291a4da6f439e72481177a5c2",
@ -218,6 +222,7 @@
"build/assets/ba_data/audio/menuMusic.ogg": "b25ee0041baf71b08c7650ae9f4daab0",
"build/assets/ba_data/audio/metalHit.ogg": "ced0188b46245cd60b248fc7bc13b706",
"build/assets/ba_data/audio/metalSkid.ogg": "a069f5022be74229c008a19b3e00e64c",
"build/assets/ba_data/audio/nice.ogg": "fb362c97a244ed3a2c2f98c38691fb00",
"build/assets/ba_data/audio/ninjaAttack1.ogg": "82c98bc81f3e3e224ddd3151be918dd6",
"build/assets/ba_data/audio/ninjaAttack2.ogg": "1f0952bb9df1877fbb9e05b8649b63da",
"build/assets/ba_data/audio/ninjaAttack3.ogg": "e5edefe727e4fe2bcb16ab90edc36c39",
@ -287,6 +292,7 @@
"build/assets/ba_data/audio/raceBeep1.ogg": "2d271c487cefbd67f15a21a0904b3e1f",
"build/assets/ba_data/audio/raceBeep2.ogg": "47cf9d039e19a3446444bfeb82b394b6",
"build/assets/ba_data/audio/refWhistle.ogg": "8ebbde5488b834e73f1d8ee4dad8b1f3",
"build/assets/ba_data/audio/revUp.ogg": "19862620186556029821e41b403f3a11",
"build/assets/ba_data/audio/robot1.ogg": "feed97d9abb23097d659b10c6276bab8",
"build/assets/ba_data/audio/robot2.ogg": "d9fc996427452f76b0527d5ca84983af",
"build/assets/ba_data/audio/robot3.ogg": "bf766b82d438323f89fe4688dd78f5ef",
@ -395,7 +401,11 @@
"build/assets/ba_data/audio/wizardFall.ogg": "e422bd05ae30a28b02b1c55fb0c1fa00",
"build/assets/ba_data/audio/wizardHit1.ogg": "7686120c658c811064efda94ac3e90ca",
"build/assets/ba_data/audio/wizardHit2.ogg": "d245e07803be7158da49d4962ccf483a",
"build/assets/ba_data/audio/woo.ogg": "e84ef95cb1d97b65ff529687d6076da9",
"build/assets/ba_data/audio/woo2.ogg": "5dfb2e489f406d11772f96ded5611c4f",
"build/assets/ba_data/audio/woo3.ogg": "637878bb31decb36e6db7a5295eea933",
"build/assets/ba_data/audio/woodDebrisFall.ogg": "e163c84d87821e3e19ec8b0bf1fef9a7",
"build/assets/ba_data/audio/wow.ogg": "581d224d771195bde72548509cc1cc32",
"build/assets/ba_data/audio/wrestler1.ogg": "7486e02349206e8082b44105d0a1195c",
"build/assets/ba_data/audio/wrestler2.ogg": "0f197b1d7e6c2e0cc86e57d1b53581aa",
"build/assets/ba_data/audio/wrestler3.ogg": "911ad7c64018e8d5fa5f722a04de8837",
@ -404,6 +414,7 @@
"build/assets/ba_data/audio/wrestlerFall.ogg": "3c6bb84fb09a0829fd60066b1807a16c",
"build/assets/ba_data/audio/wrestlerHit1.ogg": "1950d463514448069f0d3c0f00108eaa",
"build/assets/ba_data/audio/wrestlerHit2.ogg": "5b549fb2406fd72d1d0947fc8173cc08",
"build/assets/ba_data/audio/yeah.ogg": "2c55f21c39cf5f41a81317dec3f5d7fa",
"build/assets/ba_data/audio/zoeAttack01.ogg": "0b0536b8afba7cb773beffeaa2e4bb90",
"build/assets/ba_data/audio/zoeAttack02.ogg": "931a5b3d78e2322443fe1e51e6c25b99",
"build/assets/ba_data/audio/zoeAttack03.ogg": "e1d1f58f038bedda8c22fc518aa37c7e",
@ -421,21 +432,21 @@
"build/assets/ba_data/audio/zoeOw.ogg": "b2d705c31c9dcc1efdc71394764c3beb",
"build/assets/ba_data/audio/zoePickup01.ogg": "e9366dc2d2b8ab8b0c4e2c14c02d0789",
"build/assets/ba_data/audio/zoeScream01.ogg": "903e0e45ee9b3373e9d9ce20c814374e",
"build/assets/ba_data/data/langdata.json": "ce2f76ab5f36cbc0212d1b3c424eb954",
"build/assets/ba_data/data/langdata.json": "dbbd8f26d2f85c0b649d461e991b80cb",
"build/assets/ba_data/data/languages/arabic.json": "3c22e7b6d7b09a812a2e28b35c9e9241",
"build/assets/ba_data/data/languages/belarussian.json": "0b60a9d4496d1213c2d0b647d346ce30",
"build/assets/ba_data/data/languages/chinese.json": "fc45d2838b834889c06920ae7c2102fa",
"build/assets/ba_data/data/languages/chinesetraditional.json": "904b35b656c53f9830e406565edd5120",
"build/assets/ba_data/data/languages/croatian.json": "1e541070309ff6be95b0c39940aa7e99",
"build/assets/ba_data/data/languages/croatian.json": "e131a87cf5783e0fbb3d211a927efe1a",
"build/assets/ba_data/data/languages/czech.json": "d18b7d1c6bf51fc81af4084ef0e69e3e",
"build/assets/ba_data/data/languages/danish.json": "8e57db30c5250df2abff14a822f83ea7",
"build/assets/ba_data/data/languages/dutch.json": "f4e1e8e9231cda9d1bcc7e87a7f8821e",
"build/assets/ba_data/data/languages/english.json": "b5917c3b975155e35fedb655dbd7568c",
"build/assets/ba_data/data/languages/dutch.json": "4085dec5af362cf068b494524ced3872",
"build/assets/ba_data/data/languages/english.json": "527d106870b0690cc39a80b88e60ab7a",
"build/assets/ba_data/data/languages/esperanto.json": "0e397cfa5f3fb8cef5f4a64f21cda880",
"build/assets/ba_data/data/languages/filipino.json": "3d9269a90a2fee164d0a7513c4f130a3",
"build/assets/ba_data/data/languages/french.json": "6d20655730b1017ef187fd828b91d43c",
"build/assets/ba_data/data/languages/german.json": "a150dbb5c0f43984757f7db295d96203",
"build/assets/ba_data/data/languages/gibberish.json": "df76e851aee59657b69e34efd54fee06",
"build/assets/ba_data/data/languages/german.json": "b92ec951b5a0ce4f73677051ca59a06b",
"build/assets/ba_data/data/languages/gibberish.json": "2569fe1b2f686670f825e2faaa8c5dc3",
"build/assets/ba_data/data/languages/greek.json": "d28d1092fbb00ed857cbd53124c0dc78",
"build/assets/ba_data/data/languages/hindi.json": "567e6976b3c72f891431ad7fcc62ab16",
"build/assets/ba_data/data/languages/hungarian.json": "9d88004a98f0fbe2ea72edd5e0b3002e",
@ -451,7 +462,7 @@
"build/assets/ba_data/data/languages/russian.json": "fc64ed6b6356ea11385ee5c20748425a",
"build/assets/ba_data/data/languages/serbian.json": "623fa4129a1154c2f32ed7867e56ff6a",
"build/assets/ba_data/data/languages/slovak.json": "c11c29708b3742cdc2a92b4fa0d6d29f",
"build/assets/ba_data/data/languages/spanish.json": "499b464318a8c9d1fb271cf480862b57",
"build/assets/ba_data/data/languages/spanish.json": "f8ab976d219e579546bb98b6d7fd12ce",
"build/assets/ba_data/data/languages/swedish.json": "3b179e7333183c70adb0811246b09959",
"build/assets/ba_data/data/languages/tamil.json": "ead39b864228696a9b0d19344bc4b5ec",
"build/assets/ba_data/data/languages/thai.json": "383540a1e9c7c131ac579f51afc87471",
@ -1362,10 +1373,18 @@
"build/assets/ba_data/textures/chestIconMulti.ktx": "c026aa573aab141044b503437ffdaeea",
"build/assets/ba_data/textures/chestIconMulti.pvr": "3f41eaa108068751ba40e08964c45ec0",
"build/assets/ba_data/textures/chestIconMulti_preview.png": "47b6839bdc19ad9e3d3d6282ce0138dd",
"build/assets/ba_data/textures/chestIconTint.dds": "04c079c1a79d548ca02969e9934d591b",
"build/assets/ba_data/textures/chestIconTint.ktx": "ff39e97211bb71f59091aac2f51ebbed",
"build/assets/ba_data/textures/chestIconTint.pvr": "129b44259f8b62c0e1d4c08612b9c691",
"build/assets/ba_data/textures/chestIconTint_preview.png": "022f3f8bb8bc9c082dbb21958ec8722f",
"build/assets/ba_data/textures/chestIcon_preview.png": "d3046ccf2fedefa9ad766054e350a5d6",
"build/assets/ba_data/textures/chestOpenIcon.dds": "325e93c7147c3bf098857d0e3eff4a73",
"build/assets/ba_data/textures/chestOpenIcon.ktx": "562afa582aa621b946c22460e0caca61",
"build/assets/ba_data/textures/chestOpenIcon.pvr": "703c88d07b9ab22cbab647677eb05370",
"build/assets/ba_data/textures/chestOpenIconTint.dds": "c3003ed402d46da9d5c8351f028c5d95",
"build/assets/ba_data/textures/chestOpenIconTint.ktx": "d32fe2152a1bf6feff1f2416627b2023",
"build/assets/ba_data/textures/chestOpenIconTint.pvr": "bd665e8289bfd5c369c473e973dd2d65",
"build/assets/ba_data/textures/chestOpenIconTint_preview.png": "e5a2377c127998faaef83e06d4cc2584",
"build/assets/ba_data/textures/chestOpenIcon_preview.png": "40ae165451f052265d07ea62f7d08713",
"build/assets/ba_data/textures/circle.dds": "0f4c08ab481dcadce164227a146fdb62",
"build/assets/ba_data/textures/circle.ktx": "a6ff1b802324d1874aaa9c24050cfcf3",
@ -2359,6 +2378,10 @@
"build/assets/ba_data/textures/sparks.ktx": "919af9b0b1b2e756c232c34bc15d506a",
"build/assets/ba_data/textures/sparks.pvr": "249e98bbadad09b59f0e8bdd46b1c9b1",
"build/assets/ba_data/textures/sparks_preview.png": "9b6b74000a0f56899dd09a956a864ec9",
"build/assets/ba_data/textures/spinner.dds": "54b0a3a695689974defcb7d0ddc40101",
"build/assets/ba_data/textures/spinner.ktx": "9e47f8b7dcca8f061bff6a04a30a2833",
"build/assets/ba_data/textures/spinner.pvr": "312c7ffa74c39d262d00971881ecd93b",
"build/assets/ba_data/textures/spinner_preview.png": "680af969ab856865098dfcf4fb8b6845",
"build/assets/ba_data/textures/star.dds": "e799668a604f3e680f7676994e894c1d",
"build/assets/ba_data/textures/star.ktx": "725da9d09a93c636bd825788f859c62d",
"build/assets/ba_data/textures/star.pvr": "bb68b74f455c36dcd7a40f9d38b5c74f",
@ -4103,47 +4126,47 @@
"build/assets/windows/Win32/ucrtbased.dll": "bfd1180c269d3950b76f35a63655e9e1",
"build/assets/windows/Win32/vc_redist.x86.exe": "15a5f1f876503885adbdf5b3989b3718",
"build/assets/windows/Win32/vcruntime140d.dll": "865b2af4d1e26a1a8073c89acb06e599",
"build/prefab/full/linux_arm64_gui/debug/ballisticakit": "390472d8d44b0a650796bfd6022d0549",
"build/prefab/full/linux_arm64_gui/release/ballisticakit": "23f27d6f139c653f23a15a0af7f7e02c",
"build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "097b082f8f5abb18d824b2044da1de78",
"build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "c027478f038af2faedcc4103e75bc39d",
"build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "5c7f18d5ff9eb14421fe3743550ddf57",
"build/prefab/full/linux_x86_64_gui/release/ballisticakit": "cef0bc0d149a4c433c2e4bee5b47414a",
"build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "25546af5cf64a0e1e1d1f46b38298820",
"build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "7f172d6c531c371837302f370f77db08",
"build/prefab/full/mac_arm64_gui/debug/ballisticakit": "0d79db47846defaffaa0bf97ec6b23fc",
"build/prefab/full/mac_arm64_gui/release/ballisticakit": "45373e10bb60989787e415ae938756fe",
"build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "0cc3834e81761efdc50babed9e5c7151",
"build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "4e28c4abb0e7128d08f2b84a87d8a765",
"build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "96b856d3db5d061527383b42e588a333",
"build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "124afb5eec04365e42b059b0c2ec98e7",
"build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "0271c53794bdb4a762ae7210d9828fb7",
"build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "f8b3744f90503502618130c8d28f1aa4",
"build/prefab/lib/linux_arm64_gui/debug/libballisticaplus.a": "33a0ae6f1ea5a0b0c60055ce01478488",
"build/prefab/lib/linux_arm64_gui/release/libballisticaplus.a": "aad882eaf2230b89973e2cf4f13c9759",
"build/prefab/lib/linux_arm64_server/debug/libballisticaplus.a": "33a0ae6f1ea5a0b0c60055ce01478488",
"build/prefab/lib/linux_arm64_server/release/libballisticaplus.a": "aad882eaf2230b89973e2cf4f13c9759",
"build/prefab/lib/linux_x86_64_gui/debug/libballisticaplus.a": "c20929c73caa78445525c5788b6963e0",
"build/prefab/lib/linux_x86_64_gui/release/libballisticaplus.a": "0f21a43d99552df99e0d21c646e6e698",
"build/prefab/lib/linux_x86_64_server/debug/libballisticaplus.a": "c20929c73caa78445525c5788b6963e0",
"build/prefab/lib/linux_x86_64_server/release/libballisticaplus.a": "0f21a43d99552df99e0d21c646e6e698",
"build/prefab/lib/mac_arm64_gui/debug/libballisticaplus.a": "01dab862a43d9e7c4ee4e49212442d42",
"build/prefab/lib/mac_arm64_gui/release/libballisticaplus.a": "ae4e3f563892f6b9311c4b7284f28c11",
"build/prefab/lib/mac_arm64_server/debug/libballisticaplus.a": "01dab862a43d9e7c4ee4e49212442d42",
"build/prefab/lib/mac_arm64_server/release/libballisticaplus.a": "ae4e3f563892f6b9311c4b7284f28c11",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "2f581e3dead7038e5b94bc096a7b8c80",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "4538ad0d6b4794de96fb78742ebcdc84",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "0d1a2f2066ae412549034e981ae39e2c",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "55180e66455f91d3af8f15c0c81607a4",
"build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "bb2c15187840ef373ae79cdf1623d3b5",
"build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "8ee1bb4440c364f9fa91791276d64b87",
"build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "863f70d88ab9b3c7c6910d94e26b6f35",
"build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "bb928df1aa05c84889b45f2a00544e64",
"build/prefab/full/linux_arm64_gui/debug/ballisticakit": "2444899f2f36667dc27fcf5537fc76b6",
"build/prefab/full/linux_arm64_gui/release/ballisticakit": "efebe5318abe421031108d9908b4cb84",
"build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "9b4928361ff7e3aa85ea6cca5f3903d8",
"build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "9fad073f6fd49702ceb1ee5057c4f9de",
"build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "ac52ed0994f33ea4997da21d0a7f6877",
"build/prefab/full/linux_x86_64_gui/release/ballisticakit": "43f429f37c145fc2bf80c01dfd095a7e",
"build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "ea87a5838b465c40b60ff911cccbe189",
"build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "3cafa3287637bba5e56b363bb4fc8f9a",
"build/prefab/full/mac_arm64_gui/debug/ballisticakit": "1fbe4ab96f25d20b421c8206c9230948",
"build/prefab/full/mac_arm64_gui/release/ballisticakit": "0a4dbc635f8e9b3f42e89dd2027bd9d3",
"build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "6114856f2ab42ef6b20a1affdf53032d",
"build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "bfa75071d993efc3853fa350c1de2bcb",
"build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "3971d8deb2c33cfd18c4e3b338d48ec3",
"build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "bd0c352337d72f020b431feca7974c3a",
"build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "073b3690cfb03847ac3b3fe9c017e520",
"build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "c7f0d9c4db7a67b4d72f3c406c585897",
"build/prefab/lib/linux_arm64_gui/debug/libballisticaplus.a": "1c375e8003442dd3d059bc0baa260e61",
"build/prefab/lib/linux_arm64_gui/release/libballisticaplus.a": "40daac4bbc8990d5140f97e792bc4fb1",
"build/prefab/lib/linux_arm64_server/debug/libballisticaplus.a": "1c375e8003442dd3d059bc0baa260e61",
"build/prefab/lib/linux_arm64_server/release/libballisticaplus.a": "40daac4bbc8990d5140f97e792bc4fb1",
"build/prefab/lib/linux_x86_64_gui/debug/libballisticaplus.a": "19af08729bed7249eaf9acd697966f3f",
"build/prefab/lib/linux_x86_64_gui/release/libballisticaplus.a": "97d2486072bf3a83edb0250ea2d3e69e",
"build/prefab/lib/linux_x86_64_server/debug/libballisticaplus.a": "19af08729bed7249eaf9acd697966f3f",
"build/prefab/lib/linux_x86_64_server/release/libballisticaplus.a": "97d2486072bf3a83edb0250ea2d3e69e",
"build/prefab/lib/mac_arm64_gui/debug/libballisticaplus.a": "51884d81e2d7bdeb6b59a72f0247c8e1",
"build/prefab/lib/mac_arm64_gui/release/libballisticaplus.a": "36bb6f32ab12e2a46b82155a93b2e527",
"build/prefab/lib/mac_arm64_server/debug/libballisticaplus.a": "51884d81e2d7bdeb6b59a72f0247c8e1",
"build/prefab/lib/mac_arm64_server/release/libballisticaplus.a": "36bb6f32ab12e2a46b82155a93b2e527",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "f629e97c70198ab3688f11ab4a2d1e97",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "a868c0116adf9f48f503b50b50c70cd8",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "04216607232edde485d139e9e96c6612",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "bdc158a2dfd592cd8dcb235a162a82fe",
"build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "4a0340814c6034b39b2c08287935e26a",
"build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "546ccf01f342463d46302b05c0171044",
"build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "67bb2ff991e00433c1abba2044a0d21e",
"build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "8653a52a8fd75c527c976abb9d223e90",
"src/assets/ba_data/python/babase/_mgen/__init__.py": "f885fed7f2ed98ff2ba271f9dbe3391c",
"src/assets/ba_data/python/babase/_mgen/enums.py": "794d258d59fd17a61752843a9a0551ad",
"src/ballistica/base/mgen/pyembed/binding_base.inc": "06042d31df0ff9af96b99477162e2a91",
"src/ballistica/base/mgen/pyembed/binding_base_app.inc": "2d228e7c5578261d394f9c407f4becb1",
"src/ballistica/classic/mgen/pyembed/binding_classic.inc": "fc09126750304b2761049aa9d93a951e",
"src/ballistica/classic/mgen/pyembed/binding_classic.inc": "8ab156122cde23d9718923abe1b4ae5b",
"src/ballistica/core/mgen/pyembed/binding_core.inc": "217c84a30f866aaca3a4373e82af7db2",
"src/ballistica/core/mgen/pyembed/env.inc": "f015d726b44d2922112fc14d9f146d8b",
"src/ballistica/core/mgen/python_modules_monolithic.h": "fb967ed1c7db0c77d8deb4f00a7103c5",

View File

@ -1,4 +1,4 @@
### 1.7.37 (build 22155, api 9, 2024-12-31)
### 1.7.37 (build 22178, api 9, 2025-01-11)
- Bumping api version to 9. As you'll see below, there's some UI changes that
will require a bit of work for any UI mods to adapt to. If your mods don't
touch UI stuff at all you can simply bump your api version and call it a day.
@ -176,6 +176,21 @@
should use arrow keys for navigation. To update any old UI code, search for
and remove any 'claims_tab' arguments to UI calls since that argument no
longer exists.
- Added a `get_unknown_type_fallback()` method to `dataclassio.IOMultiType`.
This be defined to allow multi-type data to be loadable even in the presence
of new types it doesn't recognize.
- Added a `lossy` arg to `dataclassio.dataclass_from_dict()` and
`dataclassio.dataclass_from_json()`. Enum value fallbacks and the new
multitype fallbacks are now only applied when `lossy` is True. This also flags
the returned dataclass to prevent it from being serialized back out. Fallbacks
are useful for forward compatibility, but they are also dangerous in that they
can silently modify/destroy data, so this mechanism will hopefully help keep
them used safely.
- Added a spinner widget (creatable via `bauiv1.spinnerwidget()`). This should
help things look more alive than the static 'loading...' text I've been using
in various places.
- Tournament now award chests instead of tickets.
- Tournaments are now free to enter if you are running this build or newer.
### 1.7.36 (build 21944, api 8, 2024-07-26)
- Wired up Tokens, BombSquad's new purchasable currency. The first thing these

View File

@ -772,6 +772,8 @@ set(BALLISTICA_SOURCES
${BA_SRC_ROOT}/ballistica/ui_v1/widget/row_widget.h
${BA_SRC_ROOT}/ballistica/ui_v1/widget/scroll_widget.cc
${BA_SRC_ROOT}/ballistica/ui_v1/widget/scroll_widget.h
${BA_SRC_ROOT}/ballistica/ui_v1/widget/spinner_widget.cc
${BA_SRC_ROOT}/ballistica/ui_v1/widget/spinner_widget.h
${BA_SRC_ROOT}/ballistica/ui_v1/widget/stack_widget.cc
${BA_SRC_ROOT}/ballistica/ui_v1/widget/stack_widget.h
${BA_SRC_ROOT}/ballistica/ui_v1/widget/text_widget.cc

View File

@ -764,6 +764,8 @@
<ClInclude Include="..\..\src\ballistica\ui_v1\widget\row_widget.h" />
<ClCompile Include="..\..\src\ballistica\ui_v1\widget\scroll_widget.cc" />
<ClInclude Include="..\..\src\ballistica\ui_v1\widget\scroll_widget.h" />
<ClCompile Include="..\..\src\ballistica\ui_v1\widget\spinner_widget.cc" />
<ClInclude Include="..\..\src\ballistica\ui_v1\widget\spinner_widget.h" />
<ClCompile Include="..\..\src\ballistica\ui_v1\widget\stack_widget.cc" />
<ClInclude Include="..\..\src\ballistica\ui_v1\widget\stack_widget.h" />
<ClCompile Include="..\..\src\ballistica\ui_v1\widget\text_widget.cc" />

View File

@ -1726,6 +1726,12 @@
<ClInclude Include="..\..\src\ballistica\ui_v1\widget\scroll_widget.h">
<Filter>ballistica\ui_v1\widget</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ballistica\ui_v1\widget\spinner_widget.cc">
<Filter>ballistica\ui_v1\widget</Filter>
</ClCompile>
<ClInclude Include="..\..\src\ballistica\ui_v1\widget\spinner_widget.h">
<Filter>ballistica\ui_v1\widget</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ballistica\ui_v1\widget\stack_widget.cc">
<Filter>ballistica\ui_v1\widget</Filter>
</ClCompile>

View File

@ -759,6 +759,8 @@
<ClInclude Include="..\..\src\ballistica\ui_v1\widget\row_widget.h" />
<ClCompile Include="..\..\src\ballistica\ui_v1\widget\scroll_widget.cc" />
<ClInclude Include="..\..\src\ballistica\ui_v1\widget\scroll_widget.h" />
<ClCompile Include="..\..\src\ballistica\ui_v1\widget\spinner_widget.cc" />
<ClInclude Include="..\..\src\ballistica\ui_v1\widget\spinner_widget.h" />
<ClCompile Include="..\..\src\ballistica\ui_v1\widget\stack_widget.cc" />
<ClInclude Include="..\..\src\ballistica\ui_v1\widget\stack_widget.h" />
<ClCompile Include="..\..\src\ballistica\ui_v1\widget\text_widget.cc" />

View File

@ -1726,6 +1726,12 @@
<ClInclude Include="..\..\src\ballistica\ui_v1\widget\scroll_widget.h">
<Filter>ballistica\ui_v1\widget</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ballistica\ui_v1\widget\spinner_widget.cc">
<Filter>ballistica\ui_v1\widget</Filter>
</ClCompile>
<ClInclude Include="..\..\src\ballistica\ui_v1\widget\spinner_widget.h">
<Filter>ballistica\ui_v1\widget</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ballistica\ui_v1\widget\stack_widget.cc">
<Filter>ballistica\ui_v1\widget</Filter>
</ClCompile>

View File

@ -1,5 +1,5 @@
cpplint==2.0.0
dmgbuild==1.6.2
dmgbuild==1.6.4
filelock==3.16.1
furo==2024.8.6
mypy==1.14.1

View File

@ -52,6 +52,7 @@
"ba_data/audio/assassinFall.ogg",
"ba_data/audio/assassinHit1.ogg",
"ba_data/audio/assassinHit2.ogg",
"ba_data/audio/aww.ogg",
"ba_data/audio/bear1.ogg",
"ba_data/audio/bear2.ogg",
"ba_data/audio/bear3.ogg",
@ -94,6 +95,7 @@
"ba_data/audio/cheer.ogg",
"ba_data/audio/click01.ogg",
"ba_data/audio/corkPop.ogg",
"ba_data/audio/corkPop2.ogg",
"ba_data/audio/cowboy1.ogg",
"ba_data/audio/cowboy2.ogg",
"ba_data/audio/cowboy3.ogg",
@ -120,6 +122,7 @@
"ba_data/audio/dingSmallHigh.ogg",
"ba_data/audio/dripity.ogg",
"ba_data/audio/drumRoll.ogg",
"ba_data/audio/drumRollShort.ogg",
"ba_data/audio/error.ogg",
"ba_data/audio/explosion01.ogg",
"ba_data/audio/explosion02.ogg",
@ -146,6 +149,7 @@
"ba_data/audio/frostyHit02.ogg",
"ba_data/audio/frostyHit03.ogg",
"ba_data/audio/fuse01.ogg",
"ba_data/audio/gasp.ogg",
"ba_data/audio/gladiator1.ogg",
"ba_data/audio/gladiator2.ogg",
"ba_data/audio/gladiator3.ogg",
@ -217,6 +221,7 @@
"ba_data/audio/menuMusic.ogg",
"ba_data/audio/metalHit.ogg",
"ba_data/audio/metalSkid.ogg",
"ba_data/audio/nice.ogg",
"ba_data/audio/ninjaAttack1.ogg",
"ba_data/audio/ninjaAttack2.ogg",
"ba_data/audio/ninjaAttack3.ogg",
@ -286,6 +291,7 @@
"ba_data/audio/raceBeep1.ogg",
"ba_data/audio/raceBeep2.ogg",
"ba_data/audio/refWhistle.ogg",
"ba_data/audio/revUp.ogg",
"ba_data/audio/robot1.ogg",
"ba_data/audio/robot2.ogg",
"ba_data/audio/robot3.ogg",
@ -394,7 +400,11 @@
"ba_data/audio/wizardFall.ogg",
"ba_data/audio/wizardHit1.ogg",
"ba_data/audio/wizardHit2.ogg",
"ba_data/audio/woo.ogg",
"ba_data/audio/woo2.ogg",
"ba_data/audio/woo3.ogg",
"ba_data/audio/woodDebrisFall.ogg",
"ba_data/audio/wow.ogg",
"ba_data/audio/wrestler1.ogg",
"ba_data/audio/wrestler2.ogg",
"ba_data/audio/wrestler3.ogg",
@ -403,6 +413,7 @@
"ba_data/audio/wrestlerFall.ogg",
"ba_data/audio/wrestlerHit1.ogg",
"ba_data/audio/wrestlerHit2.ogg",
"ba_data/audio/yeah.ogg",
"ba_data/audio/zoeAttack01.ogg",
"ba_data/audio/zoeAttack02.ogg",
"ba_data/audio/zoeAttack03.ogg",
@ -1401,10 +1412,18 @@
"ba_data/textures/chestIconMulti.ktx",
"ba_data/textures/chestIconMulti.pvr",
"ba_data/textures/chestIconMulti_preview.png",
"ba_data/textures/chestIconTint.dds",
"ba_data/textures/chestIconTint.ktx",
"ba_data/textures/chestIconTint.pvr",
"ba_data/textures/chestIconTint_preview.png",
"ba_data/textures/chestIcon_preview.png",
"ba_data/textures/chestOpenIcon.dds",
"ba_data/textures/chestOpenIcon.ktx",
"ba_data/textures/chestOpenIcon.pvr",
"ba_data/textures/chestOpenIconTint.dds",
"ba_data/textures/chestOpenIconTint.ktx",
"ba_data/textures/chestOpenIconTint.pvr",
"ba_data/textures/chestOpenIconTint_preview.png",
"ba_data/textures/chestOpenIcon_preview.png",
"ba_data/textures/circle.dds",
"ba_data/textures/circle.ktx",
@ -2398,6 +2417,10 @@
"ba_data/textures/sparks.ktx",
"ba_data/textures/sparks.pvr",
"ba_data/textures/sparks_preview.png",
"ba_data/textures/spinner.dds",
"ba_data/textures/spinner.ktx",
"ba_data/textures/spinner.pvr",
"ba_data/textures/spinner_preview.png",
"ba_data/textures/star.dds",
"ba_data/textures/star.ktx",
"ba_data/textures/star.pvr",

View File

@ -77,6 +77,8 @@
"ba_data/python/baclassic/__pycache__/_appmode.cpython-312.opt-1.pyc",
"ba_data/python/baclassic/__pycache__/_appsubsystem.cpython-312.opt-1.pyc",
"ba_data/python/baclassic/__pycache__/_benchmark.cpython-312.opt-1.pyc",
"ba_data/python/baclassic/__pycache__/_chest.cpython-312.opt-1.pyc",
"ba_data/python/baclassic/__pycache__/_clienteffect.cpython-312.opt-1.pyc",
"ba_data/python/baclassic/__pycache__/_input.cpython-312.opt-1.pyc",
"ba_data/python/baclassic/__pycache__/_music.cpython-312.opt-1.pyc",
"ba_data/python/baclassic/__pycache__/_net.cpython-312.opt-1.pyc",
@ -93,6 +95,8 @@
"ba_data/python/baclassic/_appmode.py",
"ba_data/python/baclassic/_appsubsystem.py",
"ba_data/python/baclassic/_benchmark.py",
"ba_data/python/baclassic/_chest.py",
"ba_data/python/baclassic/_clienteffect.py",
"ba_data/python/baclassic/_input.py",
"ba_data/python/baclassic/_music.py",
"ba_data/python/baclassic/_net.py",
@ -107,6 +111,7 @@
"ba_data/python/bacommon/__pycache__/app.cpython-312.opt-1.pyc",
"ba_data/python/bacommon/__pycache__/assets.cpython-312.opt-1.pyc",
"ba_data/python/bacommon/__pycache__/bacloud.cpython-312.opt-1.pyc",
"ba_data/python/bacommon/__pycache__/bs.cpython-312.opt-1.pyc",
"ba_data/python/bacommon/__pycache__/build.cpython-312.opt-1.pyc",
"ba_data/python/bacommon/__pycache__/cloud.cpython-312.opt-1.pyc",
"ba_data/python/bacommon/__pycache__/loggercontrol.cpython-312.opt-1.pyc",
@ -118,6 +123,7 @@
"ba_data/python/bacommon/app.py",
"ba_data/python/bacommon/assets.py",
"ba_data/python/bacommon/bacloud.py",
"ba_data/python/bacommon/bs.py",
"ba_data/python/bacommon/build.py",
"ba_data/python/bacommon/cloud.py",
"ba_data/python/bacommon/loggercontrol.py",
@ -603,6 +609,7 @@
"ba_data/python/efro/dataclassio/__pycache__/_pathcapture.cpython-312.opt-1.pyc",
"ba_data/python/efro/dataclassio/__pycache__/_prep.cpython-312.opt-1.pyc",
"ba_data/python/efro/dataclassio/__pycache__/extras.cpython-312.opt-1.pyc",
"ba_data/python/efro/dataclassio/__pycache__/templatemultitype.cpython-312.opt-1.pyc",
"ba_data/python/efro/dataclassio/_api.py",
"ba_data/python/efro/dataclassio/_base.py",
"ba_data/python/efro/dataclassio/_inputter.py",
@ -610,6 +617,7 @@
"ba_data/python/efro/dataclassio/_pathcapture.py",
"ba_data/python/efro/dataclassio/_prep.py",
"ba_data/python/efro/dataclassio/extras.py",
"ba_data/python/efro/dataclassio/templatemultitype.py",
"ba_data/python/efro/debug.py",
"ba_data/python/efro/error.py",
"ba_data/python/efro/logging.py",

View File

@ -206,6 +206,8 @@ SCRIPT_TARGETS_PY_PUBLIC = \
$(BUILD_DIR)/ba_data/python/baclassic/_appmode.py \
$(BUILD_DIR)/ba_data/python/baclassic/_appsubsystem.py \
$(BUILD_DIR)/ba_data/python/baclassic/_benchmark.py \
$(BUILD_DIR)/ba_data/python/baclassic/_chest.py \
$(BUILD_DIR)/ba_data/python/baclassic/_clienteffect.py \
$(BUILD_DIR)/ba_data/python/baclassic/_input.py \
$(BUILD_DIR)/ba_data/python/baclassic/_music.py \
$(BUILD_DIR)/ba_data/python/baclassic/_net.py \
@ -486,6 +488,8 @@ SCRIPT_TARGETS_PYC_PUBLIC = \
$(BUILD_DIR)/ba_data/python/baclassic/__pycache__/_appmode.cpython-312.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/baclassic/__pycache__/_appsubsystem.cpython-312.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/baclassic/__pycache__/_benchmark.cpython-312.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/baclassic/__pycache__/_chest.cpython-312.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/baclassic/__pycache__/_clienteffect.cpython-312.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/baclassic/__pycache__/_input.cpython-312.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/baclassic/__pycache__/_music.cpython-312.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/baclassic/__pycache__/_net.cpython-312.opt-1.pyc \
@ -738,6 +742,7 @@ SCRIPT_TARGETS_PY_PUBLIC_TOOLS = \
$(BUILD_DIR)/ba_data/python/bacommon/app.py \
$(BUILD_DIR)/ba_data/python/bacommon/assets.py \
$(BUILD_DIR)/ba_data/python/bacommon/bacloud.py \
$(BUILD_DIR)/ba_data/python/bacommon/bs.py \
$(BUILD_DIR)/ba_data/python/bacommon/build.py \
$(BUILD_DIR)/ba_data/python/bacommon/cloud.py \
$(BUILD_DIR)/ba_data/python/bacommon/loggercontrol.py \
@ -759,6 +764,7 @@ SCRIPT_TARGETS_PY_PUBLIC_TOOLS = \
$(BUILD_DIR)/ba_data/python/efro/dataclassio/_pathcapture.py \
$(BUILD_DIR)/ba_data/python/efro/dataclassio/_prep.py \
$(BUILD_DIR)/ba_data/python/efro/dataclassio/extras.py \
$(BUILD_DIR)/ba_data/python/efro/dataclassio/templatemultitype.py \
$(BUILD_DIR)/ba_data/python/efro/debug.py \
$(BUILD_DIR)/ba_data/python/efro/error.py \
$(BUILD_DIR)/ba_data/python/efro/logging.py \
@ -778,6 +784,7 @@ SCRIPT_TARGETS_PYC_PUBLIC_TOOLS = \
$(BUILD_DIR)/ba_data/python/bacommon/__pycache__/app.cpython-312.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/bacommon/__pycache__/assets.cpython-312.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/bacommon/__pycache__/bacloud.cpython-312.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/bacommon/__pycache__/bs.cpython-312.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/bacommon/__pycache__/build.cpython-312.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/bacommon/__pycache__/cloud.cpython-312.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/bacommon/__pycache__/loggercontrol.cpython-312.opt-1.pyc \
@ -799,6 +806,7 @@ SCRIPT_TARGETS_PYC_PUBLIC_TOOLS = \
$(BUILD_DIR)/ba_data/python/efro/dataclassio/__pycache__/_pathcapture.cpython-312.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/efro/dataclassio/__pycache__/_prep.cpython-312.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/efro/dataclassio/__pycache__/extras.cpython-312.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/efro/dataclassio/__pycache__/templatemultitype.cpython-312.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/efro/__pycache__/debug.cpython-312.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/efro/__pycache__/error.cpython-312.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/efro/__pycache__/logging.cpython-312.opt-1.pyc \
@ -5259,6 +5267,7 @@ AUDIO_TARGETS = \
$(BUILD_DIR)/ba_data/audio/assassinFall.ogg \
$(BUILD_DIR)/ba_data/audio/assassinHit1.ogg \
$(BUILD_DIR)/ba_data/audio/assassinHit2.ogg \
$(BUILD_DIR)/ba_data/audio/aww.ogg \
$(BUILD_DIR)/ba_data/audio/bear1.ogg \
$(BUILD_DIR)/ba_data/audio/bear2.ogg \
$(BUILD_DIR)/ba_data/audio/bear3.ogg \
@ -5301,6 +5310,7 @@ AUDIO_TARGETS = \
$(BUILD_DIR)/ba_data/audio/cheer.ogg \
$(BUILD_DIR)/ba_data/audio/click01.ogg \
$(BUILD_DIR)/ba_data/audio/corkPop.ogg \
$(BUILD_DIR)/ba_data/audio/corkPop2.ogg \
$(BUILD_DIR)/ba_data/audio/cowboy1.ogg \
$(BUILD_DIR)/ba_data/audio/cowboy2.ogg \
$(BUILD_DIR)/ba_data/audio/cowboy3.ogg \
@ -5327,6 +5337,7 @@ AUDIO_TARGETS = \
$(BUILD_DIR)/ba_data/audio/dingSmallHigh.ogg \
$(BUILD_DIR)/ba_data/audio/dripity.ogg \
$(BUILD_DIR)/ba_data/audio/drumRoll.ogg \
$(BUILD_DIR)/ba_data/audio/drumRollShort.ogg \
$(BUILD_DIR)/ba_data/audio/error.ogg \
$(BUILD_DIR)/ba_data/audio/explosion01.ogg \
$(BUILD_DIR)/ba_data/audio/explosion02.ogg \
@ -5353,6 +5364,7 @@ AUDIO_TARGETS = \
$(BUILD_DIR)/ba_data/audio/frostyHit02.ogg \
$(BUILD_DIR)/ba_data/audio/frostyHit03.ogg \
$(BUILD_DIR)/ba_data/audio/fuse01.ogg \
$(BUILD_DIR)/ba_data/audio/gasp.ogg \
$(BUILD_DIR)/ba_data/audio/gladiator1.ogg \
$(BUILD_DIR)/ba_data/audio/gladiator2.ogg \
$(BUILD_DIR)/ba_data/audio/gladiator3.ogg \
@ -5424,6 +5436,7 @@ AUDIO_TARGETS = \
$(BUILD_DIR)/ba_data/audio/menuMusic.ogg \
$(BUILD_DIR)/ba_data/audio/metalHit.ogg \
$(BUILD_DIR)/ba_data/audio/metalSkid.ogg \
$(BUILD_DIR)/ba_data/audio/nice.ogg \
$(BUILD_DIR)/ba_data/audio/ninjaAttack1.ogg \
$(BUILD_DIR)/ba_data/audio/ninjaAttack2.ogg \
$(BUILD_DIR)/ba_data/audio/ninjaAttack3.ogg \
@ -5493,6 +5506,7 @@ AUDIO_TARGETS = \
$(BUILD_DIR)/ba_data/audio/raceBeep1.ogg \
$(BUILD_DIR)/ba_data/audio/raceBeep2.ogg \
$(BUILD_DIR)/ba_data/audio/refWhistle.ogg \
$(BUILD_DIR)/ba_data/audio/revUp.ogg \
$(BUILD_DIR)/ba_data/audio/robot1.ogg \
$(BUILD_DIR)/ba_data/audio/robot2.ogg \
$(BUILD_DIR)/ba_data/audio/robot3.ogg \
@ -5601,7 +5615,11 @@ AUDIO_TARGETS = \
$(BUILD_DIR)/ba_data/audio/wizardFall.ogg \
$(BUILD_DIR)/ba_data/audio/wizardHit1.ogg \
$(BUILD_DIR)/ba_data/audio/wizardHit2.ogg \
$(BUILD_DIR)/ba_data/audio/woo.ogg \
$(BUILD_DIR)/ba_data/audio/woo2.ogg \
$(BUILD_DIR)/ba_data/audio/woo3.ogg \
$(BUILD_DIR)/ba_data/audio/woodDebrisFall.ogg \
$(BUILD_DIR)/ba_data/audio/wow.ogg \
$(BUILD_DIR)/ba_data/audio/wrestler1.ogg \
$(BUILD_DIR)/ba_data/audio/wrestler2.ogg \
$(BUILD_DIR)/ba_data/audio/wrestler3.ogg \
@ -5610,6 +5628,7 @@ AUDIO_TARGETS = \
$(BUILD_DIR)/ba_data/audio/wrestlerFall.ogg \
$(BUILD_DIR)/ba_data/audio/wrestlerHit1.ogg \
$(BUILD_DIR)/ba_data/audio/wrestlerHit2.ogg \
$(BUILD_DIR)/ba_data/audio/yeah.ogg \
$(BUILD_DIR)/ba_data/audio/zoeAttack01.ogg \
$(BUILD_DIR)/ba_data/audio/zoeAttack02.ogg \
$(BUILD_DIR)/ba_data/audio/zoeAttack03.ogg \
@ -5723,7 +5742,9 @@ TEX2D_DDS_TARGETS = \
$(BUILD_DIR)/ba_data/textures/chestIcon.dds \
$(BUILD_DIR)/ba_data/textures/chestIconEmpty.dds \
$(BUILD_DIR)/ba_data/textures/chestIconMulti.dds \
$(BUILD_DIR)/ba_data/textures/chestIconTint.dds \
$(BUILD_DIR)/ba_data/textures/chestOpenIcon.dds \
$(BUILD_DIR)/ba_data/textures/chestOpenIconTint.dds \
$(BUILD_DIR)/ba_data/textures/circle.dds \
$(BUILD_DIR)/ba_data/textures/circleNoAlpha.dds \
$(BUILD_DIR)/ba_data/textures/circleOutline.dds \
@ -5972,6 +5993,7 @@ TEX2D_DDS_TARGETS = \
$(BUILD_DIR)/ba_data/textures/softRect2.dds \
$(BUILD_DIR)/ba_data/textures/softRectVertical.dds \
$(BUILD_DIR)/ba_data/textures/sparks.dds \
$(BUILD_DIR)/ba_data/textures/spinner.dds \
$(BUILD_DIR)/ba_data/textures/star.dds \
$(BUILD_DIR)/ba_data/textures/startButton.dds \
$(BUILD_DIR)/ba_data/textures/stepRightUpLevelColor.dds \
@ -6135,7 +6157,9 @@ TEX2D_PVR_TARGETS = \
$(BUILD_DIR)/ba_data/textures/chestIcon.pvr \
$(BUILD_DIR)/ba_data/textures/chestIconEmpty.pvr \
$(BUILD_DIR)/ba_data/textures/chestIconMulti.pvr \
$(BUILD_DIR)/ba_data/textures/chestIconTint.pvr \
$(BUILD_DIR)/ba_data/textures/chestOpenIcon.pvr \
$(BUILD_DIR)/ba_data/textures/chestOpenIconTint.pvr \
$(BUILD_DIR)/ba_data/textures/circle.pvr \
$(BUILD_DIR)/ba_data/textures/circleNoAlpha.pvr \
$(BUILD_DIR)/ba_data/textures/circleOutline.pvr \
@ -6384,6 +6408,7 @@ TEX2D_PVR_TARGETS = \
$(BUILD_DIR)/ba_data/textures/softRect2.pvr \
$(BUILD_DIR)/ba_data/textures/softRectVertical.pvr \
$(BUILD_DIR)/ba_data/textures/sparks.pvr \
$(BUILD_DIR)/ba_data/textures/spinner.pvr \
$(BUILD_DIR)/ba_data/textures/star.pvr \
$(BUILD_DIR)/ba_data/textures/startButton.pvr \
$(BUILD_DIR)/ba_data/textures/stepRightUpLevelColor.pvr \
@ -6547,7 +6572,9 @@ TEX2D_KTX_TARGETS = \
$(BUILD_DIR)/ba_data/textures/chestIcon.ktx \
$(BUILD_DIR)/ba_data/textures/chestIconEmpty.ktx \
$(BUILD_DIR)/ba_data/textures/chestIconMulti.ktx \
$(BUILD_DIR)/ba_data/textures/chestIconTint.ktx \
$(BUILD_DIR)/ba_data/textures/chestOpenIcon.ktx \
$(BUILD_DIR)/ba_data/textures/chestOpenIconTint.ktx \
$(BUILD_DIR)/ba_data/textures/circle.ktx \
$(BUILD_DIR)/ba_data/textures/circleNoAlpha.ktx \
$(BUILD_DIR)/ba_data/textures/circleOutline.ktx \
@ -6796,6 +6823,7 @@ TEX2D_KTX_TARGETS = \
$(BUILD_DIR)/ba_data/textures/softRect2.ktx \
$(BUILD_DIR)/ba_data/textures/softRectVertical.ktx \
$(BUILD_DIR)/ba_data/textures/sparks.ktx \
$(BUILD_DIR)/ba_data/textures/spinner.ktx \
$(BUILD_DIR)/ba_data/textures/star.ktx \
$(BUILD_DIR)/ba_data/textures/startButton.ktx \
$(BUILD_DIR)/ba_data/textures/stepRightUpLevelColor.ktx \
@ -6958,7 +6986,9 @@ TEX2D_PREVIEW_PNG_TARGETS = \
$(BUILD_DIR)/ba_data/textures/characterIconMask_preview.png \
$(BUILD_DIR)/ba_data/textures/chestIconEmpty_preview.png \
$(BUILD_DIR)/ba_data/textures/chestIconMulti_preview.png \
$(BUILD_DIR)/ba_data/textures/chestIconTint_preview.png \
$(BUILD_DIR)/ba_data/textures/chestIcon_preview.png \
$(BUILD_DIR)/ba_data/textures/chestOpenIconTint_preview.png \
$(BUILD_DIR)/ba_data/textures/chestOpenIcon_preview.png \
$(BUILD_DIR)/ba_data/textures/circleNoAlpha_preview.png \
$(BUILD_DIR)/ba_data/textures/circleOutlineNoAlpha_preview.png \
@ -7208,6 +7238,7 @@ TEX2D_PREVIEW_PNG_TARGETS = \
$(BUILD_DIR)/ba_data/textures/softRectVertical_preview.png \
$(BUILD_DIR)/ba_data/textures/softRect_preview.png \
$(BUILD_DIR)/ba_data/textures/sparks_preview.png \
$(BUILD_DIR)/ba_data/textures/spinner_preview.png \
$(BUILD_DIR)/ba_data/textures/star_preview.png \
$(BUILD_DIR)/ba_data/textures/startButton_preview.png \
$(BUILD_DIR)/ba_data/textures/stepRightUpLevelColor_preview.png \

View File

@ -68,9 +68,10 @@ class App:
health_monitor: AppHealthMonitor
# How long we allow shutdown tasks to run before killing them.
# Currently the entire app hard-exits if shutdown takes 10 seconds,
# so we need to keep it under that.
SHUTDOWN_TASK_TIMEOUT_SECONDS = 5
# Currently the entire app hard-exits if shutdown takes 15 seconds,
# so we need to keep it under that. Staying above 10 should allow
# 10 second network timeouts to happen though.
SHUTDOWN_TASK_TIMEOUT_SECONDS = 12
class State(Enum):
"""High level state the app can be in."""

View File

@ -30,7 +30,9 @@ def utc_now_cloud() -> datetime.datetime:
Applies offsets pulled from server communication/etc.
"""
# FIXME - do something smart here.
# TODO: wire this up. Just using local time for now. Make sure that
# BaseFeatureSet::TimeSinceEpochCloudSeconds() and this are synced
# up.
return utc_now()

View File

@ -2,18 +2,11 @@
#
"""Components for the classic BombSquad experience.
This package is used as a dumping ground for functionality that is
necessary to keep classic BombSquad working, but which may no longer be
the best way to do things going forward.
New code should try to avoid using code from here when possible.
Functionality in this package should be exposed through the
ClassicAppSubsystem. This allows type-checked code to go through the
babase.app.classic singleton which forces it to explicitly handle the
possibility of babase.app.classic being None. When code instead imports
classic submodules directly, it is much harder to make it cleanly handle
classic not being present.
This package/feature-set contains functionality related to the classic
BombSquad experience. Note that much legacy BombSquad code is still a
bit tangled and thus this feature-set is largely inseperable from
scenev1 and uiv1. Future feature-sets will be designed in a more modular
way.
"""
# ba_meta require api 9
@ -29,8 +22,16 @@ from efro.util import set_canonical_module_names
from baclassic._appmode import ClassicAppMode
from baclassic._appsubsystem import ClassicAppSubsystem
from baclassic._achievement import Achievement, AchievementSubsystem
from baclassic._chest import (
ChestAppearanceDisplayInfo,
CHEST_APPEARANCE_DISPLAY_INFO_DEFAULT,
CHEST_APPEARANCE_DISPLAY_INFOS,
)
__all__ = [
'ChestAppearanceDisplayInfo',
'CHEST_APPEARANCE_DISPLAY_INFO_DEFAULT',
'CHEST_APPEARANCE_DISPLAY_INFOS',
'ClassicAppMode',
'ClassicAppSubsystem',
'Achievement',

View File

@ -197,9 +197,9 @@ class ClassicAppMode(babase.AppMode):
if account is None:
self._account_data_sub = None
_baclassic.set_root_ui_account_values(
tickets_text='',
tokens_text='',
league_rank_text='',
tickets=-1,
tokens=-1,
league_rank=-1,
league_type='',
achievements_percent_text='',
level_text='',
@ -250,7 +250,7 @@ class ClassicAppMode(babase.AppMode):
print(f'GOT SUB TEST UPDATE: {val}')
def _on_classic_account_data_change(
self, val: bacommon.cloud.BSClassicAccountLiveData
self, val: bacommon.bs.ClassicAccountLiveData
) -> None:
# print('ACCOUNT CHANGED:', val)
achp = round(val.achievements / max(val.achievements_total, 1) * 100.0)
@ -264,11 +264,9 @@ class ClassicAppMode(babase.AppMode):
chest3 = val.chests.get('3')
_baclassic.set_root_ui_account_values(
tickets_text=str(val.tickets),
tokens_text=str(val.tokens),
league_rank_text=(
'-' if val.league_rank is None else f'#{val.league_rank}'
),
tickets=val.tickets,
tokens=val.tokens,
league_rank=(-1 if val.league_rank is None else val.league_rank),
league_type=(
'' if val.league_type is None else val.league_type.value
),
@ -292,17 +290,35 @@ class ClassicAppMode(babase.AppMode):
chest_0_unlock_time=(
-1.0 if chest0 is None else chest0.unlock_time.timestamp()
),
chest_1_unlock_time=-1.0,
chest_2_unlock_time=-1.0,
chest_3_unlock_time=-1.0,
chest_1_unlock_time=(
-1.0 if chest1 is None else chest1.unlock_time.timestamp()
),
chest_2_unlock_time=(
-1.0 if chest2 is None else chest2.unlock_time.timestamp()
),
chest_3_unlock_time=(
-1.0 if chest3 is None else chest3.unlock_time.timestamp()
),
chest_0_ad_allow_time=(
-1.0
if chest0 is None or chest0.ad_allow_time is None
else chest0.ad_allow_time.timestamp()
),
chest_1_ad_allow_time=-1.0,
chest_2_ad_allow_time=-1.0,
chest_3_ad_allow_time=-1.0,
chest_1_ad_allow_time=(
-1.0
if chest1 is None or chest1.ad_allow_time is None
else chest1.ad_allow_time.timestamp()
),
chest_2_ad_allow_time=(
-1.0
if chest2 is None or chest2.ad_allow_time is None
else chest2.ad_allow_time.timestamp()
),
chest_3_ad_allow_time=(
-1.0
if chest3 is None or chest3.ad_allow_time is None
else chest3.ad_allow_time.timestamp()
),
)
# Note that we have values and updated faded state accordingly.

View File

@ -3,10 +3,10 @@
"""Provides classic app subsystem."""
from __future__ import annotations
from typing import TYPE_CHECKING, override
import random
import logging
import weakref
from typing import TYPE_CHECKING, override, assert_never
from efro.dataclassio import dataclass_from_dict
import babase
@ -26,6 +26,7 @@ from baclassic import _input
if TYPE_CHECKING:
from typing import Callable, Any, Sequence
import bacommon.bs
from bascenev1lib.actor import spazappearance
from bauiv1lib.party import PartyWindow
@ -509,11 +510,36 @@ class ClassicAppSubsystem(babase.AppSubsystem):
request, 'post', data, callback, response_type
).start()
def get_tournament_prize_strings(self, entry: dict[str, Any]) -> list[str]:
def set_tournament_prize_image(
self, entry: dict[str, Any], index: int, image: bauiv1.Widget
) -> None:
"""Given a tournament entry, return strings for its prize levels."""
from baclassic import _tournament
return _tournament.get_tournament_prize_strings(entry)
return _tournament.set_tournament_prize_chest_image(entry, index, image)
def create_in_game_tournament_prize_image(
self,
entry: dict[str, Any],
index: int,
position: tuple[float, float],
) -> None:
"""Given a tournament entry, return strings for its prize levels."""
from baclassic import _tournament
_tournament.create_in_game_tournament_prize_image(
entry, index, position
)
def get_tournament_prize_strings(
self, entry: dict[str, Any], include_tickets: bool
) -> list[str]:
"""Given a tournament entry, return strings for its prize levels."""
from baclassic import _tournament
return _tournament.get_tournament_prize_strings(
entry, include_tickets=include_tickets
)
def getcampaign(self, name: str) -> bascenev1.Campaign:
"""Return a campaign by name."""
@ -852,3 +878,48 @@ class ClassicAppSubsystem(babase.AppSubsystem):
is_top_level=True,
suppress_warning=True,
)
@staticmethod
def run_bs_client_effects(effects: list[bacommon.bs.ClientEffect]) -> None:
"""Run client effects sent from the master server."""
from baclassic._clienteffect import run_bs_client_effects
run_bs_client_effects(effects)
@staticmethod
def basic_client_ui_button_label_str(
label: bacommon.bs.BasicClientUI.ButtonLabel,
) -> babase.Lstr:
"""Given a client-ui label, return an Lstr."""
import bacommon.bs
cls = bacommon.bs.BasicClientUI.ButtonLabel
if label is cls.UNKNOWN:
# Server should not be sending us unknown stuff; make noise
# if they do.
logging.error(
'Got BasicClientUI.ButtonLabel.UNKNOWN; should not happen.'
)
return babase.Lstr(value='<error>')
rsrc: str | None = None
if label is cls.OK:
rsrc = 'okText'
elif label is cls.APPLY:
rsrc = 'applyText'
elif label is cls.CANCEL:
rsrc = 'cancelText'
elif label is cls.ACCEPT:
rsrc = 'gatherWindow.partyInviteAcceptText'
elif label is cls.DECLINE:
rsrc = 'gatherWindow.partyInviteDeclineText'
elif label is cls.IGNORE:
rsrc = 'gatherWindow.partyInviteIgnoreText'
elif label is cls.CLAIM:
rsrc = 'claimText'
elif label is cls.DISCARD:
rsrc = 'discardText'
else:
assert_never(label)
return babase.Lstr(resource=rsrc)

View File

@ -0,0 +1,91 @@
# Released under the MIT License. See LICENSE for details.
#
"""Chest related functionality."""
from __future__ import annotations
from dataclasses import dataclass
from typing import TYPE_CHECKING
from bacommon.bs import ClassicChestAppearance
if TYPE_CHECKING:
pass
@dataclass
class ChestAppearanceDisplayInfo:
"""Info about how to locally display chest appearances."""
# NOTE TO SELF: Don't rename these attrs; the C++ layer is hard
# coded to look for them.
texclosed: str
texclosedtint: str
texopen: str
texopentint: str
color: tuple[float, float, float]
tint: tuple[float, float, float]
tint2: tuple[float, float, float]
# Info for chest types we know how to draw. Anything not found in here
# should fall back to the DEFAULT entry.
CHEST_APPEARANCE_DISPLAY_INFO_DEFAULT = ChestAppearanceDisplayInfo(
texclosed='chestIcon',
texclosedtint='chestIconTint',
texopen='chestOpenIcon',
texopentint='chestOpenIconTint',
color=(1, 1, 1),
tint=(1, 1, 1),
tint2=(1, 1, 1),
)
CHEST_APPEARANCE_DISPLAY_INFOS: dict[
ClassicChestAppearance, ChestAppearanceDisplayInfo
] = {
ClassicChestAppearance.L2: ChestAppearanceDisplayInfo(
texclosed='chestIcon',
texclosedtint='chestIconTint',
texopen='chestOpenIcon',
texopentint='chestOpenIconTint',
color=(0.8, 1.0, 0.93),
tint=(0.65, 1.0, 0.8),
tint2=(0.65, 1.0, 0.8),
),
ClassicChestAppearance.L3: ChestAppearanceDisplayInfo(
texclosed='chestIcon',
texclosedtint='chestIconTint',
texopen='chestOpenIcon',
texopentint='chestOpenIconTint',
color=(0.75, 0.9, 1.3),
tint=(0.7, 1, 1.9),
tint2=(0.7, 1, 1.9),
),
ClassicChestAppearance.L4: ChestAppearanceDisplayInfo(
texclosed='chestIcon',
texclosedtint='chestIconTint',
texopen='chestOpenIcon',
texopentint='chestOpenIconTint',
color=(0.7, 1.0, 1.4),
tint=(1.4, 1.6, 2.0),
tint2=(1.4, 1.6, 2.0),
),
ClassicChestAppearance.L5: ChestAppearanceDisplayInfo(
texclosed='chestIcon',
texclosedtint='chestIconTint',
texopen='chestOpenIcon',
texopentint='chestOpenIconTint',
color=(0.75, 0.5, 2.4),
tint=(1.0, 0.8, 0.0),
tint2=(1.0, 0.8, 0.0),
),
ClassicChestAppearance.L6: ChestAppearanceDisplayInfo(
texclosed='chestIcon',
texclosedtint='chestIconTint',
texopen='chestOpenIcon',
texopentint='chestOpenIconTint',
color=(1.1, 0.8, 0.0),
tint=(2, 2, 2),
tint2=(2, 2, 2),
),
}

View File

@ -0,0 +1,77 @@
# Released under the MIT License. See LICENSE for details.
#
"""Functionality related to running client-effects from the master server."""
from __future__ import annotations
import logging
from typing import TYPE_CHECKING, assert_never
from efro.util import strict_partial
import bacommon.bs
import bauiv1
if TYPE_CHECKING:
pass
def run_bs_client_effects(effects: list[bacommon.bs.ClientEffect]) -> None:
"""Run effects."""
# pylint: disable=too-many-branches
delay = 0.0
for effect in effects:
if isinstance(effect, bacommon.bs.ClientEffectScreenMessage):
textfin = bauiv1.Lstr(
translate=('serverResponses', effect.message)
).evaluate()
if effect.subs is not None:
# Should always be even.
assert len(effect.subs) % 2 == 0
for j in range(0, len(effect.subs) - 1, 2):
textfin = textfin.replace(
effect.subs[j],
effect.subs[j + 1],
)
bauiv1.apptimer(
delay,
strict_partial(
bauiv1.screenmessage, textfin, color=effect.color
),
)
elif isinstance(effect, bacommon.bs.ClientEffectSound):
smcls = bacommon.bs.ClientEffectSound.Sound
soundfile: str | None = None
if effect.sound is smcls.UNKNOWN:
# Server should avoid sending us sounds we don't
# support. Make some noise if it happens.
logging.error('Got unrecognized bacommon.bs.ClientEffectSound.')
elif effect.sound is smcls.CASH_REGISTER:
soundfile = 'cashRegister'
elif effect.sound is smcls.ERROR:
soundfile = 'error'
elif effect.sound is smcls.POWER_DOWN:
soundfile = 'powerdown01'
elif effect.sound is smcls.GUN_COCKING:
soundfile = 'gunCocking'
else:
assert_never(effect.sound)
if soundfile is not None:
bauiv1.apptimer(
delay,
strict_partial(
bauiv1.getsound(soundfile).play, volume=effect.volume
),
)
elif isinstance(effect, bacommon.bs.ClientEffectDelay):
delay += effect.seconds
else:
# Server should not send us stuff we can't digest. Make
# some noise if it happens.
logging.error(
'Got unrecognized bacommon.bs.ClientEffect;'
' should not happen.'
)

View File

@ -6,13 +6,23 @@ from __future__ import annotations
from typing import TYPE_CHECKING
from bacommon.bs import ClassicChestAppearance
import babase
import bauiv1
import bascenev1
from baclassic._chest import (
CHEST_APPEARANCE_DISPLAY_INFOS,
CHEST_APPEARANCE_DISPLAY_INFO_DEFAULT,
)
if TYPE_CHECKING:
from typing import Any
def get_tournament_prize_strings(entry: dict[str, Any]) -> list[str]:
def get_tournament_prize_strings(
entry: dict[str, Any], include_tickets: bool
) -> list[str]:
"""Given a tournament entry, return strings for its prize levels."""
# pylint: disable=too-many-locals
from bascenev1 import get_trophy_string
@ -27,7 +37,7 @@ def get_tournament_prize_strings(entry: dict[str, Any]) -> list[str]:
trophy_type_2 = entry.get('prizeTrophy2')
trophy_type_3 = entry.get('prizeTrophy3')
out_vals = []
for rng, prize, trophy_type in (
for rng, ticket_prize, trophy_type in (
(range1, prize1, trophy_type_1),
(range2, prize2, trophy_type_2),
(range3, prize3, trophy_type_3),
@ -45,14 +55,100 @@ def get_tournament_prize_strings(entry: dict[str, Any]) -> list[str]:
if trophy_type is not None:
pvval += get_trophy_string(trophy_type)
# If we've got trophies but not for this entry, throw some space
# in to compensate so the ticket counts line up.
if prize is not None:
if ticket_prize is not None and include_tickets:
pvval = (
babase.charstr(babase.SpecialChar.TICKET_BACKING)
+ str(prize)
+ str(ticket_prize)
+ pvval
)
out_vals.append(prval)
out_vals.append(pvval)
return out_vals
def set_tournament_prize_chest_image(
entry: dict[str, Any], index: int, image: bauiv1.Widget
) -> None:
"""Set image attrs representing a tourney prize chest."""
ranges = [
entry.get('prizeRange1'),
entry.get('prizeRange2'),
entry.get('prizeRange3'),
]
chests = [
entry.get('prizeChest1'),
entry.get('prizeChest2'),
entry.get('prizeChest3'),
]
assert 0 <= index < 3
# If tourney doesn't include this prize, just hide the image.
if ranges[index] is None:
bauiv1.imagewidget(edit=image, opacity=0.0)
return
try:
appearance = ClassicChestAppearance(chests[index])
except ValueError:
appearance = ClassicChestAppearance.DEFAULT
chestdisplayinfo = CHEST_APPEARANCE_DISPLAY_INFOS.get(
appearance, CHEST_APPEARANCE_DISPLAY_INFO_DEFAULT
)
bauiv1.imagewidget(
edit=image,
opacity=1.0,
color=chestdisplayinfo.color,
texture=bauiv1.gettexture(chestdisplayinfo.texclosed),
tint_texture=bauiv1.gettexture(chestdisplayinfo.texclosedtint),
tint_color=chestdisplayinfo.tint,
tint2_color=chestdisplayinfo.tint2,
)
def create_in_game_tournament_prize_image(
entry: dict[str, Any], index: int, position: tuple[float, float]
) -> None:
"""Create a display for the prize chest (if any) in-game."""
from bascenev1lib.actor.image import Image
ranges = [
entry.get('prizeRange1'),
entry.get('prizeRange2'),
entry.get('prizeRange3'),
]
chests = [
entry.get('prizeChest1'),
entry.get('prizeChest2'),
entry.get('prizeChest3'),
]
# If tourney doesn't include this prize, no-op.
if ranges[index] is None:
return
try:
appearance = ClassicChestAppearance(chests[index])
except ValueError:
appearance = ClassicChestAppearance.DEFAULT
chestdisplayinfo = CHEST_APPEARANCE_DISPLAY_INFOS.get(
appearance, CHEST_APPEARANCE_DISPLAY_INFO_DEFAULT
)
Image(
# Provide magical extended dict version of texture that Image
# actor supports.
texture={
'texture': bascenev1.gettexture(chestdisplayinfo.texclosed),
'tint_texture': bascenev1.gettexture(
chestdisplayinfo.texclosedtint
),
'tint_color': chestdisplayinfo.tint,
'tint2_color': chestdisplayinfo.tint2,
'mask_texture': None,
},
color=chestdisplayinfo.color + (1.0,),
position=position,
scale=(48.0, 48.0),
transition=Image.Transition.FADE_IN,
transition_delay=2.0,
).autoretain()

View File

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

View File

@ -12,6 +12,7 @@ import _baplus
if TYPE_CHECKING:
from typing import Callable, Any
import bacommon.bs
from babase import AccountV2Subsystem
from baplus._cloud import CloudSubsystem

View File

@ -15,6 +15,7 @@ if TYPE_CHECKING:
from efro.message import Message, Response
import bacommon.cloud
import bacommon.bs
# TODO: Should make it possible to define a protocol in bacommon.cloud and
@ -120,45 +121,45 @@ class CloudSubsystem(babase.AppSubsystem):
@overload
def send_message_cb(
self,
msg: bacommon.cloud.BSPrivatePartyMessage,
msg: bacommon.bs.PrivatePartyMessage,
on_response: Callable[
[bacommon.cloud.BSPrivatePartyResponse | Exception], None
[bacommon.bs.PrivatePartyResponse | Exception], None
],
) -> None: ...
@overload
def send_message_cb(
self,
msg: bacommon.cloud.BSInboxRequestMessage,
msg: bacommon.bs.InboxRequestMessage,
on_response: Callable[
[bacommon.cloud.BSInboxRequestResponse | Exception], None
[bacommon.bs.InboxRequestResponse | Exception], None
],
) -> None: ...
@overload
def send_message_cb(
self,
msg: bacommon.cloud.BSInboxEntryProcessMessage,
msg: bacommon.bs.ClientUIActionMessage,
on_response: Callable[
[bacommon.cloud.BSInboxEntryProcessResponse | Exception], None
[bacommon.bs.ClientUIActionResponse | Exception], None
],
) -> None: ...
@overload
def send_message_cb(
self,
msg: bacommon.cloud.BSChestInfoMessage,
msg: bacommon.bs.ChestInfoMessage,
on_response: Callable[
[bacommon.cloud.BSChestInfoResponse | Exception], None
[bacommon.bs.ChestInfoResponse | Exception], None
],
) -> None: ...
@overload
def send_message_cb(
self,
msg: bacommon.cloud.BSChestActionMessage,
msg: bacommon.bs.ChestActionMessage,
on_response: Callable[
[bacommon.cloud.BSChestActionResponse | Exception], None
[bacommon.bs.ChestActionResponse | Exception], None
],
) -> None: ...
@ -229,7 +230,7 @@ class CloudSubsystem(babase.AppSubsystem):
def subscribe_classic_account_data(
self,
updatecall: Callable[[bacommon.cloud.BSClassicAccountLiveData], None],
updatecall: Callable[[bacommon.bs.ClassicAccountLiveData], None],
) -> babase.CloudSubscription:
"""Subscribe to classic account data."""
raise NotImplementedError(

View File

@ -357,6 +357,7 @@ class CoopScoreScreen(bs.Activity[bs.Player, bs.Team]):
h_offs = 7.0
v_offs = -280.0
v_offs2 = -236.0
# We wanna prevent controllers users from popping up browsers
# or game-center widgets in cases where they can't easily get back
@ -384,7 +385,7 @@ class CoopScoreScreen(bs.Activity[bs.Player, bs.Team]):
bui.buttonwidget(
parent=rootc,
color=(0.45, 0.4, 0.5),
position=(160, v_offs + 439),
position=(240, v_offs2 + 439),
size=(350, 62),
label=(
bui.Lstr(resource='tournamentStandingsText')
@ -406,7 +407,7 @@ class CoopScoreScreen(bs.Activity[bs.Player, bs.Team]):
show_next_button = self._is_more_levels and not (env.demo or env.arcade)
if not show_next_button:
h_offs += 70
h_offs += 60
# Due to virtual-bounds changes, have to squish buttons a bit to
# avoid overlapping with tips at bottom. Could look nicer to
@ -614,7 +615,6 @@ class CoopScoreScreen(bs.Activity[bs.Player, bs.Team]):
@override
def on_begin(self) -> None:
# FIXME: Clean this up.
# pylint: disable=too-many-statements
# pylint: disable=too-many-branches
# pylint: disable=too-many-locals
@ -882,7 +882,7 @@ class CoopScoreScreen(bs.Activity[bs.Player, bs.Team]):
# If we're not doing the world's-best button, just show a title
# instead.
ts_height = 300
ts_h_offs = 210
ts_h_offs = 290
v_offs = 40
txt = Text(
(
@ -956,7 +956,6 @@ class CoopScoreScreen(bs.Activity[bs.Player, bs.Team]):
if display_scores[i][1] is None:
name_str = '-'
else:
# noinspection PyUnresolvedReferences
name_str = ', '.join(
[p['name'] for p in display_scores[i][1]['players']]
)
@ -1025,9 +1024,8 @@ class CoopScoreScreen(bs.Activity[bs.Player, bs.Team]):
ts_h_offs = -480
v_offs = 40
# Only make this if we don't have the button
# (never want clients to see it so no need for client-only
# version, etc).
# Only make this if we don't have the button (never want clients
# to see it so no need for client-only version, etc).
if self._have_achievements:
if not self._account_has_achievements:
Text(
@ -1069,7 +1067,6 @@ class CoopScoreScreen(bs.Activity[bs.Player, bs.Team]):
).autoretain()
def _got_friend_score_results(self, results: list[Any] | None) -> None:
# FIXME: tidy this up
# pylint: disable=too-many-locals
# pylint: disable=too-many-branches
# pylint: disable=too-many-statements
@ -1205,7 +1202,6 @@ class CoopScoreScreen(bs.Activity[bs.Player, bs.Team]):
).autoretain()
def _got_score_results(self, results: dict[str, Any] | None) -> None:
# FIXME: tidy this up
# pylint: disable=too-many-locals
# pylint: disable=too-many-branches
# pylint: disable=too-many-statements
@ -1222,11 +1218,12 @@ class CoopScoreScreen(bs.Activity[bs.Player, bs.Team]):
# Delay a bit if results come in too fast.
assert self._begin_time is not None
base_delay = max(0, 2.7 - (bs.time() - self._begin_time))
v_offs = 20
# v_offs = 20
v_offs = 64
if results is None:
self._score_loading_status = Text(
bs.Lstr(resource='worldScoresUnavailableText'),
position=(230, 150 + v_offs),
position=(280, 130 + v_offs),
color=(1, 1, 1, 0.4),
transition=Text.Transition.FADE_IN,
transition_delay=base_delay + 0.3,
@ -1271,7 +1268,7 @@ class CoopScoreScreen(bs.Activity[bs.Player, bs.Team]):
(1.5 + base_delay),
bs.WeakCall(self._show_world_rank, offs_x),
)
ts_h_offs = 200
ts_h_offs = 280
ts_height = 300
# Show world tops.
@ -1299,7 +1296,7 @@ class CoopScoreScreen(bs.Activity[bs.Player, bs.Team]):
transition_delay=base_delay + 0.3,
).autoretain()
else:
v_offs += 20
v_offs += 40
h_offs_extra = 0
v_offs_names = 0
@ -1326,6 +1323,37 @@ class CoopScoreScreen(bs.Activity[bs.Player, bs.Team]):
random.randrange(0, len(times) + 1),
(base_delay + i * 0.05, base_delay + 0.4 + i * 0.05),
)
# Conundrum: We want to place line numbers to the
# left of our score column based on the largest
# score width. However scores may use Lstrs and thus
# may have different widths in different languages.
# We don't want to bake down the Lstrs we display
# because then clients can't view scores in their
# own language. So as a compromise lets measure
# max-width based on baked down Lstrs but then
# display regular Lstrs with max-width set based on
# that. Hopefully that'll look reasonable for most
# languages.
max_score_width = 10.0
for tval in self._show_info['tops']:
score = int(tval[0])
name_str = tval[1]
if name_str != '-':
max_score_width = max(
max_score_width,
bui.get_string_width(
(
str(score)
if self._score_type == 'points'
else bs.timestring(
(score * 10) / 1000.0
).evaluate()
),
suppress_warning=True,
),
)
for i, tval in enumerate(self._show_info['tops']):
score = int(tval[0])
name_str = tval[1]
@ -1347,12 +1375,37 @@ class CoopScoreScreen(bs.Activity[bs.Player, bs.Team]):
tdelay2 = times[i][1]
if name_str != '-':
Text(
(
sstr = (
str(score)
if self._score_type == 'points'
else bs.timestring((score * 10) / 1000.0)
)
# Line number.
Text(
str(i + 1),
position=(
ts_h_offs
+ 20
+ h_offs_extra
- max_score_width
- 8.0,
ts_height / 2
+ -ts_height * (i + 1) / 10
+ v_offs
- 30.0,
),
scale=0.5,
h_align=Text.HAlign.RIGHT,
v_align=Text.VAlign.CENTER,
color=(0.3, 0.3, 0.3),
transition=Text.Transition.IN_LEFT,
transition_delay=tdelay1,
).autoretain()
# Score.
Text(
sstr,
position=(
ts_h_offs + 20 + h_offs_extra,
ts_height / 2
@ -1360,6 +1413,7 @@ class CoopScoreScreen(bs.Activity[bs.Player, bs.Team]):
+ v_offs
- 30.0,
),
maxwidth=max_score_width,
h_align=Text.HAlign.RIGHT,
v_align=Text.VAlign.CENTER,
color=color0,
@ -1367,6 +1421,7 @@ class CoopScoreScreen(bs.Activity[bs.Player, bs.Team]):
transition=Text.Transition.IN_LEFT,
transition_delay=tdelay1,
).autoretain()
# Player name.
Text(
bs.Lstr(value=name_str),
position=(
@ -1470,16 +1525,12 @@ class CoopScoreScreen(bs.Activity[bs.Player, bs.Team]):
]
# pylint: disable=useless-suppression
# pylint: disable=unbalanced-tuple-unpacking
(
pr1,
pv1,
pr2,
pv2,
pr3,
pv3,
) = bs.app.classic.get_tournament_prize_strings(
tourney_info
(pr1, pv1, pr2, pv2, pr3, pv3) = (
bs.app.classic.get_tournament_prize_strings(
tourney_info, include_tickets=False
)
)
# pylint: enable=unbalanced-tuple-unpacking
# pylint: enable=useless-suppression
@ -1495,10 +1546,14 @@ class CoopScoreScreen(bs.Activity[bs.Player, bs.Team]):
transition_delay=2.0,
).autoretain()
vval = -107 + 70
for rng, val in ((pr1, pv1), (pr2, pv2), (pr3, pv3)):
for i, rng, val in (
(0, pr1, pv1),
(1, pr2, pv2),
(2, pr3, pv3),
):
Text(
rng,
position=(-410 + 10, vval),
position=(-430 + 10, vval),
color=(1, 1, 1, 0.7),
h_align=Text.HAlign.RIGHT,
v_align=Text.VAlign.CENTER,
@ -1509,7 +1564,7 @@ class CoopScoreScreen(bs.Activity[bs.Player, bs.Team]):
).autoretain()
Text(
val,
position=(-390 + 10, vval),
position=(-410 + 10, vval),
color=(0.7, 0.7, 0.7, 1.0),
h_align=Text.HAlign.LEFT,
v_align=Text.VAlign.CENTER,
@ -1518,6 +1573,9 @@ class CoopScoreScreen(bs.Activity[bs.Player, bs.Team]):
maxwidth=300,
transition_delay=2.0,
).autoretain()
bs.app.classic.create_in_game_tournament_prize_image(
tourney_info, i, (-410 + 70, vval)
)
vval -= 35
except Exception:
logging.exception('Error showing prize ranges.')

View File

@ -56,15 +56,21 @@ class Image(bs.Actor):
# pylint: disable=too-many-locals
super().__init__()
# If they provided a dict as texture, assume its an icon.
# otherwise its just a texture value itself.
# If they provided a dict as texture, use it to wire up extended
# stuff like tints and masks.
mask_texture: bs.Texture | None
if isinstance(texture, dict):
tint_color = texture['tint_color']
tint2_color = texture['tint2_color']
tint_texture = texture['tint_texture']
# Assume we're dealing with a character icon but allow
# overriding.
mask_tex_name = texture.get('mask_texture', 'characterIconMask')
mask_texture = (
None if mask_tex_name is None else bs.gettexture(mask_tex_name)
)
texture = texture['texture']
mask_texture = bs.gettexture('characterIconMask')
else:
tint_color = (1, 1, 1)
tint2_color = None

View File

@ -114,9 +114,12 @@ from _bauiv1 import (
hscrollwidget,
imagewidget,
Mesh,
root_ui_pause_updates,
root_ui_resume_updates,
rowwidget,
scrollwidget,
set_party_window_open,
spinnerwidget,
Sound,
Texture,
textwidget,
@ -218,6 +221,8 @@ __all__ = [
'quit',
'QuitType',
'request_permission',
'root_ui_pause_updates',
'root_ui_resume_updates',
'rowwidget',
'safecolor',
'screenmessage',
@ -228,6 +233,7 @@ __all__ = [
'set_ui_input_device',
'Sound',
'SpecialChar',
'spinnerwidget',
'supports_max_fps',
'supports_vsync',
'supports_unicode_display',

View File

@ -100,17 +100,20 @@ class AccountViewerWindow(PopupWindow):
)
bui.widget(edit=self._scrollwidget, autoselect=True)
# Note to self: Make sure to always update loading text and
# spinner visibility together.
self._loading_text = bui.textwidget(
parent=self._scrollwidget,
scale=0.5,
text=bui.Lstr(
value='${A}...',
subs=[('${A}', bui.Lstr(resource='loadingText'))],
),
text='',
size=(self._width - 60, 100),
h_align='center',
v_align='center',
)
self._loading_spinner = bui.spinnerwidget(
parent=self.root_widget,
position=(self._width * 0.5, self._height * 0.5),
)
# In cases where the user most likely has a browser/email, lets
# offer a 'report this user' button.
@ -227,9 +230,11 @@ class AccountViewerWindow(PopupWindow):
edit=self._loading_text,
text=bui.Lstr(resource='internal.unavailableNoConnectionText'),
)
bui.spinnerwidget(edit=self._loading_spinner, visible=False)
else:
try:
self._loading_text.delete()
self._loading_spinner.delete()
trophystr = ''
try:
trophystr = data['trophies']

File diff suppressed because it is too large Load Diff

View File

@ -57,7 +57,7 @@ class WaitForConnectivityWindow(bui.Window):
)
bui.textwidget(
parent=self._root_widget,
position=(self._width * 0.5, self._height * 0.65),
position=(self._width * 0.5, self._height * 0.7),
size=(0, 0),
scale=1.2,
h_align='center',
@ -65,9 +65,15 @@ class WaitForConnectivityWindow(bui.Window):
text=bui.Lstr(resource='internal.connectingToPartyText'),
maxwidth=self._width * 0.9,
)
self._spinner = bui.spinnerwidget(
parent=self._root_widget,
position=(self._width * 0.5, self._height * 0.54),
)
self._info_text = bui.textwidget(
parent=self._root_widget,
position=(self._width * 0.5, self._height * 0.45),
position=(self._width * 0.5, self._height * 0.4),
size=(0, 0),
color=(0.6, 0.5, 0.6),
flatness=1.0,
@ -115,6 +121,15 @@ class WaitForConnectivityWindow(bui.Window):
def _connected(self) -> None:
if not self._root_widget or self._root_widget.transitioning_out:
return
# Show 'connected.' and kill the spinner for the brief moment
# we're visible on our way out.
bui.textwidget(
edit=self._info_text, text=bui.Lstr(resource='remote_app.connected')
)
if self._spinner:
self._spinner.delete()
bui.containerwidget(
edit=self._root_widget,
transition=('out_scale'),

View File

@ -231,11 +231,11 @@ class CoopBrowserWindow(bui.MainWindow):
# Don't want initial construction affecting our last-selected.
self._do_selection_callbacks = False
v = self._height - 95
txt = bui.textwidget(
bui.textwidget(
parent=self._root_widget,
position=(
self._width * 0.5,
v + 40 - (0 if uiscale is bui.UIScale.SMALL else 0),
v + 40 - (25 if uiscale is bui.UIScale.SMALL else 0),
),
size=(0, 0),
text=bui.Lstr(
@ -244,14 +244,11 @@ class CoopBrowserWindow(bui.MainWindow):
),
h_align='center',
color=app.ui_v1.title_color,
scale=1.5,
maxwidth=500,
scale=0.85 if uiscale is bui.UIScale.SMALL else 1.5,
maxwidth=280 if uiscale is bui.UIScale.SMALL else 500,
v_align='center',
)
if uiscale is bui.UIScale.SMALL:
bui.textwidget(edit=txt, text='')
self._selected_row = cfg.get('Selected Coop Row', None)
self._scroll_width = self._width - (130 + 2 * x_inset)

View File

@ -12,6 +12,11 @@ import bauiv1 as bui
if TYPE_CHECKING:
from typing import Any, Callable
# As of 1.7.37, no longer charging entry fees for tourneys (tourneys now
# reward chests and the game now makes its money from tokens/ads used to
# speed up chest openings).
USE_ENTRY_FEES = False
class TournamentButton:
"""Button showing a tournament in coop window."""
@ -25,6 +30,7 @@ class TournamentButton:
on_pressed: Callable[[TournamentButton], None],
) -> None:
# pylint: disable=too-many-positional-arguments
# pylint: disable=too-many-statements
self._r = 'coopSelectWindow'
sclx = 300
scly = 195.0
@ -37,6 +43,7 @@ class TournamentButton:
self.has_time_remaining: bool = False
self.leader: Any = None
self.required_league: str | None = None
self._base_x_offs = 0 if USE_ENTRY_FEES else -45.0
self.button = btn = bui.buttonwidget(
parent=parent,
position=(x + 23, y + 4),
@ -96,7 +103,10 @@ class TournamentButton:
header_color = (0.43, 0.4, 0.5, 1)
value_color = (0.6, 0.6, 0.6, 1)
x_offs = 0
x_offs = self._base_x_offs
# No longer using entry fees.
if USE_ENTRY_FEES:
bui.textwidget(
parent=parent,
draw_controller=btn,
@ -180,8 +190,8 @@ class TournamentButton:
self.button_y = y
self.button_scale_y = scly
xo2 = 0
prize_value_scale = 1.5
# Offset for prize range/values.
xo2 = 0.0
self.prize_range_1_text = bui.textwidget(
parent=parent,
@ -191,7 +201,7 @@ class TournamentButton:
h_align='right',
v_align='center',
maxwidth=50,
text='-',
text='',
scale=0.8,
color=header_color,
flatness=1.0,
@ -202,13 +212,21 @@ class TournamentButton:
position=(x + 380 + xo2 + x_offs, y + scly - 93),
size=(0, 0),
h_align='left',
text='-',
text='',
v_align='center',
maxwidth=100,
scale=prize_value_scale,
color=value_color,
flatness=1.0,
)
self._chestsz = 50
self.prize_chest_1_image = bui.imagewidget(
parent=parent,
draw_controller=btn,
texture=bui.gettexture('white'),
position=(x + 380 + xo2 + x_offs, y + scly - 93),
size=(self._chestsz, self._chestsz),
opacity=0.0,
)
self.prize_range_2_text = bui.textwidget(
parent=parent,
@ -216,6 +234,7 @@ class TournamentButton:
position=(x + 355 + xo2 + x_offs, y + scly - 93),
size=(0, 0),
h_align='right',
text='',
v_align='center',
maxwidth=50,
scale=0.8,
@ -231,10 +250,17 @@ class TournamentButton:
text='',
v_align='center',
maxwidth=100,
scale=prize_value_scale,
color=value_color,
flatness=1.0,
)
self.prize_chest_2_image = bui.imagewidget(
parent=parent,
draw_controller=btn,
texture=bui.gettexture('white'),
position=(x + 380 + xo2 + x_offs, y + scly - 93),
size=(self._chestsz, self._chestsz),
opacity=0.0,
)
self.prize_range_3_text = bui.textwidget(
parent=parent,
@ -242,6 +268,7 @@ class TournamentButton:
position=(x + 355 + xo2 + x_offs, y + scly - 93),
size=(0, 0),
h_align='right',
text='',
v_align='center',
maxwidth=50,
scale=0.8,
@ -257,15 +284,22 @@ class TournamentButton:
text='',
v_align='center',
maxwidth=100,
scale=prize_value_scale,
color=value_color,
flatness=1.0,
)
self.prize_chest_3_image = bui.imagewidget(
parent=parent,
draw_controller=btn,
texture=bui.gettexture('white'),
position=(x + 380 + xo2 + x_offs, y + scly - 93),
size=(self._chestsz, self._chestsz),
opacity=0.0,
)
bui.textwidget(
parent=parent,
draw_controller=btn,
position=(x + 620 + x_offs, y + scly - 20),
position=(x + 625 + x_offs, y + scly - 20),
size=(0, 0),
h_align='center',
text=bui.Lstr(resource=f'{self._r}.currentBestText'),
@ -279,7 +313,7 @@ class TournamentButton:
parent=parent,
draw_controller=btn,
position=(
x + 620 + x_offs - (170 / 1.4) * 0.5,
x + 625 + x_offs - (170 / 1.4) * 0.5,
y + scly - 60 - 40 * 0.5,
),
selectable=True,
@ -299,7 +333,7 @@ class TournamentButton:
self.current_leader_score_text = bui.textwidget(
parent=parent,
draw_controller=btn,
position=(x + 620 + x_offs, y + scly - 113 + 10),
position=(x + 625 + x_offs, y + scly - 113 + 10),
size=(0, 0),
h_align='center',
text='-',
@ -312,7 +346,7 @@ class TournamentButton:
self.more_scores_button = bui.buttonwidget(
parent=parent,
position=(x + 620 + x_offs - 60, y + scly - 50 - 125),
position=(x + 625 + x_offs - 60, y + scly - 50 - 125),
color=(0.5, 0.5, 0.6),
textcolor=(0.7, 0.7, 0.8),
label='-',
@ -330,7 +364,7 @@ class TournamentButton:
bui.textwidget(
parent=parent,
draw_controller=btn,
position=(x + 820 + x_offs, y + scly - 20),
position=(x + 840 + x_offs, y + scly - 20),
size=(0, 0),
h_align='center',
text=bui.Lstr(resource=f'{self._r}.timeRemainingText'),
@ -343,7 +377,7 @@ class TournamentButton:
self.time_remaining_value_text = bui.textwidget(
parent=parent,
draw_controller=btn,
position=(x + 820 + x_offs, y + scly - 68),
position=(x + 840 + x_offs, y + scly - 68),
size=(0, 0),
h_align='center',
text='-',
@ -356,7 +390,7 @@ class TournamentButton:
self.time_remaining_out_of_text = bui.textwidget(
parent=parent,
draw_controller=btn,
position=(x + 820 + x_offs, y + scly - 110),
position=(x + 840 + x_offs, y + scly - 110),
size=(0, 0),
h_align='center',
text='-',
@ -415,26 +449,26 @@ class TournamentButton:
plus = bui.app.plus
assert plus is not None
assert bui.app.classic is not None
classic = bui.app.classic
assert classic is not None
prize_y_offs = (
34
if 'prizeRange3' in entry
else 20 if 'prizeRange2' in entry else 12
)
x_offs = 90
x_offs = self._base_x_offs + 90
# pylint: disable=useless-suppression
# pylint: disable=unbalanced-tuple-unpacking
(
pr1,
pv1,
pr2,
pv2,
pr3,
pv3,
) = bui.app.classic.get_tournament_prize_strings(entry)
# pylint: enable=unbalanced-tuple-unpacking
# pylint: enable=useless-suppression
# Special offset for prize ranges/vals.
x_offs2 = x_offs - 20.0
# Special offset for prize chests.
x_offs2c = x_offs2 + 50
# Fetch prize range and trophy strings.
(pr1, pv1, pr2, pv2, pr3, pv3) = classic.get_tournament_prize_strings(
entry, include_tickets=False
)
enabled = 'requiredLeague' not in entry
bui.buttonwidget(
@ -446,74 +480,91 @@ class TournamentButton:
edit=self.prize_range_1_text,
text='-' if pr1 == '' else pr1,
position=(
self.button_x + 365 + x_offs,
self.button_x + 365 + x_offs2,
self.button_y + self.button_scale_y - 93 + prize_y_offs,
),
)
# We want to draw values containing tickets a bit smaller
# (scratch that; we now draw medals a bit bigger).
ticket_char = bui.charstr(bui.SpecialChar.TICKET_BACKING)
prize_value_scale_large = 1.0
prize_value_scale_small = 1.0
bui.textwidget(
edit=self.prize_value_1_text,
text='-' if pv1 == '' else pv1,
scale=(
prize_value_scale_large
if ticket_char not in pv1
else prize_value_scale_small
),
position=(
self.button_x + 380 + x_offs,
self.button_x + 380 + x_offs2,
self.button_y + self.button_scale_y - 93 + prize_y_offs,
),
)
bui.imagewidget(
edit=self.prize_chest_1_image,
position=(
self.button_x + 380 + x_offs2c,
self.button_y
+ self.button_scale_y
- 93
+ prize_y_offs
- 0.5 * self._chestsz,
),
)
classic.set_tournament_prize_image(entry, 0, self.prize_chest_1_image)
bui.textwidget(
edit=self.prize_range_2_text,
text=pr2,
position=(
self.button_x + 365 + x_offs,
self.button_x + 365 + x_offs2,
self.button_y + self.button_scale_y - 93 - 45 + prize_y_offs,
),
)
bui.textwidget(
edit=self.prize_value_2_text,
text=pv2,
scale=(
prize_value_scale_large
if ticket_char not in pv2
else prize_value_scale_small
),
position=(
self.button_x + 380 + x_offs,
self.button_x + 380 + x_offs2,
self.button_y + self.button_scale_y - 93 - 45 + prize_y_offs,
),
)
bui.imagewidget(
edit=self.prize_chest_2_image,
position=(
self.button_x + 380 + x_offs2c,
self.button_y
+ self.button_scale_y
- 93
- 45
+ prize_y_offs
- 0.5 * self._chestsz,
),
)
classic.set_tournament_prize_image(entry, 1, self.prize_chest_2_image)
bui.textwidget(
edit=self.prize_range_3_text,
text=pr3,
position=(
self.button_x + 365 + x_offs,
self.button_x + 365 + x_offs2,
self.button_y + self.button_scale_y - 93 - 90 + prize_y_offs,
),
)
bui.textwidget(
edit=self.prize_value_3_text,
text=pv3,
scale=(
prize_value_scale_large
if ticket_char not in pv3
else prize_value_scale_small
),
position=(
self.button_x + 380 + x_offs,
self.button_x + 380 + x_offs2,
self.button_y + self.button_scale_y - 93 - 90 + prize_y_offs,
),
)
bui.imagewidget(
edit=self.prize_chest_3_image,
position=(
self.button_x + 380 + x_offs2c,
self.button_y
+ self.button_scale_y
- 93
- 90
+ prize_y_offs
- 0.5 * self._chestsz,
),
)
classic.set_tournament_prize_image(entry, 2, self.prize_chest_3_image)
leader_name = '-'
leader_score: str | bui.Lstr = '-'
@ -599,6 +650,7 @@ class TournamentButton:
)
fee = entry['fee']
assert isinstance(fee, int | None)
if fee is None:
fee_var = None
@ -610,18 +662,23 @@ class TournamentButton:
fee_var = 'price.tournament_entry_2'
elif fee == 1:
fee_var = 'price.tournament_entry_1'
elif fee == -1:
fee_var = None
else:
if fee != 0:
print('Unknown fee value:', fee)
fee_var = 'price.tournament_entry_0'
self.allow_ads = allow_ads = entry['allowAds']
self.allow_ads = allow_ads = (
entry['allowAds'] if USE_ENTRY_FEES else False
)
final_fee: int | None = (
final_fee = (
None
if fee_var is None
else plus.get_v1_account_misc_read_val(fee_var, '?')
)
assert isinstance(final_fee, int | None)
final_fee_str: str | bui.Lstr
if fee_var is None:
@ -638,12 +695,15 @@ class TournamentButton:
ad_tries_remaining = bui.app.classic.accounts.tournament_info[
self.tournament_id
]['adTriesRemaining']
assert isinstance(ad_tries_remaining, int | None)
free_tries_remaining = bui.app.classic.accounts.tournament_info[
self.tournament_id
]['freeTriesRemaining']
assert isinstance(free_tries_remaining, int | None)
# Now, if this fee allows ads and we support video ads, show
# the 'or ad' version.
if USE_ENTRY_FEES:
if allow_ads and plus.has_video_ads():
ads_enabled = plus.have_incentivized_ad()
bui.imagewidget(
@ -651,7 +711,9 @@ class TournamentButton:
opacity=1.0 if ads_enabled else 0.25,
)
or_text = (
bui.Lstr(resource='orText', subs=[('${A}', ''), ('${B}', '')])
bui.Lstr(
resource='orText', subs=[('${A}', ''), ('${B}', '')]
)
.evaluate()
.strip()
)

View File

@ -62,7 +62,7 @@ class PrivateGatherTab(GatherTab):
self._state: State = State()
self._last_datacode_refresh_time: float | None = None
self._hostingstate = PrivateHostingState()
self._v2state: bacommon.cloud.BSPrivatePartyResponse | None = None
self._v2state: bacommon.bs.PrivatePartyResponse | None = None
self._join_sub_tab_text: bui.Widget | None = None
self._host_sub_tab_text: bui.Widget | None = None
self._update_timer: bui.AppTimer | None = None
@ -339,7 +339,7 @@ class PrivateGatherTab(GatherTab):
if plus.accounts.primary is not None:
with plus.accounts.primary:
plus.cloud.send_message_cb(
bacommon.cloud.BSPrivatePartyMessage(
bacommon.bs.PrivatePartyMessage(
need_datacode=(
self._last_datacode_refresh_time is None
or time.monotonic()
@ -355,7 +355,7 @@ class PrivateGatherTab(GatherTab):
self._last_v2_state_query_time = now
def _on_private_party_query_response(
self, response: bacommon.cloud.BSPrivatePartyResponse | Exception
self, response: bacommon.bs.PrivatePartyResponse | Exception
) -> None:
if isinstance(response, Exception):
self._debug_server_comm('got pp v2 state response (err)')

View File

@ -367,6 +367,7 @@ class PublicGatherTab(GatherTab):
self._last_server_list_query_time: float | None = None
self._join_list_column: bui.Widget | None = None
self._join_status_text: bui.Widget | None = None
self._join_status_spinner: bui.Widget | None = None
self._no_servers_found_text: bui.Widget | None = None
self._host_max_party_size_value: bui.Widget | None = None
self._host_max_party_size_minus_button: bui.Widget | None = None
@ -665,6 +666,9 @@ class PublicGatherTab(GatherTab):
size=(400, 400),
claims_left_right=True,
)
# Create join status text and join spinner. Always make sure to
# update both of these together.
self._join_status_text = bui.textwidget(
parent=self._container,
text='',
@ -678,6 +682,10 @@ class PublicGatherTab(GatherTab):
color=(0.6, 0.6, 0.6),
position=(c_width * 0.5, c_height * 0.5),
)
self._join_status_spinner = bui.spinnerwidget(
parent=self._container, position=(c_width * 0.5, c_height * 0.5)
)
self._no_servers_found_text = bui.textwidget(
parent=self._container,
text='',
@ -944,37 +952,51 @@ class PublicGatherTab(GatherTab):
name = cast(str, bui.textwidget(query=self._host_name_text))
bs.set_public_party_name(name)
# Update status text.
status_text = self._join_status_text
if status_text:
# Update status text and loading spinner.
if self._join_status_text:
assert self._join_status_spinner
if not signed_in:
bui.textwidget(
edit=status_text, text=bui.Lstr(resource='notSignedInText')
edit=self._join_status_text,
text=bui.Lstr(resource='notSignedInText'),
)
bui.spinnerwidget(edit=self._join_status_spinner, visible=False)
else:
# If we have a valid list, show no status; just the list.
# Otherwise show either 'loading...' or 'error' depending
# on whether this is our first go-round.
if self._have_valid_server_list:
bui.textwidget(edit=status_text, text='')
bui.textwidget(edit=self._join_status_text, text='')
bui.spinnerwidget(
edit=self._join_status_spinner, visible=False
)
else:
if self._have_server_list_response:
bui.textwidget(
edit=status_text,
edit=self._join_status_text,
text=bui.Lstr(resource='errorText'),
)
else:
bui.textwidget(
edit=status_text,
text=bui.Lstr(
value='${A}...',
subs=[
(
'${A}',
bui.Lstr(resource='store.loadingText'),
bui.spinnerwidget(
edit=self._join_status_spinner, visible=False
)
],
),
else:
# Show our loading spinner.
bui.textwidget(edit=self._join_status_text, text='')
# bui.textwidget(
# edit=self._join_status_text,
# text=bui.Lstr(
# value='${A}...',
# subs=[
# (
# '${A}',
#
# bui.Lstr(resource='store.loadingText'),
# )
# ],
# ),
# )
bui.spinnerwidget(
edit=self._join_status_spinner, visible=True
)
self._update_party_rows()
@ -1005,12 +1027,7 @@ class PublicGatherTab(GatherTab):
self._ui_rows = self._ui_rows[:-clipcount]
# If we have no parties to show, we're done.
if not self._parties_displayed:
text = self._join_status_text
if (
plus.get_v1_account_state() == 'signed_in'
and cast(str, bui.textwidget(query=text)) == ''
):
if self._have_valid_server_list and not self._parties_displayed:
bui.textwidget(
edit=self._no_servers_found_text,
text=bui.Lstr(resource='noServersFoundText'),

View File

@ -863,7 +863,7 @@ def show_get_tokens_prompt() -> None:
if bool(True):
ConfirmWindow(
bui.Lstr(resource='tokens.notEnoughTokensText'),
GetTokensWindow,
_show_get_tokens,
ok_text=bui.Lstr(resource='tokens.getTokensText'),
width=460,
height=130,
@ -875,3 +875,30 @@ def show_get_tokens_prompt() -> None:
width=460,
height=130,
)
def _show_get_tokens() -> None:
# NOTE TO USERS: The code below is not the proper way to do things;
# whenever possible one should use a MainWindow's
# main_window_replace() or main_window_back() methods. We just need
# to do things a bit more manually in this case.
prev_main_window = bui.app.ui_v1.get_main_window()
# Special-case: If it seems we're already in the account window, do
# nothing.
if isinstance(prev_main_window, GetTokensWindow):
return
# Set our new main window.
bui.app.ui_v1.set_main_window(
GetTokensWindow(),
from_window=False,
is_auxiliary=True,
suppress_warning=True,
)
# Transition out any previous main window.
if prev_main_window is not None:
prev_main_window.main_window_close()

View File

@ -6,30 +6,100 @@ from __future__ import annotations
import weakref
from dataclasses import dataclass
from typing import override
from typing import override, assert_never
from efro.error import CommunicationError
import bacommon.cloud
import bacommon.bs
import bauiv1 as bui
# Messages with format versions higher than this will show up as
# 'app needs to be updated to view this'
SUPPORTED_INBOX_MESSAGE_FORMAT_VERSION = 1
class _Section:
def get_height(self) -> float:
"""Return section height."""
raise NotImplementedError()
def draw(self, subcontainer: bui.Widget, y: float) -> None:
"""Draw the section."""
class _TextSection(_Section):
def __init__(
self,
sub_width: float,
text: str,
*,
subs: list[str],
spacing_top: float = 0.0,
spacing_bottom: float = 0.0,
scale: float = 0.6,
color: tuple[float, float, float, float] = (1.0, 1.0, 1.0, 1.0),
) -> None:
self.sub_width = sub_width
self.spacing_top = spacing_top
self.spacing_bottom = spacing_bottom
self.color = color
self.textfin = bui.Lstr(translate=('serverResponses', text)).evaluate()
assert len(subs) % 2 == 0 # Should always be even.
for j in range(0, len(subs) - 1, 2):
self.textfin = self.textfin.replace(subs[j], subs[j + 1])
# Calc scale to fit width and then see what height we need at
# that scale.
t_width = max(
10.0,
bui.get_string_width(self.textfin, suppress_warning=True) * scale,
)
self.text_scale = scale * min(1.0, (sub_width * 0.9) / t_width)
self.text_height = (
0.0
if not self.textfin
else bui.get_string_height(self.textfin, suppress_warning=True)
) * self.text_scale
self.full_height = self.text_height + spacing_top + spacing_bottom
@override
def get_height(self) -> float:
return self.full_height
@override
def draw(self, subcontainer: bui.Widget, y: float) -> None:
bui.textwidget(
parent=subcontainer,
position=(
self.sub_width * 0.5,
y - self.spacing_top - self.text_height * 0.5,
# y - self.height * 0.5 - 23.0,
),
color=self.color,
scale=self.text_scale,
flatness=1.0,
shadow=0.0,
text=self.textfin,
size=(0, 0),
h_align='center',
v_align='center',
)
@dataclass
class _MessageEntry:
type: bacommon.cloud.BSInboxEntryType
class _EntryDisplay:
interaction_style: bacommon.bs.BasicClientUI.InteractionStyle
button_label_positive: bacommon.bs.BasicClientUI.ButtonLabel
button_label_negative: bacommon.bs.BasicClientUI.ButtonLabel
sections: list[_Section]
id: str
height: float
text_height: float
scale: float
text: str
total_height: float
color: tuple[float, float, float]
backing: bui.Widget | None = None
button_positive: bui.Widget | None = None
button_spinner_positive: bui.Widget | None = None
button_negative: bui.Widget | None = None
message_text: bui.Widget | None = None
button_spinner_negative: bui.Widget | None = None
# message_text: bui.Widget | None = None
processing_complete: bool = False
@ -45,15 +115,15 @@ class InboxWindow(bui.MainWindow):
assert bui.app.classic is not None
uiscale = bui.app.ui_v1.uiscale
self._message_entries: list[_MessageEntry] = []
self._entry_displays: list[_EntryDisplay] = []
self._width = 600 if uiscale is bui.UIScale.SMALL else 450
self._width = 800 if uiscale is bui.UIScale.SMALL else 500
self._height = (
375
455
if uiscale is bui.UIScale.SMALL
else 370 if uiscale is bui.UIScale.MEDIUM else 450
)
yoffs = -47 if uiscale is bui.UIScale.SMALL else 0
yoffs = -42 if uiscale is bui.UIScale.SMALL else 0
super().__init__(
root_widget=bui.containerwidget(
@ -62,9 +132,9 @@ class InboxWindow(bui.MainWindow):
'menu_full' if uiscale is bui.UIScale.SMALL else 'menu_full'
),
scale=(
2.3
1.7
if uiscale is bui.UIScale.SMALL
else 1.65 if uiscale is bui.UIScale.MEDIUM else 1.23
else 1.5 if uiscale is bui.UIScale.MEDIUM else 1.15
),
stack_offset=(
(0, 0)
@ -101,7 +171,7 @@ class InboxWindow(bui.MainWindow):
position=(
self._width * 0.5,
self._height
- (27 if uiscale is bui.UIScale.SMALL else 20)
- (24 if uiscale is bui.UIScale.SMALL else 20)
+ yoffs,
),
size=(0, 0),
@ -122,11 +192,15 @@ class InboxWindow(bui.MainWindow):
flatness=1.0,
color=(0.4, 0.4, 0.5),
shadow=0.0,
text=bui.Lstr(resource='loadingText'),
text='',
size=(0, 0),
h_align='center',
v_align='center',
)
self._loading_spinner = bui.spinnerwidget(
parent=self._root_widget,
position=(self._width * 0.5, self._height * 0.5),
)
self._scrollwidget = bui.scrollwidget(
parent=self._root_widget,
size=(
@ -141,6 +215,7 @@ class InboxWindow(bui.MainWindow):
simple_culling_v=200,
claims_left_right=True,
claims_up_down=True,
center_small_content_horizontally=True,
)
bui.widget(edit=self._scrollwidget, autoselect=True)
if uiscale is bui.UIScale.SMALL:
@ -163,7 +238,7 @@ class InboxWindow(bui.MainWindow):
with plus.accounts.primary:
plus.cloud.send_message_cb(
bacommon.cloud.BSInboxRequestMessage(),
bacommon.bs.InboxRequestMessage(),
on_response=bui.WeakCall(self._on_inbox_request_response),
)
@ -179,26 +254,33 @@ class InboxWindow(bui.MainWindow):
def _error(self, errmsg: bui.Lstr | str) -> None:
"""Put ourself in a permanent error state."""
bui.spinnerwidget(edit=self._loading_spinner, visible=False)
bui.textwidget(
edit=self._infotext,
color=(1, 0, 0),
text=errmsg,
)
def _on_message_entry_press(
def _on_entry_display_press(
self,
entry_weak: weakref.ReferenceType[_MessageEntry],
process_type: bacommon.cloud.BSInboxEntryProcessType,
display_weak: weakref.ReferenceType[_EntryDisplay],
action: bacommon.bs.ClientUIAction,
) -> None:
entry = entry_weak()
if entry is None:
display = display_weak()
if display is None:
return
self._neuter_message_entry(entry)
bui.getsound('click01').play()
# We don't do anything for invalid messages.
if entry.type is bacommon.cloud.BSInboxEntryType.UNKNOWN:
entry.processing_complete = True
self._neuter_entry_display(display)
# We currently only recognize basic entries and their possible
# interaction types.
if (
display.interaction_style
is bacommon.bs.BasicClientUI.InteractionStyle.UNKNOWN
):
display.processing_complete = True
self._close_soon_if_all_processed()
return
@ -211,38 +293,43 @@ class InboxWindow(bui.MainWindow):
bui.getsound('error').play()
return
# Message the master-server to process the entry.
# Ask the master-server to run our action.
with plus.accounts.primary:
plus.cloud.send_message_cb(
bacommon.cloud.BSInboxEntryProcessMessage(
entry.id, process_type
),
bacommon.bs.ClientUIActionMessage(display.id, action),
on_response=bui.WeakCall(
self._on_inbox_entry_process_response,
entry_weak,
process_type,
self._on_client_ui_action_response,
display_weak,
action,
),
)
# Tweak the button to show this is in progress.
# Tweak the UI to show that things are in motion.
button = (
entry.button_positive
if process_type is bacommon.cloud.BSInboxEntryProcessType.POSITIVE
else entry.button_negative
display.button_positive
if action is bacommon.bs.ClientUIAction.BUTTON_PRESS_POSITIVE
else display.button_negative
)
button_spinner = (
display.button_spinner_positive
if action is bacommon.bs.ClientUIAction.BUTTON_PRESS_POSITIVE
else display.button_spinner_negative
)
if button is not None:
bui.buttonwidget(edit=button, label='...')
bui.buttonwidget(edit=button, label='')
if button_spinner is not None:
bui.spinnerwidget(edit=button_spinner, visible=True)
def _close_soon_if_all_processed(self) -> None:
bui.apptimer(0.25, bui.WeakCall(self._close_if_all_processed))
def _close_if_all_processed(self) -> None:
if not all(m.processing_complete for m in self._message_entries):
if not all(m.processing_complete for m in self._entry_displays):
return
self.main_window_back()
def _neuter_message_entry(self, entry: _MessageEntry) -> None:
def _neuter_entry_display(self, entry: _EntryDisplay) -> None:
errsound = bui.getsound('error')
if entry.button_positive is not None:
bui.buttonwidget(
@ -260,22 +347,20 @@ class InboxWindow(bui.MainWindow):
)
if entry.backing is not None:
bui.imagewidget(edit=entry.backing, color=(0.4, 0.4, 0.4))
if entry.message_text is not None:
bui.textwidget(edit=entry.message_text, color=(0.5, 0.5, 0.5, 0.5))
def _on_inbox_entry_process_response(
def _on_client_ui_action_response(
self,
entry_weak: weakref.ReferenceType[_MessageEntry],
process_type: bacommon.cloud.BSInboxEntryProcessType,
response: bacommon.cloud.BSInboxEntryProcessResponse | Exception,
display_weak: weakref.ReferenceType[_EntryDisplay],
action: bacommon.bs.ClientUIAction,
response: bacommon.bs.ClientUIActionResponse | Exception,
) -> None:
# pylint: disable=too-many-branches
entry = entry_weak()
if entry is None:
display = display_weak()
if display is None:
return
assert not entry.processing_complete
entry.processing_complete = True
assert not display.processing_complete
display.processing_complete = True
self._close_soon_if_all_processed()
# No-op if our UI is dead or on its way out.
@ -284,10 +369,18 @@ class InboxWindow(bui.MainWindow):
# Tweak the button to show results.
button = (
entry.button_positive
if process_type is bacommon.cloud.BSInboxEntryProcessType.POSITIVE
else entry.button_negative
display.button_positive
if action is bacommon.bs.ClientUIAction.BUTTON_PRESS_POSITIVE
else display.button_negative
)
button_spinner = (
display.button_spinner_positive
if action is bacommon.bs.ClientUIAction.BUTTON_PRESS_POSITIVE
else display.button_spinner_negative
)
# Always hide spinner at this point.
if button_spinner is not None:
bui.spinnerwidget(edit=button_spinner, visible=False)
# See if we should show an error message.
if isinstance(response, Exception):
@ -297,9 +390,11 @@ class InboxWindow(bui.MainWindow):
)
else:
error_message = bui.Lstr(resource='errorText')
elif response.error is not None:
elif response.error_type is not None:
# If error_type is set, error should be also.
assert response.error_message is not None
error_message = bui.Lstr(
translate=('serverResponses', response.error)
translate=('serverResponses', response.error_message)
)
else:
error_message = None
@ -314,31 +409,25 @@ class InboxWindow(bui.MainWindow):
)
return
# Success!
assert not isinstance(response, Exception)
# Run any bundled effects.
assert bui.app.classic is not None
bui.app.classic.run_bs_client_effects(response.effects)
# Whee; no error. Mark as done.
if button is not None:
# If we have full unicode, just show a checkmark in all cases.
label: str | bui.Lstr
if bui.supports_unicode_display():
label = ''
else:
# For positive claim buttons, say 'success'.
# Otherwise default to 'done.'
if (
entry.type
in {
bacommon.cloud.BSInboxEntryType.CLAIM,
bacommon.cloud.BSInboxEntryType.CLAIM_DISCARD,
}
and process_type
is bacommon.cloud.BSInboxEntryProcessType.POSITIVE
):
label = bui.Lstr(resource='successText')
else:
label = bui.Lstr(resource='doneText')
bui.buttonwidget(edit=button, label=label)
def _on_inbox_request_response(
self, response: bacommon.cloud.BSInboxRequestResponse | Exception
self, response: bacommon.bs.InboxRequestResponse | Exception
) -> None:
# pylint: disable=too-many-locals
# pylint: disable=too-many-statements
@ -364,11 +453,12 @@ class InboxWindow(bui.MainWindow):
self._error(errmsg)
return
assert isinstance(response, bacommon.cloud.BSInboxRequestResponse)
assert isinstance(response, bacommon.bs.InboxRequestResponse)
# If we got no messages, don't touch anything. This keeps
# keyboard control working in the empty case.
if not response.entries:
if not response.wrappers:
bui.spinnerwidget(edit=self._loading_spinner, visible=False)
bui.textwidget(
edit=self._infotext,
color=(0.4, 0.4, 0.5),
@ -376,63 +466,96 @@ class InboxWindow(bui.MainWindow):
)
return
bui.spinnerwidget(edit=self._loading_spinner, visible=False)
bui.textwidget(edit=self._infotext, text='')
sub_width = self._width - 90
# Even though our window size varies with uiscale, we want
# notifications to target a fixed width.
sub_width = 400.0
sub_height = 0.0
# Run the math on row heights/etc.
for i, entry in enumerate(response.entries):
# Construct entries for everything we'll display.
for i, wrapper in enumerate(response.wrappers):
# We need to flatten text here so we can measure it.
textfin: str
# textfin: str
color: tuple[float, float, float]
# Messages with either newer formatting or unrecognized
# types show up as 'upgrade your app to see this'.
if (
entry.format_version > SUPPORTED_INBOX_MESSAGE_FORMAT_VERSION
or entry.type is bacommon.cloud.BSInboxEntryType.UNKNOWN
):
textfin = bui.Lstr(
translate=(
'serverResponses',
'You must update the app to view this.',
)
).evaluate()
color = (0.6, 0.6, 0.6)
else:
# Translate raw response and apply any replacements.
textfin = bui.Lstr(
translate=('serverResponses', entry.message)
).evaluate()
assert len(entry.subs) % 2 == 0 # Should always be even.
for j in range(0, len(entry.subs) - 1, 2):
textfin = textfin.replace(entry.subs[j], entry.subs[j + 1])
color = (0.55, 0.5, 0.7)
interaction_style: bacommon.bs.BasicClientUI.InteractionStyle
button_label_positive: bacommon.bs.BasicClientUI.ButtonLabel
button_label_negative: bacommon.bs.BasicClientUI.ButtonLabel
# Calc scale to fit width and then see what height we need
# at that scale.
t_width = max(
10.0, bui.get_string_width(textfin, suppress_warning=True)
sections: list[_Section] = []
total_height = 90.0
# Display only entries where we recognize all style/label
# values and ui component types.
if (
isinstance(wrapper.ui, bacommon.bs.BasicClientUI)
and not wrapper.ui.contains_unknown_elements()
):
color = (0.55, 0.5, 0.7)
interaction_style = wrapper.ui.interaction_style
button_label_positive = wrapper.ui.button_label_positive
button_label_negative = wrapper.ui.button_label_negative
idcls = bacommon.bs.BasicClientUIComponentTypeID
for component in wrapper.ui.components:
ctypeid = component.get_type_id()
if ctypeid is idcls.TEXT:
assert isinstance(
component, bacommon.bs.BasicClientUIComponentText
)
scale = min(0.6, (sub_width * 0.9) / t_width)
t_height = (
max(10.0, bui.get_string_height(textfin, suppress_warning=True))
* scale
section = _TextSection(
sub_width=sub_width,
text=component.text,
subs=component.subs,
color=component.color,
scale=component.scale,
spacing_top=component.spacing_top,
spacing_bottom=component.spacing_bottom,
)
entry_height = 90.0 + t_height
self._message_entries.append(
_MessageEntry(
type=entry.type,
id=entry.id,
height=entry_height,
text_height=t_height,
scale=scale,
text=textfin,
total_height += section.get_height()
sections.append(section)
elif ctypeid is idcls.UNKNOWN:
raise RuntimeError('Should not get here.')
else:
# Make sure we handle all types.
assert_never(ctypeid)
else:
# Display anything with unknown components as an
# 'upgrade your app to see this' message.
color = (0.6, 0.6, 0.6)
interaction_style = (
bacommon.bs.BasicClientUI.InteractionStyle.UNKNOWN
)
button_label_positive = bacommon.bs.BasicClientUI.ButtonLabel.OK
button_label_negative = (
bacommon.bs.BasicClientUI.ButtonLabel.CANCEL
)
section = _TextSection(
sub_width=sub_width,
text='You must update the app to view this.',
subs=[],
)
total_height += section.get_height()
sections.append(section)
self._entry_displays.append(
_EntryDisplay(
interaction_style=interaction_style,
button_label_positive=button_label_positive,
button_label_negative=button_label_negative,
id=wrapper.id,
sections=sections,
total_height=total_height,
color=color,
)
)
sub_height += entry_height
sub_height += total_height
subcontainer = bui.containerwidget(
id='inboxsub',
@ -446,98 +569,116 @@ class InboxWindow(bui.MainWindow):
backing_tex = bui.gettexture('buttonSquareWide')
assert bui.app.classic is not None
buttonrows: list[list[bui.Widget]] = []
y = sub_height
for i, _entry in enumerate(response.entries):
message_entry = self._message_entries[i]
message_entry_weak = weakref.ref(message_entry)
for i, _wrapper in enumerate(response.wrappers):
entry_display = self._entry_displays[i]
entry_display_weak = weakref.ref(entry_display)
bwidth = 140
bheight = 40
ysection = y - 23.0
# Backing.
message_entry.backing = img = bui.imagewidget(
entry_display.backing = img = bui.imagewidget(
parent=subcontainer,
position=(-0.022 * sub_width, y - message_entry.height * 1.09),
position=(
-0.022 * sub_width,
y - entry_display.total_height * 1.09,
),
texture=backing_tex,
size=(sub_width * 1.07, message_entry.height * 1.15),
color=message_entry.color,
size=(sub_width * 1.07, entry_display.total_height * 1.15),
color=entry_display.color,
opacity=0.9,
)
bui.widget(edit=img, depth_range=(0, 0.1))
# Section contents.
for sec in entry_display.sections:
sec.draw(subcontainer, ysection)
ysection -= sec.get_height()
buttonrow: list[bui.Widget] = []
have_negative_button = (
message_entry.type
is bacommon.cloud.BSInboxEntryType.CLAIM_DISCARD
entry_display.interaction_style
is (
bacommon.bs.BasicClientUI
).InteractionStyle.BUTTON_POSITIVE_NEGATIVE
)
message_entry.button_positive = btn = bui.buttonwidget(
parent=subcontainer,
position=(
bpos = (
(
(sub_width - bwidth - 25)
if have_negative_button
else ((sub_width - bwidth) * 0.5)
),
y - message_entry.height + 15.0,
),
size=(bwidth, bheight),
label=bui.Lstr(
resource=(
'claimText'
if message_entry.type
in {
bacommon.cloud.BSInboxEntryType.CLAIM,
bacommon.cloud.BSInboxEntryType.CLAIM_DISCARD,
}
else 'okText'
y - entry_display.total_height + 15.0,
)
entry_display.button_positive = btn = bui.buttonwidget(
parent=subcontainer,
position=bpos,
size=(bwidth, bheight),
label=bui.app.classic.basic_client_ui_button_label_str(
entry_display.button_label_positive
),
color=message_entry.color,
color=entry_display.color,
textcolor=(0, 1, 0),
on_activate_call=bui.WeakCall(
self._on_message_entry_press,
message_entry_weak,
bacommon.cloud.BSInboxEntryProcessType.POSITIVE,
self._on_entry_display_press,
entry_display_weak,
bacommon.bs.ClientUIAction.BUTTON_PRESS_POSITIVE,
),
enable_sound=False,
)
bui.widget(edit=btn, depth_range=(0.1, 1.0))
buttonrow.append(btn)
spinner = entry_display.button_spinner_positive = bui.spinnerwidget(
parent=subcontainer,
position=(
bpos[0] + 0.5 * bwidth,
bpos[1] + 0.5 * bheight,
),
visible=False,
)
bui.widget(edit=spinner, depth_range=(0.1, 1.0))
if have_negative_button:
message_entry.button_negative = btn2 = bui.buttonwidget(
bpos = (25, y - entry_display.total_height + 15.0)
entry_display.button_negative = btn2 = bui.buttonwidget(
parent=subcontainer,
position=(25, y - message_entry.height + 15.0),
position=bpos,
size=(bwidth, bheight),
label=bui.Lstr(resource='discardText'),
label=bui.app.classic.basic_client_ui_button_label_str(
entry_display.button_label_negative
),
color=(0.85, 0.5, 0.7),
textcolor=(1, 0.4, 0.4),
on_activate_call=bui.WeakCall(
self._on_message_entry_press,
message_entry_weak,
bacommon.cloud.BSInboxEntryProcessType.NEGATIVE,
self._on_entry_display_press,
entry_display_weak,
(bacommon.bs.ClientUIAction).BUTTON_PRESS_NEGATIVE,
),
enable_sound=False,
)
bui.widget(edit=btn2, depth_range=(0.1, 1.0))
buttonrow.append(btn2)
spinner = entry_display.button_spinner_negative = (
bui.spinnerwidget(
parent=subcontainer,
position=(
bpos[0] + 0.5 * bwidth,
bpos[1] + 0.5 * bheight,
),
visible=False,
)
)
bui.widget(edit=spinner, depth_range=(0.1, 1.0))
buttonrows.append(buttonrow)
message_entry.message_text = bui.textwidget(
parent=subcontainer,
position=(
sub_width * 0.5,
y - message_entry.text_height * 0.5 - 23.0,
),
scale=message_entry.scale,
flatness=1.0,
shadow=0.0,
text=message_entry.text,
size=(0, 0),
h_align='center',
v_align='center',
)
y -= message_entry.height
y -= entry_display.total_height
uiscale = bui.app.ui_v1.uiscale
above_widget = (

View File

@ -39,6 +39,7 @@ class LeagueRankWindow(bui.MainWindow):
self._league_text: bui.Widget | None = None
self._league_number_text: bui.Widget | None = None
self._your_power_ranking_text: bui.Widget | None = None
self._loading_spinner: bui.Widget | None = None
self._season_ends_text: bui.Widget | None = None
self._power_ranking_rank_text: bui.Widget | None = None
self._to_ranked_text: bui.Widget | None = None
@ -150,7 +151,7 @@ class LeagueRankWindow(bui.MainWindow):
self._doing_power_ranking_query = False
self._subcontainer: bui.Widget | None = None
self._subcontainerwidth = 800
self._subcontainerwidth = max(800, self._scroll_width)
self._subcontainerheight = 483
self._power_ranking_score_widgets: list[bui.Widget] = []
@ -330,13 +331,8 @@ class LeagueRankWindow(bui.MainWindow):
bui.textwidget(edit=self._league_title_text, text='')
bui.textwidget(edit=self._league_text, text='')
bui.textwidget(edit=self._league_number_text, text='')
bui.textwidget(
edit=self._your_power_ranking_text,
text=bui.Lstr(
value='${A}...',
subs=[('${A}', bui.Lstr(resource='loadingText'))],
),
)
bui.textwidget(edit=self._your_power_ranking_text, text='')
bui.spinnerwidget(edit=self._loading_spinner, visible=True)
bui.textwidget(edit=self._to_ranked_text, text='')
bui.textwidget(edit=self._power_ranking_rank_text, text='')
bui.textwidget(edit=self._season_ends_text, text='')
@ -618,6 +614,14 @@ class LeagueRankWindow(bui.MainWindow):
flatness=1.0,
)
self._loading_spinner = bui.spinnerwidget(
parent=w_parent,
position=(
self._subcontainerwidth * 0.5,
self._subcontainerheight * 0.5,
),
size=64,
)
self._your_power_ranking_text = bui.textwidget(
parent=w_parent,
position=(self._xoffs + 470, v - 142 - 70),
@ -968,6 +972,7 @@ class LeagueRankWindow(bui.MainWindow):
else ''
),
)
bui.spinnerwidget(edit=self._loading_spinner, visible=False)
bui.textwidget(
edit=self._power_ranking_rank_text,

View File

@ -431,7 +431,6 @@ class MainMenuWindow(bui.MainWindow):
)
# Credits button.
# self._tdelay += self._t_delay_inc
thistdelay = self._tdelay + td5 * self._t_delay_inc
h += side_button_width * side_button_scale * 0.5 + hspace2
@ -454,15 +453,16 @@ class MainMenuWindow(bui.MainWindow):
transition_delay=thistdelay,
on_activate_call=self._credits,
)
# self._tdelay += self._t_delay_inc
self._quit_button: bui.Widget | None
if self._have_quit_button:
v -= 1.1 * side_button_2_height * side_button_2_scale
# Nudge this a tiny bit right so we can press right from the
# credits button to get to it.
self._quit_button = quit_button = bui.buttonwidget(
parent=self._root_widget,
autoselect=self._use_autoselect,
position=(h, v),
position=(h + 4.0, v),
size=(side_button_2_width, side_button_2_height),
scale=side_button_2_scale,
label=bui.Lstr(

View File

@ -579,6 +579,10 @@ class PartyQueueWindow(bui.Window):
if plus.get_v1_account_ticket_count() < self._boost_tickets:
bui.getsound('error').play()
bui.screenmessage(
bui.Lstr(resource='notEnoughTicketsText'),
color=(1, 0, 0),
)
# gettickets.show_get_tickets_prompt()
return

View File

@ -42,9 +42,9 @@ class PlayWindow(bui.MainWindow):
self._playlist_select_context = playlist_select_context
uiscale = bui.app.ui_v1.uiscale
width = 1100 if uiscale is bui.UIScale.SMALL else 800
x_offs = 150 if uiscale is bui.UIScale.SMALL else 0
y_offs = -60 if uiscale is bui.UIScale.SMALL else 0
width = 1100 if uiscale is bui.UIScale.SMALL else 1000
x_offs = 150 if uiscale is bui.UIScale.SMALL else 90
y_offs = -60 if uiscale is bui.UIScale.SMALL else 45
height = 650 if uiscale is bui.UIScale.SMALL else 550
button_width = 400
@ -89,7 +89,7 @@ class PlayWindow(bui.MainWindow):
else:
self._back_button = bui.buttonwidget(
parent=self._root_widget,
position=(55 + x_offs, height - 132 + y_offs),
position=(5 + x_offs, height - 162 + y_offs),
size=(60, 60),
scale=1.1,
text_res_scale=1.5,
@ -103,11 +103,12 @@ class PlayWindow(bui.MainWindow):
edit=self._root_widget, cancel_button=self._back_button
)
txt = bui.textwidget(
bui.textwidget(
parent=self._root_widget,
position=(width * 0.5, height - 101 + y_offs),
# position=(width * 0.5, height -
# (101 if main_menu else 61)),
position=(
width * 0.5,
height - (83 if uiscale is bui.UIScale.SMALL else 131) + y_offs,
),
size=(0, 0),
text=bui.Lstr(
resource=(
@ -116,17 +117,13 @@ class PlayWindow(bui.MainWindow):
else 'playlistsText'
)
),
scale=1.7,
scale=1.2 if uiscale is bui.UIScale.SMALL else 1.7,
res_scale=2.0,
maxwidth=400,
color=bui.app.ui_v1.heading_color,
h_align='center',
v_align='center',
)
if uiscale is bui.UIScale.SMALL:
bui.textwidget(edit=txt, text='')
v = (
height
- (110 if self._playlist_select_context is None else 90)
@ -134,14 +131,14 @@ class PlayWindow(bui.MainWindow):
)
v -= 100
clr = (0.6, 0.7, 0.6, 1.0)
v -= 280 if self._playlist_select_context is None else 180
v += 30 if uiscale is bui.UIScale.SMALL else 0
v -= 270 if self._playlist_select_context is None else 280
v += 65 if uiscale is bui.UIScale.SMALL else 0
hoffs = (
x_offs + 80
x_offs - 45
if self._playlist_select_context is None
else x_offs - 100
)
scl = 1.13 if self._playlist_select_context is None else 0.68
scl = 0.75 if self._playlist_select_context is None else 0.68
self._lineup_tex = bui.gettexture('playerLineup')
angry_computer_transparent_mesh = bui.getmesh(
@ -167,16 +164,15 @@ class PlayWindow(bui.MainWindow):
if self._playlist_select_context is None:
self._coop_button = btn = bui.buttonwidget(
parent=self._root_widget,
position=(hoffs, v + (scl * 15)),
position=(hoffs, v),
size=(
scl * button_width,
scl * 300,
scl * 360,
),
extra_touch_border_scale=0.1,
autoselect=True,
label='',
button_type='square',
text_scale=1.13,
on_activate_call=self._coop,
)
@ -247,7 +243,7 @@ class PlayWindow(bui.MainWindow):
h_align='center',
v_align='center',
color=(0.7, 0.9, 0.7, 1.0),
scale=scl * 2.3,
scale=scl * 1.5,
)
bui.textwidget(
@ -264,34 +260,29 @@ class PlayWindow(bui.MainWindow):
color=clr,
)
scl = 0.5 if self._playlist_select_context is None else 0.68
hoffs += 440 if self._playlist_select_context is None else 216
v += 180 if self._playlist_select_context is None else -68
scl = 0.75 if self._playlist_select_context is None else 0.68
hoffs += 300 if self._playlist_select_context is None else 216
# v += 0 if self._playlist_select_context is None else -68
self._teams_button = btn = bui.buttonwidget(
parent=self._root_widget,
position=(
hoffs,
v + (scl * 15 if self._playlist_select_context is None else 0),
v,
# v + (scl * 15 if
# self._playlist_select_context is None else 0),
),
size=(
scl * button_width,
scl * (300 if self._playlist_select_context is None else 360),
scl * (360 if self._playlist_select_context is None else 360),
),
extra_touch_border_scale=0.1,
autoselect=True,
label='',
button_type='square',
text_scale=1.13,
on_activate_call=self._team_tourney,
)
bui.widget(
edit=btn,
up_widget=bui.get_special_widget('get_tokens_button'),
right_widget=bui.get_special_widget('squad_button'),
)
xxx = -14
self._draw_dude(
2,
@ -381,7 +372,7 @@ class PlayWindow(bui.MainWindow):
h_align='center',
v_align='center',
color=(0.7, 0.9, 0.7, 1.0),
scale=scl * 2.3,
scale=scl * 1.5,
)
bui.textwidget(
parent=self._root_widget,
@ -392,29 +383,30 @@ class PlayWindow(bui.MainWindow):
h_align='center',
v_align='center',
res_scale=1.5,
scale=0.9 * scl,
scale=0.83 * scl,
flatness=1.0,
maxwidth=scl * button_width * 0.7,
color=clr,
)
hoffs += 0 if self._playlist_select_context is None else 300
v -= 155 if self._playlist_select_context is None else 0
hoffs += 300 if self._playlist_select_context is None else 300
# v -= 0 if self._playlist_select_context is None else 0
self._free_for_all_button = btn = bui.buttonwidget(
parent=self._root_widget,
position=(
hoffs,
v + (scl * 15 if self._playlist_select_context is None else 0),
v,
# v + (scl * 15
# if self._playlist_select_context is None else 0),
),
size=(
scl * button_width,
scl * (300 if self._playlist_select_context is None else 360),
scl * (360 if self._playlist_select_context is None else 360),
),
extra_touch_border_scale=0.1,
autoselect=True,
label='',
button_type='square',
text_scale=1.13,
on_activate_call=self._free_for_all,
)
@ -505,7 +497,7 @@ class PlayWindow(bui.MainWindow):
h_align='center',
v_align='center',
color=(0.7, 0.9, 0.7, 1.0),
scale=scl * 1.9,
scale=scl * 1.5,
)
bui.textwidget(
parent=self._root_widget,
@ -515,7 +507,7 @@ class PlayWindow(bui.MainWindow):
text=bui.Lstr(resource=f'{self._r}.twoToEightPlayersText'),
h_align='center',
v_align='center',
scale=0.9 * scl,
scale=0.83 * scl,
flatness=1.0,
maxwidth=scl * button_width * 0.7,
color=clr,

View File

@ -17,6 +17,8 @@ if TYPE_CHECKING:
from bauiv1lib.play import PlaylistSelectContext
REQUIRE_PRO = False
class PlayOptionsWindow(PopupWindow):
"""A popup window for configuring play options."""
@ -316,7 +318,7 @@ class PlayOptionsWindow(PopupWindow):
label=bui.Lstr(resource='teamNamesColorText'),
)
assert bui.app.classic is not None
if not bui.app.classic.accounts.have_pro():
if REQUIRE_PRO and not bui.app.classic.accounts.have_pro():
bui.imagewidget(
parent=self.root_widget,
size=(30, 30),
@ -440,7 +442,7 @@ class PlayOptionsWindow(PopupWindow):
assert plus is not None
assert bui.app.classic is not None
if not bui.app.classic.accounts.have_pro():
if REQUIRE_PRO and not bui.app.classic.accounts.have_pro():
if plus.get_v1_account_state() != 'signed_in':
show_sign_in_prompt()
else:

View File

@ -205,7 +205,10 @@ class ProfileUpgradeWindow(bui.Window):
tickets = plus.get_v1_account_ticket_count()
if tickets < self._cost:
bui.getsound('error').play()
print('FIXME - show not-enough-tickets msg.')
bui.screenmessage(
bui.Lstr(resource='notEnoughTicketsText'),
color=(1, 0, 0),
)
# gettickets.show_get_tickets_prompt()
return
bui.screenmessage(

View File

@ -162,7 +162,6 @@ class PurchaseWindow(bui.Window):
bui.containerwidget(edit=self._root_widget, transition='out_left')
def _purchase(self) -> None:
# from bauiv1lib import gettickets
plus = bui.app.plus
assert plus is not None
@ -176,9 +175,12 @@ class PurchaseWindow(bui.Window):
except Exception:
ticket_count = None
if ticket_count is not None and ticket_count < self._price:
# gettickets.show_get_tickets_prompt()
print('FIXME - show not-enough-tickets msg')
bui.getsound('error').play()
bui.screenmessage(
bui.Lstr(resource='notEnoughTicketsText'),
color=(1, 0, 0),
)
# gettickets.show_get_tickets_prompt()
return
def do_it() -> None:

View File

@ -53,24 +53,25 @@ class ResourceTypeInfoWindow(PopupWindow):
iconscale=1.2,
)
yoffs = self._height - 150
yoffs = self._height - 145
if resource_type == 'tickets':
rdesc = (
'Tickets can be used to unlock characters,\n'
'maps, minigames, and more in the store.\n'
'\n'
'Earn tickets by completing achievements\n'
'or by opening chests won in the game.'
'Tickets can be found in chests won through\n'
'campaigns, tournaments, and achievements.'
)
texname = 'tickets'
elif resource_type == 'tokens':
rdesc = (
'Tokens have various uses in the game such as\n'
'speeding up chest unlocks.\n'
'Tokens are used to speed up chest unlocks\n'
'and for other game and account features.\n'
'\n'
'You can buy packs of tokens or you can buy a\n'
'Gold Pass to get unlimited tokens.\n'
'You can win tokens in the game or buy them\n'
'in packs. Or buy a Gold Pass to get infinite\n'
'tokens forever and never hear of them again.'
)
texname = 'coin'
elif resource_type == 'trophies':

View File

@ -10,7 +10,7 @@ import logging
import bauiv1 as bui
if TYPE_CHECKING:
pass
from typing import Callable
class AllSettingsWindow(bui.MainWindow):
@ -21,7 +21,6 @@ class AllSettingsWindow(bui.MainWindow):
transition: str | None = 'in_right',
origin_widget: bui.Widget | None = None,
):
# pylint: disable=too-many-statements
# pylint: disable=too-many-locals
# Preload some modules we use in a background thread so we won't
@ -31,12 +30,12 @@ class AllSettingsWindow(bui.MainWindow):
bui.set_analytics_screen('Settings Window')
assert bui.app.classic is not None
uiscale = bui.app.ui_v1.uiscale
width = 1000 if uiscale is bui.UIScale.SMALL else 580
width = 1000 if uiscale is bui.UIScale.SMALL else 900
x_inset = 125 if uiscale is bui.UIScale.SMALL else 0
height = 500 if uiscale is bui.UIScale.SMALL else 435
height = 500 if uiscale is bui.UIScale.SMALL else 450
self._r = 'settingsWindow'
top_extra = 20 if uiscale is bui.UIScale.SMALL else 0
yoffs = -30 if uiscale is bui.UIScale.SMALL else 0
yoffs = -30 if uiscale is bui.UIScale.SMALL else -30
uiscale = bui.app.ui_v1.uiscale
super().__init__(
@ -50,10 +49,7 @@ class AllSettingsWindow(bui.MainWindow):
scale=(
1.5
if uiscale is bui.UIScale.SMALL
else 1.25 if uiscale is bui.UIScale.MEDIUM else 1.0
),
stack_offset=(
(0, 0) if uiscale is bui.UIScale.SMALL else (0, 0)
else 1.1 if uiscale is bui.UIScale.MEDIUM else 0.8
),
),
transition=transition,
@ -69,12 +65,12 @@ class AllSettingsWindow(bui.MainWindow):
self._back_button = btn = bui.buttonwidget(
parent=self._root_widget,
autoselect=True,
position=(40 + x_inset, height - 55 + yoffs),
size=(130, 60),
position=(40 + x_inset, height - 60 + yoffs),
size=(70, 70),
scale=0.8,
text_scale=1.2,
label=bui.Lstr(resource='backText'),
button_type='back',
label=bui.charstr(bui.SpecialChar.BACK),
button_type='backSmall',
on_activate_call=self.main_window_back,
)
bui.containerwidget(edit=self._root_widget, cancel_button=btn)
@ -87,131 +83,116 @@ class AllSettingsWindow(bui.MainWindow):
color=bui.app.ui_v1.title_color,
h_align='center',
v_align='center',
scale=1.1,
maxwidth=130,
)
if self._back_button is not None:
bui.buttonwidget(
edit=self._back_button,
button_type='backSmall',
size=(60, 60),
label=bui.charstr(bui.SpecialChar.BACK),
bwidth = 200
bheight = 230
margin = 1
all_buttons_width = 4.0 * bwidth + 3.0 * margin
x = width * 0.5 - all_buttons_width * 0.5
y = height + yoffs - 320.0
def _button(
position: tuple[float, float],
label: bui.Lstr,
call: Callable[[], None],
texture: bui.Texture,
imgsize: float,
*,
color: tuple[float, float, float] = (1.0, 1.0, 1.0),
imgoffs: tuple[float, float] = (0.0, 0.0),
) -> bui.Widget:
x, y = position
btn = bui.buttonwidget(
parent=self._root_widget,
autoselect=True,
position=(x, y),
size=(bwidth, bheight),
button_type='square',
label='',
on_activate_call=call,
)
v = height - 80 + yoffs
v -= 145
basew = 280 if uiscale is bui.UIScale.SMALL else 230
baseh = 170
x_offs = (
x_inset + (105 if uiscale is bui.UIScale.SMALL else 72) - basew
) # now unused
x_offs2 = x_offs + basew - 7
x_offs3 = x_offs + 2 * (basew - 7)
x_offs4 = x_offs2
x_offs5 = x_offs3
def _b_title(
x: float, y: float, button: bui.Widget, text: str | bui.Lstr
) -> None:
bui.textwidget(
parent=self._root_widget,
text=text,
position=(x + basew * 0.47, y + baseh * 0.22),
maxwidth=basew * 0.7,
text=label,
position=(x + bwidth * 0.5, y + bheight * 0.25),
maxwidth=bwidth * 0.7,
size=(0, 0),
h_align='center',
v_align='center',
draw_controller=button,
draw_controller=btn,
color=(0.7, 0.9, 0.7, 1.0),
)
ctb = self._controllers_button = bui.buttonwidget(
bui.imagewidget(
parent=self._root_widget,
autoselect=True,
position=(x_offs2, v),
size=(basew, baseh),
button_type='square',
label='',
on_activate_call=self._do_controllers,
position=(
x + bwidth * 0.5 - imgsize * 0.5 + imgoffs[0],
y + bheight * 0.56 - imgsize * 0.5 + imgoffs[1],
),
size=(imgsize, imgsize),
texture=texture,
draw_controller=btn,
color=color,
)
return btn
self._controllers_button = _button(
position=(x, y),
label=bui.Lstr(resource=f'{self._r}.controllersText'),
call=self._do_controllers,
texture=bui.gettexture('controllerIcon'),
imgsize=150,
imgoffs=(-2.0, 2.0),
)
x += bwidth + margin
self._graphics_button = _button(
position=(x, y),
label=bui.Lstr(resource=f'{self._r}.graphicsText'),
call=self._do_graphics,
texture=bui.gettexture('graphicsIcon'),
imgsize=135,
imgoffs=(0, 4.0),
)
x += bwidth + margin
self._audio_button = _button(
position=(x, y),
label=bui.Lstr(resource=f'{self._r}.audioText'),
call=self._do_audio,
texture=bui.gettexture('audioIcon'),
imgsize=150,
color=(1, 1, 0),
)
x += bwidth + margin
self._advanced_button = _button(
position=(x, y),
label=bui.Lstr(resource=f'{self._r}.advancedText'),
call=self._do_advanced,
texture=bui.gettexture('advancedIcon'),
imgsize=150,
color=(0.8, 0.95, 1),
imgoffs=(0, 5.0),
)
# Hmm; we're now wide enough that being limited to pressing up
# might be ok.
if bool(False):
# Left from our leftmost button should go to back button.
if self._back_button is None:
bbtn = bui.get_special_widget('back_button')
bui.widget(edit=ctb, left_widget=bbtn)
_b_title(
x_offs2, v, ctb, bui.Lstr(resource=f'{self._r}.controllersText')
)
imgw = imgh = 130
bui.imagewidget(
parent=self._root_widget,
position=(x_offs2 + basew * 0.49 - imgw * 0.5, v + 35),
size=(imgw, imgh),
texture=bui.gettexture('controllerIcon'),
draw_controller=ctb,
bui.widget(edit=self._controllers_button, left_widget=bbtn)
# Right from our rightmost widget should go to squad button.
bui.widget(
edit=self._advanced_button,
right_widget=bui.get_special_widget('squad_button'),
)
gfxb = self._graphics_button = bui.buttonwidget(
parent=self._root_widget,
autoselect=True,
position=(x_offs3, v),
size=(basew, baseh),
button_type='square',
label='',
on_activate_call=self._do_graphics,
)
pbtn = bui.get_special_widget('squad_button')
bui.widget(edit=gfxb, up_widget=pbtn, right_widget=pbtn)
_b_title(x_offs3, v, gfxb, bui.Lstr(resource=f'{self._r}.graphicsText'))
imgw = imgh = 110
bui.imagewidget(
parent=self._root_widget,
position=(x_offs3 + basew * 0.49 - imgw * 0.5, v + 42),
size=(imgw, imgh),
texture=bui.gettexture('graphicsIcon'),
draw_controller=gfxb,
)
v -= baseh - 5
abtn = self._audio_button = bui.buttonwidget(
parent=self._root_widget,
autoselect=True,
position=(x_offs4, v),
size=(basew, baseh),
button_type='square',
label='',
on_activate_call=self._do_audio,
)
_b_title(x_offs4, v, abtn, bui.Lstr(resource=f'{self._r}.audioText'))
imgw = imgh = 120
bui.imagewidget(
parent=self._root_widget,
position=(x_offs4 + basew * 0.49 - imgw * 0.5 + 5, v + 35),
size=(imgw, imgh),
color=(1, 1, 0),
texture=bui.gettexture('audioIcon'),
draw_controller=abtn,
)
avb = self._advanced_button = bui.buttonwidget(
parent=self._root_widget,
autoselect=True,
position=(x_offs5, v),
size=(basew, baseh),
button_type='square',
label='',
on_activate_call=self._do_advanced,
)
_b_title(x_offs5, v, avb, bui.Lstr(resource=f'{self._r}.advancedText'))
imgw = imgh = 120
bui.imagewidget(
parent=self._root_widget,
position=(x_offs5 + basew * 0.49 - imgw * 0.5 + 5, v + 35),
size=(imgw, imgh),
color=(0.8, 0.95, 1),
texture=bui.gettexture('advancedIcon'),
draw_controller=avb,
)
self._restore_state()
@override

View File

@ -34,7 +34,10 @@ class AudioSettingsWindow(bui.MainWindow):
spacing = 50.0
width = 460.0
height = 210.0
height = 240.0
uiscale = bui.app.ui_v1.uiscale
yoffs = -5.0
# Update: hard-coding head-relative audio to true now,
# so not showing options.
@ -49,11 +52,10 @@ class AudioSettingsWindow(bui.MainWindow):
show_soundtracks = True
height += spacing * 2.0
uiscale = bui.app.ui_v1.uiscale
base_scale = (
2.05
1.9
if uiscale is bui.UIScale.SMALL
else 1.6 if uiscale is bui.UIScale.MEDIUM else 1.0
else 1.5 if uiscale is bui.UIScale.MEDIUM else 1.0
)
popup_menu_scale = base_scale * 1.2
@ -61,9 +63,6 @@ class AudioSettingsWindow(bui.MainWindow):
root_widget=bui.containerwidget(
size=(width, height),
scale=base_scale,
stack_offset=(
(0, -20) if uiscale is bui.UIScale.SMALL else (0, 0)
),
toolbar_visibility=(
None if uiscale is bui.UIScale.SMALL else 'menu_full'
),
@ -74,21 +73,20 @@ class AudioSettingsWindow(bui.MainWindow):
self._back_button = back_button = btn = bui.buttonwidget(
parent=self._root_widget,
position=(35, height - 55),
size=(120, 60),
position=(35, height + yoffs - 55),
size=(60, 60),
scale=0.8,
text_scale=1.2,
label=bui.Lstr(resource='backText'),
button_type='back',
label=bui.charstr(bui.SpecialChar.BACK),
button_type='backSmall',
on_activate_call=self.main_window_back,
autoselect=True,
)
bui.containerwidget(edit=self._root_widget, cancel_button=btn)
v = height - 60
v -= spacing * 1.0
bui.textwidget(
parent=self._root_widget,
position=(width * 0.5, height - 32),
position=(width * 0.5, height + yoffs - 32),
size=(0, 0),
text=bui.Lstr(resource=f'{self._r}.titleText'),
color=bui.app.ui_v1.title_color,
@ -97,12 +95,8 @@ class AudioSettingsWindow(bui.MainWindow):
v_align='center',
)
bui.buttonwidget(
edit=self._back_button,
button_type='backSmall',
size=(60, 60),
label=bui.charstr(bui.SpecialChar.BACK),
)
v = height + yoffs - 60
v -= spacing * 1.0
self._sound_volume_numedit = svne = ConfigNumberEdit(
parent=self._root_widget,

View File

@ -141,11 +141,11 @@ class StoreBrowserWindow(bui.MainWindow):
parent=self._root_widget,
position=(
self._width * 0.5,
self._height - (53 if uiscale is bui.UIScale.SMALL else 44),
self._height - (55 if uiscale is bui.UIScale.SMALL else 44),
),
size=(0, 0),
color=app.ui_v1.title_color,
scale=1.5,
scale=1.1 if uiscale is bui.UIScale.SMALL else 1.5,
h_align='center',
v_align='center',
text=bui.Lstr(resource='storeText'),
@ -536,7 +536,10 @@ class StoreBrowserWindow(bui.MainWindow):
our_tickets = plus.get_v1_account_ticket_count()
if price is not None and our_tickets < price:
bui.getsound('error').play()
print('FIXME - show not-enough-tickets info.')
bui.screenmessage(
bui.Lstr(resource='notEnoughTicketsText'),
color=(1, 0, 0),
)
# gettickets.show_get_tickets_prompt()
else:

View File

@ -33,6 +33,8 @@ class TournamentEntryWindow(PopupWindow):
# pylint: disable=too-many-branches
# pylint: disable=too-many-statements
from bauiv1lib.coop.tournamentbutton import USE_ENTRY_FEES
assert bui.app.classic is not None
assert bui.app.plus
bui.set_analytics_screen('Tournament Entry Window')
@ -42,9 +44,15 @@ class TournamentEntryWindow(PopupWindow):
self._tournament_id
]
self._purchase_name: str | None
self._purchase_price_name: str | None
# Set a few vars depending on the tourney fee.
self._fee = self._tournament_info['fee']
self._allow_ads = self._tournament_info['allowAds']
assert isinstance(self._fee, int | None)
self._allow_ads = (
self._tournament_info['allowAds'] if USE_ENTRY_FEES else False
)
if self._fee == 4:
self._purchase_name = 'tournament_entry_4'
self._purchase_price_name = 'price.tournament_entry_4'
@ -57,6 +65,9 @@ class TournamentEntryWindow(PopupWindow):
elif self._fee == 1:
self._purchase_name = 'tournament_entry_1'
self._purchase_price_name = 'price.tournament_entry_1'
elif self._fee is None or self._fee == -1:
self._purchase_name = None
self._purchase_price_name = 'FREE-WOOT'
else:
if self._fee != 0:
raise ValueError('invalid fee: ' + str(self._fee))
@ -218,7 +229,7 @@ class TournamentEntryWindow(PopupWindow):
h_align='center',
v_align='center',
scale=0.6,
# Note: AdMob now requires rewarded ad usage
# Note to self: AdMob requires rewarded ad usage
# specifically says 'Ad' in it.
text=bui.Lstr(resource='watchAnAdText'),
maxwidth=95,
@ -439,29 +450,52 @@ class TournamentEntryWindow(PopupWindow):
)
# Keep price up-to-date and update the button with it.
self._purchase_price = plus.get_v1_account_misc_read_val(
if self._purchase_price_name is not None:
self._purchase_price = (
0
if self._purchase_price_name == 'FREE-WOOT'
else plus.get_v1_account_misc_read_val(
self._purchase_price_name, None
)
)
# HACK - this is always free now, so just have this say 'PLAY'
bui.textwidget(
edit=self._ticket_cost_text,
text=(
bui.Lstr(resource='getTicketsWindow.freeText')
if self._purchase_price == 0
else bui.Lstr(
resource='getTicketsWindow.ticketsText',
subs=[
(
'${COUNT}',
(
str(self._purchase_price)
if self._purchase_price is not None
else '?'
),
)
],
)
bui.Lstr(resource='playText')
# if self._purchase_price == 0
# else bui.Lstr(
# resource='getTicketsWindow.ticketsText',
# subs=[
# (
# '${COUNT}',
# (
# str(self._purchase_price)
# if self._purchase_price is not None
# else '?'
# ),
# )
# ],
# )
),
# text=(
# bui.Lstr(resource='getTicketsWindow.freeText')
# if self._purchase_price == 0
# else bui.Lstr(
# resource='getTicketsWindow.ticketsText',
# subs=[
# (
# '${COUNT}',
# (
# str(self._purchase_price)
# if self._purchase_price is not None
# else '?'
# ),
# )
# ],
# )
# ),
position=(
self._ticket_cost_text_position_free
if self._purchase_price == 0
@ -472,19 +506,20 @@ class TournamentEntryWindow(PopupWindow):
bui.textwidget(
edit=self._free_plays_remaining_text,
text=(
''
if (
self._tournament_info['freeTriesRemaining'] in [None, 0]
or self._purchase_price != 0
)
else '' + str(self._tournament_info['freeTriesRemaining'])
),
# text=(
# ''
# if (
# self._tournament_info['freeTriesRemaining'] in [None, 0]
# or self._purchase_price != 0
# )
# else '' + str(self._tournament_info['freeTriesRemaining'])
# ),
text='', # No longer relevant.
)
bui.imagewidget(
edit=self._ticket_img,
opacity=0.2 if self._purchase_price == 0 else 1.0,
opacity=0.0 if self._purchase_price == 0 else 1.0,
position=(
self._ticket_img_pos_free
if self._purchase_price == 0
@ -547,15 +582,16 @@ class TournamentEntryWindow(PopupWindow):
self._launched = True
launched = False
# If they gave us an existing, non-consistent
# practice activity, just restart it.
# If they gave us an existing, non-consistent practice activity,
# just restart it.
if (
self._tournament_activity is not None
and not practice == self._tournament_activity.session.submit_score
):
try:
if not practice:
bui.apptimer(0.1, bui.getsound('cashRegister').play)
bui.apptimer(0.1, bui.getsound('drumRollShort').play)
# bui.apptimer(0.1, bui.getsound('cashRegister').play)
bui.screenmessage(
bui.Lstr(
translate=(
@ -584,7 +620,8 @@ class TournamentEntryWindow(PopupWindow):
# launch a new session.
if not launched:
if not practice:
bui.apptimer(0.1, bui.getsound('cashRegister').play)
bui.apptimer(0.1, bui.getsound('drumRollShort').play)
# bui.apptimer(0.1, bui.getsound('cashRegister').play)
bui.screenmessage(
bui.Lstr(
translate=('serverResponses', 'Entering tournament...')
@ -653,14 +690,19 @@ class TournamentEntryWindow(PopupWindow):
ticket_count = None
ticket_cost = self._purchase_price
if ticket_count is not None and ticket_count < ticket_cost:
# gettickets.show_get_tickets_prompt()
print('FIXME - show not-enough-tickets msg.')
bui.getsound('error').play()
bui.screenmessage(
bui.Lstr(resource='notEnoughTicketsText'),
color=(1, 0, 0),
)
# gettickets.show_get_tickets_prompt()
self._transition_out()
return
cur_time = bui.apptime()
self._last_ticket_press_time = cur_time
if self._purchase_name is not None:
assert isinstance(ticket_cost, int)
plus.in_game_purchase(self._purchase_name, ticket_cost)
@ -759,30 +801,20 @@ class TournamentEntryWindow(PopupWindow):
plus.run_v1_account_transactions()
self._launch()
# def _on_get_tickets_press(self) -> None:
# from bauiv1lib import gettickets
# # If we're already entering, ignore presses.
# if self._entering:
# return
# # Bring up get-tickets window and then kill ourself (we're on the
# # overlay layer so we'd show up above it).
# gettickets.GetTicketsWindow(
# modal=True, origin_widget=self._get_tickets_button
# )
# self._transition_out()
def _on_cancel(self) -> None:
plus = bui.app.plus
assert plus is not None
# Don't allow canceling for several seconds after poking an enter
# button if it looks like we're waiting on a purchase or entering
# the tournament.
if (bui.apptime() - self._last_ticket_press_time < 6.0) and (
if (
(bui.apptime() - self._last_ticket_press_time < 6.0)
and self._purchase_name is not None
and (
plus.have_outstanding_v1_account_transactions()
or plus.get_v1_account_product_purchased(self._purchase_name)
or self._entering
)
):
bui.getsound('error').play()
return

View File

@ -138,7 +138,7 @@ auto AppAdapterApple::TryRender() -> bool {
// Keep on drawing until the drawn window size
// matches what we have (or until we try for too long or fail at drawing).
seconds_t start_time = g_core->GetAppTimeSeconds();
seconds_t start_time = g_core->AppTimeSeconds();
for (int i = 0; i < 5; ++i) {
bool size_differs =
((std::abs(resize_target_resolution_.x
@ -147,7 +147,7 @@ auto AppAdapterApple::TryRender() -> bool {
|| (std::abs(resize_target_resolution_.y
- g_base->graphics_server->screen_pixel_height())
> 0.01f));
if (size_differs && g_core->GetAppTimeSeconds() - start_time < 0.1
if (size_differs && g_core->AppTimeSeconds() - start_time < 0.1
&& result) {
result = g_base->graphics_server->TryRender();
}

View File

@ -210,7 +210,7 @@ void AppAdapterSDL::RunMainThreadEventLoopToCompletion() {
assert(g_core->InMainThread());
while (!done_) {
microsecs_t cycle_start_time = g_core->GetAppTimeMicrosecs();
microsecs_t cycle_start_time = g_core->AppTimeMicrosecs();
// Events.
SDL_Event event;
@ -274,7 +274,7 @@ void AppAdapterSDL::SleepUntilNextEventCycle_(microsecs_t cycle_start_time) {
// Normally we just calc when our next draw should happen and sleep 'til
// then.
microsecs_t now = g_core->GetAppTimeMicrosecs();
microsecs_t now = g_core->AppTimeMicrosecs();
auto used_max_fps = max_fps_;
millisecs_t millisecs_per_frame = 1000000 / used_max_fps;
@ -319,7 +319,7 @@ void AppAdapterSDL::SleepUntilNextEventCycle_(microsecs_t cycle_start_time) {
// Maintain an 'oversleep' amount to compensate for the timer not being
// exact. This should keep us exactly at our target frame-rate in the
// end.
now = g_core->GetAppTimeMicrosecs();
now = g_core->AppTimeMicrosecs();
oversleep_ = now - target_time;
// Prevent oversleep from compensating by more than a few millisecs per
@ -438,7 +438,7 @@ void AppAdapterSDL::HandleSDLEvent_(const SDL_Event& event) {
break;
case SDL_QUIT:
if (g_core->GetAppTimeSeconds() - last_windowevent_close_time_ < 0.1) {
if (g_core->AppTimeSeconds() - last_windowevent_close_time_ < 0.1) {
// If they hit the window close button, skip the confirm.
g_base->QuitApp(false);
} else {
@ -459,7 +459,7 @@ void AppAdapterSDL::HandleSDLEvent_(const SDL_Event& event) {
case SDL_WINDOWEVENT_CLOSE: {
// Simply note that this happened. We use this to adjust our
// SDL_QUIT behavior (quit is called right after this).
last_windowevent_close_time_ = g_core->GetAppTimeSeconds();
last_windowevent_close_time_ = g_core->AppTimeSeconds();
break;
}

View File

@ -11,7 +11,7 @@ namespace ballistica::base {
Asset::Asset() {
assert(g_base);
assert(g_base->InLogicThread());
last_used_time_ = g_core->GetAppTimeMillisecs();
last_used_time_ = g_core->AppTimeMillisecs();
}
auto Asset::AssetTypeName(AssetType assettype) -> const char* {
@ -65,9 +65,9 @@ void Asset::Preload(bool already_locked) {
return std::string("preloading ") + AssetTypeName(GetAssetType()) + " "
+ GetName();
});
preload_start_time_ = g_core->GetAppTimeMillisecs();
preload_start_time_ = g_core->AppTimeMillisecs();
DoPreload();
preload_end_time_ = g_core->GetAppTimeMillisecs();
preload_end_time_ = g_core->AppTimeMillisecs();
preloaded_ = true;
}
}
@ -87,9 +87,9 @@ void Asset::Load(bool already_locked) {
return std::string("loading ") + AssetTypeName(GetAssetType()) + " "
+ GetName();
});
load_start_time_ = g_core->GetAppTimeMillisecs();
load_start_time_ = g_core->AppTimeMillisecs();
DoLoad();
load_end_time_ = g_core->GetAppTimeMillisecs();
load_end_time_ = g_core->AppTimeMillisecs();
BA_DEBUG_FUNCTION_TIMER_END_THREAD_EX(50, GetName());
loaded_ = true;
}

View File

@ -165,6 +165,7 @@ void Assets::StartLoading() {
LoadSystemTexture(SysTextureID::kCharacterIconMask, "characterIconMask");
LoadSystemTexture(SysTextureID::kBlack, "black");
LoadSystemTexture(SysTextureID::kWings, "wings");
LoadSystemTexture(SysTextureID::kSpinner, "spinner");
// System cube map textures:
LoadSystemCubeMapTexture(SysCubeMapTextureID::kReflectionChar,
@ -479,7 +480,7 @@ auto Assets::GetAsset(const std::string& file_name,
have_pending_loads_[static_cast<int>(d->GetAssetType())] = true;
MarkAssetForLoad(d.get());
}
d->set_last_used_time(g_core->GetAppTimeMillisecs());
d->set_last_used_time(g_core->AppTimeMillisecs());
return Object::Ref<T>(d);
}
}
@ -499,7 +500,7 @@ auto Assets::GetTexture(TextPacker* packer) -> Object::Ref<TextureAsset> {
have_pending_loads_[static_cast<int>(d->GetAssetType())] = true;
MarkAssetForLoad(d.get());
}
d->set_last_used_time(g_core->GetAppTimeMillisecs());
d->set_last_used_time(g_core->AppTimeMillisecs());
return Object::Ref<TextureAsset>(d);
}
}
@ -519,7 +520,7 @@ auto Assets::GetQRCodeTexture(const std::string& url)
have_pending_loads_[static_cast<int>(d->GetAssetType())] = true;
MarkAssetForLoad(d.get());
}
d->set_last_used_time(g_core->GetAppTimeMillisecs());
d->set_last_used_time(g_core->AppTimeMillisecs());
return Object::Ref<TextureAsset>(d);
}
}
@ -542,7 +543,7 @@ auto Assets::GetCubeMapTexture(const std::string& file_name)
have_pending_loads_[static_cast<int>(d->GetAssetType())] = true;
MarkAssetForLoad(d.get());
}
d->set_last_used_time(g_core->GetAppTimeMillisecs());
d->set_last_used_time(g_core->AppTimeMillisecs());
return Object::Ref<TextureAsset>(d);
}
}
@ -598,7 +599,7 @@ auto Assets::GetTexture(const std::string& file_name)
have_pending_loads_[static_cast<int>(d->GetAssetType())] = true;
MarkAssetForLoad(d.get());
}
d->set_last_used_time(g_core->GetAppTimeMillisecs());
d->set_last_used_time(g_core->AppTimeMillisecs());
return Object::Ref<TextureAsset>(d);
}
}
@ -750,7 +751,7 @@ auto Assets::RunPendingLoadsLogicThread() -> bool {
template <typename T>
auto Assets::RunPendingLoadList(std::vector<Object::Ref<T>*>* c_list) -> bool {
bool flush = false;
millisecs_t starttime = g_core->GetAppTimeMillisecs();
millisecs_t starttime = g_core->AppTimeMillisecs();
std::vector<Object::Ref<T>*> l;
std::vector<Object::Ref<T>*> l_unfinished;
@ -760,8 +761,7 @@ auto Assets::RunPendingLoadList(std::vector<Object::Ref<T>*>* c_list) -> bool {
// If we're already out of time.
if (!flush
&& g_core->GetAppTimeMillisecs() - starttime
> PENDING_LOAD_PROCESS_TIME) {
&& g_core->AppTimeMillisecs() - starttime > PENDING_LOAD_PROCESS_TIME) {
bool return_val = (!c_list->empty());
return return_val;
}
@ -790,8 +790,7 @@ auto Assets::RunPendingLoadList(std::vector<Object::Ref<T>*>* c_list) -> bool {
// If the load finished, pop it on our "done-loading" list.. otherwise
// keep it around.
l_finished.push_back(*i); // else l_unfinished.push_back(*i);
if (g_core->GetAppTimeMillisecs() - starttime
> PENDING_LOAD_PROCESS_TIME
if (g_core->AppTimeMillisecs() - starttime > PENDING_LOAD_PROCESS_TIME
&& !flush) {
out_of_time = true;
}
@ -832,7 +831,7 @@ auto Assets::RunPendingLoadList(std::vector<Object::Ref<T>*>* c_list) -> bool {
void Assets::Prune(int level) {
assert(g_base->InLogicThread());
millisecs_t current_time = g_core->GetAppTimeMillisecs();
millisecs_t current_time = g_core->AppTimeMillisecs();
// Need lists locked while accessing/modifying them.
AssetListLock lock;
@ -1168,11 +1167,11 @@ auto Assets::FindAssetFile(FileType type, const std::string& name)
// We wanna fail gracefully for some types.
if (type == FileType::kSound && name != "blank") {
g_core->Log(LogName::kBaAssets, LogLevel::kError,
"Unable to load audio: '" + name + "'; trying fallback...");
"Unable to load audio: '" + name + "'.");
return FindAssetFile(type, "blank");
} else if (type == FileType::kTexture && name != "white") {
g_core->Log(LogName::kBaAssets, LogLevel::kError,
"Unable to load texture: '" + name + "'; trying fallback...");
"Unable to load texture: '" + name + "'.");
return FindAssetFile(type, "white");
}
@ -1560,8 +1559,8 @@ auto DoCompileResourceString(cJSON* obj) -> std::string {
return result;
}
auto Assets::CompileResourceString(const std::string& s, const std::string& loc,
bool* valid) -> std::string {
auto Assets::CompileResourceString(const std::string& s, bool* valid)
-> std::string {
bool dummyvalid;
if (valid == nullptr) {
valid = &dummyvalid;
@ -1577,8 +1576,7 @@ auto Assets::CompileResourceString(const std::string& s, const std::string& loc,
cJSON* root = cJSON_Parse(s.c_str());
if (root == nullptr) {
g_core->Log(LogName::kBaAssets, LogLevel::kError,
"CompileResourceString failed (loc " + loc
+ "); invalid json: '" + s + "'");
"CompileResourceString failed; invalid json: '" + s + "'");
*valid = false;
return "";
}
@ -1588,8 +1586,8 @@ auto Assets::CompileResourceString(const std::string& s, const std::string& loc,
*valid = true;
} catch (const std::exception& e) {
g_core->Log(LogName::kBaAssets, LogLevel::kError,
"CompileResourceString failed (loc " + loc
+ "): " + std::string(e.what()) + "; str='" + s + "'");
"CompileResourceString failed: " + std::string(e.what())
+ "; str='" + s + "'");
result = "<error>";
*valid = false;
}

View File

@ -111,8 +111,8 @@ class Assets {
const std::unordered_map<std::string, std::string>& language);
auto GetResourceString(const std::string& key) -> std::string;
auto CharStr(SpecialChar id) -> std::string;
auto CompileResourceString(const std::string& s, const std::string& loc,
bool* valid = nullptr) -> std::string;
auto CompileResourceString(const std::string& s, bool* valid = nullptr)
-> std::string;
auto sys_assets_loaded() const { return sys_assets_loaded_; }

View File

@ -324,7 +324,7 @@ void SoundAsset::DoUnload() {
}
void SoundAsset::UpdatePlayTime() {
last_play_time_ = g_core->GetAppTimeMillisecs();
last_play_time_ = g_core->AppTimeMillisecs();
}
} // namespace ballistica::base

View File

@ -156,7 +156,7 @@ auto Audio::SourceBeginExisting(uint32_t play_id, int debug_id)
}
auto Audio::ShouldPlay(SoundAsset* sound) -> bool {
millisecs_t time = g_core->GetAppTimeMillisecs();
millisecs_t time = g_core->AppTimeMillisecs();
assert(sound);
return (time - sound->last_play_time() > 50);
}

View File

@ -212,9 +212,8 @@ void AudioServer::OpenALSoftLogCallback(const std::string& msg) {
std::scoped_lock lock(openalsoft_android_log_mutex_);
if (openalsoft_android_log_.size() < log_cap) {
openalsoft_android_log_ += "openal-log("
+ std::to_string(g_core->GetAppTimeSeconds())
+ "s): " + msg;
openalsoft_android_log_ +=
"openal-log(" + std::to_string(g_core->AppTimeSeconds()) + "s): " + msg;
if (openalsoft_android_log_.size() >= log_cap) {
openalsoft_android_log_ +=
"\n<max openalsoft log storage size reached>\n";
@ -477,7 +476,7 @@ void AudioServer::OnAppStartInThread_() {
// Now make available any stopped sources (should be all of them).
UpdateAvailableSources_();
last_started_playing_time_ = g_core->GetAppTimeSeconds();
last_started_playing_time_ = g_core->AppTimeSeconds();
#endif // BA_ENABLE_AUDIO
}
@ -487,7 +486,7 @@ void AudioServer::Shutdown() {
return;
}
shutting_down_ = true;
shutdown_start_time_ = g_core->GetAppTimeSeconds();
shutdown_start_time_ = g_core->AppTimeSeconds();
// Stop all playing sounds and note the time. We'll then give everything a
// moment to come to a halt before we tear down the audio context to
@ -538,8 +537,8 @@ struct AudioServer::SoundFadeNode_ {
bool out;
SoundFadeNode_(uint32_t play_id_in, millisecs_t duration_in, bool out_in)
: play_id(play_id_in),
starttime(g_core->GetAppTimeMillisecs()),
endtime(g_core->GetAppTimeMillisecs() + duration_in),
starttime(g_core->AppTimeMillisecs()),
endtime(g_core->AppTimeMillisecs() + duration_in),
out(out_in) {}
};
@ -566,15 +565,15 @@ void AudioServer::SetSuspended_(bool suspend) {
try {
g_core->platform->LowLevelDebugLog(
"Calling alcDevicePauseSOFT at "
+ std::to_string(g_core->GetAppTimeSeconds()));
+ std::to_string(g_core->AppTimeSeconds()));
alcDevicePauseSOFT(device);
} catch (const std::exception& e) {
g_core->Log(LogName::kBaAudio, LogLevel::kError,
g_core->Log(
LogName::kBaAudio, LogLevel::kError,
"Error in alcDevicePauseSOFT at time "
+ std::to_string(g_core->GetAppTimeSeconds())
+ "( playing since "
+ std::to_string(last_started_playing_time_) + "): "
+ g_core->platform->DemangleCXXSymbol(typeid(e).name())
+ std::to_string(g_core->AppTimeSeconds()) + "( playing since "
+ std::to_string(last_started_playing_time_)
+ "): " + g_core->platform->DemangleCXXSymbol(typeid(e).name())
+ " " + e.what());
} catch (...) {
g_core->Log(LogName::kBaAudio, LogLevel::kError,
@ -609,12 +608,12 @@ void AudioServer::SetSuspended_(bool suspend) {
try {
g_core->platform->LowLevelDebugLog(
"Calling alcDeviceResumeSOFT at "
+ std::to_string(g_core->GetAppTimeSeconds()));
+ std::to_string(g_core->AppTimeSeconds()));
alcDeviceResumeSOFT(device);
} catch (const std::exception& e) {
g_core->Log(LogName::kBaAudio, LogLevel::kError,
"Error in alcDeviceResumeSOFT at time "
+ std::to_string(g_core->GetAppTimeSeconds()) + ": "
+ std::to_string(g_core->AppTimeSeconds()) + ": "
+ g_core->platform->DemangleCXXSymbol(typeid(e).name())
+ " " + e.what());
} catch (...) {
@ -622,7 +621,7 @@ void AudioServer::SetSuspended_(bool suspend) {
"Unknown error in alcDeviceResumeSOFT");
}
#endif
last_started_playing_time_ = g_core->GetAppTimeSeconds();
last_started_playing_time_ = g_core->AppTimeSeconds();
suspended_ = false;
#if BA_ENABLE_AUDIO
CHECK_AL_ERROR;
@ -774,7 +773,7 @@ void AudioServer::UpdateAvailableSources_() {
// and see how many are in use, how many are currently locked by the client,
// etc.
#if (BA_DEBUG_BUILD || BA_TEST_BUILD)
millisecs_t t = g_core->GetAppTimeMillisecs();
millisecs_t t = g_core->AppTimeMillisecs();
if (t - last_sanity_check_time_ > 5000) {
last_sanity_check_time_ = t;
@ -1033,7 +1032,7 @@ void AudioServer::OnDeviceDisconnected() {
void AudioServer::Process_() {
assert(g_base->InAudioThread());
seconds_t real_time_seconds = g_core->GetAppTimeSeconds();
seconds_t real_time_seconds = g_core->AppTimeSeconds();
millisecs_t real_time_millisecs = real_time_seconds * 1000;
// Only do real work if we're in normal running mode.
@ -1085,7 +1084,7 @@ void AudioServer::Process_() {
// for the mixer to spit out some silence so we don't hear sudden cut-offs
// in one or both ears.
if (shutting_down_ && !shutdown_completed_) {
if (g_core->GetAppTimeSeconds() - shutdown_start_time_ > 0.2) {
if (g_core->AppTimeSeconds() - shutdown_start_time_ > 0.2) {
CompleteShutdown_();
}
}
@ -1125,13 +1124,13 @@ void AudioServer::ProcessSoundFades_() {
AudioServer::ThreadSource_* s = GetPlayingSound_(i->second.play_id);
if (s) {
if (g_core->GetAppTimeMillisecs() > i->second.endtime) {
if (g_core->AppTimeMillisecs() > i->second.endtime) {
StopSound(i->second.play_id);
sound_fade_nodes_.erase(i);
} else {
float fade_val =
1
- (static_cast<float>(g_core->GetAppTimeMillisecs()
- (static_cast<float>(g_core->AppTimeMillisecs()
- i->second.starttime)
/ static_cast<float>(i->second.endtime - i->second.starttime));
s->SetFade(fade_val);
@ -1639,12 +1638,12 @@ void AudioServer::ClearSoundRefDeleteList() {
// g_base->audio_server->PushSetSuspendedCall(true);
// // Wait a reasonable amount of time for the thread to act on it.
// millisecs_t t = g_core->GetAppTimeMillisecs();
// millisecs_t t = g_core->AppTimeMillisecs();
// while (true) {
// if (g_base->audio_server->suspended()) {
// break;
// }
// if (g_core->GetAppTimeMillisecs() - t > 1000) {
// if (g_core->AppTimeMillisecs() - t > 1000) {
// Log(LogLevel::kError, "Timed out waiting for audio suspend.");
// break;
// }
@ -1657,12 +1656,12 @@ void AudioServer::ClearSoundRefDeleteList() {
// g_base->audio_server->PushSetSuspendedCall(false);
// // Wait a reasonable amount of time for the thread to act on it.
// millisecs_t t = g_core->GetAppTimeMillisecs();
// millisecs_t t = g_core->AppTimeMillisecs();
// while (true) {
// if (!g_base->audio_server->suspended()) {
// break;
// }
// if (g_core->GetAppTimeMillisecs() - t > 1000) {
// if (g_core->AppTimeMillisecs() - t > 1000) {
// Log(LogLevel::kError, "Timed out waiting for audio unsuspend.");
// break;
// }

View File

@ -103,7 +103,7 @@ void AudioSource::Lock(int debug_id) {
BA_DEBUG_FUNCTION_TIMER_BEGIN();
mutex_.lock();
#if BA_DEBUG_BUILD
last_lock_time_ = g_core->GetAppTimeMillisecs();
last_lock_time_ = g_core->AppTimeMillisecs();
lock_debug_id_ = debug_id;
locked_ = true;
#endif
@ -115,7 +115,7 @@ auto AudioSource::TryLock(int debug_id) -> bool {
#if (BA_DEBUG_BUILD || BA_TEST_BUILD)
if (locked) {
locked_ = true;
last_lock_time_ = g_core->GetAppTimeMillisecs();
last_lock_time_ = g_core->AppTimeMillisecs();
lock_debug_id_ = debug_id;
}
#endif

View File

@ -174,6 +174,11 @@ void BaseFeatureSet::ErrorScreenMessage() {
}
auto BaseFeatureSet::GetV2AccountID() -> std::optional<std::string> {
// Guard against this getting called early.
if (!IsAppStarted()) {
return {};
}
auto gil = Python::ScopedInterpreterLock();
auto result =
python->objs().Get(BasePython::ObjID::kGetV2AccountIdCall).Call();
@ -199,7 +204,7 @@ void BaseFeatureSet::StartApp() {
BA_PRECONDITION(g_core->InMainThread());
BA_PRECONDITION(g_base);
auto start_time = g_core->GetAppTimeSeconds();
auto start_time = g_core->AppTimeSeconds();
// Currently limiting this to once per process.
BA_PRECONDITION(!called_start_app_);
@ -253,7 +258,7 @@ void BaseFeatureSet::StartApp() {
// Make some noise if this takes more than a few seconds. If we pass 5
// seconds or so we start to trigger App-Not-Responding reports which
// isn't good.
auto duration = g_core->GetAppTimeSeconds() - start_time;
auto duration = g_core->AppTimeSeconds() - start_time;
if (duration > 3.0) {
char buffer[128];
snprintf(buffer, sizeof(buffer),
@ -272,7 +277,7 @@ void BaseFeatureSet::SuspendApp() {
return;
}
millisecs_t start_time{core::CorePlatform::GetCurrentMillisecs()};
millisecs_t start_time{core::CorePlatform::TimeMonotonicMillisecs()};
// Apple mentioned 5 seconds to run stuff once backgrounded or they bring
// down the hammer. Let's aim to stay under 2.
@ -280,7 +285,7 @@ void BaseFeatureSet::SuspendApp() {
g_core->platform->LowLevelDebugLog(
"SuspendApp@"
+ std::to_string(core::CorePlatform::GetCurrentMillisecs()));
+ std::to_string(core::CorePlatform::TimeMonotonicMillisecs()));
app_suspended_ = true;
// IMPORTANT: Any pause related stuff that event-loop-threads need to do
@ -311,13 +316,13 @@ void BaseFeatureSet::SuspendApp() {
g_core->Log(
LogName::kBa, LogLevel::kDebug,
"SuspendApp() completed in "
+ std::to_string(core::CorePlatform::GetCurrentMillisecs()
+ std::to_string(core::CorePlatform::TimeMonotonicMillisecs()
- start_time)
+ "ms.");
}
return;
}
} while (std::abs(core::CorePlatform::GetCurrentMillisecs() - start_time)
} while (std::abs(core::CorePlatform::TimeMonotonicMillisecs() - start_time)
< max_duration);
// If we made it here, we timed out. Complain.
@ -325,7 +330,8 @@ void BaseFeatureSet::SuspendApp() {
std::string("SuspendApp() took too long; ")
+ std::to_string(running_loops.size())
+ " event-loops not yet suspended after "
+ std::to_string(core::CorePlatform::GetCurrentMillisecs() - start_time)
+ std::to_string(core::CorePlatform::TimeMonotonicMillisecs()
- start_time)
+ " ms: (";
bool first = true;
for (auto* loop : running_loops) {
@ -383,10 +389,10 @@ void BaseFeatureSet::UnsuspendApp() {
"AppAdapter::UnsuspendApp() called with app not in suspendedstate.");
return;
}
millisecs_t start_time{core::CorePlatform::GetCurrentMillisecs()};
millisecs_t start_time{core::CorePlatform::TimeMonotonicMillisecs()};
g_core->platform->LowLevelDebugLog(
"UnsuspendApp@"
+ std::to_string(core::CorePlatform::GetCurrentMillisecs()));
+ std::to_string(core::CorePlatform::TimeMonotonicMillisecs()));
app_suspended_ = false;
// Spin all event-loops back up.
@ -397,9 +403,10 @@ void BaseFeatureSet::UnsuspendApp() {
g_base->networking->OnAppUnsuspend();
if (g_buildconfig.debug_build()) {
g_core->Log(LogName::kBa, LogLevel::kDebug,
g_core->Log(
LogName::kBa, LogLevel::kDebug,
"UnsuspendApp() completed in "
+ std::to_string(core::CorePlatform::GetCurrentMillisecs()
+ std::to_string(core::CorePlatform::TimeMonotonicMillisecs()
- start_time)
+ "ms.");
}
@ -571,7 +578,7 @@ auto BaseFeatureSet::GetAppInstanceUUID() -> const std::string& {
g_core->Log(LogName::kBa, LogLevel::kWarning,
"GetSessionUUID() using rand fallback.");
srand(static_cast<unsigned int>(
core::CorePlatform::GetCurrentMillisecs())); // NOLINT
core::CorePlatform::TimeMonotonicMillisecs())); // NOLINT
app_instance_uuid =
std::to_string(static_cast<uint32_t>(rand())); // NOLINT
have_app_instance_uuid = true;
@ -966,7 +973,7 @@ void BaseFeatureSet::SetAppActive(bool active) {
g_core->platform->LowLevelDebugLog(
"SetAppActive(" + std::to_string(active) + ")@"
+ std::to_string(core::CorePlatform::GetCurrentMillisecs()));
+ std::to_string(core::CorePlatform::TimeMonotonicMillisecs()));
// Issue a gentle warning if they are feeding us the same state twice in a
// row; might imply faulty logic on an app-adapter or whatnot.
@ -990,6 +997,12 @@ void BaseFeatureSet::Reset() {
audio->Reset();
}
auto BaseFeatureSet::TimeSinceEpochCloudSeconds() -> seconds_t {
// TODO(ericf): wire this up. Just using local time for now. And make sure
// that this and utc_now_cloud() in the Python layer are synced up.
return core::CorePlatform::TimeSinceEpochSeconds();
}
void BaseFeatureSet::SetUIScale(UIScale scale) {
assert(InLogicThread());

View File

@ -476,7 +476,8 @@ enum class SysTextureID : uint8_t {
kFontExtras4,
kCharacterIconMask,
kBlack,
kWings
kWings,
kSpinner
};
enum class SysCubeMapTextureID : uint8_t {
@ -761,8 +762,8 @@ class BaseFeatureSet : public FeatureSetNativeComponent,
void PushMainThreadRunnable(Runnable* runnable) override;
/// Return the currently signed in V2 account id as
/// reported by the Python layer.
/// Return the currently signed in V2 account id as reported by the Python
/// layer.
auto GetV2AccountID() -> std::optional<std::string>;
/// Return whether clipboard operations are supported at all. This gets
@ -784,6 +785,10 @@ class BaseFeatureSet : public FeatureSetNativeComponent,
/// Set overall ui scale for the app.
void SetUIScale(UIScale scale);
/// Time since epoch on the master-server. Tries to
/// be correct even if local time is set wrong.
auto TimeSinceEpochCloudSeconds() -> seconds_t;
// Const subsystems.
AppAdapter* const app_adapter;
AppConfig* const app_config;

View File

@ -96,7 +96,7 @@ class BGDynamicsServer::Terrain {
if (collision_mesh_) {
Object::Ref<CollisionMeshAsset>* ref = collision_mesh_;
g_base->logic->event_loop()->PushCall([ref] {
(**ref).set_last_used_time(g_core->GetAppTimeMillisecs());
(**ref).set_last_used_time(g_core->AppTimeMillisecs());
delete ref;
});
collision_mesh_ = nullptr;

View File

@ -90,12 +90,12 @@ void RendererGL::CheckGLError(const char* file, int line) {
BA_PRECONDITION_FATAL(vendor);
const char* renderer = (const char*)glGetString(GL_RENDERER);
BA_PRECONDITION_FATAL(renderer);
g_core->Log(
LogName::kBaGraphics, LogLevel::kError,
"OpenGL Error at " + std::string(file) + " line " + std::to_string(line)
+ ": " + GLErrorToString(err) + "\nrenderer: " + renderer
+ "\nvendor: " + vendor + "\nversion: " + version
+ "\ntime: " + std::to_string(g_core->GetAppTimeMillisecs()));
g_core->Log(LogName::kBaGraphics, LogLevel::kError,
"OpenGL Error at " + std::string(file) + " line "
+ std::to_string(line) + ": " + GLErrorToString(err)
+ "\nrenderer: " + renderer + "\nvendor: " + vendor
+ "\nversion: " + version
+ "\ntime: " + std::to_string(g_core->AppTimeMillisecs()));
}
}

View File

@ -253,13 +253,13 @@ auto Graphics::GraphicsQualityFromAppConfig() -> GraphicsQualityRequest {
void Graphics::SetGyroEnabled(bool enable) {
// If we're turning back on, suppress gyro updates for a bit.
if (enable && !gyro_enabled_) {
last_suppress_gyro_time_ = g_core->GetAppTimeMicrosecs();
last_suppress_gyro_time_ = g_core->AppTimeMicrosecs();
}
gyro_enabled_ = enable;
}
void Graphics::UpdateProgressBarProgress(float target) {
millisecs_t real_time = g_core->GetAppTimeMillisecs();
millisecs_t real_time = g_core->AppTimeMillisecs();
float p = target;
if (p < 0) {
p = 0;
@ -274,7 +274,7 @@ void Graphics::UpdateProgressBarProgress(float target) {
}
void Graphics::DrawProgressBar(RenderPass* pass, float opacity) {
millisecs_t real_time = g_core->GetAppTimeMillisecs();
millisecs_t real_time = g_core->AppTimeMillisecs();
float amount = progress_bar_progress_;
if (amount < 0) {
amount = 0;
@ -361,9 +361,9 @@ void Graphics::DrawMiscOverlays(FrameDef* frame_def) {
assert(g_base && g_base->InLogicThread());
// Every now and then, update our stats.
while (g_core->GetAppTimeMillisecs() >= next_stat_update_time_) {
if (g_core->GetAppTimeMillisecs() - next_stat_update_time_ > 1000) {
next_stat_update_time_ = g_core->GetAppTimeMillisecs() + 1000;
while (g_core->AppTimeMillisecs() >= next_stat_update_time_) {
if (g_core->AppTimeMillisecs() - next_stat_update_time_ > 1000) {
next_stat_update_time_ = g_core->AppTimeMillisecs() + 1000;
} else {
next_stat_update_time_ += 1000;
}
@ -482,7 +482,7 @@ void Graphics::DrawMiscOverlays(FrameDef* frame_def) {
// Draw any debug graphs.
{
float debug_graph_y = 50.0;
auto now = g_core->GetAppTimeMillisecs();
auto now = g_core->AppTimeMillisecs();
for (auto it = debug_graphs_.begin(); it != debug_graphs_.end();) {
assert(it->second.exists());
if (now - it->second->LastUsedTime() > 1000) {
@ -508,7 +508,7 @@ auto Graphics::GetDebugGraph(const std::string& name, bool smoothed)
debug_graphs_[name]->SetLabel(name);
debug_graphs_[name]->SetSmoothed(smoothed);
}
debug_graphs_[name]->SetLastUsedTime(g_core->GetAppTimeMillisecs());
debug_graphs_[name]->SetLastUsedTime(g_core->AppTimeMillisecs());
return debug_graphs_[name].get();
}
@ -770,7 +770,7 @@ void Graphics::BuildAndPushFrameDef() {
assert(!building_frame_def_);
building_frame_def_ = true;
microsecs_t app_time_microsecs = g_core->GetAppTimeMicrosecs();
microsecs_t app_time_microsecs = g_core->AppTimeMicrosecs();
// Store how much time this frame_def represents.
auto display_time_microsecs = g_base->logic->display_time_microsecs();
@ -1222,7 +1222,7 @@ void Graphics::EnableProgressBar(bool fade_in) {
if (progress_bar_loads_ > 0) {
progress_bar_ = true;
progress_bar_fade_in_ = fade_in;
last_progress_bar_draw_time_ = g_core->GetAppTimeMillisecs();
last_progress_bar_draw_time_ = g_core->AppTimeMillisecs();
last_progress_bar_start_time_ = last_progress_bar_draw_time_;
progress_bar_progress_ = 0.0f;
}

View File

@ -147,7 +147,7 @@ auto GraphicsServer::TryRender() -> bool {
auto GraphicsServer::WaitForRenderFrameDef_() -> FrameDef* {
assert(g_base->app_adapter->InGraphicsContext());
millisecs_t start_time = g_core->GetAppTimeMillisecs();
millisecs_t start_time = g_core->AppTimeMillisecs();
// Spin and wait for a short bit for a frame_def to appear.
while (true) {
@ -176,7 +176,7 @@ auto GraphicsServer::WaitForRenderFrameDef_() -> FrameDef* {
}
// If there's no frame_def for us, sleep for a bit and wait for it.
millisecs_t t = g_core->GetAppTimeMillisecs() - start_time;
millisecs_t t = g_core->AppTimeMillisecs() - start_time;
if (t >= 1000) {
if (g_buildconfig.debug_build()) {
g_core->Log(LogName::kBaGraphics, LogLevel::kWarning,

View File

@ -650,7 +650,7 @@ void Renderer::UpdatePixelScaleAndBackingBuffer(FrameDef* frame_def) {
}
void Renderer::LoadMedia(FrameDef* frame_def) {
millisecs_t t = g_core->GetAppTimeMillisecs();
millisecs_t t = g_core->AppTimeMillisecs();
for (auto&& i : frame_def->media_components()) {
Asset* mc = i.get();
assert(mc);
@ -667,7 +667,7 @@ void Renderer::LoadMedia(FrameDef* frame_def) {
// // default about 1 second after a res change, etc...
// // so if we're using a non-1.0 gamma, lets keep setting it periodically
// // to force the issue
// millisecs_t t = g_core->GetAppTimeMillisecs();
// millisecs_t t = g_core->AppTimeMillisecs();
// if (screen_gamma_requested_ != screen_gamma_
// || (t - last_screen_gamma_update_time_ > 300 && screen_gamma_ != 1.0f))
// {

View File

@ -207,10 +207,9 @@ void Camera::UpdatePosition() {
lr_jitter = 0.0f;
} else {
lr_jitter =
sinf(static_cast<float>(g_core->GetAppTimeMillisecs()) / 108.0f)
sinf(static_cast<float>(g_core->AppTimeMillisecs()) / 108.0f)
* 0.4f
+ sinf(static_cast<float>(g_core->GetAppTimeMillisecs())
/ 268.0f)
+ sinf(static_cast<float>(g_core->AppTimeMillisecs()) / 268.0f)
* 1.0f;
lr_jitter *= 0.05f;
}
@ -891,7 +890,7 @@ void Camera::SetMode(CameraMode m) {
if (mode_ != m) {
mode_ = m;
smooth_next_frame_ = false;
// last_mode_set_time_ = g_core->GetAppTimeMillisecs();
// last_mode_set_time_ = g_core->AppTimeMillisecs();
// last_mode_set_time_ = time_;
heading_ = kInitialHeading;
}

View File

@ -65,8 +65,8 @@ void ScreenMessages::DrawMiscOverlays(FrameDef* frame_def) {
// Delete old ones.
if (!screen_messages_.empty()) {
millisecs_t cutoff;
if (g_core->GetAppTimeMillisecs() > 5000) {
cutoff = g_core->GetAppTimeMillisecs() - 5000;
if (g_core->AppTimeMillisecs() > 5000) {
cutoff = g_core->AppTimeMillisecs() - 5000;
for (auto i = screen_messages_.begin(); i != screen_messages_.end();) {
if (i->creation_time < cutoff) {
auto next = i;
@ -128,7 +128,7 @@ void ScreenMessages::DrawMiscOverlays(FrameDef* frame_def) {
// which is calculated as part of it.
i->GetText();
millisecs_t age = g_core->GetAppTimeMillisecs() - i->creation_time;
millisecs_t age = g_core->AppTimeMillisecs() - i->creation_time;
youngest_age = std::min(youngest_age, age);
float s_extra = 1.0f;
if (age < 100) {
@ -244,7 +244,7 @@ void ScreenMessages::DrawMiscOverlays(FrameDef* frame_def) {
for (auto i = screen_messages_.rbegin(); i != screen_messages_.rend();
i++) {
millisecs_t age = g_core->GetAppTimeMillisecs() - i->creation_time;
millisecs_t age = g_core->AppTimeMillisecs() - i->creation_time;
youngest_age = std::min(youngest_age, age);
float s_extra = 1.0f;
if (age < 100) {
@ -315,8 +315,8 @@ void ScreenMessages::DrawMiscOverlays(FrameDef* frame_def) {
// Delete old ones.
if (!screen_messages_top_.empty()) {
millisecs_t cutoff;
if (g_core->GetAppTimeMillisecs() > 5000) {
cutoff = g_core->GetAppTimeMillisecs() - 5000;
if (g_core->AppTimeMillisecs() > 5000) {
cutoff = g_core->AppTimeMillisecs() - 5000;
for (auto i = screen_messages_top_.begin();
i != screen_messages_top_.end();) {
if (i->creation_time < cutoff) {
@ -354,7 +354,7 @@ void ScreenMessages::DrawMiscOverlays(FrameDef* frame_def) {
// Update the translation if need be.
i->UpdateTranslation();
millisecs_t age = g_core->GetAppTimeMillisecs() - i->creation_time;
millisecs_t age = g_core->AppTimeMillisecs() - i->creation_time;
float s_extra = 1.0f;
if (age < 100) {
s_extra = std::min(1.1f, 1.1f * (static_cast<float>(age) / 100.0f));
@ -466,13 +466,13 @@ void ScreenMessages::AddScreenMessage(const std::string& msg,
start_v,
std::max(-100.0f, screen_messages_top_.back().v_smoothed - 25.0f));
}
screen_messages_top_.emplace_back(m, true, g_core->GetAppTimeMillisecs(),
screen_messages_top_.emplace_back(m, true, g_core->AppTimeMillisecs(),
color, texture, tint_texture, tint,
tint2);
screen_messages_top_.back().v_smoothed = start_v;
} else {
screen_messages_.emplace_back(m, false, g_core->GetAppTimeMillisecs(),
color, texture, tint_texture, tint, tint2);
screen_messages_.emplace_back(m, false, g_core->AppTimeMillisecs(), color,
texture, tint_texture, tint, tint2);
}
}
@ -534,8 +534,7 @@ auto ScreenMessages::ScreenMessageEntry::GetText() -> TextGroup& {
void ScreenMessages::ScreenMessageEntry::UpdateTranslation() {
if (translation_dirty) {
s_translated = g_base->assets->CompileResourceString(
s_raw, "Graphics::ScreenMessageEntry::UpdateTranslation");
s_translated = g_base->assets->CompileResourceString(s_raw);
translation_dirty = false;
mesh_dirty = true;
}

View File

@ -37,7 +37,7 @@ JoystickInput::JoystickInput(int sdl_joystick_id,
calibration_break_threshold_(kJoystickCalibrationBreakThreshold),
custom_device_name_(custom_device_name),
can_configure_(can_configure),
creation_time_(g_core->GetAppTimeMillisecs()),
creation_time_(g_core->AppTimeMillisecs()),
calibrate_(calibrate) {
// This is the default calibration for 'non-full' analog calibration.
for (float& analog_calibration_val : analog_calibration_vals_) {
@ -374,7 +374,7 @@ void JoystickInput::Update() {
// Let's take this opportunity to update our calibration
// (should probably have a specific place to do that but this works)
if (calibrate_) {
millisecs_t time = g_core->GetAppTimeMillisecs();
millisecs_t time = g_core->AppTimeMillisecs();
// If we're doing 'aggressive' auto-recalibration we expand extents outward
// but suck them inward a tiny bit too to account for jitter or random fluke
@ -545,7 +545,7 @@ void JoystickInput::HandleSDLEvent(const SDL_Event* e) {
return;
}
millisecs_t time = g_core->GetAppTimeMillisecs();
millisecs_t time = g_core->AppTimeMillisecs();
SDL_Event e2;
// Ignore analog-stick input while we're holding a hat switch or d-pad
@ -959,7 +959,7 @@ void JoystickInput::HandleSDLEvent(const SDL_Event* e) {
&& (e->jbutton.button != hold_position_button_)
&& (e->jbutton.button != back_button_)) {
if (ui_only_ || e->jbutton.button == remote_enter_button_) {
millisecs_t current_time = g_core->GetAppTimeMillisecs();
millisecs_t current_time = g_core->AppTimeMillisecs();
if (current_time - last_ui_only_print_time_ > 5000) {
g_base->python->objs()
.Get(BasePython::ObjID::kUIRemotePressCall)

View File

@ -93,7 +93,7 @@ TouchInput::TouchInput() {
TouchInput::~TouchInput() = default;
void TouchInput::UpdateButtons(bool new_touch) {
millisecs_t real_time = g_core->GetAppTimeMillisecs();
millisecs_t real_time = g_core->AppTimeMillisecs();
float spread_scaled_actions =
kButtonSpread * base_controls_scale_ * controls_scale_actions_;
float width = g_base->graphics->screen_virtual_width();
@ -134,7 +134,7 @@ void TouchInput::UpdateButtons(bool new_touch) {
closest_to_bomb = true;
}
if (buttons_touch_) {
last_buttons_touch_time_ = g_core->GetAppTimeMillisecs();
last_buttons_touch_time_ = g_core->AppTimeMillisecs();
}
// Handle swipe mode.

View File

@ -158,7 +158,7 @@ void Input::AnnounceConnects_() {
// For the first announcement just say "X controllers detected" and don't
// have a sound.
if (first_print && g_core->GetAppTimeSeconds() < 3.0) {
if (first_print && g_core->AppTimeSeconds() < 3.0) {
first_print = false;
// If there's been several connected, just give a number.
@ -225,7 +225,7 @@ void Input::ShowStandardInputDeviceConnectedMessage_(InputDevice* j) {
// On Android we never show messages for initial input-devices; we often
// get large numbers of strange virtual devices that aren't actually
// controllers so this is more confusing than helpful.
if (g_buildconfig.ostype_android() && g_core->GetAppTimeSeconds() < 3.0) {
if (g_buildconfig.ostype_android() && g_core->AppTimeSeconds() < 3.0) {
return;
}
@ -554,7 +554,7 @@ void Input::OnScreenSizeChange() { assert(g_base->InLogicThread()); }
void Input::StepDisplayTime() {
assert(g_base->InLogicThread());
millisecs_t real_time = g_core->GetAppTimeMillisecs();
millisecs_t real_time = g_core->AppTimeMillisecs();
// If input has been locked an excessively long amount of time, unlock it.
if (input_lock_count_temp_) {
@ -622,13 +622,13 @@ void Input::LockAllInput(bool permanent, const std::string& label) {
} else {
input_lock_count_temp_++;
if (input_lock_count_temp_ == 1) {
last_input_temp_lock_time_ = g_core->GetAppTimeMillisecs();
last_input_temp_lock_time_ = g_core->AppTimeMillisecs();
}
input_lock_temp_labels_.push_back(label);
recent_input_locks_unlocks_.push_back(
"temp lock: " + label + " time "
+ std::to_string(g_core->GetAppTimeMillisecs()));
+ std::to_string(g_core->AppTimeMillisecs()));
while (recent_input_locks_unlocks_.size() > 10) {
recent_input_locks_unlocks_.pop_front();
}
@ -641,7 +641,7 @@ void Input::UnlockAllInput(bool permanent, const std::string& label) {
recent_input_locks_unlocks_.push_back(
permanent ? "permanent unlock: "
: "temp unlock: " + label + " time "
+ std::to_string(g_core->GetAppTimeMillisecs()));
+ std::to_string(g_core->AppTimeMillisecs()));
while (recent_input_locks_unlocks_.size() > 10) {
recent_input_locks_unlocks_.pop_front();
}
@ -667,7 +667,7 @@ void Input::UnlockAllInput(bool permanent, const std::string& label) {
if (input_lock_count_temp_ < 0) {
g_core->Log(LogName::kBaInput, LogLevel::kWarning,
"temp input unlock at time "
+ std::to_string(g_core->GetAppTimeMillisecs())
+ std::to_string(g_core->AppTimeMillisecs())
+ " with no active lock: '" + label + "'");
// This is to be expected since we can reset this to 0.
input_lock_count_temp_ = 0;
@ -684,7 +684,7 @@ void Input::UnlockAllInput(bool permanent, const std::string& label) {
void Input::PrintLockLabels_() {
std::string s = "INPUT LOCK REPORT (time="
+ std::to_string(g_core->GetAppTimeMillisecs()) + "):";
+ std::to_string(g_core->AppTimeMillisecs()) + "):";
int num;
s += "\n " + std::to_string(input_lock_temp_labels_.size()) + " TEMP LOCKS:";
@ -926,7 +926,7 @@ void Input::HandleKeyPress_(const SDL_Keysym& keysym) {
// fluke repeat key press event due to funky OS circumstances.
static int count{};
static seconds_t last_count_reset_time{};
auto now = g_core->GetAppTimeSeconds();
auto now = g_core->AppTimeSeconds();
if (now - last_count_reset_time > 2.0) {
count = 0;
last_count_reset_time = now;
@ -1238,7 +1238,7 @@ void Input::HandleSmoothMouseScroll_(const Vector2f& velocity, bool momentum) {
WidgetMessage(WidgetMessage::Type::kMouseWheelVelocityH, nullptr,
cursor_pos_x_, cursor_pos_y_, velocity.x, momentum));
last_mouse_move_time_ = g_core->GetAppTimeSeconds();
last_mouse_move_time_ = g_core->AppTimeSeconds();
mouse_move_count_++;
Camera* camera = g_base->graphics->camera();
@ -1282,7 +1282,7 @@ void Input::HandleMouseMotion_(const Vector2f& position) {
cursor_pos_y_ = g_base->graphics->PixelToVirtualY(
position.y * g_base->graphics->screen_pixel_height());
last_mouse_move_time_ = g_core->GetAppTimeSeconds();
last_mouse_move_time_ = g_core->AppTimeSeconds();
mouse_move_count_++;
// If we have a touch-input in editing mode, pass along events to it. (it
@ -1324,7 +1324,7 @@ void Input::HandleMouseDown_(int button, const Vector2f& position) {
return;
}
last_mouse_move_time_ = g_core->GetAppTimeSeconds();
last_mouse_move_time_ = g_core->AppTimeSeconds();
mouse_move_count_++;
// Convert normalized view coords to our virtual ones.
@ -1333,7 +1333,7 @@ void Input::HandleMouseDown_(int button, const Vector2f& position) {
cursor_pos_y_ = g_base->graphics->PixelToVirtualY(
position.y * g_base->graphics->screen_pixel_height());
millisecs_t click_time = g_core->GetAppTimeMillisecs();
millisecs_t click_time = g_core->AppTimeMillisecs();
bool double_click = (click_time - last_click_time_ <= double_click_time_);
last_click_time_ = click_time;
@ -1528,7 +1528,7 @@ auto Input::IsCursorVisible() const -> bool {
bool val;
// Show our cursor only if its been moved recently.
val = (g_core->GetAppTimeSeconds() - last_mouse_move_time_ < 2.071);
val = (g_core->AppTimeSeconds() - last_mouse_move_time_ < 2.071);
return val;
}

View File

@ -221,7 +221,7 @@ void RemoteAppServer::HandleData(int socket, uint8_t* buffer, size_t amt,
RemoteAppClient* client = clients_ + joystick_id;
// Take note that we heard from them.
client->last_contact_time = g_core->GetAppTimeMillisecs();
client->last_contact_time = g_core->AppTimeMillisecs();
// Ok now iterate.
uint8_t* val = buffer + 4;
@ -389,7 +389,7 @@ auto RemoteAppServer::GetClient(int request_id, struct sockaddr* addr,
}
// Don't reuse a slot for 5 seconds (if its been heard from since this time).
millisecs_t cooldown_time = g_core->GetAppTimeMillisecs() - 5000;
millisecs_t cooldown_time = g_core->AppTimeMillisecs() - 5000;
// Ok, not there already.. now look for a non-taken one and return that.
for (int i = 0; i < kMaxRemoteAppClients; i++) {
@ -412,7 +412,7 @@ auto RemoteAppServer::GetClient(int request_id, struct sockaddr* addr,
strcpy(clients_[i].display_name, clients_[i].name); // NOLINT
char* c = strchr(clients_[i].display_name, '#');
if (c) *c = 0;
clients_[i].last_contact_time = g_core->GetAppTimeMillisecs();
clients_[i].last_contact_time = g_core->AppTimeMillisecs();
clients_[i].request_id = request_id;
char m[256];

View File

@ -236,7 +236,7 @@ void Logic::OnAppShutdown() {
assert(shutting_down_);
// Nuke the app from orbit if we get stuck while shutting down.
g_core->StartSuicideTimer("shutdown", 10000);
g_core->StartSuicideTimer("shutdown", 15000);
// Tell base to disallow shutdown-suppressors from here on out.
g_base->ShutdownSuppressDisallow();
@ -402,7 +402,7 @@ void Logic::UpdateDisplayTimeForHeadlessMode_() {
// scheduled (or at least close enough so we can fudge it and tell them
// its that exact time).
auto app_time_microsecs = g_core->GetAppTimeMicrosecs();
auto app_time_microsecs = g_core->AppTimeMicrosecs();
// Set our int based time vals so we can exactly hit timers.
auto old_display_time_microsecs = display_time_microsecs_;
@ -438,7 +438,7 @@ void Logic::PostUpdateDisplayTimeForHeadlessMode_() {
[headless_display_step_microsecs] {
auto sleepsecs =
static_cast<double>(headless_display_step_microsecs) / 1000000.0;
auto apptimesecs = g_core->GetAppTimeSeconds();
auto apptimesecs = g_core->AppTimeSeconds();
char buffer[256];
snprintf(buffer, sizeof(buffer),
"will try to sleep for %.4f at app-time %.4f (until %.4f)",
@ -467,7 +467,7 @@ void Logic::UpdateDisplayTimeForFrameDraw_() {
// - 'current' should mostly show '(avg)'; rarely '(sample)'.
// - these can vary briefly during load spikes/etc. but should quickly
// reconverge to stability. If not, this may need further calibration.
auto current_app_time = g_core->GetAppTimeSeconds();
auto current_app_time = g_core->AppTimeSeconds();
// We handle the first measurement specially.
if (last_display_time_update_app_time_ < 0) {

View File

@ -330,8 +330,8 @@ static auto PyAppTime(PyObject* self, PyObject* args, PyObject* keywds)
const_cast<char**>(kwlist))) {
return nullptr;
}
return PyFloat_FromDouble(
0.001 * static_cast<double>(g_core->GetAppTimeMillisecs()));
return PyFloat_FromDouble(0.001
* static_cast<double>(g_core->AppTimeMillisecs()));
BA_PYTHON_CATCH;
}

View File

@ -497,7 +497,7 @@ static auto PyEvaluateLstr(PyObject* self, PyObject* args, PyObject* keywds)
return nullptr;
}
return PyUnicode_FromString(
g_base->assets->CompileResourceString(value, "evaluate_lstr").c_str());
g_base->assets->CompileResourceString(value).c_str());
BA_PYTHON_CATCH;
}
@ -533,7 +533,7 @@ static auto PyGetStringHeight(PyObject* self, PyObject* args, PyObject* keywds)
}
s = g_base->python->GetPyLString(s_obj);
#if BA_DEBUG_BUILD
if (g_base->assets->CompileResourceString(s, "get_string_height test") != s) {
if (g_base->assets->CompileResourceString(s) != s) {
BA_LOG_PYTHON_TRACE(
"resource-string passed to get_string_height; this should be avoided");
}
@ -579,8 +579,7 @@ static auto PyGetStringWidth(PyObject* self, PyObject* args, PyObject* keywds)
}
s = g_base->python->GetPyLString(s_obj);
#if BA_DEBUG_BUILD
if (g_base->assets->CompileResourceString(s, "get_string_width debug test")
!= s) {
if (g_base->assets->CompileResourceString(s) != s) {
BA_LOG_PYTHON_TRACE(
"resource-string passed to get_string_width; this should be avoided");
}

View File

@ -7,6 +7,7 @@
#include <vector>
#include "ballistica/base/base.h"
#include "ballistica/shared/math/vector3f.h"
namespace ballistica::base {
@ -46,6 +47,11 @@ class ClassicSoftInterface {
virtual auto GetV1AccountTypeIconString(int account_type) -> std::string = 0;
virtual auto V1AccountTypeToString(int account_type) -> std::string = 0;
virtual void PlayMusic(const std::string& music_type, bool continuous) = 0;
virtual void GetClassicChestDisplayInfo(const std::string& id,
std::string* texclosed,
std::string* texclosedtint,
Vector3f* color, Vector3f* tint,
Vector3f* tint2) = 0;
};
} // namespace ballistica::base

View File

@ -15,6 +15,7 @@ namespace ballistica::base {
// other mechanisms are set up to preserve and restore context before
// running, and objects can also be invalidated or otherwise cleaned up
// when the context they were created under dies.
//
// The end goal of all this is to support api styles for end users where
// standalone snippets of code can be useful; ie: something like
// bs.newnode() to create something meaningful without having to worry
@ -36,26 +37,27 @@ class ContextRef {
ContextRef();
explicit ContextRef(Context* sgc);
/// ContextRefs are considered equal if both are pointing to the exact same
/// Context object (or both are pointing to no Context).
/// ContextRefs are considered equal if both are pointing to the exact
/// same Context object (or both are pointing to no Context).
auto operator==(const ContextRef& other) const -> bool;
template <typename T>
auto GetContextTyped() const -> T* {
// Ew; dynamic cast.
// Note: if it ever seems like speed is an issue here, we can
// cache the results with std::type_index entries. There should
// generally be a very small number of types involved.
//
// Note: if it ever seems like speed is an issue here, we can cache the
// results with std::type_index entries. There should generally be a
// very small number of types involved.
return dynamic_cast<T*>(target_.get());
}
/// An empty context-ref was explicitly set to an empty state.
/// Note that this is different than an expired context-ref, which
/// originally pointed to some context that has since died.
/// An empty context-ref was explicitly set to an empty state. Note that
/// this is different than an expired context-ref, which originally
/// pointed to some context that has since died.
auto IsEmpty() const { return empty_; }
/// Has this context died since it was set?
/// Note that a context created as empty is not considered expired.
/// Has this context died since it was set? Note that a context created as
/// empty is not considered expired.
auto IsExpired() const -> bool {
if (empty_) {
return false; // Can't kill what was never alive.
@ -64,7 +66,8 @@ class ContextRef {
}
/// Return the context this ref points to. This will be nullptr for empty
/// contexts. Throws an exception if a target context was set but has expired.
/// contexts. Throws an exception if a target context was set but has
/// expired.
auto Get() const -> Context* {
auto* target = target_.get();
if (target == nullptr && !empty_) {
@ -84,14 +87,13 @@ class ContextRef {
bool empty_;
};
/// Object containing the actual context_ref data/information.
/// App-modes can subclass this to provide the actual context_ref they desire,
/// and then code can use CurrentTyped() to safely retrieve context_ref as that
/// type.
/// Object containing the actual context_ref data/information. App-modes can
/// subclass this to provide the actual context_ref they desire, and then
/// code can use CurrentTyped() to safely retrieve context_ref as that type.
class Context : public Object {
public:
/// Return the current context_ref cast to a desired type.
/// Throws an Exception if the context_ref is unset or is another type.
/// Return the current context_ref cast to a desired type. Throws an
/// Exception if the context_ref is unset or is another type.
template <typename T>
static auto CurrentTyped() -> T& {
T* t = g_base->CurrentContext().GetContextTyped<T>();
@ -102,13 +104,13 @@ class Context : public Object {
return *t;
}
/// Called when a PythonContextCall is created in this context_ref.
/// The context_ref class may want to store a weak-reference to the
/// call and inform the call when the context_ref is going down so that
/// resources may be freed. Other permanent contexts may not need to
/// bother.
/// FIXME: This mechanism can probably be generalized so that other
/// things such as assets and timers can use it.
/// Called when a PythonContextCall is created in this context_ref. The
/// context_ref class may want to store a weak-reference to the call and
/// inform the call when the context_ref is going down so that resources
/// may be freed. Other permanent contexts may not need to bother.
///
/// FIXME: This mechanism can probably be generalized so that other things
/// such as assets and timers can use it.
virtual void RegisterContextCall(PythonContextCall* call);
/// Return a short description of the context_ref; will be used when
@ -118,9 +120,9 @@ class Context : public Object {
/// Return whether this context should allow default timer-types to be
/// created within it (AppTimer, DisplayTimer). Scene type contexts
/// generally have their own timer types which are better integrated
/// with scenes (responding to changes in game speed/etc.) so this can
/// be used to encourage/enforce usage of those timers.
/// generally have their own timer types which are better integrated with
/// scenes (responding to changes in game speed/etc.) so this can be used
/// to encourage/enforce usage of those timers.
virtual auto ContextAllowsDefaultTimerTypes() -> bool;
};

View File

@ -1683,7 +1683,7 @@ auto DevConsole::PasteFromClipboard() -> bool {
}
void DevConsole::UpdateCarat_() {
last_carat_x_change_time_ = g_core->GetAppTimeMillisecs();
last_carat_x_change_time_ = g_core->AppTimeMillisecs();
auto unichars = Utils::UnicodeFromUTF8(input_string_, "fjfwef");
auto unichars_clamped = unichars;

View File

@ -370,7 +370,7 @@ void UI::SetUIInputDevice(InputDevice* input_device) {
ui_input_device_ = input_device;
// So they dont get stolen from immediately.
last_input_device_use_time_ = g_core->GetAppTimeMillisecs();
last_input_device_use_time_ = g_core->AppTimeMillisecs();
}
void UI::Reset() {
@ -432,7 +432,7 @@ auto UI::GetWidgetForInput(InputDevice* input_device) -> ui_v1::Widget* {
return nullptr;
}
millisecs_t time = g_core->GetAppTimeMillisecs();
millisecs_t time = g_core->AppTimeMillisecs();
bool print_menu_owner{};
ui_v1::Widget* ret_val;

View File

@ -250,4 +250,11 @@ void ClassicFeatureSet::PlayMusic(const std::string& music_type,
python->PlayMusic(music_type, continuous);
}
void ClassicFeatureSet::GetClassicChestDisplayInfo(
const std::string& id, std::string* texclosed, std::string* texclosedtint,
Vector3f* color, Vector3f* tint, Vector3f* tint2) {
python->GetClassicChestDisplayInfo(id, texclosed, texclosedtint, color, tint,
tint2);
}
} // namespace ballistica::classic

View File

@ -108,6 +108,9 @@ class ClassicFeatureSet : public FeatureSetNativeComponent,
auto GetV1AccountTypeIconString(int account_type) -> std::string override;
auto V1AccountTypeToString(int account_type) -> std::string override;
auto GetV1AccountType() -> int override;
void GetClassicChestDisplayInfo(const std::string& id, std::string* texclosed,
std::string* texclosedtint, Vector3f* color,
Vector3f* tint, Vector3f* tint2) override;
ClassicPython* const python;
V1Account* const v1_account;

View File

@ -4,6 +4,7 @@
#include <string>
#include "ballistica/base/python/base_python.h"
#include "ballistica/classic/python/methods/python_methods_classic.h"
#include "ballistica/classic/support/classic_app_mode.h"
#include "ballistica/shared/python/python_command.h" // IWYU pragma: keep.
@ -30,6 +31,60 @@ extern "C" auto PyInit__baclassic() -> PyObject* {
void ClassicPython::ImportPythonObjs() {
#include "ballistica/classic/mgen/pyembed/binding_classic.inc"
// Cache some basic display values for chests from the Python layer. This
// way C++ UI stuff doesn't have to call out to Python when drawing the
// root UI/etc.
// Pull default chest display info.
chest_display_default_ = {ChestDisplayFromPython(
objs().Get(ObjID::kChestAppearanceDisplayInfoDefault))};
// And overrides.
for (auto&& item :
objs().Get(ObjID::kChestAppearanceDisplayInfos).DictItems()) {
chest_displays_[item.first.GetAttr("value").ValueAsString()] =
ChestDisplayFromPython(item.second);
}
}
auto ClassicPython::ChestDisplayFromPython(const PythonRef& ref)
-> ChestDisplay_ {
ChestDisplay_ out;
out.texclosed = ref.GetAttr("texclosed").ValueAsString().c_str();
out.texclosedtint = ref.GetAttr("texclosedtint").ValueAsString().c_str();
out.color = base::BasePython::GetPyVector3f(ref.GetAttr("color").get());
out.tint = base::BasePython::GetPyVector3f(ref.GetAttr("tint").get());
out.tint2 = base::BasePython::GetPyVector3f(ref.GetAttr("tint2").get());
return out;
}
void ClassicPython::GetClassicChestDisplayInfo(const std::string& id,
std::string* texclosed,
std::string* texclosedtint,
Vector3f* color, Vector3f* tint,
Vector3f* tint2) {
assert(texclosed);
assert(texclosedtint);
assert(color);
assert(tint);
assert(tint2);
auto&& display{chest_displays_.find(id)};
if (display != chest_displays_.end()) {
*texclosed = display->second.texclosed;
*texclosedtint = display->second.texclosedtint;
*color = display->second.color;
*tint = display->second.tint;
*tint2 = display->second.tint2;
} else {
*texclosed = chest_display_default_.texclosed;
*texclosedtint = chest_display_default_.texclosedtint;
*color = chest_display_default_.color;
*tint = chest_display_default_.tint;
*tint2 = chest_display_default_.tint2;
}
}
void ClassicPython::PlayMusic(const std::string& music_type, bool continuous) {

View File

@ -4,9 +4,11 @@
#define BALLISTICA_CLASSIC_PYTHON_CLASSIC_PYTHON_H_
#include <string>
#include <unordered_map>
#include "ballistica/base/base.h"
#include "ballistica/classic/classic.h"
#include "ballistica/shared/math/vector3f.h"
#include "ballistica/shared/python/python_object_set.h"
namespace ballistica::classic {
@ -20,6 +22,8 @@ class ClassicPython {
enum class ObjID {
kDoPlayMusicCall,
kGetInputDeviceMappedValueCall,
kChestAppearanceDisplayInfoDefault,
kChestAppearanceDisplayInfos,
kLast // Sentinel; must be at end.
};
@ -34,7 +38,22 @@ class ClassicPython {
const auto& objs() { return objs_; }
void GetClassicChestDisplayInfo(const std::string& id, std::string* texclosed,
std::string* texclosedtint, Vector3f* color,
Vector3f* tint, Vector3f* tint2);
private:
struct ChestDisplay_ {
Vector3f color;
std::string texclosed;
std::string texclosedtint;
Vector3f tint;
Vector3f tint2;
};
auto ChestDisplayFromPython(const PythonRef& ref) -> ChestDisplay_;
ChestDisplay_ chest_display_default_;
std::unordered_map<std::string, ChestDisplay_> chest_displays_;
PythonObjectSet<ObjID> objs_;
};

View File

@ -3,7 +3,6 @@
#include "ballistica/classic/python/methods/python_methods_classic.h"
#include <algorithm>
#include <cstdio>
#include <string>
#include <vector>
@ -296,9 +295,9 @@ static auto PySetRootUIAccountValues(PyObject* self, PyObject* args,
PyObject* keywds) -> PyObject* {
BA_PYTHON_TRY;
const char* tickets_text;
const char* tokens_text;
const char* league_rank_text;
int tickets;
int tokens;
int league_rank;
const char* league_type;
const char* achievements_percent_text;
const char* level_text;
@ -308,19 +307,19 @@ static auto PySetRootUIAccountValues(PyObject* self, PyObject* args,
const char* chest_1_appearance;
const char* chest_2_appearance;
const char* chest_3_appearance;
float chest_0_unlock_time;
float chest_1_unlock_time;
float chest_2_unlock_time;
float chest_3_unlock_time;
float chest_0_ad_allow_time;
float chest_1_ad_allow_time;
float chest_2_ad_allow_time;
float chest_3_ad_allow_time;
double chest_0_unlock_time;
double chest_1_unlock_time;
double chest_2_unlock_time;
double chest_3_unlock_time;
double chest_0_ad_allow_time;
double chest_1_ad_allow_time;
double chest_2_ad_allow_time;
double chest_3_ad_allow_time;
int gold_pass{};
static const char* kwlist[] = {"tickets_text",
"tokens_text",
"league_rank_text",
static const char* kwlist[] = {"tickets",
"tokens",
"league_rank",
"league_type",
"achievements_percent_text",
"level_text",
@ -341,8 +340,8 @@ static auto PySetRootUIAccountValues(PyObject* self, PyObject* args,
"chest_3_ad_allow_time",
nullptr};
if (!PyArg_ParseTupleAndKeywords(
args, keywds, "sssssssspssssffffffff", const_cast<char**>(kwlist),
&tickets_text, &tokens_text, &league_rank_text, &league_type,
args, keywds, "iiissssspssssdddddddd", const_cast<char**>(kwlist),
&tickets, &tokens, &league_rank, &league_type,
&achievements_percent_text, &level_text, &xp_text, &inbox_count_text,
&gold_pass, &chest_0_appearance, &chest_1_appearance,
&chest_2_appearance, &chest_3_appearance, &chest_0_unlock_time,
@ -357,20 +356,21 @@ static auto PySetRootUIAccountValues(PyObject* self, PyObject* args,
// Pass these all along to the app-mode which will store them and forward
// them to any existing UI.
appmode->SetRootUITicketsMeterText(tickets_text);
appmode->SetRootUITokensMeterText(tokens_text);
appmode->SetRootUILeagueRankText(league_rank_text);
appmode->SetRootUITicketsMeterValue(tickets);
appmode->SetRootUITokensMeterValue(tokens);
appmode->SetRootUILeagueRankValue(league_rank);
appmode->SetRootUILeagueType(league_type);
appmode->SetRootUIAchievementsPercentText(achievements_percent_text);
appmode->SetRootUILevelText(level_text);
appmode->SetRootUIXPText(xp_text);
appmode->SetRootUIInboxCountText(inbox_count_text);
appmode->SetRootUIGoldPass(gold_pass);
appmode->SetRootUIChests(chest_0_appearance, chest_1_appearance,
chest_2_appearance, chest_3_appearance);
appmode->SetRootUIChests(
chest_0_appearance, chest_1_appearance, chest_2_appearance,
chest_3_appearance, chest_0_unlock_time, chest_1_unlock_time,
chest_2_unlock_time, chest_3_unlock_time, chest_0_ad_allow_time,
chest_1_ad_allow_time, chest_2_ad_allow_time, chest_3_ad_allow_time);
printf("WOULD SET TIMES TO %.2f %.2f\n", chest_0_unlock_time,
chest_0_ad_allow_time);
Py_RETURN_NONE;
BA_PYTHON_CATCH;
}
@ -381,9 +381,9 @@ static PyMethodDef PySetRootUIAccountValuesDef = {
METH_VARARGS | METH_KEYWORDS, // flags
"set_root_ui_account_values(*,\n"
" tickets_text: str,\n"
" tokens_text: str,\n"
" league_rank_text: str,\n"
" tickets: int,\n"
" tokens: int,\n"
" league_rank: int,\n"
" league_type: str,\n"
" achievements_percent_text: str,\n"
" level_text: str,\n"

View File

@ -149,10 +149,10 @@ void ClassicAppMode::Reset_() {
// At this point uiv1 is in a reset-to-default state. Now plug in our
// current values for everything.
if (auto* root_widget = uiv1_->root_widget()) {
root_widget->SetTicketsMeterText(root_ui_tickets_meter_text_);
root_widget->SetTokensMeterText(root_ui_tokens_meter_text_,
root_widget->SetTicketsMeterValue(root_ui_tickets_meter_value_);
root_widget->SetTokensMeterValue(root_ui_tokens_meter_value_,
root_ui_gold_pass_);
root_widget->SetLeagueRankText(root_ui_league_rank_text_);
root_widget->SetLeagueRankValue(root_ui_league_rank_value_);
root_widget->SetLeagueType(root_ui_league_type_);
root_widget->SetAchievementPercentText(root_ui_achievement_percent_text_);
root_widget->SetLevelText(root_ui_level_text_);
@ -160,7 +160,11 @@ void ClassicAppMode::Reset_() {
root_widget->SetInboxCountText(root_ui_inbox_count_text_);
root_widget->SetChests(
root_ui_chest_0_appearance_, root_ui_chest_1_appearance_,
root_ui_chest_2_appearance_, root_ui_chest_3_appearance_);
root_ui_chest_2_appearance_, root_ui_chest_3_appearance_,
root_ui_chest_0_unlock_time_, root_ui_chest_1_unlock_time_,
root_ui_chest_2_unlock_time_, root_ui_chest_3_unlock_time_,
root_ui_chest_0_ad_allow_time_, root_ui_chest_1_ad_allow_time_,
root_ui_chest_2_ad_allow_time_, root_ui_chest_3_ad_allow_time_);
root_widget->SetHaveLiveValues(root_ui_have_live_values_);
}
}
@ -332,7 +336,7 @@ void ClassicAppMode::HostScanCycle() {
&((reinterpret_cast<sockaddr_in*>(&from))->sin_addr),
buffer2, sizeof(buffer2));
entry.last_query_id = query_id;
entry.last_contact_time = g_core->GetAppTimeMillisecs();
entry.last_contact_time = g_core->AppTimeMillisecs();
}
}
PruneScanResults_();
@ -357,7 +361,7 @@ void ClassicAppMode::EndHostScanning() {
}
void ClassicAppMode::PruneScanResults_() {
millisecs_t t = g_core->GetAppTimeMillisecs();
millisecs_t t = g_core->AppTimeMillisecs();
auto i = scan_results_.begin();
while (i != scan_results_.end()) {
auto i_next = i;
@ -516,8 +520,8 @@ auto ClassicAppMode::GetHeadlessNextDisplayTimeStep() -> microsecs_t {
void ClassicAppMode::StepDisplayTime() {
assert(g_base->InLogicThread());
auto startms{core::CorePlatform::GetCurrentMillisecs()};
millisecs_t app_time = g_core->GetAppTimeMillisecs();
auto startms{core::CorePlatform::TimeMonotonicMillisecs()};
millisecs_t app_time = g_core->AppTimeMillisecs();
g_core->platform->SetDebugKey("LastUpdateTime", std::to_string(startms));
in_update_ = true;
@ -592,7 +596,7 @@ void ClassicAppMode::StepDisplayTime() {
// Report excessively long updates.
if (g_core->core_config().debug_timing
&& app_time >= next_long_update_report_time_) {
auto duration{core::CorePlatform::GetCurrentMillisecs() - startms};
auto duration{core::CorePlatform::TimeMonotonicMillisecs() - startms};
// Complain when our full update takes longer than 1/60th second.
if (duration > (1000 / 60)) {
@ -762,7 +766,7 @@ void ClassicAppMode::UpdateKickVote_() {
kick_vote_in_progress_ = false;
return;
}
millisecs_t current_time{g_core->GetAppTimeMillisecs()};
millisecs_t current_time{g_core->AppTimeMillisecs()};
int total_client_count = 0;
int yes_votes = 0;
int no_votes = 0;
@ -859,7 +863,7 @@ void ClassicAppMode::UpdateKickVote_() {
void ClassicAppMode::StartKickVote(scene_v1::ConnectionToClient* starter,
scene_v1::ConnectionToClient* target) {
// Restrict votes per client.
millisecs_t current_time = g_core->GetAppTimeMillisecs();
millisecs_t current_time = g_core->AppTimeMillisecs();
if (starter == target) {
// Don't let anyone kick themselves.
@ -1413,7 +1417,7 @@ auto ClassicAppMode::ShouldAnnouncePartyJoinsAndLeaves() -> bool {
}
auto ClassicAppMode::IsPlayerBanned(const scene_v1::PlayerSpec& spec) -> bool {
millisecs_t current_time = g_core->GetAppTimeMillisecs();
millisecs_t current_time = g_core->AppTimeMillisecs();
// Now is a good time to prune no-longer-banned specs.
while (!banned_players_.empty()
@ -1431,7 +1435,7 @@ auto ClassicAppMode::IsPlayerBanned(const scene_v1::PlayerSpec& spec) -> bool {
void ClassicAppMode::BanPlayer(const scene_v1::PlayerSpec& spec,
millisecs_t duration) {
banned_players_.emplace_back(g_core->GetAppTimeMillisecs() + duration, spec);
banned_players_.emplace_back(g_core->AppTimeMillisecs() + duration, spec);
}
void ClassicAppMode::HandleQuitOnIdle_() {
@ -1550,51 +1554,51 @@ void ClassicAppMode::RunMainMenu() {
}
}
void ClassicAppMode::SetRootUITicketsMeterText(const std::string text) {
void ClassicAppMode::SetRootUITicketsMeterValue(int value) {
BA_PRECONDITION(g_base->InLogicThread());
if (text == root_ui_tickets_meter_text_) {
if (value == root_ui_tickets_meter_value_) {
return;
}
// Store the value.
root_ui_tickets_meter_text_ = text;
root_ui_tickets_meter_value_ = value;
// Apply it to any existing UI.
if (uiv1_) {
if (auto* root_widget = uiv1_->root_widget()) {
root_widget->SetTicketsMeterText(root_ui_tickets_meter_text_);
root_widget->SetTicketsMeterValue(root_ui_tickets_meter_value_);
}
}
}
void ClassicAppMode::SetRootUITokensMeterText(const std::string text) {
void ClassicAppMode::SetRootUITokensMeterValue(int value) {
BA_PRECONDITION(g_base->InLogicThread());
if (text == root_ui_tokens_meter_text_) {
if (value == root_ui_tokens_meter_value_) {
return;
}
// Store the value.
root_ui_tokens_meter_text_ = text;
root_ui_tokens_meter_value_ = value;
// Apply it to any existing UI.
if (uiv1_) {
if (auto* root_widget = uiv1_->root_widget()) {
root_widget->SetTokensMeterText(root_ui_tokens_meter_text_,
root_widget->SetTokensMeterValue(root_ui_tokens_meter_value_,
root_ui_gold_pass_);
}
}
}
void ClassicAppMode::SetRootUILeagueRankText(const std::string text) {
void ClassicAppMode::SetRootUILeagueRankValue(int value) {
BA_PRECONDITION(g_base->InLogicThread());
if (text == root_ui_league_rank_text_) {
if (value == root_ui_league_rank_value_) {
return;
}
// Store the value.
root_ui_league_rank_text_ = text;
root_ui_league_rank_value_ = value;
// Apply it to any existing UI.
if (uiv1_) {
if (auto* root_widget = uiv1_->root_widget()) {
root_widget->SetLeagueRankText(root_ui_league_rank_text_);
root_widget->SetLeagueRankValue(root_ui_league_rank_value_);
}
}
}
@ -1694,7 +1698,7 @@ void ClassicAppMode::SetRootUIGoldPass(bool enabled) {
// Apply it to any existing UI.
if (uiv1_) {
if (auto* root_widget = uiv1_->root_widget()) {
root_widget->SetTokensMeterText(root_ui_tokens_meter_text_,
root_widget->SetTokensMeterValue(root_ui_tokens_meter_value_,
root_ui_gold_pass_);
}
}
@ -1716,15 +1720,28 @@ void ClassicAppMode::SetRootUIHaveLiveValues(bool have_live_values) {
}
}
void ClassicAppMode::SetRootUIChests(const std::string& chest_0_appearance,
void ClassicAppMode::SetRootUIChests(
const std::string& chest_0_appearance,
const std::string& chest_1_appearance,
const std::string& chest_2_appearance,
const std::string& chest_3_appearance) {
const std::string& chest_3_appearance, seconds_t chest_0_unlock_time,
seconds_t chest_1_unlock_time, seconds_t chest_2_unlock_time,
seconds_t chest_3_unlock_time, seconds_t chest_0_ad_allow_time,
seconds_t chest_1_ad_allow_time, seconds_t chest_2_ad_allow_time,
seconds_t chest_3_ad_allow_time) {
BA_PRECONDITION(g_base->InLogicThread());
if (chest_0_appearance == root_ui_chest_0_appearance_
&& chest_1_appearance == root_ui_chest_1_appearance_
&& chest_2_appearance == root_ui_chest_2_appearance_
&& chest_3_appearance == root_ui_chest_3_appearance_) {
&& chest_3_appearance == root_ui_chest_3_appearance_
&& chest_0_unlock_time == root_ui_chest_0_unlock_time_
&& chest_1_unlock_time == root_ui_chest_1_unlock_time_
&& chest_2_unlock_time == root_ui_chest_2_unlock_time_
&& chest_3_unlock_time == root_ui_chest_3_unlock_time_
&& chest_0_ad_allow_time == root_ui_chest_0_ad_allow_time_
&& chest_1_ad_allow_time == root_ui_chest_1_ad_allow_time_
&& chest_2_ad_allow_time == root_ui_chest_2_ad_allow_time_
&& chest_3_ad_allow_time == root_ui_chest_3_ad_allow_time_) {
return;
}
@ -1733,13 +1750,25 @@ void ClassicAppMode::SetRootUIChests(const std::string& chest_0_appearance,
root_ui_chest_1_appearance_ = chest_1_appearance;
root_ui_chest_2_appearance_ = chest_2_appearance;
root_ui_chest_3_appearance_ = chest_3_appearance;
root_ui_chest_0_unlock_time_ = chest_0_unlock_time;
root_ui_chest_1_unlock_time_ = chest_1_unlock_time;
root_ui_chest_2_unlock_time_ = chest_2_unlock_time;
root_ui_chest_3_unlock_time_ = chest_3_unlock_time;
root_ui_chest_0_ad_allow_time_ = chest_0_ad_allow_time;
root_ui_chest_1_ad_allow_time_ = chest_1_ad_allow_time;
root_ui_chest_2_ad_allow_time_ = chest_2_ad_allow_time;
root_ui_chest_3_ad_allow_time_ = chest_3_ad_allow_time;
// Apply it to any existing UI.
if (uiv1_) {
if (auto* root_widget = uiv1_->root_widget()) {
root_widget->SetChests(
root_ui_chest_0_appearance_, root_ui_chest_1_appearance_,
root_ui_chest_2_appearance_, root_ui_chest_3_appearance_);
root_ui_chest_2_appearance_, root_ui_chest_3_appearance_,
root_ui_chest_0_unlock_time_, root_ui_chest_1_unlock_time_,
root_ui_chest_2_unlock_time_, root_ui_chest_3_unlock_time_,
root_ui_chest_0_ad_allow_time_, root_ui_chest_1_ad_allow_time_,
root_ui_chest_2_ad_allow_time_, root_ui_chest_3_ad_allow_time_);
}
}
}

View File

@ -215,19 +215,24 @@ class ClassicAppMode : public base::AppMode {
public_party_public_address_ipv6_ = val;
}
void SetRootUITicketsMeterText(const std::string text);
void SetRootUITokensMeterText(const std::string text);
void SetRootUILeagueRankText(const std::string text);
void SetRootUITicketsMeterValue(int value);
void SetRootUITokensMeterValue(int value);
void SetRootUILeagueRankValue(int value);
void SetRootUILeagueType(const std::string text);
void SetRootUIAchievementsPercentText(const std::string text);
void SetRootUILevelText(const std::string text);
void SetRootUIXPText(const std::string text);
void SetRootUIInboxCountText(const std::string text);
void SetRootUIGoldPass(bool enabled);
void SetRootUIChests(const std::string& chest_0_appearance,
void SetRootUIChests(
const std::string& chest_0_appearance,
const std::string& chest_1_appearance,
const std::string& chest_2_appearance,
const std::string& chest_3_appearance);
const std::string& chest_3_appearance, seconds_t chest_0_unlock_time,
seconds_t chest_1_unlock_time, seconds_t chest_2_unlock_time,
seconds_t chest_3_unlock_time, seconds_t chest_0_ad_allow_time,
seconds_t chest_1_ad_allow_time, seconds_t chest_2_ad_allow_time,
seconds_t chest_3_ad_allow_time);
void SetRootUIHaveLiveValues(bool val);
private:
@ -250,6 +255,15 @@ class ClassicAppMode : public base::AppMode {
std::string root_ui_chest_1_appearance_;
std::string root_ui_chest_2_appearance_;
std::string root_ui_chest_3_appearance_;
seconds_t root_ui_chest_0_unlock_time_;
seconds_t root_ui_chest_1_unlock_time_;
seconds_t root_ui_chest_2_unlock_time_;
seconds_t root_ui_chest_3_unlock_time_;
seconds_t root_ui_chest_0_ad_allow_time_;
seconds_t root_ui_chest_1_ad_allow_time_;
seconds_t root_ui_chest_2_ad_allow_time_;
seconds_t root_ui_chest_3_ad_allow_time_;
uint32_t next_scan_query_id_{};
int scan_socket_{-1};
int host_protocol_version_{-1};
@ -301,6 +315,9 @@ class ClassicAppMode : public base::AppMode {
int public_party_max_size_{8};
int public_party_player_count_{0};
int public_party_max_player_count_{8};
int root_ui_tickets_meter_value_;
int root_ui_tokens_meter_value_;
int root_ui_league_rank_value_;
float debug_speed_mult_{1.0f};
float replay_speed_mult_{1.0f};
std::set<std::string> admin_public_ids_;
@ -308,9 +325,6 @@ class ClassicAppMode : public base::AppMode {
std::string public_party_name_;
std::string public_party_min_league_;
std::string public_party_stats_url_;
std::string root_ui_tickets_meter_text_;
std::string root_ui_tokens_meter_text_;
std::string root_ui_league_rank_text_;
std::string root_ui_league_type_;
std::string root_ui_achievement_percent_text_;
std::string root_ui_level_text_;

View File

@ -53,7 +53,7 @@ void StressTest::ProcessInputs(int player_count) {
assert(g_base->InLogicThread());
assert(player_count >= 0);
millisecs_t time = g_core->GetAppTimeMillisecs();
millisecs_t time = g_core->AppTimeMillisecs();
// FIXME: If we don't check for stress_test_last_leave_time_ we totally
// confuse the game.. need to be able to survive that.

View File

@ -72,7 +72,7 @@ auto CoreFeatureSet::Import(const CoreConfig* config) -> CoreFeatureSet* {
}
void CoreFeatureSet::DoImport_(const CoreConfig& config) {
millisecs_t start_millisecs = CorePlatform::GetCurrentMillisecs();
millisecs_t start_millisecs = CorePlatform::TimeMonotonicMillisecs();
assert(g_core == nullptr);
g_core = new CoreFeatureSet(config);
@ -87,7 +87,7 @@ CoreFeatureSet::CoreFeatureSet(CoreConfig config)
python{new CorePython()},
platform{CorePlatform::Create()},
core_config_{std::move(config)},
last_app_time_measure_microsecs_{CorePlatform::GetCurrentMicrosecs()},
last_app_time_measure_microsecs_{CorePlatform::TimeMonotonicMicrosecs()},
vr_mode_{config.vr_mode} {
// We're a singleton. If there's already one of us, something's wrong.
assert(g_core == nullptr);
@ -313,7 +313,7 @@ auto CoreFeatureSet::SoftImportBase() -> BaseSoftInterface* {
// // We include time-since-start as part of the message here.
// char buffer[128];
// snprintf(buffer, sizeof(buffer), "%s @ %.3fs.", msg,
// g_core->GetAppTimeSeconds() + offset_seconds);
// g_core->AppTimeSeconds() + offset_seconds);
// Log(LogName::kBaLifecycle, LogLevel::kDebug, buffer);
// } else {
// Log(LogName::kBaLifecycle, LogLevel::kDebug, msg);
@ -355,23 +355,23 @@ static void WaitThenDie(millisecs_t wait, const std::string& action) {
FatalError("Timed out waiting for " + action + ".");
}
auto CoreFeatureSet::GetAppTimeMillisecs() -> millisecs_t {
auto CoreFeatureSet::AppTimeMillisecs() -> millisecs_t {
UpdateAppTime_();
return app_time_microsecs_ / 1000;
}
auto CoreFeatureSet::GetAppTimeMicrosecs() -> microsecs_t {
auto CoreFeatureSet::AppTimeMicrosecs() -> microsecs_t {
UpdateAppTime_();
return app_time_microsecs_;
}
auto CoreFeatureSet::GetAppTimeSeconds() -> seconds_t {
auto CoreFeatureSet::AppTimeSeconds() -> seconds_t {
UpdateAppTime_();
return static_cast<seconds_t>(app_time_microsecs_) / 1000000;
}
void CoreFeatureSet::UpdateAppTime_() {
microsecs_t t = CorePlatform::GetCurrentMicrosecs();
microsecs_t t = CorePlatform::TimeMonotonicMicrosecs();
// If we're at a different time than our last query, do our funky math.
if (t != last_app_time_measure_microsecs_) {

View File

@ -73,21 +73,21 @@ class CoreFeatureSet {
/// App-time is basically the total time that the engine has been actively
/// running. (The 'App' here is a slight misnomer). It will stop
/// progressing while the app is suspended and will never go backwards.
auto GetAppTimeMillisecs() -> millisecs_t;
auto AppTimeMillisecs() -> millisecs_t;
/// Return current app-time in microseconds.
///
/// App-time is basically the total time that the engine has been actively
/// running. (The 'App' here is a slight misnomer). It will stop
/// progressing while the app is suspended and will never go backwards.
auto GetAppTimeMicrosecs() -> microsecs_t;
auto AppTimeMicrosecs() -> microsecs_t;
/// Return current app-time in seconds.
///
/// App-time is basically the total time that the engine has been actively
/// running. (The 'App' here is a slight misnomer). It will stop
/// progressing while the app is suspended and will never go backwards.
auto GetAppTimeSeconds() -> seconds_t;
auto AppTimeSeconds() -> seconds_t;
/// Are we in the 'main' thread? The thread that first inited Core is
/// considered the 'main' thread; on most platforms it is the one where

View File

@ -116,7 +116,8 @@ void CorePlatform::LowLevelDebugLog(const std::string& msg) {
HandleLowLevelDebugLog(msg);
}
CorePlatform::CorePlatform() : start_time_millisecs_(GetCurrentMillisecs()) {}
CorePlatform::CorePlatform()
: start_time_millisecs_(TimeMonotonicMillisecs()) {}
void CorePlatform::PostInit() {
// Hmm; we seem to get some funky invalid utf8 out of
@ -960,8 +961,8 @@ auto CorePlatform::SetSocketNonBlocking(int sd) -> bool {
#endif
}
auto CorePlatform::GetTicks() const -> millisecs_t {
return GetCurrentMillisecs() - start_time_millisecs_;
auto CorePlatform::TimeSinceLaunchMillisecs() const -> millisecs_t {
return TimeMonotonicMillisecs() - start_time_millisecs_;
}
auto CorePlatform::GetPlatformName() -> std::string {
@ -1072,27 +1073,27 @@ void CorePlatform::SetDebugKey(const std::string& key,
void CorePlatform::HandleLowLevelDebugLog(const std::string& msg) {}
auto CorePlatform::GetCurrentMillisecs() -> millisecs_t {
auto CorePlatform::TimeMonotonicMillisecs() -> millisecs_t {
return std::chrono::time_point_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now())
.time_since_epoch()
.count();
}
auto CorePlatform::GetCurrentMicrosecs() -> millisecs_t {
auto CorePlatform::TimeMonotonicMicrosecs() -> millisecs_t {
return std::chrono::time_point_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now())
.time_since_epoch()
.count();
}
auto CorePlatform::GetSecondsSinceEpoch() -> double {
auto CorePlatform::TimeSinceEpochSeconds() -> double {
return std::chrono::duration<double>(
std::chrono::system_clock::now().time_since_epoch())
.count();
}
auto CorePlatform::GetCurrentWholeSeconds() -> int64_t {
auto CorePlatform::TimeMonotonicWholeSeconds() -> int64_t {
return std::chrono::time_point_cast<std::chrono::seconds>(
std::chrono::steady_clock::now())
.time_since_epoch()

View File

@ -346,31 +346,31 @@ class CorePlatform {
/// monotonic. For most purposes, AppTime values are preferable since
/// their progression pauses during app suspension and they are 100%
/// guaranteed to not go backwards.
auto GetTicks() const -> millisecs_t;
auto TimeSinceLaunchMillisecs() const -> millisecs_t;
/// Return a raw current milliseconds value. It *should* be monotonic. It
/// is relative to an undefined start point; only use it for time
/// differences. Generally the AppTime values are preferable since their
/// progression pauses during app suspension and they are 100% guaranteed
/// to not go backwards.
static auto GetCurrentMillisecs() -> millisecs_t;
static auto TimeMonotonicMillisecs() -> millisecs_t;
/// Return a raw current microseconds value. It *should* be monotonic. It
/// is relative to an undefined start point; only use it for time
/// differences. Generally the AppTime values are preferable since their
/// progression pauses during app suspension and they are 100% guaranteed
/// to not go backwards.
static auto GetCurrentMicrosecs() -> microsecs_t;
static auto TimeMonotonicMicrosecs() -> microsecs_t;
/// Return a raw current seconds integer value. It *should* be monotonic.
/// It is relative to an undefined start point; only use it for time
/// differences. Generally the AppTime values are preferable since their
/// progression pauses during app suspension and they are 100% guaranteed
/// to not go backwards.
static auto GetCurrentWholeSeconds() -> int64_t;
static auto TimeMonotonicWholeSeconds() -> int64_t;
/// Return seconds since the epoch; same as Python's time.time().
static auto GetSecondsSinceEpoch() -> double;
static auto TimeSinceEpochSeconds() -> double;
static void SleepSeconds(seconds_t duration);
static void SleepMillisecs(millisecs_t duration);

View File

@ -10,8 +10,8 @@
namespace ballistica::core {
/// Collection of low level options for a run of the engine; passed
/// when initing the core feature-set.
/// A collection of low level options for a run of the engine; passed when
/// initing the core feature-set.
class CoreConfig {
public:
static auto ForArgsAndEnvVars(int argc, char** argv) -> CoreConfig;

View File

@ -34,7 +34,7 @@ const int kPingMeasureInterval = 2000;
Connection::Connection() {
// NOLINTNEXTLINE(cppcoreguidelines-prefer-member-initializer)
creation_time_ = last_average_update_time_ = g_core->GetAppTimeMillisecs();
creation_time_ = last_average_update_time_ = g_core->AppTimeMillisecs();
}
void Connection::ProcessWaitingMessages() {
@ -181,13 +181,13 @@ void Connection::HandleGamePacket(const std::vector<uint8_t>& data) {
"Error: got invalid BA_SCENEPACKET_KEEPALIVE packet.");
return;
}
millisecs_t real_time = g_core->GetAppTimeMillisecs();
millisecs_t real_time = g_core->AppTimeMillisecs();
HandleResends(real_time, data, 1);
break;
}
case BA_SCENEPACKET_MESSAGE: {
millisecs_t real_time = g_core->GetAppTimeMillisecs();
millisecs_t real_time = g_core->AppTimeMillisecs();
// Expect 1 byte type, 2 byte num, 3 byte acks, at least 1 byte payload.
if (data.size() < 7) {
@ -211,7 +211,7 @@ void Connection::HandleGamePacket(const std::vector<uint8_t>& data) {
ReliableMessageIn& msg(in_messages_[num]);
msg.data.resize(data.size() - 6);
memcpy(&(msg.data[0]), &(data[6]), msg.data.size());
msg.arrival_time = g_core->GetAppTimeMillisecs();
msg.arrival_time = g_core->AppTimeMillisecs();
// Now run all in-order packets we've got.
ProcessWaitingMessages();
@ -308,7 +308,7 @@ void Connection::SendReliableMessage(const std::vector<uint8_t>& data) {
assert(out_messages_.find(num) == out_messages_.end());
ReliableMessageOut& msg(out_messages_[num]);
millisecs_t real_time = g_core->GetAppTimeMillisecs();
millisecs_t real_time = g_core->AppTimeMillisecs();
msg.data = data;
msg.first_send_time = msg.last_send_time = real_time;
@ -341,7 +341,7 @@ void Connection::SendUnreliableMessage(const std::vector<uint8_t>& data) {
}
uint16_t num = next_out_unreliable_message_num_++;
millisecs_t real_time = g_core->GetAppTimeMillisecs();
millisecs_t real_time = g_core->AppTimeMillisecs();
// Add our header/acks and go ahead and send this one out.
// 1 byte for type, 2 for packet-num, 2 for unreliable packet-num, 3 for acks.
@ -367,7 +367,7 @@ void Connection::SendJMessage(cJSON* val) {
}
void Connection::Update() {
millisecs_t real_time = g_core->GetAppTimeMillisecs();
millisecs_t real_time = g_core->AppTimeMillisecs();
// Update our averages once per second.
while (real_time - last_average_update_time_ > 1000) {

View File

@ -95,7 +95,7 @@ ConnectionToClient::~ConnectionToClient() {
void ConnectionToClient::Update() {
Connection::Update(); // Handles common stuff.
millisecs_t real_time = g_core->GetAppTimeMillisecs();
millisecs_t real_time = g_core->AppTimeMillisecs();
// If we're waiting for handshake response still, keep sending out handshake
// attempts.
@ -246,7 +246,7 @@ void ConnectionToClient::HandleGamePacket(const std::vector<uint8_t>& data) {
// Don't allow fresh clients to start kick votes for a while.
next_kick_vote_allow_time_ =
g_core->GetAppTimeMillisecs() + kNewClientKickVoteDelay;
g_core->AppTimeMillisecs() + kNewClientKickVoteDelay;
// At this point we have their name, so lets announce their arrival.
if (appmode->ShouldAnnouncePartyJoinsAndLeaves()) {
@ -263,7 +263,7 @@ void ConnectionToClient::HandleGamePacket(const std::vector<uint8_t>& data) {
// Also mark the time for flashing the 'someone just joined your
// party' message in the corner.
appmode->set_last_connection_to_client_join_time(
g_core->GetAppTimeMillisecs());
g_core->AppTimeMillisecs());
// Added midway through protocol 29:
// We now send a json dict of info about ourself first thing. This
@ -338,8 +338,7 @@ void ConnectionToClient::SendScreenMessage(const std::string& s, float r,
// Older clients don't support the screen-message message, so in that case
// we just send it as a chat-message from <HOST>.
if (build_number() < 14248) {
std::string value =
g_base->assets->CompileResourceString(s, "sendScreenMessage");
std::string value = g_base->assets->CompileResourceString(s);
std::string our_spec_string =
PlayerSpec::GetDummyPlayerSpec("<HOST>").GetSpecString();
std::vector<uint8_t> msg_out(1 + 1 + our_spec_string.size() + value.size());
@ -500,7 +499,7 @@ void ConnectionToClient::HandleMessagePacket(
case BA_MESSAGE_CHAT: {
// We got a chat message from a client.
millisecs_t now = g_core->GetAppTimeMillisecs();
millisecs_t now = g_core->AppTimeMillisecs();
// Ignore this if they're chat blocked.
if (now >= chat_block_time_) {
@ -636,7 +635,7 @@ void ConnectionToClient::HandleMessagePacket(
}
case BA_MESSAGE_REMOVE_REMOTE_PLAYER: {
last_remove_player_time_ = g_core->GetAppTimeMillisecs();
last_remove_player_time_ = g_core->AppTimeMillisecs();
if (buffer.size() != 2) {
g_core->Log(LogName::kBaNetworking, LogLevel::kError,
"Error: invalid remove-remote-player packet");
@ -693,7 +692,7 @@ void ConnectionToClient::HandleMessagePacket(
// master-server info for this client, delay their join (we'll
// eventually give up and just give them a blank slate).
if (still_waiting_for_auth
&& (g_core->GetAppTimeMillisecs() - creation_time() < 10000)) {
&& (g_core->AppTimeMillisecs() - creation_time() < 10000)) {
SendScreenMessage(
"{\"v\":\"${A}...\",\"s\":[[\"${A}\",{\"r\":"
"\"loadingTryAgainText\",\"f\":\"loadingText\"}]]}",

View File

@ -58,7 +58,7 @@ ConnectionToHost::~ConnectionToHost() {
}
void ConnectionToHost::Update() {
millisecs_t real_time = g_core->GetAppTimeMillisecs();
millisecs_t real_time = g_core->AppTimeMillisecs();
// Send out null messages occasionally for ping measurement purposes.
// Note that we currently only do this from the client since we might not

View File

@ -514,7 +514,7 @@ void Dynamics::ProcessCollision_() {
void Dynamics::Process() {
in_process_ = true;
// Update this once so we can recycle results.
real_time_ = g_core->GetAppTimeMillisecs();
real_time_ = g_core->AppTimeMillisecs();
ProcessCollision_();
dWorldQuickStep(ode_world_, kGameStepSeconds);
dJointGroupEmpty(ode_contact_group_);

View File

@ -27,7 +27,7 @@ class GlobalsNodeType : public NodeType {
public:
#define BA_NODE_TYPE_CLASS GlobalsNode
BA_NODE_CREATE_CALL(CreateGlobals);
BA_INT64_ATTR_READONLY(real_time, GetAppTimeMillisecs);
BA_INT64_ATTR_READONLY(real_time, AppTimeMillisecs);
BA_INT64_ATTR_READONLY(time, GetTime);
BA_INT64_ATTR_READONLY(step, GetStep);
BA_FLOAT_ATTR(debris_friction, debris_friction, SetDebrisFriction);
@ -206,7 +206,7 @@ auto GlobalsNode::IsCurrentGlobals() const -> bool {
&& scene->globals_node() == this);
}
auto GlobalsNode::GetAppTimeMillisecs() -> millisecs_t {
auto GlobalsNode::AppTimeMillisecs() -> millisecs_t {
// Pull this from our scene so we return consistent values throughout a step.
return scene()->last_step_real_time();
}

View File

@ -17,7 +17,7 @@ class GlobalsNode : public Node {
~GlobalsNode() override;
void SetAsForeground();
auto IsCurrentGlobals() const -> bool;
auto GetAppTimeMillisecs() -> millisecs_t;
auto AppTimeMillisecs() -> millisecs_t;
auto GetTime() -> millisecs_t;
auto GetStep() -> int64_t;
auto debris_friction() const -> float { return debris_friction_; }

View File

@ -12,7 +12,7 @@ class SessionGlobalsNodeType : public NodeType {
public:
#define BA_NODE_TYPE_CLASS SessionGlobalsNode
BA_NODE_CREATE_CALL(CreateSessionGlobals);
BA_INT64_ATTR_READONLY(real_time, GetAppTimeMillisecs);
BA_INT64_ATTR_READONLY(real_time, AppTimeMillisecs);
BA_INT64_ATTR_READONLY(time, GetTime);
BA_INT64_ATTR_READONLY(step, GetStep);
#undef BA_NODE_TYPE_CLASS
@ -38,7 +38,7 @@ SessionGlobalsNode::SessionGlobalsNode(Scene* scene) : Node(scene, node_type) {
SessionGlobalsNode::~SessionGlobalsNode() = default;
auto SessionGlobalsNode::GetAppTimeMillisecs() -> millisecs_t {
auto SessionGlobalsNode::AppTimeMillisecs() -> millisecs_t {
// Pull this from our scene so we return consistent values throughout a step.
return scene()->last_step_real_time();
}

View File

@ -12,7 +12,7 @@ class SessionGlobalsNode : public Node {
static auto InitType() -> NodeType*;
explicit SessionGlobalsNode(Scene* scene);
~SessionGlobalsNode() override;
auto GetAppTimeMillisecs() -> millisecs_t;
auto AppTimeMillisecs() -> millisecs_t;
auto GetTime() -> millisecs_t;
auto GetStep() -> int64_t;
};

View File

@ -142,7 +142,7 @@ void SoundNode::Step() {
}
}
if (positional_ && position_dirty_ && playing_) {
millisecs_t t = g_core->GetAppTimeMillisecs();
millisecs_t t = g_core->AppTimeMillisecs();
if (t - last_position_update_time_ > 100) {
base::AudioSource* s = g_base->audio->SourceBeginExisting(play_id_, 107);
if (s) {

View File

@ -1797,7 +1797,7 @@ void SpazNode::DoFlyPress() {
// Keep from doing too many sparkles.
static millisecs_t last_sparkle_time = 0;
millisecs_t t = g_core->GetAppTimeMillisecs();
millisecs_t t = g_core->AppTimeMillisecs();
if (t - last_sparkle_time > 200) {
last_sparkle_time = t;
auto* s = g_base->audio->SourceBeginNew();

View File

@ -104,7 +104,7 @@ TerrainNode::~TerrainNode() {
// without our reference.
if (collision_mesh_.exists()) {
collision_mesh_->collision_mesh_data()->set_last_used_time(
g_core->GetAppTimeMillisecs());
g_core->AppTimeMillisecs());
}
}
@ -123,7 +123,7 @@ void TerrainNode::set_collision_mesh(SceneCollisionMesh* val) {
// if we had an old one, mark its last-used time so caching works properly..
if (collision_mesh_.exists()) {
collision_mesh_->collision_mesh_data()->set_last_used_time(
g_core->GetAppTimeMillisecs());
g_core->AppTimeMillisecs());
}
collision_mesh_ = val;

View File

@ -117,8 +117,7 @@ void TextNode::SetText(const std::string& val) {
if (do_format_check) {
bool valid;
g_base->assets->CompileResourceString(val, "setText format check",
&valid);
g_base->assets->CompileResourceString(val, &valid);
if (!valid) {
BA_LOG_ONCE(
LogName::kBa, LogLevel::kError,
@ -354,8 +353,7 @@ void TextNode::Draw(base::FrameDef* frame_def) {
// Apply subs/resources to get our actual text if need be.
if (text_translation_dirty_) {
text_translated_ =
g_base->assets->CompileResourceString(text_raw_, "TextNode::OnDraw");
text_translated_ = g_base->assets->CompileResourceString(text_raw_);
text_translation_dirty_ = false;
text_group_dirty_ = true;
text_width_dirty_ = true;

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