mirror of
https://github.com/RYDE-WORK/ballistica.git
synced 2026-01-21 22:33:58 +08:00
Merge branch 'efroemling:master' into master
This commit is contained in:
commit
42e1fdd302
136
.efrocachemap
generated
136
.efrocachemap
generated
@ -421,41 +421,41 @@
|
||||
"build/assets/ba_data/audio/zoeOw.ogg": "74befe45a8417e95b6a2233c51992a26",
|
||||
"build/assets/ba_data/audio/zoePickup01.ogg": "48ab8cddfcde36a750856f3f81dd20c8",
|
||||
"build/assets/ba_data/audio/zoeScream01.ogg": "2b468aedfa8741090247f04eb9e6df55",
|
||||
"build/assets/ba_data/data/langdata.json": "2a2c4783fddc4b24d07b4ce0d8a74393",
|
||||
"build/assets/ba_data/data/languages/arabic.json": "295c559911fa251f401f8cdcad91c226",
|
||||
"build/assets/ba_data/data/languages/belarussian.json": "e151808b6b4f6dc159cf55ee62adad3c",
|
||||
"build/assets/ba_data/data/languages/chinese.json": "0a9d9534e7329d1e886adae6fdc007c4",
|
||||
"build/assets/ba_data/data/langdata.json": "dca47b4976752e76e852f7a4be2c7aa3",
|
||||
"build/assets/ba_data/data/languages/arabic.json": "e0001d8542c904bc3c98a174f256efd9",
|
||||
"build/assets/ba_data/data/languages/belarussian.json": "7fe38341815ca6ff4d95224196e7a67e",
|
||||
"build/assets/ba_data/data/languages/chinese.json": "a87b1ebef100d9cdce0d29123735e90c",
|
||||
"build/assets/ba_data/data/languages/chinesetraditional.json": "f858da49be0a5374157c627857751078",
|
||||
"build/assets/ba_data/data/languages/croatian.json": "766532c67af5bd0144c2d63cab0516fa",
|
||||
"build/assets/ba_data/data/languages/czech.json": "93c5fe0d884d95435da6c675f64e30e0",
|
||||
"build/assets/ba_data/data/languages/danish.json": "3fd69080783d5c9dcc0af737f02b6f1e",
|
||||
"build/assets/ba_data/data/languages/dutch.json": "22b44a33bf81142ba2befad14eb5746e",
|
||||
"build/assets/ba_data/data/languages/english.json": "6fb6ec37e79064edb4b8864eabdd024d",
|
||||
"build/assets/ba_data/data/languages/english.json": "fe32cbe79483153db283e363da039a7a",
|
||||
"build/assets/ba_data/data/languages/esperanto.json": "0e397cfa5f3fb8cef5f4a64f21cda880",
|
||||
"build/assets/ba_data/data/languages/filipino.json": "2efdfb879135b196a272dd47fc2039a2",
|
||||
"build/assets/ba_data/data/languages/french.json": "4e218dcd488fa63e7db5b4da2261b9e1",
|
||||
"build/assets/ba_data/data/languages/filipino.json": "afbda3adf14555e1567ee63c32e340e7",
|
||||
"build/assets/ba_data/data/languages/french.json": "49ff6d211537b8003b8241438dca661d",
|
||||
"build/assets/ba_data/data/languages/german.json": "450fa41ae264f29a5d1af22143d0d0ad",
|
||||
"build/assets/ba_data/data/languages/gibberish.json": "63c6212c774622346f3ad0d87ff31e80",
|
||||
"build/assets/ba_data/data/languages/gibberish.json": "00a9f70bccdfa407043e69a42e5b944c",
|
||||
"build/assets/ba_data/data/languages/greek.json": "287c0ec437b38772284ef9d3e4fb2fc3",
|
||||
"build/assets/ba_data/data/languages/hindi.json": "8ea0c58a44a24edb131d0e53b074d1f6",
|
||||
"build/assets/ba_data/data/languages/hungarian.json": "796a290a8c44a1e7635208c2ff5fdc6e",
|
||||
"build/assets/ba_data/data/languages/indonesian.json": "53961b1484a1831f32bec2cc2941e672",
|
||||
"build/assets/ba_data/data/languages/italian.json": "58ecf53a963dbeca1bbf3605e5ab6a2f",
|
||||
"build/assets/ba_data/data/languages/indonesian.json": "ba39cade3c965b2ddbee122880b6f0db",
|
||||
"build/assets/ba_data/data/languages/italian.json": "b19a9f0b868a6c89e13a04609bf377dd",
|
||||
"build/assets/ba_data/data/languages/korean.json": "ca1122a9ee551da3f75ae632012bd0e2",
|
||||
"build/assets/ba_data/data/languages/malay.json": "832562ce997fc70704b9234c95fb2e38",
|
||||
"build/assets/ba_data/data/languages/persian.json": "a391d80ff58ea22926499e4b19d2c0d0",
|
||||
"build/assets/ba_data/data/languages/polish.json": "7a4a6cb882cf90dad32e6607215525bf",
|
||||
"build/assets/ba_data/data/languages/portuguese.json": "51e362956f89da3eec980f587c092253",
|
||||
"build/assets/ba_data/data/languages/persian.json": "89f5c6f900317aaa20ec520e5fd65d32",
|
||||
"build/assets/ba_data/data/languages/polish.json": "8bb8f7baa5efcf6cdd627140d209c2b2",
|
||||
"build/assets/ba_data/data/languages/portuguese.json": "7374b6a5cadcf573ee4ab4a5b4bc8c0a",
|
||||
"build/assets/ba_data/data/languages/romanian.json": "aeebdd54f65939c2facc6ac50c117826",
|
||||
"build/assets/ba_data/data/languages/russian.json": "561504cca28eb3204ac194950029e565",
|
||||
"build/assets/ba_data/data/languages/russian.json": "9e068a3afdec43b207c83aedfb812e82",
|
||||
"build/assets/ba_data/data/languages/serbian.json": "d7452dd72ac0e51680cb39b5ebaa1c69",
|
||||
"build/assets/ba_data/data/languages/slovak.json": "27962d53dc3f7dd4e877cd40faafeeef",
|
||||
"build/assets/ba_data/data/languages/spanish.json": "5a4dbd505060dd02d634bd4de4d5faab",
|
||||
"build/assets/ba_data/data/languages/swedish.json": "77d671f10613291ebf9c71da66f18a18",
|
||||
"build/assets/ba_data/data/languages/tamil.json": "65ab7798d637fa62a703750179eeb723",
|
||||
"build/assets/ba_data/data/languages/spanish.json": "0fca8361c6873e6445f0bea34a211b78",
|
||||
"build/assets/ba_data/data/languages/swedish.json": "5142a96597d17d8344be96a603da64ac",
|
||||
"build/assets/ba_data/data/languages/tamil.json": "b4de1a2851afe4869c82e9acd94cd89c",
|
||||
"build/assets/ba_data/data/languages/thai.json": "33f63753c9af9a5b238d229a0bf23fbc",
|
||||
"build/assets/ba_data/data/languages/turkish.json": "776d1a0c9ef2333a9110d93558ab19e2",
|
||||
"build/assets/ba_data/data/languages/ukrainian.json": "f72eb51abfbbb56e27866895d7e947d2",
|
||||
"build/assets/ba_data/data/languages/turkish.json": "c3674336e746bf7b23617a6ff296c69a",
|
||||
"build/assets/ba_data/data/languages/ukrainian.json": "e5c861187c4c6db37d1a033f4ef3dd5a",
|
||||
"build/assets/ba_data/data/languages/venetian.json": "9fe1a58d9e5dfb00f31ce3b2eb9993f4",
|
||||
"build/assets/ba_data/data/languages/vietnamese.json": "921cd1e50f60fe3e101f246e172750ba",
|
||||
"build/assets/ba_data/data/maps/big_g.json": "1dd301d490643088a435ce75df971054",
|
||||
@ -1524,9 +1524,9 @@
|
||||
"build/assets/ba_data/textures/fontBig.ktx": "94b56c2488d6c9ebabfbbb740eca07dd",
|
||||
"build/assets/ba_data/textures/fontBig.pvr": "dff3f6c04a8c7b0bb937001640b42c8d",
|
||||
"build/assets/ba_data/textures/fontBig_preview.png": "f8b15cb04f0deca7774def335a72f053",
|
||||
"build/assets/ba_data/textures/fontExtras.dds": "7ab11df1b3a3daa651dfad34219b89f5",
|
||||
"build/assets/ba_data/textures/fontExtras.ktx": "30c3c8ca2cdf1209ff177017bb10f0a8",
|
||||
"build/assets/ba_data/textures/fontExtras.pvr": "fd3b0bd902c30e4b7aa5fe00e1eec4be",
|
||||
"build/assets/ba_data/textures/fontExtras.dds": "0a5a39028853c443cd88bc2492cb6ad9",
|
||||
"build/assets/ba_data/textures/fontExtras.ktx": "5b14075ce3d1d29c6d5635602e2176d8",
|
||||
"build/assets/ba_data/textures/fontExtras.pvr": "8cc68ca85ba327c20c45bad73b000d8c",
|
||||
"build/assets/ba_data/textures/fontExtras2.dds": "18063a12912dadc9528afd90d1cf2369",
|
||||
"build/assets/ba_data/textures/fontExtras2.ktx": "36da7f6cfbfb8d32fb14371de0a8f660",
|
||||
"build/assets/ba_data/textures/fontExtras2.pvr": "7a4e8e64ac05313b1782fb5b958150d0",
|
||||
@ -1539,7 +1539,7 @@
|
||||
"build/assets/ba_data/textures/fontExtras4.ktx": "6d872ac15e2e874c1252f63b4584722b",
|
||||
"build/assets/ba_data/textures/fontExtras4.pvr": "6a0a0a1a8bbbc3ee9d6b8b914e7aa697",
|
||||
"build/assets/ba_data/textures/fontExtras4_preview.png": "363e2647621917b3821c9068267d2516",
|
||||
"build/assets/ba_data/textures/fontExtras_preview.png": "9c9c58aff612e7b6386f3522c0b4f1f6",
|
||||
"build/assets/ba_data/textures/fontExtras_preview.png": "b6503267cc15e9e2524f41fabd94e773",
|
||||
"build/assets/ba_data/textures/fontSmall0.dds": "b30bfe5f9e436be7be8b5eae6e8490c3",
|
||||
"build/assets/ba_data/textures/fontSmall0.ktx": "7e6058f37e6c5a4ea628f35b5f92c227",
|
||||
"build/assets/ba_data/textures/fontSmall0.pvr": "c66e3d6aa1f7def83aaacd8a6c9185e5",
|
||||
@ -4056,53 +4056,53 @@
|
||||
"build/assets/windows/Win32/ucrtbased.dll": "2def5335207d41b21b9823f6805997f1",
|
||||
"build/assets/windows/Win32/vc_redist.x86.exe": "b08a55e2e77623fe657bea24f223a3ae",
|
||||
"build/assets/windows/Win32/vcruntime140d.dll": "865b2af4d1e26a1a8073c89acb06e599",
|
||||
"build/prefab/full/linux_arm64_gui/debug/ballisticakit": "ad13d636bcb25150044a7644846b8a09",
|
||||
"build/prefab/full/linux_arm64_gui/release/ballisticakit": "7c5df955611590ef491bf614fbd60179",
|
||||
"build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "ae38bd212ae64b51482a2ccb9c1cbfd3",
|
||||
"build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "d0bcee2dd5567719aa35667c5206dffc",
|
||||
"build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "eb3cd4f86175afcf8ffa2749afa32fa3",
|
||||
"build/prefab/full/linux_x86_64_gui/release/ballisticakit": "6b86ba36c3719773008feaa6cdc0d0f8",
|
||||
"build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "877c9ae4532fef809a3dcbd8ffea343c",
|
||||
"build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "7df313c48c87460f56fa837502965088",
|
||||
"build/prefab/full/mac_arm64_gui/debug/ballisticakit": "bc40bb549d26437fb8679c1e9d088272",
|
||||
"build/prefab/full/mac_arm64_gui/release/ballisticakit": "47aa08cb9f5e660023f0f3c0e4ffd65e",
|
||||
"build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "bbb0a8383d6ce1ca887190ea49223f4f",
|
||||
"build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "7dd91e3407d49981c1c975d4f01ac205",
|
||||
"build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "c87883aa2f832e792e945fd9208d712a",
|
||||
"build/prefab/full/mac_x86_64_gui/release/ballisticakit": "fea8fd84d8c060f2f82f402902b8c54e",
|
||||
"build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "0a454a8be47f37231655761d15e3f7e5",
|
||||
"build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "4c79db3a882eb0b8b225a8df0339b1cc",
|
||||
"build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "43b9ef321f8e80da29ddb19a760dbd77",
|
||||
"build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "6f891004f2f07c452dea29bd53f29d30",
|
||||
"build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "bf7a1ce0e7a2015d538406c6f6df761c",
|
||||
"build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "cf213dce81901a67c9970b3befdaa320",
|
||||
"build/prefab/lib/linux_arm64_gui/debug/libballisticaplus.a": "473e7e6c0cf90b9e6ac653552b18f68d",
|
||||
"build/prefab/lib/linux_arm64_gui/release/libballisticaplus.a": "4e11b895cbf2e1339cf34bc06c54a4ea",
|
||||
"build/prefab/lib/linux_arm64_server/debug/libballisticaplus.a": "473e7e6c0cf90b9e6ac653552b18f68d",
|
||||
"build/prefab/lib/linux_arm64_server/release/libballisticaplus.a": "4e11b895cbf2e1339cf34bc06c54a4ea",
|
||||
"build/prefab/lib/linux_x86_64_gui/debug/libballisticaplus.a": "d9af1a429cff9346e0cad6fcea017e5b",
|
||||
"build/prefab/lib/linux_x86_64_gui/release/libballisticaplus.a": "ae5f87286947575463c386cfe1c443e4",
|
||||
"build/prefab/lib/linux_x86_64_server/debug/libballisticaplus.a": "d9af1a429cff9346e0cad6fcea017e5b",
|
||||
"build/prefab/lib/linux_x86_64_server/release/libballisticaplus.a": "ae5f87286947575463c386cfe1c443e4",
|
||||
"build/prefab/lib/mac_arm64_gui/debug/libballisticaplus.a": "110eef3dc285a35a1899510e368c73b1",
|
||||
"build/prefab/lib/mac_arm64_gui/release/libballisticaplus.a": "2692dc69f7cb2501f0aaa8675f559987",
|
||||
"build/prefab/lib/mac_arm64_server/debug/libballisticaplus.a": "110eef3dc285a35a1899510e368c73b1",
|
||||
"build/prefab/lib/mac_arm64_server/release/libballisticaplus.a": "2692dc69f7cb2501f0aaa8675f559987",
|
||||
"build/prefab/lib/mac_x86_64_gui/debug/libballisticaplus.a": "344954c4f788d7d9b4d7035ebb6131d8",
|
||||
"build/prefab/lib/mac_x86_64_gui/release/libballisticaplus.a": "48c4873dae2344c1d4092a1d85dab424",
|
||||
"build/prefab/lib/mac_x86_64_server/debug/libballisticaplus.a": "abcede4e60fa8877f18e66e086fb7387",
|
||||
"build/prefab/lib/mac_x86_64_server/release/libballisticaplus.a": "48c4873dae2344c1d4092a1d85dab424",
|
||||
"build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "6149911c660a9864b651cc1a8e50eec1",
|
||||
"build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "57cef68ab703ba819bd0fbe9e4b1c331",
|
||||
"build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "a47bab28b86c7cefce891b8e5c8b687a",
|
||||
"build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "d68ebb1139363d711b044de65e17b204",
|
||||
"build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "ea1349137f64f3d662b9a95278ca4c02",
|
||||
"build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "c8731ff226716cee3d1e46027ead1cfe",
|
||||
"build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "4ee6b633a99c5bcbea4f5dee5bda186e",
|
||||
"build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "2c3bd4952b30d88247229ad309f73092",
|
||||
"build/prefab/full/linux_arm64_gui/debug/ballisticakit": "a2c1b1a31a42a24e5f2188983ff97c0e",
|
||||
"build/prefab/full/linux_arm64_gui/release/ballisticakit": "8d146105a6df3376ec2f575401f29ec2",
|
||||
"build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "5e0e63f5fea108212bfb35c4fdc05dd2",
|
||||
"build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "85797e98f9a21f168a986b7eb3219688",
|
||||
"build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "95bc2213d2fb30e8248ddf06764a3663",
|
||||
"build/prefab/full/linux_x86_64_gui/release/ballisticakit": "f09d7055120d50f37b605ff3a4cbf61d",
|
||||
"build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "8f23de3825b9e9cf6ed4715125aeb956",
|
||||
"build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "f7b88875b37a39db57d77bebb05f822f",
|
||||
"build/prefab/full/mac_arm64_gui/debug/ballisticakit": "0b719dc609de364814299a5baa9a5b0b",
|
||||
"build/prefab/full/mac_arm64_gui/release/ballisticakit": "07aa53835bdd2d2a4113d61a89a7da1d",
|
||||
"build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "1a1c4db580157e38effe4b81c35aca52",
|
||||
"build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "1e44d0a7160d442b1eb4d348952b0d06",
|
||||
"build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "a85fcf6a12483bde66559dbdce88b730",
|
||||
"build/prefab/full/mac_x86_64_gui/release/ballisticakit": "d53e31771fe31615c66bd0d1e1292b2f",
|
||||
"build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "e1804380d5ebec56f8f0af2e2547316e",
|
||||
"build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "d9665cdf870247b6d8e9475419b04038",
|
||||
"build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "0cac36823713816d03a6922b96cecd0a",
|
||||
"build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "bd741122315ced7b703de1fed2d372cd",
|
||||
"build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "f11edd5da3ce1a3e804bfc61b7ca9c1e",
|
||||
"build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "3d647f5f63b3ac3900903cf72496cef2",
|
||||
"build/prefab/lib/linux_arm64_gui/debug/libballisticaplus.a": "f812111f1eb4ea76e99558305ce3980f",
|
||||
"build/prefab/lib/linux_arm64_gui/release/libballisticaplus.a": "c16c1b360b27850f1ac4d3dc55194b0a",
|
||||
"build/prefab/lib/linux_arm64_server/debug/libballisticaplus.a": "f812111f1eb4ea76e99558305ce3980f",
|
||||
"build/prefab/lib/linux_arm64_server/release/libballisticaplus.a": "c16c1b360b27850f1ac4d3dc55194b0a",
|
||||
"build/prefab/lib/linux_x86_64_gui/debug/libballisticaplus.a": "473c84d44b472d3ae73df6a2f1b56839",
|
||||
"build/prefab/lib/linux_x86_64_gui/release/libballisticaplus.a": "970c415cbb7a3087a163fa6e96e634a1",
|
||||
"build/prefab/lib/linux_x86_64_server/debug/libballisticaplus.a": "473c84d44b472d3ae73df6a2f1b56839",
|
||||
"build/prefab/lib/linux_x86_64_server/release/libballisticaplus.a": "970c415cbb7a3087a163fa6e96e634a1",
|
||||
"build/prefab/lib/mac_arm64_gui/debug/libballisticaplus.a": "86498db547380838a1c95b45ef17d28b",
|
||||
"build/prefab/lib/mac_arm64_gui/release/libballisticaplus.a": "bf11ae92718ae3dd5116d623799dd27f",
|
||||
"build/prefab/lib/mac_arm64_server/debug/libballisticaplus.a": "86498db547380838a1c95b45ef17d28b",
|
||||
"build/prefab/lib/mac_arm64_server/release/libballisticaplus.a": "bf11ae92718ae3dd5116d623799dd27f",
|
||||
"build/prefab/lib/mac_x86_64_gui/debug/libballisticaplus.a": "147c5a5210a55978060381cc44a122db",
|
||||
"build/prefab/lib/mac_x86_64_gui/release/libballisticaplus.a": "380d456bc409849174a36c5d2f61aa1b",
|
||||
"build/prefab/lib/mac_x86_64_server/debug/libballisticaplus.a": "4fae9b286a6d1c5c729c49c483c1422a",
|
||||
"build/prefab/lib/mac_x86_64_server/release/libballisticaplus.a": "380d456bc409849174a36c5d2f61aa1b",
|
||||
"build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "93f75d31841fe18d6bc66681c99b05a1",
|
||||
"build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "cd6500ab076fa3bdff92fc8b65bc1d64",
|
||||
"build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "0dcde1146f11e4cfd3d609f4330f7a9a",
|
||||
"build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "663e6ed62de0f857fd9a4de57067dbd3",
|
||||
"build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "ae28db2f400d3b778d4a011a72339757",
|
||||
"build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "3b13cdbaf70cf43d0a14b7a2dd744945",
|
||||
"build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "54798340a1dfedaa9052aeee180e28ff",
|
||||
"build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "18b635d88c074261a3be4f45e8244340",
|
||||
"src/assets/ba_data/python/babase/_mgen/__init__.py": "f885fed7f2ed98ff2ba271f9dbe3391c",
|
||||
"src/assets/ba_data/python/babase/_mgen/enums.py": "28323912b56ec07701eda3d41a6a4101",
|
||||
"src/ballistica/base/mgen/pyembed/binding_base.inc": "6df0f34207346d89a72924249ddd4706",
|
||||
"src/ballistica/base/mgen/pyembed/binding_base.inc": "72bfed2cce8ff19741989dec28302f3f",
|
||||
"src/ballistica/base/mgen/pyembed/binding_base_app.inc": "00f81f9bd92386ec12a6e60170678a98",
|
||||
"src/ballistica/classic/mgen/pyembed/binding_classic.inc": "3ceb412513963f0818ab39c58bf292e3",
|
||||
"src/ballistica/core/mgen/pyembed/binding_core.inc": "9d0a3c9636138e35284923e0c8311c69",
|
||||
|
||||
55
CHANGELOG.md
55
CHANGELOG.md
@ -1,4 +1,4 @@
|
||||
### 1.7.28 (build 21491, api 8, 2023-10-22)
|
||||
### 1.7.28 (build 21583, api 8, 2023-11-09)
|
||||
|
||||
- Massively cleaned up code related to rendering and window systems (OpenGL,
|
||||
SDL, etc). This code had been growing into a nasty tangle for 15 years
|
||||
@ -6,7 +6,7 @@
|
||||
huge chunks of it and put back still-relevant pieces in a much more cleanly
|
||||
designed way. This should put us in a much better place for supporting various
|
||||
platforms and making graphical improvements going forward.
|
||||
`ballistica/base/app_adapter/app_adapter_sdl.cc` for an example of the now
|
||||
`ballistica/base/app_adapter/app_adapter_sdl.cc` is an example of the now
|
||||
nicely implemented system.
|
||||
- The engine now requires OpenGL 3.0 or newer on desktop and OpenGL ES 3.0 or
|
||||
newer on mobile. This means we're cutting off a few percent of old devices on
|
||||
@ -154,6 +154,57 @@
|
||||
seems pretty awesome these days. It should support most stuff SDL does and
|
||||
with less configuring involved. Please holler if you come across something
|
||||
that doesn't work.
|
||||
- Mac build is also now using the Game Controller Framework to handle keyboard
|
||||
events. This should better handle things like modifier keys and also will
|
||||
allow us to use that exact same code on the iPad/iPhone version.
|
||||
- OS key repeat events are no longer passed through the engine. This means that
|
||||
any time we want repeating behavior, such as holding an arrow key to move
|
||||
through UI elements, we will need to wire it up ourselves. We already do this
|
||||
for things like game controllers however, so this is more consistent in a way.
|
||||
- Dev console no longer claims key events unless the Python tab is showing and
|
||||
there is a hardware keyboard attached. This allows showing dev console tabs
|
||||
above gameplay without interfering with it.
|
||||
- Added clipboard paste support to the dev console python terminal.
|
||||
- Added various text editing functionality to the dev console python terminal
|
||||
(cursor movement, deleting chars and words, etc.)
|
||||
- Internal on-screen-keyboard now has a cancel button (thanks vishal332008!)
|
||||
- Public servers list now shows 'No servers found' if there are no servers to
|
||||
show instead of just remaining mysteriously blank (thanks vishal332008!)
|
||||
- Players are now prevented from rejoining a session for 10 seconds after they
|
||||
leave to prevent game exploits. Note this is different than the existing
|
||||
system that prevents joining a *party* for 10 seconds; this covers people
|
||||
who never leave the party (Thanks EraOSBeta!).
|
||||
- Fixes an issue where servers could be crashed by flooding them with join
|
||||
requests (Thanks for the heads-up Era!).
|
||||
- The engine will now ignore empty device config dicts and fall back to
|
||||
defaults; these could theoretically happen if device config code fails
|
||||
somewhere and it previously would leave the device mysteriously inoperable.
|
||||
- The game will now show <unset> for controls with no bindings in the in-game
|
||||
guide and controller/keyboard config screens.
|
||||
- Fixed a crash that could occur if SDL couldn't find a name for connected
|
||||
joystick.
|
||||
- Simplified the app's handling of broken config files. Previously it would do
|
||||
various complex things such as offering to edit the broken config on desktop
|
||||
builds, avoiding overwriting broken configs, and automatically loading
|
||||
previous configs. Now, if it finds a broken config, it will simply back it up
|
||||
to a .broken file, log an error message, and then start up normally with a
|
||||
default config. This way, things are more consistent across platforms, and
|
||||
technical users can still fix and restore their old configs. Note that the app
|
||||
still also writes .prev configs for extra security, though it no longer uses
|
||||
them for anything itself.
|
||||
- Converted more internal engine time values from milliseconds to microseconds,
|
||||
including things like the internal EventLoop timeline. Please holler if you
|
||||
notice anything running 1000x too fast or slow. In general my strategy going
|
||||
forward is to use microseconds for exact internal time values but to mostly
|
||||
expose float seconds to the user, especially on the Python layer. There were
|
||||
starting to be a few cases were integer milliseconds was not enough precision
|
||||
for internal values. For instance, if we run with unclamped framerates and hit
|
||||
several hundred FPS, milliseconds per frame would drop to 0 which caused some
|
||||
problems. Note that scenev1 will be remaining on milliseconds internally for
|
||||
compatibility reasons. Scenev2 should move to microseconds though.
|
||||
- The V2 account id for the signed in account is now available at
|
||||
`ba*.app.plus.accounts.primary.accountid` (alongside some other existing
|
||||
account info).
|
||||
|
||||
### 1.7.27 (build 21282, api 8, 2023-08-30)
|
||||
|
||||
|
||||
@ -43,6 +43,10 @@
|
||||
### Era0S
|
||||
- Bug Fixer
|
||||
- Modder
|
||||
- Added a feature
|
||||
|
||||
### VinniTR
|
||||
- Fixes
|
||||
|
||||
### Rikko
|
||||
- Created the original "reject_recently_left_players" plugin
|
||||
|
||||
@ -360,6 +360,8 @@ set(BALLISTICA_SOURCES
|
||||
${BA_SRC_ROOT}/ballistica/base/graphics/support/net_graph.cc
|
||||
${BA_SRC_ROOT}/ballistica/base/graphics/support/net_graph.h
|
||||
${BA_SRC_ROOT}/ballistica/base/graphics/support/render_command_buffer.h
|
||||
${BA_SRC_ROOT}/ballistica/base/graphics/support/screen_messages.cc
|
||||
${BA_SRC_ROOT}/ballistica/base/graphics/support/screen_messages.h
|
||||
${BA_SRC_ROOT}/ballistica/base/graphics/text/font_page_map_data.h
|
||||
${BA_SRC_ROOT}/ballistica/base/graphics/text/text_graphics.cc
|
||||
${BA_SRC_ROOT}/ballistica/base/graphics/text/text_graphics.h
|
||||
@ -436,16 +438,19 @@ set(BALLISTICA_SOURCES
|
||||
${BA_SRC_ROOT}/ballistica/base/support/app_config.cc
|
||||
${BA_SRC_ROOT}/ballistica/base/support/app_config.h
|
||||
${BA_SRC_ROOT}/ballistica/base/support/app_timer.h
|
||||
${BA_SRC_ROOT}/ballistica/base/support/base_build_switches.cc
|
||||
${BA_SRC_ROOT}/ballistica/base/support/base_build_switches.h
|
||||
${BA_SRC_ROOT}/ballistica/base/support/classic_soft.h
|
||||
${BA_SRC_ROOT}/ballistica/base/support/context.cc
|
||||
${BA_SRC_ROOT}/ballistica/base/support/context.h
|
||||
${BA_SRC_ROOT}/ballistica/base/support/display_timer.h
|
||||
${BA_SRC_ROOT}/ballistica/base/support/huffman.cc
|
||||
${BA_SRC_ROOT}/ballistica/base/support/huffman.h
|
||||
${BA_SRC_ROOT}/ballistica/base/support/plus_soft.h
|
||||
${BA_SRC_ROOT}/ballistica/base/support/repeater.cc
|
||||
${BA_SRC_ROOT}/ballistica/base/support/repeater.h
|
||||
${BA_SRC_ROOT}/ballistica/base/support/stdio_console.cc
|
||||
${BA_SRC_ROOT}/ballistica/base/support/stdio_console.h
|
||||
${BA_SRC_ROOT}/ballistica/base/support/stress_test.cc
|
||||
${BA_SRC_ROOT}/ballistica/base/support/stress_test.h
|
||||
${BA_SRC_ROOT}/ballistica/base/ui/dev_console.cc
|
||||
${BA_SRC_ROOT}/ballistica/base/ui/dev_console.h
|
||||
${BA_SRC_ROOT}/ballistica/base/ui/ui.cc
|
||||
@ -458,6 +463,8 @@ set(BALLISTICA_SOURCES
|
||||
${BA_SRC_ROOT}/ballistica/classic/python/classic_python.h
|
||||
${BA_SRC_ROOT}/ballistica/classic/python/methods/python_methods_classic.cc
|
||||
${BA_SRC_ROOT}/ballistica/classic/python/methods/python_methods_classic.h
|
||||
${BA_SRC_ROOT}/ballistica/classic/support/stress_test.cc
|
||||
${BA_SRC_ROOT}/ballistica/classic/support/stress_test.h
|
||||
${BA_SRC_ROOT}/ballistica/classic/support/v1_account.cc
|
||||
${BA_SRC_ROOT}/ballistica/classic/support/v1_account.h
|
||||
${BA_SRC_ROOT}/ballistica/core/core.cc
|
||||
|
||||
@ -352,6 +352,8 @@
|
||||
<ClCompile Include="..\..\src\ballistica\base\graphics\support\net_graph.cc" />
|
||||
<ClInclude Include="..\..\src\ballistica\base\graphics\support\net_graph.h" />
|
||||
<ClInclude Include="..\..\src\ballistica\base\graphics\support\render_command_buffer.h" />
|
||||
<ClCompile Include="..\..\src\ballistica\base\graphics\support\screen_messages.cc" />
|
||||
<ClInclude Include="..\..\src\ballistica\base\graphics\support\screen_messages.h" />
|
||||
<ClInclude Include="..\..\src\ballistica\base\graphics\text\font_page_map_data.h" />
|
||||
<ClCompile Include="..\..\src\ballistica\base\graphics\text\text_graphics.cc" />
|
||||
<ClInclude Include="..\..\src\ballistica\base\graphics\text\text_graphics.h" />
|
||||
@ -428,16 +430,19 @@
|
||||
<ClCompile Include="..\..\src\ballistica\base\support\app_config.cc" />
|
||||
<ClInclude Include="..\..\src\ballistica\base\support\app_config.h" />
|
||||
<ClInclude Include="..\..\src\ballistica\base\support\app_timer.h" />
|
||||
<ClCompile Include="..\..\src\ballistica\base\support\base_build_switches.cc" />
|
||||
<ClInclude Include="..\..\src\ballistica\base\support\base_build_switches.h" />
|
||||
<ClInclude Include="..\..\src\ballistica\base\support\classic_soft.h" />
|
||||
<ClCompile Include="..\..\src\ballistica\base\support\context.cc" />
|
||||
<ClInclude Include="..\..\src\ballistica\base\support\context.h" />
|
||||
<ClInclude Include="..\..\src\ballistica\base\support\display_timer.h" />
|
||||
<ClCompile Include="..\..\src\ballistica\base\support\huffman.cc" />
|
||||
<ClInclude Include="..\..\src\ballistica\base\support\huffman.h" />
|
||||
<ClInclude Include="..\..\src\ballistica\base\support\plus_soft.h" />
|
||||
<ClCompile Include="..\..\src\ballistica\base\support\repeater.cc" />
|
||||
<ClInclude Include="..\..\src\ballistica\base\support\repeater.h" />
|
||||
<ClCompile Include="..\..\src\ballistica\base\support\stdio_console.cc" />
|
||||
<ClInclude Include="..\..\src\ballistica\base\support\stdio_console.h" />
|
||||
<ClCompile Include="..\..\src\ballistica\base\support\stress_test.cc" />
|
||||
<ClInclude Include="..\..\src\ballistica\base\support\stress_test.h" />
|
||||
<ClCompile Include="..\..\src\ballistica\base\ui\dev_console.cc" />
|
||||
<ClInclude Include="..\..\src\ballistica\base\ui\dev_console.h" />
|
||||
<ClCompile Include="..\..\src\ballistica\base\ui\ui.cc" />
|
||||
@ -450,6 +455,8 @@
|
||||
<ClInclude Include="..\..\src\ballistica\classic\python\classic_python.h" />
|
||||
<ClCompile Include="..\..\src\ballistica\classic\python\methods\python_methods_classic.cc" />
|
||||
<ClInclude Include="..\..\src\ballistica\classic\python\methods\python_methods_classic.h" />
|
||||
<ClCompile Include="..\..\src\ballistica\classic\support\stress_test.cc" />
|
||||
<ClInclude Include="..\..\src\ballistica\classic\support\stress_test.h" />
|
||||
<ClCompile Include="..\..\src\ballistica\classic\support\v1_account.cc" />
|
||||
<ClInclude Include="..\..\src\ballistica\classic\support\v1_account.h" />
|
||||
<ClCompile Include="..\..\src\ballistica\core\core.cc" />
|
||||
|
||||
@ -490,6 +490,12 @@
|
||||
<ClInclude Include="..\..\src\ballistica\base\graphics\support\render_command_buffer.h">
|
||||
<Filter>ballistica\base\graphics\support</Filter>
|
||||
</ClInclude>
|
||||
<ClCompile Include="..\..\src\ballistica\base\graphics\support\screen_messages.cc">
|
||||
<Filter>ballistica\base\graphics\support</Filter>
|
||||
</ClCompile>
|
||||
<ClInclude Include="..\..\src\ballistica\base\graphics\support\screen_messages.h">
|
||||
<Filter>ballistica\base\graphics\support</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\src\ballistica\base\graphics\text\font_page_map_data.h">
|
||||
<Filter>ballistica\base\graphics\text</Filter>
|
||||
</ClInclude>
|
||||
@ -718,6 +724,12 @@
|
||||
<ClInclude Include="..\..\src\ballistica\base\support\app_timer.h">
|
||||
<Filter>ballistica\base\support</Filter>
|
||||
</ClInclude>
|
||||
<ClCompile Include="..\..\src\ballistica\base\support\base_build_switches.cc">
|
||||
<Filter>ballistica\base\support</Filter>
|
||||
</ClCompile>
|
||||
<ClInclude Include="..\..\src\ballistica\base\support\base_build_switches.h">
|
||||
<Filter>ballistica\base\support</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\src\ballistica\base\support\classic_soft.h">
|
||||
<Filter>ballistica\base\support</Filter>
|
||||
</ClInclude>
|
||||
@ -727,6 +739,9 @@
|
||||
<ClInclude Include="..\..\src\ballistica\base\support\context.h">
|
||||
<Filter>ballistica\base\support</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\src\ballistica\base\support\display_timer.h">
|
||||
<Filter>ballistica\base\support</Filter>
|
||||
</ClInclude>
|
||||
<ClCompile Include="..\..\src\ballistica\base\support\huffman.cc">
|
||||
<Filter>ballistica\base\support</Filter>
|
||||
</ClCompile>
|
||||
@ -736,18 +751,18 @@
|
||||
<ClInclude Include="..\..\src\ballistica\base\support\plus_soft.h">
|
||||
<Filter>ballistica\base\support</Filter>
|
||||
</ClInclude>
|
||||
<ClCompile Include="..\..\src\ballistica\base\support\repeater.cc">
|
||||
<Filter>ballistica\base\support</Filter>
|
||||
</ClCompile>
|
||||
<ClInclude Include="..\..\src\ballistica\base\support\repeater.h">
|
||||
<Filter>ballistica\base\support</Filter>
|
||||
</ClInclude>
|
||||
<ClCompile Include="..\..\src\ballistica\base\support\stdio_console.cc">
|
||||
<Filter>ballistica\base\support</Filter>
|
||||
</ClCompile>
|
||||
<ClInclude Include="..\..\src\ballistica\base\support\stdio_console.h">
|
||||
<Filter>ballistica\base\support</Filter>
|
||||
</ClInclude>
|
||||
<ClCompile Include="..\..\src\ballistica\base\support\stress_test.cc">
|
||||
<Filter>ballistica\base\support</Filter>
|
||||
</ClCompile>
|
||||
<ClInclude Include="..\..\src\ballistica\base\support\stress_test.h">
|
||||
<Filter>ballistica\base\support</Filter>
|
||||
</ClInclude>
|
||||
<ClCompile Include="..\..\src\ballistica\base\ui\dev_console.cc">
|
||||
<Filter>ballistica\base\ui</Filter>
|
||||
</ClCompile>
|
||||
@ -784,6 +799,12 @@
|
||||
<ClInclude Include="..\..\src\ballistica\classic\python\methods\python_methods_classic.h">
|
||||
<Filter>ballistica\classic\python\methods</Filter>
|
||||
</ClInclude>
|
||||
<ClCompile Include="..\..\src\ballistica\classic\support\stress_test.cc">
|
||||
<Filter>ballistica\classic\support</Filter>
|
||||
</ClCompile>
|
||||
<ClInclude Include="..\..\src\ballistica\classic\support\stress_test.h">
|
||||
<Filter>ballistica\classic\support</Filter>
|
||||
</ClInclude>
|
||||
<ClCompile Include="..\..\src\ballistica\classic\support\v1_account.cc">
|
||||
<Filter>ballistica\classic\support</Filter>
|
||||
</ClCompile>
|
||||
|
||||
@ -347,6 +347,8 @@
|
||||
<ClCompile Include="..\..\src\ballistica\base\graphics\support\net_graph.cc" />
|
||||
<ClInclude Include="..\..\src\ballistica\base\graphics\support\net_graph.h" />
|
||||
<ClInclude Include="..\..\src\ballistica\base\graphics\support\render_command_buffer.h" />
|
||||
<ClCompile Include="..\..\src\ballistica\base\graphics\support\screen_messages.cc" />
|
||||
<ClInclude Include="..\..\src\ballistica\base\graphics\support\screen_messages.h" />
|
||||
<ClInclude Include="..\..\src\ballistica\base\graphics\text\font_page_map_data.h" />
|
||||
<ClCompile Include="..\..\src\ballistica\base\graphics\text\text_graphics.cc" />
|
||||
<ClInclude Include="..\..\src\ballistica\base\graphics\text\text_graphics.h" />
|
||||
@ -423,16 +425,19 @@
|
||||
<ClCompile Include="..\..\src\ballistica\base\support\app_config.cc" />
|
||||
<ClInclude Include="..\..\src\ballistica\base\support\app_config.h" />
|
||||
<ClInclude Include="..\..\src\ballistica\base\support\app_timer.h" />
|
||||
<ClCompile Include="..\..\src\ballistica\base\support\base_build_switches.cc" />
|
||||
<ClInclude Include="..\..\src\ballistica\base\support\base_build_switches.h" />
|
||||
<ClInclude Include="..\..\src\ballistica\base\support\classic_soft.h" />
|
||||
<ClCompile Include="..\..\src\ballistica\base\support\context.cc" />
|
||||
<ClInclude Include="..\..\src\ballistica\base\support\context.h" />
|
||||
<ClInclude Include="..\..\src\ballistica\base\support\display_timer.h" />
|
||||
<ClCompile Include="..\..\src\ballistica\base\support\huffman.cc" />
|
||||
<ClInclude Include="..\..\src\ballistica\base\support\huffman.h" />
|
||||
<ClInclude Include="..\..\src\ballistica\base\support\plus_soft.h" />
|
||||
<ClCompile Include="..\..\src\ballistica\base\support\repeater.cc" />
|
||||
<ClInclude Include="..\..\src\ballistica\base\support\repeater.h" />
|
||||
<ClCompile Include="..\..\src\ballistica\base\support\stdio_console.cc" />
|
||||
<ClInclude Include="..\..\src\ballistica\base\support\stdio_console.h" />
|
||||
<ClCompile Include="..\..\src\ballistica\base\support\stress_test.cc" />
|
||||
<ClInclude Include="..\..\src\ballistica\base\support\stress_test.h" />
|
||||
<ClCompile Include="..\..\src\ballistica\base\ui\dev_console.cc" />
|
||||
<ClInclude Include="..\..\src\ballistica\base\ui\dev_console.h" />
|
||||
<ClCompile Include="..\..\src\ballistica\base\ui\ui.cc" />
|
||||
@ -445,6 +450,8 @@
|
||||
<ClInclude Include="..\..\src\ballistica\classic\python\classic_python.h" />
|
||||
<ClCompile Include="..\..\src\ballistica\classic\python\methods\python_methods_classic.cc" />
|
||||
<ClInclude Include="..\..\src\ballistica\classic\python\methods\python_methods_classic.h" />
|
||||
<ClCompile Include="..\..\src\ballistica\classic\support\stress_test.cc" />
|
||||
<ClInclude Include="..\..\src\ballistica\classic\support\stress_test.h" />
|
||||
<ClCompile Include="..\..\src\ballistica\classic\support\v1_account.cc" />
|
||||
<ClInclude Include="..\..\src\ballistica\classic\support\v1_account.h" />
|
||||
<ClCompile Include="..\..\src\ballistica\core\core.cc" />
|
||||
|
||||
@ -490,6 +490,12 @@
|
||||
<ClInclude Include="..\..\src\ballistica\base\graphics\support\render_command_buffer.h">
|
||||
<Filter>ballistica\base\graphics\support</Filter>
|
||||
</ClInclude>
|
||||
<ClCompile Include="..\..\src\ballistica\base\graphics\support\screen_messages.cc">
|
||||
<Filter>ballistica\base\graphics\support</Filter>
|
||||
</ClCompile>
|
||||
<ClInclude Include="..\..\src\ballistica\base\graphics\support\screen_messages.h">
|
||||
<Filter>ballistica\base\graphics\support</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\src\ballistica\base\graphics\text\font_page_map_data.h">
|
||||
<Filter>ballistica\base\graphics\text</Filter>
|
||||
</ClInclude>
|
||||
@ -718,6 +724,12 @@
|
||||
<ClInclude Include="..\..\src\ballistica\base\support\app_timer.h">
|
||||
<Filter>ballistica\base\support</Filter>
|
||||
</ClInclude>
|
||||
<ClCompile Include="..\..\src\ballistica\base\support\base_build_switches.cc">
|
||||
<Filter>ballistica\base\support</Filter>
|
||||
</ClCompile>
|
||||
<ClInclude Include="..\..\src\ballistica\base\support\base_build_switches.h">
|
||||
<Filter>ballistica\base\support</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\src\ballistica\base\support\classic_soft.h">
|
||||
<Filter>ballistica\base\support</Filter>
|
||||
</ClInclude>
|
||||
@ -727,6 +739,9 @@
|
||||
<ClInclude Include="..\..\src\ballistica\base\support\context.h">
|
||||
<Filter>ballistica\base\support</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\src\ballistica\base\support\display_timer.h">
|
||||
<Filter>ballistica\base\support</Filter>
|
||||
</ClInclude>
|
||||
<ClCompile Include="..\..\src\ballistica\base\support\huffman.cc">
|
||||
<Filter>ballistica\base\support</Filter>
|
||||
</ClCompile>
|
||||
@ -736,18 +751,18 @@
|
||||
<ClInclude Include="..\..\src\ballistica\base\support\plus_soft.h">
|
||||
<Filter>ballistica\base\support</Filter>
|
||||
</ClInclude>
|
||||
<ClCompile Include="..\..\src\ballistica\base\support\repeater.cc">
|
||||
<Filter>ballistica\base\support</Filter>
|
||||
</ClCompile>
|
||||
<ClInclude Include="..\..\src\ballistica\base\support\repeater.h">
|
||||
<Filter>ballistica\base\support</Filter>
|
||||
</ClInclude>
|
||||
<ClCompile Include="..\..\src\ballistica\base\support\stdio_console.cc">
|
||||
<Filter>ballistica\base\support</Filter>
|
||||
</ClCompile>
|
||||
<ClInclude Include="..\..\src\ballistica\base\support\stdio_console.h">
|
||||
<Filter>ballistica\base\support</Filter>
|
||||
</ClInclude>
|
||||
<ClCompile Include="..\..\src\ballistica\base\support\stress_test.cc">
|
||||
<Filter>ballistica\base\support</Filter>
|
||||
</ClCompile>
|
||||
<ClInclude Include="..\..\src\ballistica\base\support\stress_test.h">
|
||||
<Filter>ballistica\base\support</Filter>
|
||||
</ClInclude>
|
||||
<ClCompile Include="..\..\src\ballistica\base\ui\dev_console.cc">
|
||||
<Filter>ballistica\base\ui</Filter>
|
||||
</ClCompile>
|
||||
@ -784,6 +799,12 @@
|
||||
<ClInclude Include="..\..\src\ballistica\classic\python\methods\python_methods_classic.h">
|
||||
<Filter>ballistica\classic\python\methods</Filter>
|
||||
</ClInclude>
|
||||
<ClCompile Include="..\..\src\ballistica\classic\support\stress_test.cc">
|
||||
<Filter>ballistica\classic\support</Filter>
|
||||
</ClCompile>
|
||||
<ClInclude Include="..\..\src\ballistica\classic\support\stress_test.h">
|
||||
<Filter>ballistica\classic\support</Filter>
|
||||
</ClInclude>
|
||||
<ClCompile Include="..\..\src\ballistica\classic\support\v1_account.cc">
|
||||
<Filter>ballistica\classic\support</Filter>
|
||||
</ClCompile>
|
||||
|
||||
@ -10,7 +10,8 @@
|
||||
"src/ballistica/base/graphics/texture/ktx.cc",
|
||||
"src/ballistica/core/platform/android/android_gl3.h",
|
||||
"src/ballistica/base/platform/apple/app_delegate.h",
|
||||
"src/ballistica/base/platform/apple/scripting_bridge_music.h",
|
||||
"src/ballistica/base/platform/apple/MacMusicApp.h",
|
||||
"src/ballistica/base/platform/apple/MacMusicAppScriptingBridge.h",
|
||||
"src/ballistica/core/platform/android/utf8/checked.h",
|
||||
"src/ballistica/core/platform/android/utf8/unchecked.h",
|
||||
"src/ballistica/core/platform/android/utf8/core.h",
|
||||
|
||||
@ -103,6 +103,7 @@ ctx.src_unchecked_paths = {
|
||||
'ballisticakit-xcode/BallisticaKit Shared/Assets.xcassets/*/*.png',
|
||||
'ballisticakit-xcode/BallisticaKit Shared/Assets.xcassets/*/*/*.png',
|
||||
'ballisticakit-xcode/BallisticaKit Shared/Assets.xcassets/*/*/*/*/*.png',
|
||||
'ballisticakit-xcode/BallisticaKit.xcodeproj/xcuserdata',
|
||||
'ballisticakit-android/BallisticaKit/src/*/res/*/*.png',
|
||||
'ballisticakit-android/BallisticaKit/src/*/assets/ballistica_files',
|
||||
'ballisticakit-android/local.properties',
|
||||
@ -274,6 +275,7 @@ ctx.filter_file_extensions = {
|
||||
'.frag',
|
||||
'.vert',
|
||||
'.xcsettings',
|
||||
'.xcstrings',
|
||||
'.filters',
|
||||
}
|
||||
|
||||
|
||||
@ -366,7 +366,6 @@
|
||||
"ba_data/python/bauiv1lib/__pycache__/characterpicker.cpython-311.opt-1.pyc",
|
||||
"ba_data/python/bauiv1lib/__pycache__/colorpicker.cpython-311.opt-1.pyc",
|
||||
"ba_data/python/bauiv1lib/__pycache__/config.cpython-311.opt-1.pyc",
|
||||
"ba_data/python/bauiv1lib/__pycache__/configerror.cpython-311.opt-1.pyc",
|
||||
"ba_data/python/bauiv1lib/__pycache__/confirm.cpython-311.opt-1.pyc",
|
||||
"ba_data/python/bauiv1lib/__pycache__/continues.cpython-311.opt-1.pyc",
|
||||
"ba_data/python/bauiv1lib/__pycache__/creditslist.cpython-311.opt-1.pyc",
|
||||
@ -418,7 +417,6 @@
|
||||
"ba_data/python/bauiv1lib/characterpicker.py",
|
||||
"ba_data/python/bauiv1lib/colorpicker.py",
|
||||
"ba_data/python/bauiv1lib/config.py",
|
||||
"ba_data/python/bauiv1lib/configerror.py",
|
||||
"ba_data/python/bauiv1lib/confirm.py",
|
||||
"ba_data/python/bauiv1lib/continues.py",
|
||||
"ba_data/python/bauiv1lib/coop/__init__.py",
|
||||
|
||||
@ -341,7 +341,6 @@ SCRIPT_TARGETS_PY_PUBLIC = \
|
||||
$(BUILD_DIR)/ba_data/python/bauiv1lib/characterpicker.py \
|
||||
$(BUILD_DIR)/ba_data/python/bauiv1lib/colorpicker.py \
|
||||
$(BUILD_DIR)/ba_data/python/bauiv1lib/config.py \
|
||||
$(BUILD_DIR)/ba_data/python/bauiv1lib/configerror.py \
|
||||
$(BUILD_DIR)/ba_data/python/bauiv1lib/confirm.py \
|
||||
$(BUILD_DIR)/ba_data/python/bauiv1lib/continues.py \
|
||||
$(BUILD_DIR)/ba_data/python/bauiv1lib/coop/__init__.py \
|
||||
@ -616,7 +615,6 @@ SCRIPT_TARGETS_PYC_PUBLIC = \
|
||||
$(BUILD_DIR)/ba_data/python/bauiv1lib/__pycache__/characterpicker.cpython-311.opt-1.pyc \
|
||||
$(BUILD_DIR)/ba_data/python/bauiv1lib/__pycache__/colorpicker.cpython-311.opt-1.pyc \
|
||||
$(BUILD_DIR)/ba_data/python/bauiv1lib/__pycache__/config.cpython-311.opt-1.pyc \
|
||||
$(BUILD_DIR)/ba_data/python/bauiv1lib/__pycache__/configerror.cpython-311.opt-1.pyc \
|
||||
$(BUILD_DIR)/ba_data/python/bauiv1lib/__pycache__/confirm.cpython-311.opt-1.pyc \
|
||||
$(BUILD_DIR)/ba_data/python/bauiv1lib/__pycache__/continues.cpython-311.opt-1.pyc \
|
||||
$(BUILD_DIR)/ba_data/python/bauiv1lib/coop/__pycache__/__init__.cpython-311.opt-1.pyc \
|
||||
|
||||
@ -64,7 +64,6 @@ from _babase import (
|
||||
is_running_on_fire_tv,
|
||||
is_xcode_build,
|
||||
lock_all_input,
|
||||
mac_music_app_get_library_source,
|
||||
mac_music_app_get_playlists,
|
||||
mac_music_app_get_volume,
|
||||
mac_music_app_init,
|
||||
@ -75,6 +74,8 @@ from _babase import (
|
||||
music_player_set_volume,
|
||||
music_player_shutdown,
|
||||
music_player_stop,
|
||||
native_review_request,
|
||||
native_review_request_supported,
|
||||
native_stack_trace,
|
||||
print_load_info,
|
||||
pushcall,
|
||||
@ -85,7 +86,6 @@ from _babase import (
|
||||
screenmessage,
|
||||
set_analytics_screen,
|
||||
set_low_level_config_value,
|
||||
set_stress_testing,
|
||||
set_thread_name,
|
||||
set_ui_input_device,
|
||||
show_progress_bar,
|
||||
@ -266,7 +266,6 @@ __all__ = [
|
||||
'lock_all_input',
|
||||
'LoginAdapter',
|
||||
'Lstr',
|
||||
'mac_music_app_get_library_source',
|
||||
'mac_music_app_get_playlists',
|
||||
'mac_music_app_get_volume',
|
||||
'mac_music_app_init',
|
||||
@ -279,6 +278,8 @@ __all__ = [
|
||||
'music_player_set_volume',
|
||||
'music_player_shutdown',
|
||||
'music_player_stop',
|
||||
'native_review_request',
|
||||
'native_review_request_supported',
|
||||
'native_stack_trace',
|
||||
'NodeNotFoundError',
|
||||
'normalized_color',
|
||||
@ -303,7 +304,6 @@ __all__ = [
|
||||
'SessionTeamNotFoundError',
|
||||
'set_analytics_screen',
|
||||
'set_low_level_config_value',
|
||||
'set_stress_testing',
|
||||
'set_thread_name',
|
||||
'set_ui_input_device',
|
||||
'show_progress_bar',
|
||||
|
||||
@ -31,6 +31,8 @@ class AccountV2Subsystem:
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
from babase._login import LoginAdapterGPGS, LoginAdapterGameCenter
|
||||
|
||||
# Whether or not everything related to an initial login
|
||||
# (or lack thereof) has completed. This includes things like
|
||||
# workspace syncing. Completion of this is what flips the app
|
||||
@ -45,16 +47,13 @@ class AccountV2Subsystem:
|
||||
self._implicit_state_changed = False
|
||||
self._can_do_auto_sign_in = True
|
||||
|
||||
if _babase.app.classic is None:
|
||||
raise RuntimeError('Needs updating for no-classic case.')
|
||||
|
||||
if (
|
||||
_babase.app.classic.platform == 'android'
|
||||
and _babase.app.classic.subplatform == 'google'
|
||||
):
|
||||
from babase._login import LoginAdapterGPGS
|
||||
|
||||
self.login_adapters[LoginType.GPGS] = LoginAdapterGPGS()
|
||||
adapter: LoginAdapter
|
||||
if _babase.using_google_play_game_services():
|
||||
adapter = LoginAdapterGPGS()
|
||||
self.login_adapters[adapter.login_type] = adapter
|
||||
if _babase.using_game_center():
|
||||
adapter = LoginAdapterGameCenter()
|
||||
self.login_adapters[adapter.login_type] = adapter
|
||||
|
||||
def on_app_loading(self) -> None:
|
||||
"""Should be called at standard on_app_loading time."""
|
||||
@ -62,10 +61,6 @@ class AccountV2Subsystem:
|
||||
for adapter in self.login_adapters.values():
|
||||
adapter.on_app_loading()
|
||||
|
||||
def set_primary_credentials(self, credentials: str | None) -> None:
|
||||
"""Set credentials for the primary app account."""
|
||||
raise NotImplementedError('This should be overridden.')
|
||||
|
||||
def have_primary_credentials(self) -> bool:
|
||||
"""Are credentials currently set for the primary app account?
|
||||
|
||||
@ -80,10 +75,6 @@ class AccountV2Subsystem:
|
||||
"""The primary account for the app, or None if not logged in."""
|
||||
return self.do_get_primary()
|
||||
|
||||
def do_get_primary(self) -> AccountV2Handle | None:
|
||||
"""Internal - should be overridden by subclass."""
|
||||
return None
|
||||
|
||||
def on_primary_account_changed(
|
||||
self, account: AccountV2Handle | None
|
||||
) -> None:
|
||||
@ -142,6 +133,8 @@ class AccountV2Subsystem:
|
||||
"""An implicit sign-in happened (called by native layer)."""
|
||||
from babase._login import LoginAdapter
|
||||
|
||||
assert _babase.in_logic_thread()
|
||||
|
||||
with _babase.ContextRef.empty():
|
||||
self.login_adapters[login_type].set_implicit_login_state(
|
||||
LoginAdapter.ImplicitLoginState(
|
||||
@ -151,6 +144,7 @@ class AccountV2Subsystem:
|
||||
|
||||
def on_implicit_sign_out(self, login_type: LoginType) -> None:
|
||||
"""An implicit sign-out happened (called by native layer)."""
|
||||
assert _babase.in_logic_thread()
|
||||
with _babase.ContextRef.empty():
|
||||
self.login_adapters[login_type].set_implicit_login_state(None)
|
||||
|
||||
@ -259,6 +253,14 @@ class AccountV2Subsystem:
|
||||
# We may want to auto-sign-in based on this new state.
|
||||
self._update_auto_sign_in()
|
||||
|
||||
def do_get_primary(self) -> AccountV2Handle | None:
|
||||
"""Internal - should be overridden by subclass."""
|
||||
raise NotImplementedError('This should be overridden.')
|
||||
|
||||
def set_primary_credentials(self, credentials: str | None) -> None:
|
||||
"""Set credentials for the primary app account."""
|
||||
raise NotImplementedError('This should be overridden.')
|
||||
|
||||
def _update_auto_sign_in(self) -> None:
|
||||
plus = _babase.app.plus
|
||||
assert plus is not None
|
||||
@ -266,7 +268,7 @@ class AccountV2Subsystem:
|
||||
# If implicit state has changed, try to respond.
|
||||
if self._implicit_state_changed:
|
||||
if self._implicit_signed_in_adapter is None:
|
||||
# If implicit back-end is signed out, follow suit
|
||||
# If implicit back-end has signed out, we follow suit
|
||||
# immediately; no need to wait for network connectivity.
|
||||
if DEBUG_LOG:
|
||||
logging.debug(
|
||||
@ -419,14 +421,11 @@ class AccountV2Handle:
|
||||
used with some operations such as cloud messaging.
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.tag = '?'
|
||||
|
||||
self.workspacename: str | None = None
|
||||
self.workspaceid: str | None = None
|
||||
|
||||
# Login types and their display-names associated with this account.
|
||||
self.logins: dict[LoginType, str] = {}
|
||||
accountid: str
|
||||
tag: str
|
||||
workspacename: str | None
|
||||
workspaceid: str | None
|
||||
logins: dict[LoginType, str]
|
||||
|
||||
def __enter__(self) -> None:
|
||||
"""Support for "with" statement.
|
||||
|
||||
@ -184,7 +184,6 @@ class App:
|
||||
# foregrounded; can be a simple way to determine if network data
|
||||
# should be refreshed/etc.
|
||||
self.fg_state = 0
|
||||
self.config_file_healthy: bool = False
|
||||
|
||||
self._subsystems: list[AppSubsystem] = []
|
||||
self._native_bootstrapping_completed = False
|
||||
@ -433,7 +432,7 @@ class App:
|
||||
"""(internal)"""
|
||||
from babase._appconfig import read_app_config
|
||||
|
||||
self._config, self.config_file_healthy = read_app_config()
|
||||
self._config = read_app_config()
|
||||
|
||||
def handle_deep_link(self, url: str) -> None:
|
||||
"""Handle a deep link URL."""
|
||||
@ -583,19 +582,6 @@ class App:
|
||||
self._aioloop = _asyncio.setup_asyncio()
|
||||
self.health_monitor = AppHealthMonitor()
|
||||
|
||||
# Only proceed if our config file is healthy so we don't
|
||||
# overwrite a broken one or whatnot and wipe out data.
|
||||
if not self.config_file_healthy:
|
||||
if self.classic is not None:
|
||||
handled = self.classic.show_config_error_window()
|
||||
if handled:
|
||||
return
|
||||
|
||||
# For now on other systems we just overwrite the bum config.
|
||||
# At this point settings are already set; lets just commit
|
||||
# them to disk.
|
||||
_appconfig.commit_app_config(force=True)
|
||||
|
||||
# __FEATURESET_APP_SUBSYSTEM_CREATE_BEGIN__
|
||||
# This section generated by batools.appmodule; do not edit.
|
||||
|
||||
@ -915,6 +901,12 @@ class App:
|
||||
_babase.lifecyclelog('fade-and-shutdown-graphics begin')
|
||||
_babase.fade_screen(False, time=0.15)
|
||||
await asyncio.sleep(0.15)
|
||||
|
||||
# Now tell the graphics system to go down and wait until
|
||||
# it has done so.
|
||||
_babase.graphics_shutdown_begin()
|
||||
while not _babase.graphics_shutdown_is_complete():
|
||||
await asyncio.sleep(0.01)
|
||||
_babase.lifecyclelog('fade-and-shutdown-graphics end')
|
||||
|
||||
async def _fade_and_shutdown_audio(self) -> None:
|
||||
|
||||
@ -101,15 +101,13 @@ class AppConfig(dict):
|
||||
self.commit()
|
||||
|
||||
|
||||
def read_app_config() -> tuple[AppConfig, bool]:
|
||||
def read_app_config() -> AppConfig:
|
||||
"""Read the app config."""
|
||||
import os
|
||||
import json
|
||||
|
||||
config_file_healthy = False
|
||||
|
||||
# NOTE: it is assumed that this only gets called once and the
|
||||
# config object will not change from here on out
|
||||
# NOTE: it is assumed that this only gets called once and the config
|
||||
# object will not change from here on out
|
||||
config_file_path = _babase.app.env.config_file_path
|
||||
config_contents = ''
|
||||
try:
|
||||
@ -119,20 +117,16 @@ def read_app_config() -> tuple[AppConfig, bool]:
|
||||
config = AppConfig(json.loads(config_contents))
|
||||
else:
|
||||
config = AppConfig()
|
||||
config_file_healthy = True
|
||||
|
||||
except Exception:
|
||||
logging.exception(
|
||||
"Error reading config file at time %.3f: '%s'.",
|
||||
"Error reading config file '%s' at time %.3f.\n"
|
||||
"Backing up broken config to'%s.broken'.",
|
||||
config_file_path,
|
||||
_babase.apptime(),
|
||||
config_file_path,
|
||||
)
|
||||
|
||||
# Whenever this happens lets back up the broken one just in case it
|
||||
# gets overwritten accidentally.
|
||||
logging.info(
|
||||
"Backing up current config file to '%s.broken'", config_file_path
|
||||
)
|
||||
try:
|
||||
import shutil
|
||||
|
||||
@ -141,23 +135,10 @@ def read_app_config() -> tuple[AppConfig, bool]:
|
||||
logging.exception('Error copying broken config.')
|
||||
config = AppConfig()
|
||||
|
||||
# Now attempt to read one of our 'prev' backup copies.
|
||||
prev_path = config_file_path + '.prev'
|
||||
try:
|
||||
if os.path.exists(prev_path):
|
||||
with open(prev_path, encoding='utf-8') as infile:
|
||||
config_contents = infile.read()
|
||||
config = AppConfig(json.loads(config_contents))
|
||||
else:
|
||||
config = AppConfig()
|
||||
config_file_healthy = True
|
||||
logging.info('Successfully read backup config.')
|
||||
except Exception:
|
||||
logging.exception('Error reading prev backup config.')
|
||||
return config, config_file_healthy
|
||||
return config
|
||||
|
||||
|
||||
def commit_app_config(force: bool = False) -> None:
|
||||
def commit_app_config() -> None:
|
||||
"""Commit the config to persistent storage.
|
||||
|
||||
Category: **General Utility Functions**
|
||||
@ -167,10 +148,4 @@ def commit_app_config(force: bool = False) -> None:
|
||||
plus = _babase.app.plus
|
||||
assert plus is not None
|
||||
|
||||
if not _babase.app.config_file_healthy and not force:
|
||||
logging.warning(
|
||||
'Current config file is broken; '
|
||||
'skipping write to avoid losing settings.'
|
||||
)
|
||||
return
|
||||
plus.mark_config_dirty()
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
"""Dev-Console functionality."""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from typing import TYPE_CHECKING
|
||||
from dataclasses import dataclass
|
||||
import logging
|
||||
@ -154,9 +155,10 @@ class DevConsoleSubsystem:
|
||||
# All tabs in the dev-console. Add your own stuff here via
|
||||
# plugins or whatnot.
|
||||
self.tabs: list[DevConsoleTabEntry] = [
|
||||
DevConsoleTabEntry('Python', DevConsoleTabPython),
|
||||
DevConsoleTabEntry('Test', DevConsoleTabTest),
|
||||
DevConsoleTabEntry('Python', DevConsoleTabPython)
|
||||
]
|
||||
if os.environ.get('BA_DEV_CONSOLE_TEST_TAB', '0') == '1':
|
||||
self.tabs.append(DevConsoleTabEntry('Test', DevConsoleTabTest))
|
||||
self.is_refreshing = False
|
||||
|
||||
def do_refresh_tab(self, tabname: str) -> None:
|
||||
|
||||
@ -33,6 +33,23 @@ def reset_to_main_menu() -> None:
|
||||
logging.warning('reset_to_main_menu: no-op due to classic not present.')
|
||||
|
||||
|
||||
def get_v2_account_id() -> str | None:
|
||||
"""Return the current V2 account id if signed in, or None if not."""
|
||||
try:
|
||||
plus = _babase.app.plus
|
||||
if plus is not None:
|
||||
account = plus.accounts.primary
|
||||
if account is not None:
|
||||
accountid = account.accountid
|
||||
# (Avoids mypy complaints when plus is not present)
|
||||
assert isinstance(accountid, (str, type(None)))
|
||||
return accountid
|
||||
return None
|
||||
except Exception:
|
||||
logging.exception('Error fetching v2 account id.')
|
||||
return None
|
||||
|
||||
|
||||
def store_config_fullscreen_on() -> None:
|
||||
"""The OS has changed our fullscreen state and we should take note."""
|
||||
_babase.app.config['Fullscreen'] = True
|
||||
@ -123,6 +140,14 @@ def error_message() -> None:
|
||||
_babase.screenmessage(Lstr(resource='errorText'), color=(1, 0, 0))
|
||||
|
||||
|
||||
def success_message() -> None:
|
||||
from babase._language import Lstr
|
||||
|
||||
if _babase.app.env.gui:
|
||||
_babase.getsimplesound('dingSmall').play()
|
||||
_babase.screenmessage(Lstr(resource='successText'), color=(0, 1, 0))
|
||||
|
||||
|
||||
def purchase_not_valid_error() -> None:
|
||||
from babase._language import Lstr
|
||||
|
||||
@ -312,6 +337,7 @@ def implicit_sign_in(
|
||||
from bacommon.login import LoginType
|
||||
|
||||
assert _babase.app.plus is not None
|
||||
|
||||
_babase.app.plus.accounts.on_implicit_sign_in(
|
||||
login_type=LoginType(login_type_str),
|
||||
login_id=login_id,
|
||||
|
||||
@ -149,7 +149,7 @@ class LoginAdapter:
|
||||
result_cb: Callable[[LoginAdapter, SignInResult | Exception], None],
|
||||
description: str,
|
||||
) -> None:
|
||||
"""Attempt an explicit sign in via this adapter.
|
||||
"""Attempt to sign in via this adapter.
|
||||
|
||||
This can be called even if the back-end is not implicitly signed in;
|
||||
the adapter will attempt to sign in if possible. An exception will
|
||||
@ -161,7 +161,7 @@ class LoginAdapter:
|
||||
# Have been seeing multiple sign-in attempts come through
|
||||
# nearly simultaneously which can be problematic server-side.
|
||||
# Let's error if a sign-in attempt is made within a few seconds
|
||||
# of the last one to address this.
|
||||
# of the last one to try and address this.
|
||||
now = time.monotonic()
|
||||
appnow = _babase.apptime()
|
||||
if self._last_sign_in_time is not None:
|
||||
@ -229,6 +229,7 @@ class LoginAdapter:
|
||||
def _got_sign_in_response(
|
||||
response: bacommon.cloud.SignInResponse | Exception,
|
||||
) -> None:
|
||||
# This likely means we couldn't communicate with the server.
|
||||
if isinstance(response, Exception):
|
||||
if DEBUG_LOG:
|
||||
logging.debug(
|
||||
@ -239,20 +240,18 @@ class LoginAdapter:
|
||||
)
|
||||
_babase.pushcall(Call(result_cb, self, response))
|
||||
else:
|
||||
if DEBUG_LOG:
|
||||
logging.debug(
|
||||
'LoginAdapter: %s adapter got successful'
|
||||
' sign-in response',
|
||||
self.login_type.name,
|
||||
)
|
||||
# This means our credentials were explicitly rejected.
|
||||
if response.credentials is None:
|
||||
result2: LoginAdapter.SignInResult | Exception = (
|
||||
RuntimeError(
|
||||
'No credentials returned after'
|
||||
' submitting sign-in-token.'
|
||||
)
|
||||
RuntimeError('Sign-in-token was rejected.')
|
||||
)
|
||||
else:
|
||||
if DEBUG_LOG:
|
||||
logging.debug(
|
||||
'LoginAdapter: %s adapter got successful'
|
||||
' sign-in response',
|
||||
self.login_type.name,
|
||||
)
|
||||
result2 = self.SignInResult(
|
||||
credentials=response.credentials
|
||||
)
|
||||
@ -269,7 +268,7 @@ class LoginAdapter:
|
||||
on_response=_got_sign_in_response,
|
||||
)
|
||||
|
||||
# Kick off the process by fetching a sign-in token.
|
||||
# Kick off the sign-in process by fetching a sign-in token.
|
||||
self.get_sign_in_token(completion_cb=_got_sign_in_token_result)
|
||||
|
||||
def is_back_end_active(self) -> bool:
|
||||
@ -282,11 +281,10 @@ class LoginAdapter:
|
||||
"""Get a sign-in token from the adapter back end.
|
||||
|
||||
This token is then passed to the master-server to complete the
|
||||
login process.
|
||||
The adapter can use this opportunity to bring up account creation
|
||||
UI, call its internal sign_in function, etc. as needed.
|
||||
The provided completion_cb should then be called with either a token
|
||||
or None if sign in failed or was cancelled.
|
||||
sign-in process. The adapter can use this opportunity to bring
|
||||
up account creation UI, call its internal sign_in function, etc.
|
||||
as needed. The provided completion_cb should then be called with
|
||||
either a token or None if sign in failed or was cancelled.
|
||||
"""
|
||||
from babase._general import Call
|
||||
|
||||
@ -340,8 +338,8 @@ class LoginAdapter:
|
||||
class LoginAdapterNative(LoginAdapter):
|
||||
"""A login adapter that does its work in the native layer."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__(LoginType.GPGS)
|
||||
def __init__(self, login_type: LoginType) -> None:
|
||||
super().__init__(login_type)
|
||||
|
||||
# Store int ids for in-flight attempts since they may go through
|
||||
# various platform layers and back.
|
||||
@ -375,3 +373,13 @@ class LoginAdapterNative(LoginAdapter):
|
||||
|
||||
class LoginAdapterGPGS(LoginAdapterNative):
|
||||
"""Google Play Game Services adapter."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__(LoginType.GPGS)
|
||||
|
||||
|
||||
class LoginAdapterGameCenter(LoginAdapterNative):
|
||||
"""Apple Game Center adapter."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__(LoginType.GAME_CENTER)
|
||||
|
||||
@ -8,6 +8,7 @@ from typing import TYPE_CHECKING
|
||||
|
||||
import babase
|
||||
import bascenev1
|
||||
import _baclassic
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Any, Sequence
|
||||
@ -84,7 +85,8 @@ def run_stress_test(
|
||||
|
||||
def stop_stress_test() -> None:
|
||||
"""End a running stress test."""
|
||||
babase.set_stress_testing(False, 0)
|
||||
|
||||
_baclassic.set_stress_testing(False, 0)
|
||||
assert babase.app.classic is not None
|
||||
try:
|
||||
if babase.app.classic.stress_test_reset_timer is not None:
|
||||
@ -134,14 +136,14 @@ def start_stress_test(args: dict[str, Any]) -> None:
|
||||
babase.Call(bascenev1.new_host_session, FreeForAllSession),
|
||||
),
|
||||
)
|
||||
babase.set_stress_testing(True, args['player_count'])
|
||||
_baclassic.set_stress_testing(True, args['player_count'])
|
||||
babase.app.classic.stress_test_reset_timer = babase.AppTimer(
|
||||
args['round_duration'], babase.Call(_reset_stress_test, args)
|
||||
)
|
||||
|
||||
|
||||
def _reset_stress_test(args: dict[str, Any]) -> None:
|
||||
babase.set_stress_testing(False, args['player_count'])
|
||||
_baclassic.set_stress_testing(False, args['player_count'])
|
||||
babase.screenmessage('Resetting stress test...')
|
||||
session = bascenev1.get_foreground_host_session()
|
||||
assert session is not None
|
||||
|
||||
@ -40,7 +40,14 @@ def get_input_device_mapped_value(
|
||||
mapping = ccfgs[devicename][unique_id]
|
||||
elif 'default' in ccfgs[devicename]:
|
||||
mapping = ccfgs[devicename]['default']
|
||||
if mapping is not None:
|
||||
|
||||
# We now use the config mapping *only* if it is not empty.
|
||||
# There have been cases of config writing code messing up
|
||||
# and leaving empty dicts in the app config, which currently
|
||||
# leaves the device unusable. Alternatively, we'd perhaps
|
||||
# want to fall back to defaults for individual missing
|
||||
# values, but that is a bigger change we can make later.
|
||||
if isinstance(mapping, dict) and mapping:
|
||||
return mapping.get(name, -1)
|
||||
|
||||
if platform == 'windows':
|
||||
|
||||
@ -165,15 +165,16 @@ class MusicSubsystem:
|
||||
|
||||
def supports_soundtrack_entry_type(self, entry_type: str) -> bool:
|
||||
"""Return whether provided soundtrack entry type is supported here."""
|
||||
uas = babase.env()['legacy_user_agent_string']
|
||||
assert isinstance(uas, str)
|
||||
|
||||
# FIXME: Generalize this.
|
||||
# Note to self; can't access babase.app.classic here because
|
||||
# we are called during its construction.
|
||||
env = babase.env()
|
||||
platform = env.get('platform')
|
||||
assert isinstance(platform, str)
|
||||
if entry_type == 'iTunesPlaylist':
|
||||
return 'Mac' in uas
|
||||
return platform == 'mac' and babase.is_xcode_build()
|
||||
if entry_type in ('musicFile', 'musicFolder'):
|
||||
return (
|
||||
'android' in uas
|
||||
platform == 'android'
|
||||
and babase.android_get_external_files_dir() is not None
|
||||
)
|
||||
if entry_type == 'default':
|
||||
|
||||
@ -627,15 +627,6 @@ class ClassicSubsystem(babase.AppSubsystem):
|
||||
"""(internal)"""
|
||||
return bascenev1.get_foreground_host_activity()
|
||||
|
||||
def show_config_error_window(self) -> bool:
|
||||
"""(internal)"""
|
||||
if self.platform in ('mac', 'linux', 'windows'):
|
||||
from bauiv1lib.configerror import ConfigErrorWindow
|
||||
|
||||
babase.pushcall(ConfigErrorWindow)
|
||||
return True
|
||||
return False
|
||||
|
||||
def value_test(
|
||||
self,
|
||||
arg: str,
|
||||
|
||||
@ -80,14 +80,13 @@ class _MacMusicAppThread(threading.Thread):
|
||||
def run(self) -> None:
|
||||
"""Run the Music.app thread."""
|
||||
babase.set_thread_name('BA_MacMusicAppThread')
|
||||
babase.mac_music_app_init()
|
||||
|
||||
# Let's mention to the user we're launching Music.app in case
|
||||
# it causes any funny business (this used to background the app
|
||||
# sometimes, though I think that is fixed now)
|
||||
def do_print() -> None:
|
||||
babase.apptimer(
|
||||
1.0,
|
||||
0.5,
|
||||
babase.Call(
|
||||
babase.screenmessage,
|
||||
babase.Lstr(resource='usingItunesText'),
|
||||
@ -97,9 +96,8 @@ class _MacMusicAppThread(threading.Thread):
|
||||
|
||||
babase.pushcall(do_print, from_other_thread=True)
|
||||
|
||||
# Here we grab this to force the actual launch.
|
||||
babase.mac_music_app_get_volume()
|
||||
babase.mac_music_app_get_library_source()
|
||||
babase.mac_music_app_init()
|
||||
|
||||
done = False
|
||||
while not done:
|
||||
self._commands_available.wait()
|
||||
|
||||
@ -52,7 +52,7 @@ if TYPE_CHECKING:
|
||||
|
||||
# Build number and version of the ballistica binary we expect to be
|
||||
# using.
|
||||
TARGET_BALLISTICA_BUILD = 21491
|
||||
TARGET_BALLISTICA_BUILD = 21583
|
||||
TARGET_BALLISTICA_VERSION = '1.7.28'
|
||||
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Provides classic app subsystem."""
|
||||
"""Provides plus app subsystem."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
"""Defines base session class."""
|
||||
from __future__ import annotations
|
||||
|
||||
import math
|
||||
import weakref
|
||||
import logging
|
||||
from typing import TYPE_CHECKING
|
||||
@ -17,6 +18,11 @@ if TYPE_CHECKING:
|
||||
|
||||
import bascenev1
|
||||
|
||||
# How long someone who left the session (but not the party) must
|
||||
# wait to rejoin the session again. Intended to prevent game exploits
|
||||
# such as skipping respawn waits.
|
||||
REJOIN_COOLDOWN = 10
|
||||
|
||||
|
||||
class Session:
|
||||
"""Defines a high level series of bascenev1.Activity-es.
|
||||
@ -203,6 +209,11 @@ class Session:
|
||||
# Instantiate our session globals node which will apply its settings.
|
||||
self._sessionglobalsnode = _bascenev1.newnode('sessionglobals')
|
||||
|
||||
# Rejoin cooldown stuff.
|
||||
self._players_on_wait: dict = {}
|
||||
self._player_requested_identifiers: dict = {}
|
||||
self._waitlist_timers: dict = {}
|
||||
|
||||
@property
|
||||
def context(self) -> bascenev1.ContextRef:
|
||||
"""A context-ref pointing at this activity."""
|
||||
@ -253,6 +264,29 @@ class Session:
|
||||
)
|
||||
return False
|
||||
|
||||
# Rejoin cooldown.
|
||||
identifier = player.get_v1_account_id()
|
||||
if identifier:
|
||||
leave_time = self._players_on_wait.get(identifier)
|
||||
if leave_time:
|
||||
diff = str(
|
||||
math.ceil(REJOIN_COOLDOWN - babase.apptime() + leave_time)
|
||||
)
|
||||
_bascenev1.broadcastmessage(
|
||||
babase.Lstr(
|
||||
translate=(
|
||||
'serverResponses',
|
||||
'You can join in ${COUNT} seconds.',
|
||||
),
|
||||
subs=[('${COUNT}', diff)],
|
||||
),
|
||||
color=(1, 1, 0),
|
||||
clients=[player.inputdevice.client_id],
|
||||
transient=True,
|
||||
)
|
||||
return False
|
||||
self._player_requested_identifiers[player.id] = identifier
|
||||
|
||||
_bascenev1.getsound('dripity').play()
|
||||
return True
|
||||
|
||||
@ -270,6 +304,16 @@ class Session:
|
||||
|
||||
activity = self._activity_weak()
|
||||
|
||||
# Rejoin cooldown.
|
||||
identifier = self._player_requested_identifiers.get(sessionplayer.id)
|
||||
if identifier:
|
||||
self._players_on_wait[identifier] = babase.apptime()
|
||||
with babase.ContextRef.empty():
|
||||
self._waitlist_timers[identifier] = babase.AppTimer(
|
||||
REJOIN_COOLDOWN,
|
||||
babase.Call(self._remove_player_from_waitlist, identifier),
|
||||
)
|
||||
|
||||
if not sessionplayer.in_game:
|
||||
# Ok, the player is still in the lobby; simply remove them.
|
||||
with self.context:
|
||||
@ -770,3 +814,9 @@ class Session:
|
||||
if pass_to_activity:
|
||||
activity.add_player(sessionplayer)
|
||||
return sessionplayer
|
||||
|
||||
def _remove_player_from_waitlist(self, identifier: str) -> None:
|
||||
try:
|
||||
self._players_on_wait.pop(identifier)
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
@ -35,7 +35,7 @@ class ControlsGuide(bs.Actor):
|
||||
delay: is the time in seconds before the overlay fades in.
|
||||
|
||||
lifespan: if not None, the overlay will fade back out and die after
|
||||
that long (in milliseconds).
|
||||
that long (in seconds).
|
||||
|
||||
bright: if True, brighter colors will be used; handy when showing
|
||||
over gameplay but may be too bright for join-screens, etc.
|
||||
@ -50,6 +50,7 @@ class ControlsGuide(bs.Actor):
|
||||
offs5 = 43.0 * scale
|
||||
ouya = False
|
||||
maxw = 50
|
||||
xtweak = -2.8 * scale
|
||||
self._lifespan = lifespan
|
||||
self._dead = False
|
||||
self._bright = bright
|
||||
@ -117,7 +118,7 @@ class ControlsGuide(bs.Actor):
|
||||
'host_only': True,
|
||||
'shadow': 1.0,
|
||||
'maxwidth': maxw,
|
||||
'position': (pos[0], pos[1] - offs5),
|
||||
'position': (pos[0] + xtweak, pos[1] - offs5),
|
||||
'color': clr,
|
||||
},
|
||||
)
|
||||
@ -145,7 +146,7 @@ class ControlsGuide(bs.Actor):
|
||||
'host_only': True,
|
||||
'shadow': 1.0,
|
||||
'maxwidth': maxw,
|
||||
'position': (pos[0], pos[1] - offs5),
|
||||
'position': (pos[0] + xtweak, pos[1] - offs5),
|
||||
'color': clr,
|
||||
},
|
||||
)
|
||||
@ -173,7 +174,7 @@ class ControlsGuide(bs.Actor):
|
||||
'host_only': True,
|
||||
'shadow': 1.0,
|
||||
'maxwidth': maxw,
|
||||
'position': (pos[0], pos[1] - offs5),
|
||||
'position': (pos[0] + xtweak, pos[1] - offs5),
|
||||
'color': clr,
|
||||
},
|
||||
)
|
||||
@ -201,7 +202,7 @@ class ControlsGuide(bs.Actor):
|
||||
'host_only': True,
|
||||
'shadow': 1.0,
|
||||
'maxwidth': maxw,
|
||||
'position': (pos[0], pos[1] - offs5),
|
||||
'position': (pos[0] + xtweak, pos[1] - offs5),
|
||||
'color': clr,
|
||||
},
|
||||
)
|
||||
@ -264,10 +265,19 @@ class ControlsGuide(bs.Actor):
|
||||
bs.timer(delay, bs.WeakCall(self._start_updating))
|
||||
|
||||
@staticmethod
|
||||
def _meaningful_button_name(device: bs.InputDevice, button: int) -> str:
|
||||
def _meaningful_button_name(
|
||||
device: bs.InputDevice, button_name: str
|
||||
) -> str:
|
||||
"""Return a flattened string button name; empty for non-meaningful."""
|
||||
if not device.has_meaningful_button_names:
|
||||
return ''
|
||||
assert bs.app.classic is not None
|
||||
button = bs.app.classic.get_input_device_mapped_value(
|
||||
device, button_name
|
||||
)
|
||||
# -1 means unset; let's show that.
|
||||
if button == -1:
|
||||
return bs.Lstr(resource='configGamepadWindow.unsetText').evaluate()
|
||||
return device.get_button_name(button).evaluate()
|
||||
|
||||
def _start_updating(self) -> None:
|
||||
@ -289,10 +299,10 @@ class ControlsGuide(bs.Actor):
|
||||
def _check_fade_in(self) -> None:
|
||||
assert bs.app.classic is not None
|
||||
|
||||
# If we have a touchscreen, we only fade in if we have a player with
|
||||
# an input device that is *not* the touchscreen.
|
||||
# (otherwise it is confusing to see the touchscreen buttons right
|
||||
# next to our display buttons)
|
||||
# If we have a touchscreen, we only fade in if we have a player
|
||||
# with an input device that is *not* the touchscreen. Otherwise
|
||||
# it is confusing to see the touchscreen buttons right next to
|
||||
# our display buttons.
|
||||
touchscreen: bs.InputDevice | None = bs.getinputdevice(
|
||||
'TouchScreen', '#1', doraise=False
|
||||
)
|
||||
@ -318,15 +328,7 @@ class ControlsGuide(bs.Actor):
|
||||
'buttonBomb',
|
||||
'buttonPickUp',
|
||||
):
|
||||
if (
|
||||
self._meaningful_button_name(
|
||||
device,
|
||||
bs.app.classic.get_input_device_mapped_value(
|
||||
device, name
|
||||
),
|
||||
)
|
||||
!= ''
|
||||
):
|
||||
if self._meaningful_button_name(device, name) != '':
|
||||
fade_in = True
|
||||
break
|
||||
if fade_in:
|
||||
@ -401,58 +403,30 @@ class ControlsGuide(bs.Actor):
|
||||
# We only care about movement buttons in the case of keyboards.
|
||||
if all_keyboards:
|
||||
right_button_names.add(
|
||||
device.get_button_name(
|
||||
classic.get_input_device_mapped_value(
|
||||
device, 'buttonRight'
|
||||
)
|
||||
)
|
||||
self._meaningful_button_name(device, 'buttonRight')
|
||||
)
|
||||
left_button_names.add(
|
||||
device.get_button_name(
|
||||
classic.get_input_device_mapped_value(
|
||||
device, 'buttonLeft'
|
||||
)
|
||||
)
|
||||
self._meaningful_button_name(device, 'buttonLeft')
|
||||
)
|
||||
down_button_names.add(
|
||||
device.get_button_name(
|
||||
classic.get_input_device_mapped_value(
|
||||
device, 'buttonDown'
|
||||
)
|
||||
)
|
||||
self._meaningful_button_name(device, 'buttonDown')
|
||||
)
|
||||
up_button_names.add(
|
||||
device.get_button_name(
|
||||
classic.get_input_device_mapped_value(
|
||||
device, 'buttonUp'
|
||||
)
|
||||
)
|
||||
self._meaningful_button_name(device, 'buttonUp')
|
||||
)
|
||||
|
||||
# Ignore empty values; things like the remote app or
|
||||
# wiimotes can return these.
|
||||
bname = self._meaningful_button_name(
|
||||
device,
|
||||
classic.get_input_device_mapped_value(device, 'buttonPunch'),
|
||||
)
|
||||
bname = self._meaningful_button_name(device, 'buttonPunch')
|
||||
if bname != '':
|
||||
punch_button_names.add(bname)
|
||||
bname = self._meaningful_button_name(
|
||||
device,
|
||||
classic.get_input_device_mapped_value(device, 'buttonJump'),
|
||||
)
|
||||
bname = self._meaningful_button_name(device, 'buttonJump')
|
||||
if bname != '':
|
||||
jump_button_names.add(bname)
|
||||
bname = self._meaningful_button_name(
|
||||
device,
|
||||
classic.get_input_device_mapped_value(device, 'buttonBomb'),
|
||||
)
|
||||
bname = self._meaningful_button_name(device, 'buttonBomb')
|
||||
if bname != '':
|
||||
bomb_button_names.add(bname)
|
||||
bname = self._meaningful_button_name(
|
||||
device,
|
||||
classic.get_input_device_mapped_value(device, 'buttonPickUp'),
|
||||
)
|
||||
bname = self._meaningful_button_name(device, 'buttonPickUp')
|
||||
if bname != '':
|
||||
pickup_button_names.add(bname)
|
||||
|
||||
@ -582,8 +556,8 @@ class ControlsGuide(bs.Actor):
|
||||
if msg.immediate:
|
||||
self._die()
|
||||
else:
|
||||
# If they don't need immediate,
|
||||
# fade out our nodes and die later.
|
||||
# If they don't need immediate, fade out our nodes and
|
||||
# die later.
|
||||
for node in self._nodes:
|
||||
bs.animate(node, 'opacity', {0: node.opacity, 3.0: 0.0})
|
||||
bs.timer(3.1, bs.WeakCall(self._die))
|
||||
|
||||
@ -66,6 +66,8 @@ from babase import (
|
||||
lock_all_input,
|
||||
LoginAdapter,
|
||||
Lstr,
|
||||
native_review_request,
|
||||
native_review_request_supported,
|
||||
NotFoundError,
|
||||
Permission,
|
||||
Plugin,
|
||||
@ -191,6 +193,8 @@ __all__ = [
|
||||
'LoginAdapter',
|
||||
'Lstr',
|
||||
'Mesh',
|
||||
'native_review_request',
|
||||
'native_review_request_supported',
|
||||
'NotFoundError',
|
||||
'open_file_externally',
|
||||
'open_url',
|
||||
|
||||
@ -51,6 +51,18 @@ class OnScreenKeyboardWindow(Window):
|
||||
else (0, 0),
|
||||
)
|
||||
)
|
||||
self._cancel_button = _bauiv1.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
scale=0.5,
|
||||
position=(30, self._height - 55),
|
||||
size=(60, 60),
|
||||
label='',
|
||||
on_activate_call=self._cancel,
|
||||
autoselect=True,
|
||||
color=(0.55, 0.5, 0.6),
|
||||
icon=_bauiv1.gettexture('crossOut'),
|
||||
iconscale=1.2,
|
||||
)
|
||||
self._done_button = _bauiv1.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
position=(self._width - 200, 44),
|
||||
|
||||
@ -64,12 +64,13 @@ class AccountSettingsWindow(bui.Window):
|
||||
)
|
||||
|
||||
# Currently we can only reset achievements on game-center.
|
||||
v1_account_type: str | None
|
||||
if self._v1_signed_in:
|
||||
v1_account_type = plus.get_v1_account_type()
|
||||
else:
|
||||
v1_account_type = None
|
||||
self._can_reset_achievements = v1_account_type == 'Game Center'
|
||||
# v1_account_type: str | None
|
||||
# if self._v1_signed_in:
|
||||
# v1_account_type = plus.get_v1_account_type()
|
||||
# else:
|
||||
# v1_account_type = None
|
||||
# self._can_reset_achievements = v1_account_type == 'Game Center'
|
||||
self._can_reset_achievements = False
|
||||
|
||||
app = bui.app
|
||||
assert app.classic is not None
|
||||
@ -98,6 +99,9 @@ class AccountSettingsWindow(bui.Window):
|
||||
if LoginType.GPGS in plus.accounts.login_adapters:
|
||||
self._show_sign_in_buttons.append('Google Play')
|
||||
|
||||
if LoginType.GAME_CENTER in plus.accounts.login_adapters:
|
||||
self._show_sign_in_buttons.append('Game Center')
|
||||
|
||||
# Always want to show our web-based v2 login option.
|
||||
self._show_sign_in_buttons.append('V2Proxy')
|
||||
|
||||
@ -227,6 +231,8 @@ class AccountSettingsWindow(bui.Window):
|
||||
plus = bui.app.plus
|
||||
assert plus is not None
|
||||
|
||||
via_lines: list[str] = []
|
||||
|
||||
primary_v2_account = plus.accounts.primary
|
||||
|
||||
v1_state = plus.get_v1_account_state()
|
||||
@ -242,9 +248,53 @@ class AccountSettingsWindow(bui.Window):
|
||||
False if gpgs_adapter is None else gpgs_adapter.is_back_end_active()
|
||||
)
|
||||
|
||||
# Ditto for Game Center.
|
||||
game_center_adapter = plus.accounts.login_adapters.get(
|
||||
LoginType.GAME_CENTER
|
||||
)
|
||||
is_game_center = (
|
||||
False
|
||||
if game_center_adapter is None
|
||||
else game_center_adapter.is_back_end_active()
|
||||
)
|
||||
|
||||
show_signed_in_as = self._v1_signed_in
|
||||
signed_in_as_space = 95.0
|
||||
|
||||
# To reduce confusion about the whole V2 account situation for
|
||||
# people used to seeing their Google Play Games or Game Center
|
||||
# account name and icon and whatnot, let's show those underneath
|
||||
# the V2 tag to help communicate that they are in fact logged in
|
||||
# through that account.
|
||||
via_space = 25.0
|
||||
if show_signed_in_as and bui.app.plus is not None:
|
||||
primary_account = bui.app.plus.accounts.primary
|
||||
if primary_account is not None:
|
||||
# Show Google Play Games account name if the current account
|
||||
# has such a login attached.
|
||||
lname = primary_account.logins.get(LoginType.GPGS)
|
||||
if lname is not None:
|
||||
icontxt = bui.charstr(
|
||||
bui.SpecialChar.GOOGLE_PLAY_GAMES_LOGO
|
||||
)
|
||||
via_lines.append(f'{icontxt}{lname}')
|
||||
|
||||
# Show Game Center account name if the current account
|
||||
# has such a login attached.
|
||||
lname = primary_account.logins.get(LoginType.GAME_CENTER)
|
||||
if lname is not None:
|
||||
icontxt = bui.charstr(bui.SpecialChar.GAME_CENTER_LOGO)
|
||||
via_lines.append(f'{icontxt}{lname}')
|
||||
|
||||
# TEMP TESTING
|
||||
if bool(False):
|
||||
icontxt = bui.charstr(bui.SpecialChar.GAME_CENTER_LOGO)
|
||||
via_lines.append(f'{icontxt}FloofDibble')
|
||||
icontxt = bui.charstr(
|
||||
bui.SpecialChar.GOOGLE_PLAY_GAMES_LOGO
|
||||
)
|
||||
via_lines.append(f'{icontxt}StinkBobble')
|
||||
|
||||
show_sign_in_benefits = not self._v1_signed_in
|
||||
sign_in_benefits_space = 80.0
|
||||
|
||||
@ -258,6 +308,11 @@ class AccountSettingsWindow(bui.Window):
|
||||
and self._signing_in_adapter is None
|
||||
and 'Google Play' in self._show_sign_in_buttons
|
||||
)
|
||||
show_game_center_sign_in_button = (
|
||||
v1_state == 'signed_out'
|
||||
and self._signing_in_adapter is None
|
||||
and 'Game Center' in self._show_sign_in_buttons
|
||||
)
|
||||
show_v2_proxy_sign_in_button = (
|
||||
v1_state == 'signed_out'
|
||||
and self._signing_in_adapter is None
|
||||
@ -281,11 +336,9 @@ class AccountSettingsWindow(bui.Window):
|
||||
show_linked_accounts_text = self._v1_signed_in
|
||||
linked_accounts_text_space = 60.0
|
||||
|
||||
show_achievements_button = self._v1_signed_in and v1_account_type in (
|
||||
'Google Play',
|
||||
'Local',
|
||||
'V2',
|
||||
)
|
||||
# Always show achievements except in the game-center case where
|
||||
# its unified UI covers them.
|
||||
show_achievements_button = self._v1_signed_in and not is_game_center
|
||||
achievements_button_space = 60.0
|
||||
|
||||
show_achievements_text = (
|
||||
@ -349,10 +402,13 @@ class AccountSettingsWindow(bui.Window):
|
||||
self._sub_height = 60.0
|
||||
if show_signed_in_as:
|
||||
self._sub_height += signed_in_as_space
|
||||
self._sub_height += via_space * len(via_lines)
|
||||
if show_signing_in_text:
|
||||
self._sub_height += signing_in_text_space
|
||||
if show_google_play_sign_in_button:
|
||||
self._sub_height += sign_in_button_space
|
||||
if show_game_center_sign_in_button:
|
||||
self._sub_height += sign_in_button_space
|
||||
if show_v2_proxy_sign_in_button:
|
||||
self._sub_height += sign_in_button_space
|
||||
if show_device_sign_in_button:
|
||||
@ -466,6 +522,54 @@ class AccountSettingsWindow(bui.Window):
|
||||
|
||||
v -= signed_in_as_space * 0.4
|
||||
|
||||
for via in via_lines:
|
||||
v -= via_space * 0.1
|
||||
sscale = 0.7
|
||||
swidth = (
|
||||
bui.get_string_width(via, suppress_warning=True) * sscale
|
||||
)
|
||||
bui.textwidget(
|
||||
parent=self._subcontainer,
|
||||
position=(self._sub_width * 0.5, v),
|
||||
size=(0, 0),
|
||||
text=via,
|
||||
scale=sscale,
|
||||
color=(0.6, 0.6, 0.6),
|
||||
flatness=1.0,
|
||||
shadow=0.0,
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
)
|
||||
bui.textwidget(
|
||||
parent=self._subcontainer,
|
||||
position=(self._sub_width * 0.5 - swidth * 0.5 - 5, v),
|
||||
size=(0, 0),
|
||||
text=bui.Lstr(
|
||||
value='(${VIA}',
|
||||
subs=[('${VIA}', bui.Lstr(resource='viaText'))],
|
||||
),
|
||||
scale=0.6,
|
||||
color=(0.4, 0.6, 0.4, 0.5),
|
||||
flatness=1.0,
|
||||
shadow=0.0,
|
||||
h_align='right',
|
||||
v_align='center',
|
||||
)
|
||||
bui.textwidget(
|
||||
parent=self._subcontainer,
|
||||
position=(self._sub_width * 0.5 + swidth * 0.5 + 10, v),
|
||||
size=(0, 0),
|
||||
text=')',
|
||||
scale=0.6,
|
||||
color=(0.4, 0.6, 0.4, 0.5),
|
||||
flatness=1.0,
|
||||
shadow=0.0,
|
||||
h_align='right',
|
||||
v_align='center',
|
||||
)
|
||||
|
||||
v -= via_space * 0.9
|
||||
|
||||
else:
|
||||
self._account_name_text = None
|
||||
self._account_name_what_is_text = None
|
||||
@ -477,22 +581,6 @@ class AccountSettingsWindow(bui.Window):
|
||||
|
||||
if show_sign_in_benefits:
|
||||
v -= sign_in_benefits_space
|
||||
app = bui.app
|
||||
assert app.classic is not None
|
||||
extra: str | bui.Lstr | None
|
||||
if (
|
||||
app.classic.platform in ['mac', 'ios']
|
||||
and app.classic.subplatform == 'appstore'
|
||||
):
|
||||
extra = bui.Lstr(
|
||||
value='\n${S}',
|
||||
subs=[
|
||||
('${S}', bui.Lstr(resource='signInWithGameCenterText'))
|
||||
],
|
||||
)
|
||||
else:
|
||||
extra = ''
|
||||
|
||||
bui.textwidget(
|
||||
parent=self._subcontainer,
|
||||
position=(
|
||||
@ -500,16 +588,7 @@ class AccountSettingsWindow(bui.Window):
|
||||
v + sign_in_benefits_space * 0.4,
|
||||
),
|
||||
size=(0, 0),
|
||||
text=bui.Lstr(
|
||||
value='${A}${B}',
|
||||
subs=[
|
||||
(
|
||||
'${A}',
|
||||
bui.Lstr(resource=self._r + '.signInInfoText'),
|
||||
),
|
||||
('${B}', extra),
|
||||
],
|
||||
),
|
||||
text=bui.Lstr(resource=self._r + '.signInInfoText'),
|
||||
max_height=sign_in_benefits_space * 0.9,
|
||||
scale=0.9,
|
||||
color=(0.75, 0.7, 0.8),
|
||||
@ -554,7 +633,13 @@ class AccountSettingsWindow(bui.Window):
|
||||
(
|
||||
'${B}',
|
||||
bui.Lstr(
|
||||
resource=self._r + '.signInWithGooglePlayText'
|
||||
resource=self._r + '.signInWithText',
|
||||
subs=[
|
||||
(
|
||||
'${SERVICE}',
|
||||
bui.Lstr(resource='googlePlayText'),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
@ -572,6 +657,45 @@ class AccountSettingsWindow(bui.Window):
|
||||
bui.widget(edit=btn, show_buffer_bottom=40, show_buffer_top=100)
|
||||
self._sign_in_text = None
|
||||
|
||||
if show_game_center_sign_in_button:
|
||||
button_width = 350
|
||||
v -= sign_in_button_space
|
||||
self._sign_in_google_play_button = btn = bui.buttonwidget(
|
||||
parent=self._subcontainer,
|
||||
position=((self._sub_width - button_width) * 0.5, v - 20),
|
||||
autoselect=True,
|
||||
size=(button_width, 60),
|
||||
label=bui.Lstr(
|
||||
value='${A}${B}',
|
||||
subs=[
|
||||
(
|
||||
'${A}',
|
||||
bui.charstr(bui.SpecialChar.GAME_CENTER_LOGO),
|
||||
),
|
||||
(
|
||||
'${B}',
|
||||
bui.Lstr(
|
||||
resource=self._r + '.signInWithText',
|
||||
subs=[('${SERVICE}', 'Game Center')],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
on_activate_call=lambda: self._sign_in_press(
|
||||
LoginType.GAME_CENTER
|
||||
),
|
||||
)
|
||||
if first_selectable is None:
|
||||
first_selectable = btn
|
||||
if bui.app.ui_v1.use_toolbars:
|
||||
bui.widget(
|
||||
edit=btn,
|
||||
right_widget=bui.get_special_widget('party_button'),
|
||||
)
|
||||
bui.widget(edit=btn, left_widget=bbtn)
|
||||
bui.widget(edit=btn, show_buffer_bottom=40, show_buffer_top=100)
|
||||
self._sign_in_text = None
|
||||
|
||||
if show_v2_proxy_sign_in_button:
|
||||
button_width = 350
|
||||
v -= sign_in_button_space
|
||||
@ -748,7 +872,11 @@ class AccountSettingsWindow(bui.Window):
|
||||
v -= game_service_button_space * 0.85
|
||||
v1_account_type = plus.get_v1_account_type()
|
||||
if v1_account_type == 'Game Center':
|
||||
v1_account_type_name = bui.Lstr(resource='gameCenterText')
|
||||
# Update: Apparently Game Center is just called 'Game Center'
|
||||
# in all languages. Can revisit if not true.
|
||||
# https://developer.apple.com/forums/thread/725779
|
||||
v1_account_type_name = bui.Lstr(value='Game Center')
|
||||
# v1_account_type_name = bui.Lstr(resource='gameCenterText')
|
||||
else:
|
||||
raise ValueError(
|
||||
"unknown account type: '" + str(v1_account_type) + "'"
|
||||
@ -1469,8 +1597,11 @@ class AccountSettingsWindow(bui.Window):
|
||||
if isinstance(result, Exception):
|
||||
# For now just make a bit of noise if anything went wrong;
|
||||
# can get more specific as needed later.
|
||||
logging.warning('Got error in v2 sign-in result: %s.', result)
|
||||
bui.screenmessage(bui.Lstr(resource='errorText'), color=(1, 0, 0))
|
||||
logging.warning('Got error in v2 sign-in result: %s', result)
|
||||
bui.screenmessage(
|
||||
bui.Lstr(resource='internal.signInNoConnectionText'),
|
||||
color=(1, 0, 0),
|
||||
)
|
||||
bui.getsound('error').play()
|
||||
else:
|
||||
# Success! Plug in these credentials which will begin
|
||||
|
||||
@ -93,6 +93,7 @@ class ConfigNumberEdit:
|
||||
displayname: str | bui.Lstr | None = None,
|
||||
changesound: bool = True,
|
||||
textscale: float = 1.0,
|
||||
as_percent: bool = False,
|
||||
):
|
||||
if displayname is None:
|
||||
displayname = configkey
|
||||
@ -103,6 +104,7 @@ class ConfigNumberEdit:
|
||||
self._increment = increment
|
||||
self._callback = callback
|
||||
self._value = bui.app.config.resolve(configkey)
|
||||
self._as_percent = as_percent
|
||||
|
||||
self.nametext = bui.textwidget(
|
||||
parent=parent,
|
||||
@ -166,4 +168,8 @@ class ConfigNumberEdit:
|
||||
bui.app.config.apply_and_commit()
|
||||
|
||||
def _update_display(self) -> None:
|
||||
bui.textwidget(edit=self.valuetext, text=f'{self._value:.1f}')
|
||||
if self._as_percent:
|
||||
val = f'{round(self._value*100.0)}%'
|
||||
else:
|
||||
val = f'{self._value:.1f}'
|
||||
bui.textwidget(edit=self.valuetext, text=val)
|
||||
|
||||
@ -1,80 +0,0 @@
|
||||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""UI for dealing with broken config files."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import bauiv1 as bui
|
||||
|
||||
|
||||
class ConfigErrorWindow(bui.Window):
|
||||
"""Window for dealing with a broken config."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._config_file_path = bui.app.env.config_file_path
|
||||
width = 800
|
||||
super().__init__(
|
||||
bui.containerwidget(size=(width, 400), transition='in_right')
|
||||
)
|
||||
padding = 20
|
||||
bui.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(padding, 220 + 60),
|
||||
size=(width - 2 * padding, 100 - 2 * padding),
|
||||
h_align='center',
|
||||
v_align='top',
|
||||
scale=0.73,
|
||||
text=(
|
||||
f'Error reading {bui.appnameupper()} config file'
|
||||
':\n\n\nCheck the console'
|
||||
' (press ~ twice) for details.\n\nWould you like to quit and'
|
||||
' try to fix it by hand\nor overwrite it with defaults?\n\n'
|
||||
'(high scores, player profiles, etc will be lost if you'
|
||||
' overwrite)'
|
||||
),
|
||||
)
|
||||
bui.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(padding, 198 + 60),
|
||||
size=(width - 2 * padding, 100 - 2 * padding),
|
||||
h_align='center',
|
||||
v_align='top',
|
||||
scale=0.5,
|
||||
text=self._config_file_path,
|
||||
)
|
||||
quit_button = bui.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
position=(35, 30),
|
||||
size=(240, 54),
|
||||
label='Quit and Edit',
|
||||
on_activate_call=self._quit,
|
||||
)
|
||||
bui.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
position=(width - 370, 30),
|
||||
size=(330, 54),
|
||||
label='Overwrite with Defaults',
|
||||
on_activate_call=self._defaults,
|
||||
)
|
||||
bui.containerwidget(
|
||||
edit=self._root_widget,
|
||||
cancel_button=quit_button,
|
||||
selected_child=quit_button,
|
||||
)
|
||||
|
||||
def _quit(self) -> None:
|
||||
bui.apptimer(0.001, self._edit_and_quit)
|
||||
bui.lock_all_input()
|
||||
|
||||
def _edit_and_quit(self) -> None:
|
||||
bui.open_file_externally(self._config_file_path)
|
||||
bui.apptimer(0.1, bui.quit)
|
||||
|
||||
def _defaults(self) -> None:
|
||||
bui.containerwidget(edit=self._root_widget, transition='out_left')
|
||||
bui.getsound('gunCocking').play()
|
||||
bui.screenmessage('settings reset.', color=(1, 1, 0))
|
||||
|
||||
# At this point settings are already set; lets just commit them
|
||||
# to disk.
|
||||
bui.commit_app_config(force=True)
|
||||
@ -458,9 +458,9 @@ class PrivateGatherTab(GatherTab):
|
||||
scale=1.5,
|
||||
size=(300, 50),
|
||||
editable=True,
|
||||
max_chars=20,
|
||||
description=bui.Lstr(resource='gatherWindow.partyCodeText'),
|
||||
autoselect=True,
|
||||
maxwidth=250,
|
||||
h_align='left',
|
||||
v_align='center',
|
||||
text='',
|
||||
@ -962,7 +962,7 @@ class PrivateGatherTab(GatherTab):
|
||||
code = cast(str, bui.textwidget(query=self._join_party_code_text))
|
||||
if not code:
|
||||
bui.screenmessage(
|
||||
bui.Lstr(resource='internal.invalidAddressErrorText'),
|
||||
bui.Lstr(translate=('serverResponses', 'Invalid code.')),
|
||||
color=(1, 0, 0),
|
||||
)
|
||||
bui.getsound('error').play()
|
||||
|
||||
@ -361,6 +361,7 @@ class PublicGatherTab(GatherTab):
|
||||
self._last_server_list_query_time: float | None = None
|
||||
self._join_list_column: bui.Widget | None = None
|
||||
self._join_status_text: bui.Widget | None = None
|
||||
self._no_servers_found_text: bui.Widget | None = None
|
||||
self._host_max_party_size_value: bui.Widget | None = None
|
||||
self._host_max_party_size_minus_button: (bui.Widget | None) = None
|
||||
self._host_max_party_size_plus_button: (bui.Widget | None) = None
|
||||
@ -658,6 +659,18 @@ class PublicGatherTab(GatherTab):
|
||||
color=(0.6, 0.6, 0.6),
|
||||
position=(c_width * 0.5, c_height * 0.5),
|
||||
)
|
||||
self._no_servers_found_text = bui.textwidget(
|
||||
parent=self._container,
|
||||
text='',
|
||||
size=(0, 0),
|
||||
scale=0.9,
|
||||
flatness=1.0,
|
||||
shadow=0.0,
|
||||
h_align='center',
|
||||
v_align='top',
|
||||
color=(0.6, 0.6, 0.6),
|
||||
position=(c_width * 0.5, c_height * 0.5),
|
||||
)
|
||||
|
||||
def _build_host_tab(
|
||||
self, region_width: float, region_height: float
|
||||
@ -950,6 +963,9 @@ class PublicGatherTab(GatherTab):
|
||||
self._update_party_rows()
|
||||
|
||||
def _update_party_rows(self) -> None:
|
||||
plus = bui.app.plus
|
||||
assert plus is not None
|
||||
|
||||
columnwidget = self._join_list_column
|
||||
if not columnwidget:
|
||||
return
|
||||
@ -963,6 +979,7 @@ class PublicGatherTab(GatherTab):
|
||||
edit=self._host_scrollwidget,
|
||||
claims_up_down=(len(self._parties_displayed) > 0),
|
||||
)
|
||||
bui.textwidget(edit=self._no_servers_found_text, text='')
|
||||
|
||||
# Clip if we have more UI rows than parties to show.
|
||||
clipcount = len(self._ui_rows) - len(self._parties_displayed)
|
||||
@ -972,6 +989,15 @@ class PublicGatherTab(GatherTab):
|
||||
|
||||
# If we have no parties to show, we're done.
|
||||
if not self._parties_displayed:
|
||||
text = self._join_status_text
|
||||
if (
|
||||
plus.get_v1_account_state() == 'signed_in'
|
||||
and cast(str, bui.textwidget(query=text)) == ''
|
||||
):
|
||||
bui.textwidget(
|
||||
edit=self._no_servers_found_text,
|
||||
text=bui.Lstr(resource='noServersFoundText'),
|
||||
)
|
||||
return
|
||||
|
||||
sub_scroll_width = 830
|
||||
|
||||
@ -1350,7 +1350,7 @@ class MainMenuWindow(bui.Window):
|
||||
bui.app.classic.resume()
|
||||
if self._root_widget:
|
||||
bui.containerwidget(edit=self._root_widget, transition='out_right')
|
||||
bui.app.ui_v1.clear_main_menu_window()
|
||||
bui.app.ui_v1.clear_main_menu_window(transition='out_right')
|
||||
|
||||
# If there's callbacks waiting for this window to go away, call them.
|
||||
for call in bui.app.ui_v1.main_menu_resume_callbacks:
|
||||
|
||||
@ -807,6 +807,8 @@ class AdvancedSettingsWindow(bui.Window):
|
||||
sel_name = 'ModdingGuide'
|
||||
elif sel == self._language_inform_checkbox:
|
||||
sel_name = 'LangInform'
|
||||
elif sel == self._show_dev_console_button_check_box.widget:
|
||||
sel_name = 'ShowDevConsole'
|
||||
else:
|
||||
raise ValueError(f'unrecognized selection \'{sel}\'')
|
||||
elif sel == self._back_button:
|
||||
@ -870,6 +872,8 @@ class AdvancedSettingsWindow(bui.Window):
|
||||
sel = self._modding_guide_button
|
||||
elif sel_name == 'LangInform':
|
||||
sel = self._language_inform_checkbox
|
||||
elif sel_name == 'ShowDevConsole':
|
||||
sel = self._show_dev_console_button_check_box.widget
|
||||
else:
|
||||
sel = None
|
||||
if sel is not None:
|
||||
|
||||
@ -121,7 +121,8 @@ class AudioSettingsWindow(bui.Window):
|
||||
displayname=bui.Lstr(resource=self._r + '.soundVolumeText'),
|
||||
minval=0.0,
|
||||
maxval=1.0,
|
||||
increment=0.1,
|
||||
increment=0.05,
|
||||
as_percent=True,
|
||||
)
|
||||
if bui.app.ui_v1.use_toolbars:
|
||||
bui.widget(
|
||||
@ -137,9 +138,10 @@ class AudioSettingsWindow(bui.Window):
|
||||
displayname=bui.Lstr(resource=self._r + '.musicVolumeText'),
|
||||
minval=0.0,
|
||||
maxval=1.0,
|
||||
increment=0.1,
|
||||
increment=0.05,
|
||||
callback=music.music_volume_changed,
|
||||
changesound=False,
|
||||
as_percent=True,
|
||||
)
|
||||
|
||||
v -= 0.5 * spacing
|
||||
|
||||
@ -545,20 +545,24 @@ class GamepadSettingsWindow(bui.Window):
|
||||
if 'analogStickLR' + self._ext in self._settings
|
||||
else 5
|
||||
if self._is_secondary
|
||||
else 1
|
||||
else None
|
||||
)
|
||||
sval2 = (
|
||||
self._settings['analogStickUD' + self._ext]
|
||||
if 'analogStickUD' + self._ext in self._settings
|
||||
else 6
|
||||
if self._is_secondary
|
||||
else 2
|
||||
)
|
||||
return (
|
||||
self._input.get_axis_name(sval1)
|
||||
+ ' / '
|
||||
+ self._input.get_axis_name(sval2)
|
||||
else None
|
||||
)
|
||||
assert isinstance(sval1, (int, type(None)))
|
||||
assert isinstance(sval2, (int, type(None)))
|
||||
if sval1 is not None and sval2 is not None:
|
||||
return (
|
||||
self._input.get_axis_name(sval1)
|
||||
+ ' / '
|
||||
+ self._input.get_axis_name(sval2)
|
||||
)
|
||||
return bui.Lstr(resource=self._r + '.unsetText')
|
||||
|
||||
# If they're looking for triggers.
|
||||
if control in ['triggerRun1' + self._ext, 'triggerRun2' + self._ext]:
|
||||
@ -573,7 +577,7 @@ class GamepadSettingsWindow(bui.Window):
|
||||
return str(1.0)
|
||||
|
||||
# For dpad buttons: show individual buttons if any are set.
|
||||
# Otherwise show whichever dpad is set (defaulting to 1).
|
||||
# Otherwise show whichever dpad is set.
|
||||
dpad_buttons = [
|
||||
'buttonLeft' + self._ext,
|
||||
'buttonRight' + self._ext,
|
||||
@ -588,24 +592,28 @@ class GamepadSettingsWindow(bui.Window):
|
||||
return bui.Lstr(resource=self._r + '.unsetText')
|
||||
|
||||
# No dpad buttons - show the dpad number for all 4.
|
||||
return bui.Lstr(
|
||||
value='${A} ${B}',
|
||||
subs=[
|
||||
('${A}', bui.Lstr(resource=self._r + '.dpadText')),
|
||||
(
|
||||
'${B}',
|
||||
str(
|
||||
self._settings['dpad' + self._ext]
|
||||
if 'dpad' + self._ext in self._settings
|
||||
else 2
|
||||
if self._is_secondary
|
||||
else 1
|
||||
),
|
||||
),
|
||||
],
|
||||
dpadnum = (
|
||||
self._settings['dpad' + self._ext]
|
||||
if 'dpad' + self._ext in self._settings
|
||||
else 2
|
||||
if self._is_secondary
|
||||
else None
|
||||
)
|
||||
assert isinstance(dpadnum, (int, type(None)))
|
||||
if dpadnum is not None:
|
||||
return bui.Lstr(
|
||||
value='${A} ${B}',
|
||||
subs=[
|
||||
('${A}', bui.Lstr(resource=self._r + '.dpadText')),
|
||||
(
|
||||
'${B}',
|
||||
str(dpadnum),
|
||||
),
|
||||
],
|
||||
)
|
||||
return bui.Lstr(resource=self._r + '.unsetText')
|
||||
|
||||
# other buttons..
|
||||
# Other buttons.
|
||||
if control in self._settings:
|
||||
return self._input.get_button_name(self._settings[control])
|
||||
return bui.Lstr(resource=self._r + '.unsetText')
|
||||
@ -616,9 +624,7 @@ class GamepadSettingsWindow(bui.Window):
|
||||
event: dict[str, Any],
|
||||
dialog: AwaitGamepadInputWindow,
|
||||
) -> None:
|
||||
# pylint: disable=too-many-nested-blocks
|
||||
# pylint: disable=too-many-branches
|
||||
# pylint: disable=too-many-statements
|
||||
assert self._settings is not None
|
||||
ext = self._ext
|
||||
|
||||
@ -648,10 +654,6 @@ class GamepadSettingsWindow(bui.Window):
|
||||
if btn in self._settings:
|
||||
del self._settings[btn]
|
||||
if event['hat'] == (2 if self._is_secondary else 1):
|
||||
# Exclude value in default case.
|
||||
if 'dpad' + ext in self._settings:
|
||||
del self._settings['dpad' + ext]
|
||||
else:
|
||||
self._settings['dpad' + ext] = event['hat']
|
||||
|
||||
# Update the 4 dpad button txt widgets.
|
||||
@ -680,10 +682,6 @@ class GamepadSettingsWindow(bui.Window):
|
||||
if abs(event['value']) > 0.5:
|
||||
axis = event['axis']
|
||||
if axis == (5 if self._is_secondary else 1):
|
||||
# Exclude value in default case.
|
||||
if 'analogStickLR' + ext in self._settings:
|
||||
del self._settings['analogStickLR' + ext]
|
||||
else:
|
||||
self._settings['analogStickLR' + ext] = axis
|
||||
bui.textwidget(
|
||||
edit=self._textwidgets['analogStickLR' + ext],
|
||||
@ -713,10 +711,6 @@ class GamepadSettingsWindow(bui.Window):
|
||||
lr_axis = 5 if self._is_secondary else 1
|
||||
if axis != lr_axis:
|
||||
if axis == (6 if self._is_secondary else 2):
|
||||
# Exclude value in default case.
|
||||
if 'analogStickUD' + ext in self._settings:
|
||||
del self._settings['analogStickUD' + ext]
|
||||
else:
|
||||
self._settings['analogStickUD' + ext] = axis
|
||||
bui.textwidget(
|
||||
edit=self._textwidgets['analogStickLR' + ext],
|
||||
@ -795,7 +789,7 @@ class GamepadSettingsWindow(bui.Window):
|
||||
),
|
||||
)
|
||||
|
||||
bui.apptimer(0, doit)
|
||||
bui.pushcall(doit)
|
||||
return btn
|
||||
|
||||
def _cancel(self) -> None:
|
||||
|
||||
@ -452,7 +452,7 @@ class GamepadAdvancedSettingsWindow(bui.Window):
|
||||
),
|
||||
)
|
||||
|
||||
bui.apptimer(0, doit)
|
||||
bui.pushcall(doit)
|
||||
return btn, btn2
|
||||
|
||||
def _inc(
|
||||
|
||||
@ -213,6 +213,12 @@ class ConfigKeyboardWindow(bui.Window):
|
||||
scale=1.0,
|
||||
)
|
||||
|
||||
def _pretty_button_name(self, button_name: str) -> bui.Lstr:
|
||||
button_id = self._settings[button_name]
|
||||
if button_id == -1:
|
||||
return bs.Lstr(resource='configGamepadWindow.unsetText')
|
||||
return self._input.get_button_name(button_id)
|
||||
|
||||
def _capture_button(
|
||||
self,
|
||||
pos: tuple[float, float],
|
||||
@ -250,7 +256,7 @@ class ConfigKeyboardWindow(bui.Window):
|
||||
v_align='top',
|
||||
scale=uiscale,
|
||||
maxwidth=maxwidth,
|
||||
text=self._input.get_button_name(self._settings[button]),
|
||||
text=self._pretty_button_name(button),
|
||||
)
|
||||
bui.buttonwidget(
|
||||
edit=btn,
|
||||
|
||||
@ -518,10 +518,13 @@ def show_offer() -> bool:
|
||||
assert plus is not None
|
||||
|
||||
app = bui.app
|
||||
assert app.classic is not None
|
||||
if app.classic is None:
|
||||
raise RuntimeError(
|
||||
'Classic feature-set is required to show offers.'
|
||||
)
|
||||
|
||||
# Space things out a bit so we don't hit the poor user with an ad and
|
||||
# then an in-game offer.
|
||||
# Space things out a bit so we don't hit the poor user with an
|
||||
# ad and then an in-game offer.
|
||||
has_been_long_enough_since_ad = True
|
||||
if app.classic.ads.last_ad_completion_time is not None and (
|
||||
bui.apptime() - app.classic.ads.last_ad_completion_time < 30.0
|
||||
@ -532,8 +535,9 @@ def show_offer() -> bool:
|
||||
app.classic.special_offer is not None
|
||||
and has_been_long_enough_since_ad
|
||||
):
|
||||
# Special case: for pro offers, store this in our prefs so we
|
||||
# can re-show it if the user kills us (set phasers to 'NAG'!!!).
|
||||
# Special case: for pro offers, store this in our prefs so
|
||||
# we can re-show it if the user kills us (set phasers to
|
||||
# 'NAG'!!!).
|
||||
if app.classic.special_offer.get('item') == 'pro_fullprice':
|
||||
cfg = app.config
|
||||
cfg['pendingSpecialOffer'] = {
|
||||
@ -543,7 +547,11 @@ def show_offer() -> bool:
|
||||
cfg.commit()
|
||||
|
||||
if app.classic.special_offer['item'] == 'rating':
|
||||
feedback.ask_for_rating()
|
||||
# Go with a native thing if we've got one.
|
||||
if bui.native_review_request_supported():
|
||||
bui.native_review_request()
|
||||
else:
|
||||
feedback.ask_for_rating()
|
||||
else:
|
||||
SpecialOfferWindow(app.classic.special_offer)
|
||||
|
||||
|
||||
@ -352,7 +352,7 @@ class StoreBrowserWindow(bui.Window):
|
||||
|
||||
plus = bui.app.plus
|
||||
assert plus is not None
|
||||
if plus.get_v1_account_state() != 'signed_in':
|
||||
if plus.accounts.primary is None:
|
||||
account.show_sign_in_prompt()
|
||||
else:
|
||||
plus.restore_purchases()
|
||||
|
||||
@ -2,64 +2,17 @@
|
||||
|
||||
#include "ballistica/base/app_adapter/app_adapter.h"
|
||||
|
||||
#if BA_OSTYPE_ANDROID // Remove conditional once android sources are public.
|
||||
#include "ballistica/base/app_adapter/app_adapter_android.h"
|
||||
#endif
|
||||
#include "ballistica/base/app_adapter/app_adapter_apple.h"
|
||||
#include "ballistica/base/app_adapter/app_adapter_headless.h"
|
||||
#include "ballistica/base/app_adapter/app_adapter_sdl.h"
|
||||
#include "ballistica/base/app_adapter/app_adapter_vr.h"
|
||||
#include "ballistica/base/graphics/graphics_server.h"
|
||||
#include "ballistica/base/graphics/renderer/renderer.h"
|
||||
#include "ballistica/base/input/input.h"
|
||||
#include "ballistica/base/networking/network_reader.h"
|
||||
#include "ballistica/base/networking/networking.h"
|
||||
#include "ballistica/base/platform/base_platform.h"
|
||||
#include "ballistica/base/python/base_python.h"
|
||||
#include "ballistica/base/support/app_config.h"
|
||||
#include "ballistica/base/support/stress_test.h"
|
||||
#include "ballistica/base/ui/ui.h"
|
||||
#include "ballistica/shared/foundation/event_loop.h"
|
||||
#include "ballistica/shared/python/python.h"
|
||||
|
||||
namespace ballistica::base {
|
||||
|
||||
auto AppAdapter::Create() -> AppAdapter* {
|
||||
assert(g_core);
|
||||
|
||||
// TEMP - need to init sdl on our legacy mac build even though its not
|
||||
// technically an SDL app. Kill this once the old mac build is gone.
|
||||
#if BA_LEGACY_MACOS_BUILD
|
||||
AppAdapterSDL::InitSDL();
|
||||
#endif
|
||||
|
||||
AppAdapter* app_adapter{};
|
||||
|
||||
#if BA_HEADLESS_BUILD
|
||||
app_adapter = new AppAdapterHeadless();
|
||||
#elif BA_OSTYPE_ANDROID
|
||||
app_adapter = new AppAdapterAndroid();
|
||||
#elif BA_XCODE_BUILD
|
||||
app_adapter = new AppAdapterApple();
|
||||
#elif BA_RIFT_BUILD
|
||||
// Rift build can spin up in either VR or regular mode.
|
||||
if (g_core->vr_mode) {
|
||||
app_adapter = new AppAdapterVR();
|
||||
} else {
|
||||
app_adapter = new AppAdapterSDL();
|
||||
}
|
||||
#elif BA_CARDBOARD_BUILD
|
||||
app_adapter = new AppAdapterVR();
|
||||
#elif BA_SDL_BUILD
|
||||
app_adapter = new AppAdapterSDL();
|
||||
#else
|
||||
#error No app adapter defined for this build.
|
||||
#endif
|
||||
|
||||
assert(app_adapter);
|
||||
return app_adapter;
|
||||
}
|
||||
|
||||
AppAdapter::AppAdapter() = default;
|
||||
|
||||
AppAdapter::~AppAdapter() = default;
|
||||
@ -67,31 +20,8 @@ AppAdapter::~AppAdapter() = default;
|
||||
auto AppAdapter::ManagesMainThreadEventLoop() const -> bool { return true; }
|
||||
|
||||
void AppAdapter::OnMainThreadStartApp() {
|
||||
assert(g_base);
|
||||
assert(g_core);
|
||||
assert(g_core->InMainThread());
|
||||
|
||||
// Add some common input devices where applicable. More specific ones (SDL
|
||||
// Joysticks, etc.) get added in subclasses.
|
||||
|
||||
// FIXME: This stuff should probably go elsewhere.
|
||||
if (!g_core->HeadlessMode()) {
|
||||
// If we've got a nice themed hardware cursor, show it. Otherwise we'll
|
||||
// render it manually, which is laggier but gets the job done.
|
||||
// g_base->platform->SetHardwareCursorVisible(g_buildconfig.hardware_cursor());
|
||||
|
||||
// On desktop systems we just assume keyboard input exists and add it
|
||||
// immediately.
|
||||
if (g_core->platform->IsRunningOnDesktop()) {
|
||||
g_base->input->PushCreateKeyboardInputDevices();
|
||||
}
|
||||
|
||||
// On non-tv, non-desktop, non-vr systems, create a touchscreen input.
|
||||
if (!g_core->platform->IsRunningOnTV() && !g_core->IsVRMode()
|
||||
&& !g_core->platform->IsRunningOnDesktop()) {
|
||||
g_base->input->CreateTouchInput();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AppAdapter::OnAppStart() { assert(g_base->InLogicThread()); }
|
||||
@ -315,4 +245,76 @@ auto AppAdapter::GetGraphicsClientContext() -> GraphicsClientContext* {
|
||||
return new GraphicsClientContext();
|
||||
}
|
||||
|
||||
auto AppAdapter::GetKeyRepeatDelay() -> float { return 0.3f; }
|
||||
auto AppAdapter::GetKeyRepeatInterval() -> float { return 0.08f; }
|
||||
|
||||
auto AppAdapter::ClipboardIsSupported() -> bool {
|
||||
// We only call our actual virtual function once.
|
||||
if (!have_clipboard_is_supported_) {
|
||||
clipboard_is_supported_ = DoClipboardIsSupported();
|
||||
have_clipboard_is_supported_ = true;
|
||||
}
|
||||
return clipboard_is_supported_;
|
||||
}
|
||||
|
||||
auto AppAdapter::ClipboardHasText() -> bool {
|
||||
// If subplatform says they don't support clipboards, don't even ask.
|
||||
if (!ClipboardIsSupported()) {
|
||||
return false;
|
||||
}
|
||||
return DoClipboardHasText();
|
||||
}
|
||||
|
||||
void AppAdapter::ClipboardSetText(const std::string& text) {
|
||||
// If subplatform says they don't support clipboards, this is an error.
|
||||
if (!ClipboardIsSupported()) {
|
||||
throw Exception("ClipboardSetText called with no clipboard support.",
|
||||
PyExcType::kRuntime);
|
||||
}
|
||||
DoClipboardSetText(text);
|
||||
}
|
||||
|
||||
auto AppAdapter::ClipboardGetText() -> std::string {
|
||||
// If subplatform says they don't support clipboards, this is an error.
|
||||
if (!ClipboardIsSupported()) {
|
||||
throw Exception("ClipboardGetText called with no clipboard support.",
|
||||
PyExcType::kRuntime);
|
||||
}
|
||||
return DoClipboardGetText();
|
||||
}
|
||||
|
||||
auto AppAdapter::DoClipboardIsSupported() -> bool { return false; }
|
||||
|
||||
auto AppAdapter::DoClipboardHasText() -> bool {
|
||||
// Shouldn't get here since we default to no clipboard support.
|
||||
FatalError("Shouldn't get here.");
|
||||
return false;
|
||||
}
|
||||
|
||||
void AppAdapter::DoClipboardSetText(const std::string& text) {
|
||||
// Shouldn't get here since we default to no clipboard support.
|
||||
FatalError("Shouldn't get here.");
|
||||
}
|
||||
|
||||
auto AppAdapter::DoClipboardGetText() -> std::string {
|
||||
// Shouldn't get here since we default to no clipboard support.
|
||||
FatalError("Shouldn't get here.");
|
||||
return "";
|
||||
}
|
||||
|
||||
auto AppAdapter::GetKeyName(int keycode) -> std::string {
|
||||
BA_LOG_ONCE(LogLevel::kWarning,
|
||||
"CorePlatform::GetKeyName not implemented here.");
|
||||
return "?";
|
||||
}
|
||||
|
||||
auto AppAdapter::NativeReviewRequestSupported() -> bool { return false; }
|
||||
|
||||
void AppAdapter::NativeReviewRequest() {
|
||||
BA_PRECONDITION(NativeReviewRequestSupported());
|
||||
PushMainThreadCall([this] { DoNativeReviewRequest(); });
|
||||
}
|
||||
|
||||
void AppAdapter::DoNativeReviewRequest() { FatalError("Fixme unimplemented."); }
|
||||
|
||||
} // namespace ballistica::base
|
||||
|
||||
@ -15,8 +15,7 @@ namespace ballistica::base {
|
||||
/// all might share the same CorePlatform and BasePlatform classes.
|
||||
class AppAdapter {
|
||||
public:
|
||||
/// Instantiate the AppAdapter subclass for the current build.
|
||||
static auto Create() -> AppAdapter*;
|
||||
AppAdapter();
|
||||
|
||||
/// Called in the main thread when the app is being started.
|
||||
virtual void OnMainThreadStartApp();
|
||||
@ -189,6 +188,13 @@ class AppAdapter {
|
||||
/// changing (it may be preferable to rely on dialogs for non-english
|
||||
/// languages/etc.). Default implementation returns false. This function
|
||||
/// should be callable from any thread.
|
||||
///
|
||||
/// Note that UI elements wanting to accept direct keyboard input should
|
||||
/// not call this directly, but instead should call
|
||||
/// UI::UIHasDirectKeyboardInput, as that takes into account other factors
|
||||
/// such as which device is currently controlling the UI (Someone
|
||||
/// navigating the UI with a game controller may still get an on-screen
|
||||
/// keyboard even if there is a physical keyboard attached).
|
||||
virtual auto HasDirectKeyboardInput() -> bool;
|
||||
|
||||
/// Called in the graphics context to apply new settings coming in from
|
||||
@ -197,9 +203,24 @@ class AppAdapter {
|
||||
/// settings coming in.
|
||||
virtual void ApplyGraphicsSettings(const GraphicsSettings* settings);
|
||||
|
||||
protected:
|
||||
AppAdapter();
|
||||
virtual ~AppAdapter();
|
||||
virtual auto GetKeyRepeatDelay() -> float;
|
||||
virtual auto GetKeyRepeatInterval() -> float;
|
||||
|
||||
/// Return whether clipboard operations are supported at all. This gets
|
||||
/// called when determining whether to display clipboard related UI
|
||||
/// elements/etc.
|
||||
auto ClipboardIsSupported() -> bool;
|
||||
|
||||
/// Return whether there is currently text on the clipboard.
|
||||
auto ClipboardHasText() -> bool;
|
||||
|
||||
/// Set current clipboard text. Raises an Exception if clipboard is
|
||||
/// unsupported.
|
||||
void ClipboardSetText(const std::string& text);
|
||||
|
||||
/// Return current text from the clipboard. Raises an Exception if
|
||||
/// clipboard is unsupported or if there's no text on the clipboard.
|
||||
auto ClipboardGetText() -> std::string;
|
||||
|
||||
/// Push a raw pointer Runnable to the platform's 'main' thread. The main
|
||||
/// thread should call its RunAndLogErrors() method and then delete it.
|
||||
@ -209,10 +230,34 @@ class AppAdapter {
|
||||
/// context. By default this is simply the main thread.
|
||||
virtual void DoPushGraphicsContextRunnable(Runnable* runnable);
|
||||
|
||||
/// Return a name for a ballistica keyboard keycode.
|
||||
virtual auto GetKeyName(int keycode) -> std::string;
|
||||
|
||||
/// Return whether there is a native 'review-this-app' prompt.
|
||||
virtual auto NativeReviewRequestSupported() -> bool;
|
||||
|
||||
/// Asynchronously kick off a native review request.
|
||||
void NativeReviewRequest();
|
||||
|
||||
protected:
|
||||
virtual ~AppAdapter();
|
||||
|
||||
virtual auto DoClipboardIsSupported() -> bool;
|
||||
virtual auto DoClipboardHasText() -> bool;
|
||||
virtual void DoClipboardSetText(const std::string& text);
|
||||
virtual auto DoClipboardGetText() -> std::string;
|
||||
|
||||
/// Override to implement native review requests. Will be called in the
|
||||
/// main thread.
|
||||
virtual void DoNativeReviewRequest();
|
||||
|
||||
private:
|
||||
void OnAppSuspend_();
|
||||
void OnAppUnsuspend_();
|
||||
bool app_suspended_{};
|
||||
|
||||
bool app_suspended_ : 1 {};
|
||||
bool have_clipboard_is_supported_ : 1 {};
|
||||
bool clipboard_is_supported_ : 1 {};
|
||||
};
|
||||
|
||||
} // namespace ballistica::base
|
||||
|
||||
@ -3,16 +3,23 @@
|
||||
|
||||
#include "ballistica/base/app_adapter/app_adapter_apple.h"
|
||||
|
||||
#include <BallisticaKit-Swift.h>
|
||||
|
||||
#include "ballistica/base/graphics/gl/renderer_gl.h"
|
||||
#include "ballistica/base/graphics/graphics.h"
|
||||
#include "ballistica/base/graphics/graphics_server.h"
|
||||
#include "ballistica/base/logic/logic.h"
|
||||
#include "ballistica/base/platform/apple/apple_utils.h"
|
||||
#include "ballistica/base/platform/apple/from_swift.h"
|
||||
#include "ballistica/base/platform/support/min_sdl_key_names.h"
|
||||
#include "ballistica/base/support/app_config.h"
|
||||
#include "ballistica/shared/ballistica.h"
|
||||
#include "ballistica/shared/foundation/event_loop.h"
|
||||
|
||||
// clang-format off
|
||||
// This needs to be below ballistica headers since it relies on
|
||||
// some types in them but does not include headers itself.
|
||||
#include <BallisticaKit-Swift.h>
|
||||
// clang-format on
|
||||
|
||||
namespace ballistica::base {
|
||||
|
||||
/// RAII-friendly way to mark the thread and calls we're allowed to run graphics
|
||||
@ -41,7 +48,17 @@ auto AppAdapterApple::ManagesMainThreadEventLoop() const -> bool {
|
||||
|
||||
void AppAdapterApple::DoPushMainThreadRunnable(Runnable* runnable) {
|
||||
// Kick this along to swift.
|
||||
BallisticaKit::FromCppPushRawRunnableToMain(runnable);
|
||||
BallisticaKit::FromCpp::pushRawRunnableToMain(runnable);
|
||||
}
|
||||
|
||||
void AppAdapterApple::OnMainThreadStartApp() {
|
||||
AppAdapter::OnMainThreadStartApp();
|
||||
#if BA_USE_STORE_KIT
|
||||
BallisticaKit::StoreKitContext::onAppStart();
|
||||
#endif
|
||||
#if BA_USE_GAME_CENTER
|
||||
BallisticaKit::GameCenterContext::onAppStart();
|
||||
#endif
|
||||
}
|
||||
|
||||
void AppAdapterApple::DoApplyAppConfig() { assert(g_base->InLogicThread()); }
|
||||
@ -122,16 +139,16 @@ auto AppAdapterApple::TryRender() -> bool {
|
||||
// matches what we have (or until we try for too long or fail at drawing).
|
||||
seconds_t start_time = g_core->GetAppTimeSeconds();
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
if (((std::abs(resize_target_resolution_.x
|
||||
bool size_differs =
|
||||
((std::abs(resize_target_resolution_.x
|
||||
- g_base->graphics_server->screen_pixel_width())
|
||||
> 0.01f)
|
||||
|| (std::abs(resize_target_resolution_.y
|
||||
- g_base->graphics_server->screen_pixel_height())
|
||||
> 0.01f))
|
||||
&& g_core->GetAppTimeSeconds() - start_time < 0.1 && result) {
|
||||
> 0.01f));
|
||||
if (size_differs && g_core->GetAppTimeSeconds() - start_time < 0.1
|
||||
&& result) {
|
||||
result = g_base->graphics_server->TryRender();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -182,13 +199,13 @@ void AppAdapterApple::SetHardwareCursorVisible(bool visible) {
|
||||
assert(g_core->InMainThread());
|
||||
|
||||
#if BA_OSTYPE_MACOS
|
||||
BallisticaKit::CocoaFromCppSetCursorVisible(visible);
|
||||
BallisticaKit::CocoaFromCpp::setCursorVisible(visible);
|
||||
#endif
|
||||
}
|
||||
|
||||
void AppAdapterApple::TerminateApp() {
|
||||
#if BA_OSTYPE_MACOS
|
||||
BallisticaKit::CocoaFromCppTerminateApp();
|
||||
BallisticaKit::CocoaFromCpp::terminateApp();
|
||||
#else
|
||||
AppAdapter::TerminateApp();
|
||||
#endif
|
||||
@ -205,7 +222,7 @@ auto AppAdapterApple::FullscreenControlAvailable() const -> bool {
|
||||
|
||||
auto AppAdapterApple::FullscreenControlGet() const -> bool {
|
||||
#if BA_OSTYPE_MACOS
|
||||
return BallisticaKit::CocoaFromCppGetMainWindowIsFullscreen();
|
||||
return BallisticaKit::CocoaFromCpp::getMainWindowIsFullscreen();
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
@ -213,7 +230,7 @@ auto AppAdapterApple::FullscreenControlGet() const -> bool {
|
||||
|
||||
void AppAdapterApple::FullscreenControlSet(bool fullscreen) {
|
||||
#if BA_OSTYPE_MACOS
|
||||
return BallisticaKit::CocoaFromCppSetMainWindowFullscreen(fullscreen);
|
||||
return BallisticaKit::CocoaFromCpp::setMainWindowFullscreen(fullscreen);
|
||||
#endif
|
||||
}
|
||||
|
||||
@ -224,6 +241,79 @@ auto AppAdapterApple::FullscreenControlKeyShortcut() const
|
||||
|
||||
auto AppAdapterApple::HasDirectKeyboardInput() -> bool { return true; };
|
||||
|
||||
auto AppAdapterApple::GetKeyRepeatDelay() -> float {
|
||||
#if BA_OSTYPE_MACOS
|
||||
return BallisticaKit::CocoaFromCpp::getKeyRepeatDelay();
|
||||
#else
|
||||
return AppAdapter::GetKeyRepeatDelay();
|
||||
#endif
|
||||
}
|
||||
|
||||
auto AppAdapterApple::GetKeyRepeatInterval() -> float {
|
||||
#if BA_OSTYPE_MACOS
|
||||
return BallisticaKit::CocoaFromCpp::getKeyRepeatInterval();
|
||||
#else
|
||||
return AppAdapter::GetKeyRepeatDelay();
|
||||
#endif
|
||||
}
|
||||
|
||||
auto AppAdapterApple::DoClipboardIsSupported() -> bool {
|
||||
#if BA_OSTYPE_MACOS
|
||||
return BallisticaKit::CocoaFromCpp::clipboardIsSupported();
|
||||
#else
|
||||
return AppAdapter::DoClipboardIsSupported();
|
||||
#endif
|
||||
}
|
||||
|
||||
auto AppAdapterApple::DoClipboardHasText() -> bool {
|
||||
#if BA_OSTYPE_MACOS
|
||||
return BallisticaKit::CocoaFromCpp::clipboardHasText();
|
||||
#else
|
||||
return AppAdapter::DoClipboardHasText();
|
||||
#endif
|
||||
}
|
||||
|
||||
void AppAdapterApple::DoClipboardSetText(const std::string& text) {
|
||||
#if BA_OSTYPE_MACOS
|
||||
BallisticaKit::CocoaFromCpp::clipboardSetText(text);
|
||||
#else
|
||||
AppAdapter::DoClipboardSetText(text);
|
||||
#endif
|
||||
}
|
||||
|
||||
auto AppAdapterApple::DoClipboardGetText() -> std::string {
|
||||
#if BA_OSTYPE_MACOS
|
||||
auto contents = BallisticaKit::CocoaFromCpp::clipboardGetText();
|
||||
if (contents) {
|
||||
return std::string(contents.get());
|
||||
}
|
||||
throw Exception("No text on clipboard.");
|
||||
#else
|
||||
return AppAdapter::DoClipboardGetText();
|
||||
#endif
|
||||
}
|
||||
|
||||
auto AppAdapterApple::GetKeyName(int keycode) -> std::string {
|
||||
return MinSDL_GetKeyName(keycode);
|
||||
}
|
||||
|
||||
auto AppAdapterApple::NativeReviewRequestSupported() -> bool {
|
||||
// StoreKit currently supports this everywhere except tvOS.
|
||||
if (g_buildconfig.xcode_build() && g_buildconfig.use_store_kit()
|
||||
&& !g_buildconfig.ostype_tvos()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void AppAdapterApple::DoNativeReviewRequest() {
|
||||
#if BA_XCODE_BUILD && BA_USE_STORE_KIT && !BA_OSTYPE_TVOS
|
||||
BallisticaKit::StoreKitContext::requestReview();
|
||||
#else
|
||||
FatalError("This should not be getting called.");
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace ballistica::base
|
||||
|
||||
#endif // BA_XCODE_BUILD
|
||||
|
||||
@ -26,6 +26,8 @@ class AppAdapterApple : public AppAdapter {
|
||||
return val;
|
||||
}
|
||||
|
||||
void OnMainThreadStartApp() override;
|
||||
|
||||
auto ManagesMainThreadEventLoop() const -> bool override;
|
||||
void DoApplyAppConfig() override;
|
||||
|
||||
@ -41,6 +43,11 @@ class AppAdapterApple : public AppAdapter {
|
||||
auto HasDirectKeyboardInput() -> bool override;
|
||||
void EnableResizeFriendlyMode(int width, int height);
|
||||
|
||||
auto GetKeyRepeatDelay() -> float override;
|
||||
auto GetKeyRepeatInterval() -> float override;
|
||||
auto GetKeyName(int keycode) -> std::string override;
|
||||
auto NativeReviewRequestSupported() -> bool override;
|
||||
|
||||
protected:
|
||||
void DoPushMainThreadRunnable(Runnable* runnable) override;
|
||||
void DoPushGraphicsContextRunnable(Runnable* runnable) override;
|
||||
@ -50,6 +57,11 @@ class AppAdapterApple : public AppAdapter {
|
||||
void SetHardwareCursorVisible(bool visible) override;
|
||||
void TerminateApp() override;
|
||||
void ApplyGraphicsSettings(const GraphicsSettings* settings) override;
|
||||
auto DoClipboardIsSupported() -> bool override;
|
||||
auto DoClipboardHasText() -> bool override;
|
||||
void DoClipboardSetText(const std::string& text) override;
|
||||
auto DoClipboardGetText() -> std::string override;
|
||||
void DoNativeReviewRequest() override;
|
||||
|
||||
private:
|
||||
class ScopedAllowGraphics_;
|
||||
|
||||
@ -51,6 +51,8 @@ AppAdapterSDL::AppAdapterSDL() {
|
||||
}
|
||||
|
||||
void AppAdapterSDL::OnMainThreadStartApp() {
|
||||
AppAdapter::OnMainThreadStartApp();
|
||||
|
||||
// App is starting. Let's fire up the ol' SDL.
|
||||
uint32_t sdl_flags{SDL_INIT_VIDEO | SDL_INIT_JOYSTICK};
|
||||
|
||||
@ -79,9 +81,8 @@ void AppAdapterSDL::OnMainThreadStartApp() {
|
||||
sdl_runnable_event_id_ = SDL_RegisterEvents(1);
|
||||
assert(sdl_runnable_event_id_ != (uint32_t)-1);
|
||||
|
||||
// Note: parent class can add some input devices so need to bring up sdl
|
||||
// before we let it run. That code should maybe be relocated/refactored.
|
||||
AppAdapter::OnMainThreadStartApp();
|
||||
// SDL builds just assume keyboard input is available.
|
||||
g_base->input->PushCreateKeyboardInputDevices();
|
||||
|
||||
if (g_buildconfig.enable_sdl_joysticks()) {
|
||||
// We want events from joysticks.
|
||||
@ -415,7 +416,9 @@ void AppAdapterSDL::HandleSDLEvent_(const SDL_Event& event) {
|
||||
}
|
||||
|
||||
case SDL_KEYDOWN: {
|
||||
g_base->input->PushKeyPressEvent(event.key.keysym);
|
||||
if (!event.key.repeat) {
|
||||
g_base->input->PushKeyPressEvent(event.key.keysym);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@ -805,23 +808,9 @@ void AppAdapterSDL::CursorPositionForDraw(float* x, float* y) {
|
||||
auto AppAdapterSDL::FullscreenControlAvailable() const -> bool { return true; }
|
||||
auto AppAdapterSDL::FullscreenControlKeyShortcut() const
|
||||
-> std::optional<std::string> {
|
||||
if (g_buildconfig.ostype_windows()) {
|
||||
// On Windows we support F11 and Alt+Enter to toggle fullscreen. Let's
|
||||
// mention Alt+Enter which seems like it might be more commonly used
|
||||
return "Alt+Enter";
|
||||
}
|
||||
if (g_buildconfig.ostype_macos()) {
|
||||
// The Mac+SDL situation is a bit of a mess. By default, there is 'Enter
|
||||
// Full Screen' in the window menu which is mapped to fn-F, but that
|
||||
// will only work if a window was created in SDL as windowed. If we
|
||||
// fullscreen that window and restart the app, we'll then have a *real*
|
||||
// fullscreen sdl window and that shortcut won't work anymore. So to
|
||||
// keep things consistent we advertise ctrl-f which we always handle
|
||||
// ourselves. Maybe this situation will be cleaned up in SDL 3, but its
|
||||
// not a huge deal anyway since our Cocoa Mac version behaves cleanly.
|
||||
return "Ctrl+F";
|
||||
}
|
||||
return {};
|
||||
// On our SDL build we support F11 and Alt+Enter to toggle fullscreen.
|
||||
// Let's mention Alt+Enter which seems like it might be more commonly used
|
||||
return "Alt+Enter";
|
||||
};
|
||||
|
||||
auto AppAdapterSDL::SupportsVSync() -> bool const { return true; }
|
||||
@ -832,6 +821,32 @@ auto AppAdapterSDL::HasDirectKeyboardInput() -> bool {
|
||||
return true;
|
||||
}
|
||||
|
||||
auto AppAdapterSDL::DoClipboardIsSupported() -> bool { return true; }
|
||||
|
||||
auto AppAdapterSDL::DoClipboardHasText() -> bool {
|
||||
return SDL_HasClipboardText();
|
||||
}
|
||||
|
||||
void AppAdapterSDL::DoClipboardSetText(const std::string& text) {
|
||||
SDL_SetClipboardText(text.c_str());
|
||||
}
|
||||
|
||||
auto AppAdapterSDL::DoClipboardGetText() -> std::string {
|
||||
// Go through SDL functionality on SDL based platforms;
|
||||
// otherwise default to no clipboard.
|
||||
char* out = SDL_GetClipboardText();
|
||||
if (out == nullptr) {
|
||||
throw Exception("Error fetching clipboard contents.", PyExcType::kRuntime);
|
||||
}
|
||||
std::string out_s{out};
|
||||
SDL_free(out);
|
||||
return out_s;
|
||||
}
|
||||
|
||||
auto AppAdapterSDL::GetKeyName(int keycode) -> std::string {
|
||||
return SDL_GetKeyName(static_cast<SDL_Keycode>(keycode));
|
||||
}
|
||||
|
||||
} // namespace ballistica::base
|
||||
|
||||
#endif // BA_SDL_BUILD
|
||||
|
||||
@ -45,6 +45,8 @@ class AppAdapterSDL : public AppAdapter {
|
||||
|
||||
auto GetGraphicsSettings() -> GraphicsSettings* override;
|
||||
|
||||
auto GetKeyName(int keycode) -> std::string override;
|
||||
|
||||
protected:
|
||||
void DoPushMainThreadRunnable(Runnable* runnable) override;
|
||||
void RunMainThreadEventLoopToCompletion() override;
|
||||
@ -52,6 +54,10 @@ class AppAdapterSDL : public AppAdapter {
|
||||
auto InGraphicsContext() -> bool override;
|
||||
void DoPushGraphicsContextRunnable(Runnable* runnable) override;
|
||||
void CursorPositionForDraw(float* x, float* y) override;
|
||||
auto DoClipboardIsSupported() -> bool override;
|
||||
auto DoClipboardHasText() -> bool override;
|
||||
void DoClipboardSetText(const std::string& text) override;
|
||||
auto DoClipboardGetText() -> std::string override;
|
||||
|
||||
private:
|
||||
class ScopedAllowGraphics_;
|
||||
|
||||
@ -5,7 +5,6 @@
|
||||
#include "ballistica/base/assets/asset.h"
|
||||
#include "ballistica/base/assets/assets.h"
|
||||
#include "ballistica/base/graphics/graphics.h"
|
||||
#include "ballistica/base/graphics/graphics_server.h"
|
||||
#include "ballistica/base/support/huffman.h"
|
||||
#include "ballistica/shared/foundation/event_loop.h"
|
||||
|
||||
@ -26,7 +25,7 @@ void AssetsServer::OnAppStartInThread() {
|
||||
// Ask our thread to give us periodic processing time (close to but
|
||||
// not *exactly* one second; try to avoid aliasing with similar updates).
|
||||
process_timer_ = event_loop()->NewTimer(
|
||||
987, true, NewLambdaRunnable([this] { Process(); }));
|
||||
987 * 1000, true, NewLambdaRunnable([this] { Process(); }).Get());
|
||||
}
|
||||
|
||||
void AssetsServer::PushPendingPreload(Object::Ref<Asset>* asset_ref_ptr) {
|
||||
@ -259,7 +258,7 @@ void AssetsServer::Process() {
|
||||
// we're writing a replay.. otherwise just sleep indefinitely.
|
||||
if (pending_preloads_.empty() && pending_preloads_audio_.empty()) {
|
||||
if (writing_replay_) {
|
||||
process_timer_->SetLength(1000);
|
||||
process_timer_->SetLength(1000 * 1000);
|
||||
} else {
|
||||
process_timer_->SetLength(-1);
|
||||
}
|
||||
|
||||
@ -15,7 +15,7 @@
|
||||
|
||||
namespace ballistica::base {
|
||||
|
||||
static void rgba8888_unpremultiply_in_place(uint8_t* src, size_t cb) {
|
||||
static void Rgba8888UnpremultiplyInPlace_(uint8_t* src, size_t cb) {
|
||||
// Compute the actual number of pixel elements in the buffer.
|
||||
size_t cpel = cb / 4;
|
||||
auto* psrc = src;
|
||||
@ -157,7 +157,7 @@ void TextureAsset::DoPreload() {
|
||||
auto* buffer = static_cast<uint8_t*>(malloc(buffer_size));
|
||||
preload_datas_[0].buffers[0] = buffer;
|
||||
memcpy(buffer, pixels, buffer_size);
|
||||
rgba8888_unpremultiply_in_place(buffer, buffer_size);
|
||||
Rgba8888UnpremultiplyInPlace_(buffer, buffer_size);
|
||||
preload_datas_[0].widths[0] = width;
|
||||
preload_datas_[0].heights[0] = height;
|
||||
preload_datas_[0].formats[0] = TextureFormat::kRGBA_8888;
|
||||
|
||||
@ -32,9 +32,9 @@ LPALCDEVICEPAUSESOFT alcDevicePauseSOFT;
|
||||
LPALCDEVICERESUMESOFT alcDeviceResumeSOFT;
|
||||
#endif
|
||||
|
||||
const int kAudioProcessIntervalNormal{500};
|
||||
const int kAudioProcessIntervalFade{50};
|
||||
const int kAudioProcessIntervalPendingLoad{1};
|
||||
const int kAudioProcessIntervalNormal{500 * 1000};
|
||||
const int kAudioProcessIntervalFade{50 * 1000};
|
||||
const int kAudioProcessIntervalPendingLoad{1 * 1000};
|
||||
|
||||
#if BA_DEBUG_BUILD || BA_TEST_BUILD
|
||||
const bool kShowInUseSounds{};
|
||||
@ -161,7 +161,7 @@ void AudioServer::OnAppStartInThread_() {
|
||||
// Get our thread to give us periodic processing time.
|
||||
process_timer_ =
|
||||
event_loop()->NewTimer(kAudioProcessIntervalNormal, true,
|
||||
NewLambdaRunnable([this] { Process_(); }));
|
||||
NewLambdaRunnable([this] { Process_(); }).Get());
|
||||
|
||||
#if BA_ENABLE_AUDIO
|
||||
|
||||
|
||||
@ -9,6 +9,7 @@
|
||||
#include "ballistica/base/audio/audio_server.h"
|
||||
#include "ballistica/base/dynamics/bg/bg_dynamics_server.h"
|
||||
#include "ballistica/base/graphics/graphics_server.h"
|
||||
#include "ballistica/base/graphics/support/screen_messages.h"
|
||||
#include "ballistica/base/graphics/text/text_graphics.h"
|
||||
#include "ballistica/base/input/input.h"
|
||||
#include "ballistica/base/logic/logic.h"
|
||||
@ -20,12 +21,12 @@
|
||||
#include "ballistica/base/python/class/python_class_feature_set_data.h"
|
||||
#include "ballistica/base/python/support/python_context_call.h"
|
||||
#include "ballistica/base/support/app_config.h"
|
||||
#include "ballistica/base/support/app_timer.h"
|
||||
#include "ballistica/base/support/base_build_switches.h"
|
||||
#include "ballistica/base/support/huffman.h"
|
||||
#include "ballistica/base/support/plus_soft.h"
|
||||
#include "ballistica/base/support/stdio_console.h"
|
||||
#include "ballistica/base/support/stress_test.h"
|
||||
#include "ballistica/base/ui/dev_console.h"
|
||||
#include "ballistica/base/ui/ui.h"
|
||||
#include "ballistica/base/ui/ui_delegate.h"
|
||||
#include "ballistica/core/python/core_python.h"
|
||||
#include "ballistica/shared/foundation/event_loop.h"
|
||||
@ -39,7 +40,7 @@ core::CoreFeatureSet* g_core{};
|
||||
BaseFeatureSet* g_base{};
|
||||
|
||||
BaseFeatureSet::BaseFeatureSet()
|
||||
: app_adapter{AppAdapter::Create()},
|
||||
: app_adapter{BaseBuildSwitches::CreateAppAdapter()},
|
||||
app_config{new AppConfig()},
|
||||
app_mode_{AppModeEmpty::GetSingleton()},
|
||||
assets{new Assets()},
|
||||
@ -51,7 +52,7 @@ BaseFeatureSet::BaseFeatureSet()
|
||||
bg_dynamics_server{g_core->HeadlessMode() ? nullptr
|
||||
: new BGDynamicsServer},
|
||||
context_ref{new ContextRef(nullptr)},
|
||||
graphics{Graphics::Create()},
|
||||
graphics{BaseBuildSwitches::CreateGraphics()},
|
||||
graphics_server{new GraphicsServer()},
|
||||
huffman{new Huffman()},
|
||||
input{new Input()},
|
||||
@ -59,11 +60,10 @@ BaseFeatureSet::BaseFeatureSet()
|
||||
network_reader{new NetworkReader()},
|
||||
network_writer{new NetworkWriter()},
|
||||
networking{new Networking()},
|
||||
platform{BasePlatform::Create()},
|
||||
platform{BaseBuildSwitches::CreatePlatform()},
|
||||
python{new BasePython()},
|
||||
stdio_console{g_buildconfig.enable_stdio_console() ? new StdioConsole()
|
||||
: nullptr},
|
||||
stress_test_{new StressTest()},
|
||||
text_graphics{new TextGraphics()},
|
||||
ui{new UI()},
|
||||
utils{new Utils()} {
|
||||
@ -151,6 +151,43 @@ auto BaseFeatureSet::IsBaseCompletelyImported() -> bool {
|
||||
return base_import_completed_ && base_native_import_completed_;
|
||||
}
|
||||
|
||||
void BaseFeatureSet::SuccessScreenMessage() {
|
||||
if (auto* event_loop = logic->event_loop()) {
|
||||
event_loop->PushCall([this] {
|
||||
python->objs().Get(BasePython::ObjID::kSuccessMessageCall).Call();
|
||||
});
|
||||
} else {
|
||||
Log(LogLevel::kError,
|
||||
"SuccessScreenMessage called without logic event_loop in place.");
|
||||
}
|
||||
}
|
||||
|
||||
void BaseFeatureSet::ErrorScreenMessage() {
|
||||
if (auto* event_loop = logic->event_loop()) {
|
||||
event_loop->PushCall([this] {
|
||||
python->objs().Get(BasePython::ObjID::kErrorMessageCall).Call();
|
||||
});
|
||||
} else {
|
||||
Log(LogLevel::kError,
|
||||
"ErrorScreenMessage called without logic event_loop in place.");
|
||||
}
|
||||
}
|
||||
|
||||
auto BaseFeatureSet::GetV2AccountID() -> std::optional<std::string> {
|
||||
auto gil = Python::ScopedInterpreterLock();
|
||||
auto result =
|
||||
python->objs().Get(BasePython::ObjID::kGetV2AccountIdCall).Call();
|
||||
if (result.Exists()) {
|
||||
if (result.ValueIsNone()) {
|
||||
return {};
|
||||
}
|
||||
return result.ValueAsString();
|
||||
} else {
|
||||
Log(LogLevel::kError, "GetV2AccountID() py call errored.");
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
void BaseFeatureSet::OnAssetsAvailable() {
|
||||
assert(InLogicThread());
|
||||
|
||||
@ -465,8 +502,9 @@ auto BaseFeatureSet::InGraphicsContext() const -> bool {
|
||||
|
||||
void BaseFeatureSet::ScreenMessage(const std::string& s,
|
||||
const Vector3f& color) {
|
||||
logic->event_loop()->PushCall(
|
||||
[this, s, color] { graphics->AddScreenMessage(s, color); });
|
||||
logic->event_loop()->PushCall([this, s, color] {
|
||||
graphics->screenmessages->AddScreenMessage(s, color);
|
||||
});
|
||||
}
|
||||
|
||||
void BaseFeatureSet::DoV1CloudLog(const std::string& msg) {
|
||||
@ -721,4 +759,8 @@ void BaseFeatureSet::QuitApp(bool confirm, QuitType quit_type) {
|
||||
}
|
||||
}
|
||||
|
||||
void BaseFeatureSet::PushMainThreadRunnable(Runnable* runnable) {
|
||||
app_adapter->DoPushMainThreadRunnable(runnable);
|
||||
}
|
||||
|
||||
} // namespace ballistica::base
|
||||
|
||||
@ -53,6 +53,7 @@ class ClassicSoftInterface;
|
||||
class CollisionMeshAsset;
|
||||
class CollisionCache;
|
||||
class DevConsole;
|
||||
class DisplayTimer;
|
||||
class Context;
|
||||
class ContextRef;
|
||||
class DataAsset;
|
||||
@ -99,13 +100,14 @@ class RenderPass;
|
||||
class RenderTarget;
|
||||
class RemoteAppServer;
|
||||
class RemoteControlInput;
|
||||
class Repeater;
|
||||
class ScoreToBeat;
|
||||
class ScreenMessages;
|
||||
class AppAdapterSDL;
|
||||
class SDLContext;
|
||||
class SoundAsset;
|
||||
class SpriteMesh;
|
||||
class StdioConsole;
|
||||
class StressTest;
|
||||
class Module;
|
||||
class TestInput;
|
||||
class TextGroup;
|
||||
@ -630,6 +632,13 @@ class BaseFeatureSet : public FeatureSetNativeComponent,
|
||||
return *context_ref;
|
||||
}
|
||||
|
||||
/// Utility call to print 'Success!' with a happy sound.
|
||||
/// Safe to call from any thread.
|
||||
void SuccessScreenMessage();
|
||||
/// Utility call to print 'Error.' with a beep sound.
|
||||
/// Safe to call from any thread.
|
||||
void ErrorScreenMessage();
|
||||
|
||||
void SetCurrentContext(const ContextRef& context);
|
||||
|
||||
/// Try to load the plus feature-set and return whether it is available.
|
||||
@ -721,6 +730,12 @@ class BaseFeatureSet : public FeatureSetNativeComponent,
|
||||
/// loading.
|
||||
void OnAssetsAvailable();
|
||||
|
||||
void PushMainThreadRunnable(Runnable* runnable) override;
|
||||
|
||||
/// Return the currently signed in V2 account id as
|
||||
/// reported by the Python layer.
|
||||
auto GetV2AccountID() -> std::optional<std::string>;
|
||||
|
||||
// Const subsystems.
|
||||
AppAdapter* const app_adapter;
|
||||
AppConfig* const app_config;
|
||||
@ -750,8 +765,6 @@ class BaseFeatureSet : public FeatureSetNativeComponent,
|
||||
void set_app_mode(AppMode* mode);
|
||||
auto* app_mode() const { return app_mode_; }
|
||||
|
||||
auto* stress_test() const { return stress_test_; }
|
||||
|
||||
/// Whether we're running under ballisticakit_server.py
|
||||
/// (affects some app behavior).
|
||||
auto server_wrapper_managed() { return server_wrapper_managed_; }
|
||||
@ -774,7 +787,6 @@ class BaseFeatureSet : public FeatureSetNativeComponent,
|
||||
AppMode* app_mode_;
|
||||
PlusSoftInterface* plus_soft_{};
|
||||
ClassicSoftInterface* classic_soft_{};
|
||||
StressTest* stress_test_;
|
||||
|
||||
std::mutex shutdown_suppress_lock_;
|
||||
bool shutdown_suppress_disallowed_{};
|
||||
|
||||
@ -185,9 +185,11 @@ void RendererGL::CheckGLCapabilities_() {
|
||||
basestr = "OpenGL";
|
||||
}
|
||||
|
||||
Log(LogLevel::kInfo, std::string("Using ") + basestr + " (vendor: " + vendor
|
||||
+ ", renderer: " + renderer
|
||||
+ ", version: " + version_str + ").");
|
||||
if (g_buildconfig.debug_build()) {
|
||||
Log(LogLevel::kInfo, std::string("Using ") + basestr + " (vendor: " + vendor
|
||||
+ ", renderer: " + renderer
|
||||
+ ", version: " + version_str + ").");
|
||||
}
|
||||
|
||||
// Build a vector of extensions. Newer GLs give us extensions as lists
|
||||
// already, but on older ones we may need to break a single string apart
|
||||
@ -453,44 +455,51 @@ void RendererGL::CheckGLCapabilities_() {
|
||||
}
|
||||
|
||||
auto RendererGL::GetMSAASamplesForFramebuffer_(int width, int height) -> int {
|
||||
#if BA_RIFT_BUILD
|
||||
return 4;
|
||||
#else
|
||||
// We currently aim for 4 up to 800 height and 2 beyond that.
|
||||
if (height > 800) {
|
||||
return 2;
|
||||
if (g_buildconfig.ostype_android()) {
|
||||
// We currently aim for 4 up to 800 height and 2 beyond that.
|
||||
if (height > 800) {
|
||||
return 2;
|
||||
} else {
|
||||
return 4;
|
||||
}
|
||||
} else {
|
||||
return 4;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void RendererGL::UpdateMSAAEnabled_() {
|
||||
#if BA_RIFT_BUILD
|
||||
if (msaa_max_samples_rgb8_ > 0) {
|
||||
if (g_buildconfig.ostype_macos()) {
|
||||
// Let's go ahead and flip this on for Apple Silicon Macs.
|
||||
#if __aarch64__
|
||||
enable_msaa_ = true;
|
||||
#else
|
||||
enable_msaa_ = false;
|
||||
#endif
|
||||
} else if (g_buildconfig.rift_build()) {
|
||||
if (msaa_max_samples_rgb8_ > 0) {
|
||||
enable_msaa_ = true;
|
||||
} else {
|
||||
enable_msaa_ = false;
|
||||
}
|
||||
} else if (g_buildconfig.ostype_android()) {
|
||||
// lets allow full 1080p msaa with newer stuff..
|
||||
int max_msaa_res = is_tegra_k1_ ? 1200 : 800;
|
||||
|
||||
// To start, see if it looks like we support msaa on paper.
|
||||
enable_msaa_ =
|
||||
((screen_render_target()->physical_height()
|
||||
<= static_cast<float>(max_msaa_res))
|
||||
&& (msaa_max_samples_rgb8_ > 0) && (msaa_max_samples_rgb565_ > 0));
|
||||
|
||||
// Ok, lets be careful here; msaa blitting/etc seems to be particular in
|
||||
// terms of supported formats/etc so let's only enable it on
|
||||
// explicitly-tested hardware for now.
|
||||
if (!is_tegra_4_ && !is_tegra_k1_ && !is_recent_adreno_) {
|
||||
enable_msaa_ = false;
|
||||
}
|
||||
} else {
|
||||
enable_msaa_ = false;
|
||||
}
|
||||
#else
|
||||
|
||||
// lets allow full 1080p msaa with newer stuff..
|
||||
int max_msaa_res = is_tegra_k1_ ? 1200 : 800;
|
||||
|
||||
// To start, see if it looks like we support msaa on paper.
|
||||
enable_msaa_ =
|
||||
((screen_render_target()->physical_height()
|
||||
<= static_cast<float>(max_msaa_res))
|
||||
&& (msaa_max_samples_rgb8_ > 0) && (msaa_max_samples_rgb565_ > 0));
|
||||
|
||||
// Ok, lets be careful here; msaa blitting/etc seems to be particular in
|
||||
// terms of supported formats/etc so let's only enable it on
|
||||
// explicitly-tested hardware for now.
|
||||
if (!is_tegra_4_ && !is_tegra_k1_ && !is_recent_adreno_) {
|
||||
enable_msaa_ = false;
|
||||
}
|
||||
|
||||
#endif // BA_RIFT_BUILD
|
||||
}
|
||||
|
||||
auto RendererGL::IsMSAAEnabled() const -> bool { return enable_msaa_; }
|
||||
|
||||
@ -5,44 +5,29 @@
|
||||
#include "ballistica/base/app_adapter/app_adapter.h"
|
||||
#include "ballistica/base/app_mode/app_mode.h"
|
||||
#include "ballistica/base/dynamics/bg/bg_dynamics.h"
|
||||
#include "ballistica/base/graphics/component/empty_component.h"
|
||||
#include "ballistica/base/graphics/component/object_component.h"
|
||||
#include "ballistica/base/graphics/component/post_process_component.h"
|
||||
#include "ballistica/base/graphics/component/simple_component.h"
|
||||
#include "ballistica/base/graphics/component/special_component.h"
|
||||
#include "ballistica/base/graphics/component/sprite_component.h"
|
||||
#include "ballistica/base/graphics/graphics_server.h"
|
||||
#include "ballistica/base/graphics/graphics_vr.h"
|
||||
#include "ballistica/base/graphics/support/camera.h"
|
||||
#include "ballistica/base/graphics/support/net_graph.h"
|
||||
#include "ballistica/base/graphics/text/text_graphics.h"
|
||||
#include "ballistica/base/graphics/support/screen_messages.h"
|
||||
#include "ballistica/base/input/input.h"
|
||||
#include "ballistica/base/logic/logic.h"
|
||||
#include "ballistica/base/platform/base_platform.h"
|
||||
#include "ballistica/base/python/support/python_context_call.h"
|
||||
#include "ballistica/base/support/app_config.h"
|
||||
#include "ballistica/base/ui/dev_console.h"
|
||||
#include "ballistica/base/ui/ui.h"
|
||||
#include "ballistica/core/core.h"
|
||||
#include "ballistica/shared/foundation/event_loop.h"
|
||||
#include "ballistica/shared/generic/utils.h"
|
||||
#include "ballistica/shared/python/python.h"
|
||||
|
||||
namespace ballistica::base {
|
||||
|
||||
const float kScreenMessageZDepth{-0.06f};
|
||||
const float kScreenMeshZDepth{-0.05f};
|
||||
const float kScreenTextZDepth{-0.06f};
|
||||
const float kProgressBarZDepth{0.0f};
|
||||
const int kProgressBarFadeTime{500};
|
||||
const float kDebugImgZDepth{-0.04f};
|
||||
|
||||
auto Graphics::Create() -> Graphics* {
|
||||
#if BA_VR_BUILD
|
||||
return new GraphicsVR();
|
||||
#else
|
||||
return new Graphics();
|
||||
#endif
|
||||
}
|
||||
const float kScreenMeshZDepth{-0.05f};
|
||||
|
||||
auto Graphics::IsShaderTransparent(ShadingType c) -> bool {
|
||||
switch (c) {
|
||||
@ -93,7 +78,7 @@ auto Graphics::IsShaderTransparent(ShadingType c) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
Graphics::Graphics() = default;
|
||||
Graphics::Graphics() : screenmessages{new ScreenMessages()} {}
|
||||
Graphics::~Graphics() = default;
|
||||
|
||||
void Graphics::OnAppStart() { assert(g_base->InLogicThread()); }
|
||||
@ -182,7 +167,7 @@ void Graphics::UpdateInitialGraphicsSettingsSend_() {
|
||||
void Graphics::StepDisplayTime() { assert(g_base->InLogicThread()); }
|
||||
|
||||
void Graphics::AddCleanFrameCommand(const Object::Ref<PythonContextCall>& c) {
|
||||
BA_PRECONDITION(g_base->InLogicThread());
|
||||
assert(g_base->InLogicThread());
|
||||
clean_frame_commands_.push_back(c);
|
||||
}
|
||||
|
||||
@ -231,11 +216,9 @@ auto Graphics::VSyncFromAppConfig() -> VSyncRequest {
|
||||
}
|
||||
|
||||
auto Graphics::GraphicsQualityFromAppConfig() -> GraphicsQualityRequest {
|
||||
// Graphics quality.
|
||||
std::string gqualstr =
|
||||
g_base->app_config->Resolve(AppConfig::StringID::kGraphicsQuality);
|
||||
GraphicsQualityRequest graphics_quality_requested;
|
||||
|
||||
if (gqualstr == "Auto") {
|
||||
graphics_quality_requested = GraphicsQualityRequest::kAuto;
|
||||
} else if (gqualstr == "Higher") {
|
||||
@ -359,40 +342,6 @@ auto Graphics::GetShadowDensity(float x, float y, float z) -> float {
|
||||
}
|
||||
}
|
||||
|
||||
class Graphics::ScreenMessageEntry {
|
||||
public:
|
||||
ScreenMessageEntry(std::string s_in, bool align_left_in, uint32_t c,
|
||||
const Vector3f& color_in, TextureAsset* texture_in,
|
||||
TextureAsset* tint_texture_in, const Vector3f& tint_in,
|
||||
const Vector3f& tint2_in)
|
||||
: align_left(align_left_in),
|
||||
creation_time(c),
|
||||
s_raw(std::move(s_in)),
|
||||
color(color_in),
|
||||
texture(texture_in),
|
||||
tint_texture(tint_texture_in),
|
||||
tint(tint_in),
|
||||
tint2(tint2_in) {}
|
||||
auto GetText() -> TextGroup&;
|
||||
void UpdateTranslation();
|
||||
bool align_left;
|
||||
uint32_t creation_time;
|
||||
Vector3f color;
|
||||
Vector3f tint;
|
||||
Vector3f tint2;
|
||||
std::string s_raw;
|
||||
std::string s_translated;
|
||||
Object::Ref<TextureAsset> texture;
|
||||
Object::Ref<TextureAsset> tint_texture;
|
||||
float v_smoothed{};
|
||||
bool translation_dirty{true};
|
||||
bool mesh_dirty{true};
|
||||
millisecs_t smooth_time{};
|
||||
|
||||
private:
|
||||
Object::Ref<TextGroup> s_mesh_;
|
||||
};
|
||||
|
||||
// Draw controls and things that lie on top of the action.
|
||||
void Graphics::DrawMiscOverlays(FrameDef* frame_def) {
|
||||
RenderPass* pass = frame_def->overlay_pass();
|
||||
@ -410,7 +359,6 @@ void Graphics::DrawMiscOverlays(FrameDef* frame_def) {
|
||||
last_fps_ = total_frames_rendered - last_total_frames_rendered_;
|
||||
last_total_frames_rendered_ = total_frames_rendered;
|
||||
}
|
||||
float v{};
|
||||
|
||||
if (show_fps_) {
|
||||
char fps_str[32];
|
||||
@ -472,7 +420,7 @@ void Graphics::DrawMiscOverlays(FrameDef* frame_def) {
|
||||
{
|
||||
auto xf = c.ScopedTransform();
|
||||
c.Translate(14.0f + (show_fps_ ? 30.0f : 0.0f), 0.1f,
|
||||
kScreenMessageZDepth);
|
||||
kScreenTextZDepth);
|
||||
c.Scale(0.7f, 0.7f);
|
||||
c.DrawMesh(ping_text_group_->GetElementMesh(e));
|
||||
}
|
||||
@ -500,7 +448,7 @@ void Graphics::DrawMiscOverlays(FrameDef* frame_def) {
|
||||
c.SetFlatness(1.0f);
|
||||
{
|
||||
auto xf = c.ScopedTransform();
|
||||
c.Translate(4.0f, (show_fps_ ? 66.0f : 40.0f), kScreenMessageZDepth);
|
||||
c.Translate(4.0f, (show_fps_ ? 66.0f : 40.0f), kScreenTextZDepth);
|
||||
c.Scale(0.7f, 0.7f);
|
||||
c.DrawMesh(net_info_text_group_->GetElementMesh(e));
|
||||
}
|
||||
@ -528,382 +476,7 @@ void Graphics::DrawMiscOverlays(FrameDef* frame_def) {
|
||||
}
|
||||
}
|
||||
|
||||
// Screen messages (bottom).
|
||||
{
|
||||
// Delete old ones.
|
||||
if (!screen_messages_.empty()) {
|
||||
millisecs_t cutoff;
|
||||
if (g_core->GetAppTimeMillisecs() > 5000) {
|
||||
cutoff = g_core->GetAppTimeMillisecs() - 5000;
|
||||
for (auto i = screen_messages_.begin(); i != screen_messages_.end();) {
|
||||
if (i->creation_time < cutoff) {
|
||||
auto next = i;
|
||||
next++;
|
||||
screen_messages_.erase(i);
|
||||
i = next;
|
||||
} else {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Delete if we have too many.
|
||||
while ((screen_messages_.size()) > 4) {
|
||||
screen_messages_.erase(screen_messages_.begin());
|
||||
}
|
||||
|
||||
// Draw all existing.
|
||||
if (!screen_messages_.empty()) {
|
||||
bool vr = g_core->IsVRMode();
|
||||
|
||||
// These are less disruptive in the middle for menus but at the bottom
|
||||
// during gameplay.
|
||||
float start_v = g_base->graphics->screen_virtual_height() * 0.05f;
|
||||
float scale;
|
||||
switch (g_base->ui->scale()) {
|
||||
case UIScale::kSmall:
|
||||
scale = 1.5f;
|
||||
break;
|
||||
case UIScale::kMedium:
|
||||
scale = 1.2f;
|
||||
break;
|
||||
default:
|
||||
scale = 1.0f;
|
||||
break;
|
||||
}
|
||||
|
||||
// Shadows.
|
||||
{
|
||||
SimpleComponent c(pass);
|
||||
c.SetTransparent(true);
|
||||
c.SetTexture(
|
||||
g_base->assets->SysTexture(SysTextureID::kSoftRectVertical));
|
||||
|
||||
float screen_width = g_base->graphics->screen_virtual_width();
|
||||
|
||||
v = start_v;
|
||||
|
||||
millisecs_t youngest_age = 9999;
|
||||
|
||||
for (auto i = screen_messages_.rbegin(); i != screen_messages_.rend();
|
||||
i++) {
|
||||
// Update the translation if need be.
|
||||
i->UpdateTranslation();
|
||||
|
||||
millisecs_t age = g_core->GetAppTimeMillisecs() - i->creation_time;
|
||||
youngest_age = std::min(youngest_age, age);
|
||||
float s_extra = 1.0f;
|
||||
if (age < 100) {
|
||||
s_extra = std::min(1.2f, 1.2f * (static_cast<float>(age) / 100.0f));
|
||||
} else if (age < 150) {
|
||||
s_extra =
|
||||
1.2f - 0.2f * ((150.0f - static_cast<float>(age)) / 50.0f);
|
||||
}
|
||||
|
||||
float a;
|
||||
if (age > 3000) {
|
||||
a = 1.0f - static_cast<float>(age - 3000) / 2000;
|
||||
} else {
|
||||
a = 1;
|
||||
}
|
||||
a *= 0.8f;
|
||||
|
||||
if (vr) {
|
||||
a *= 0.8f;
|
||||
}
|
||||
|
||||
if (i->translation_dirty) {
|
||||
BA_LOG_ONCE(
|
||||
LogLevel::kWarning,
|
||||
"Found dirty translation on screenmessage draw pass 1; raw="
|
||||
+ i->s_raw);
|
||||
}
|
||||
float str_height =
|
||||
g_base->text_graphics->GetStringHeight(i->s_translated.c_str());
|
||||
float str_width =
|
||||
g_base->text_graphics->GetStringWidth(i->s_translated.c_str());
|
||||
|
||||
if ((str_width * scale) > (screen_width - 40)) {
|
||||
s_extra *= ((screen_width - 40) / (str_width * scale));
|
||||
}
|
||||
|
||||
float r = i->color.x;
|
||||
float g = i->color.y;
|
||||
float b = i->color.z;
|
||||
GetSafeColor(&r, &g, &b);
|
||||
|
||||
float v_extra = scale * (static_cast<float>(youngest_age) * 0.01f);
|
||||
|
||||
float fade;
|
||||
if (age < 100) {
|
||||
fade = 1.0f;
|
||||
} else {
|
||||
fade = std::max(0.0f, (200.0f - static_cast<float>(age)) / 100.0f);
|
||||
}
|
||||
c.SetColor(r * fade, g * fade, b * fade, a);
|
||||
|
||||
{
|
||||
auto xf = c.ScopedTransform();
|
||||
|
||||
// This logic needs to run at a fixed hz or it breaks on high frame
|
||||
// rates.
|
||||
auto now_millisecs = pass->frame_def()->display_time_millisecs();
|
||||
i->smooth_time = std::max(i->smooth_time, now_millisecs - 100);
|
||||
while (i->smooth_time < now_millisecs) {
|
||||
i->smooth_time += 1000 / 60;
|
||||
if (i->v_smoothed == 0.0f) {
|
||||
i->v_smoothed = v + v_extra;
|
||||
} else {
|
||||
float smoothing = 0.8f;
|
||||
i->v_smoothed = smoothing * i->v_smoothed
|
||||
+ (1.0f - smoothing) * (v + v_extra);
|
||||
}
|
||||
}
|
||||
|
||||
c.Translate(screen_width * 0.5f, i->v_smoothed,
|
||||
vr ? 60 : kScreenMessageZDepth);
|
||||
if (vr) {
|
||||
// Let's drop down a bit in vr mode.
|
||||
c.Translate(0, -10.0f, 0);
|
||||
c.Scale((str_width + 60) * scale * s_extra,
|
||||
(str_height + 20) * scale * s_extra);
|
||||
|
||||
// Align our bottom with where we just scaled from.
|
||||
c.Translate(0, 0.5f, 0);
|
||||
} else {
|
||||
c.Scale((str_width + 110) * scale * s_extra,
|
||||
(str_height + 40) * scale * s_extra);
|
||||
|
||||
// Align our bottom with where we just scaled from.
|
||||
c.Translate(0, 0.5f, 0);
|
||||
}
|
||||
c.DrawMeshAsset(g_base->assets->SysMesh(SysMeshID::kImage1x1));
|
||||
}
|
||||
|
||||
v += scale * (36 + str_height);
|
||||
if (v > g_base->graphics->screen_virtual_height() + 30) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
c.Submit();
|
||||
}
|
||||
|
||||
// Now the strings themselves.
|
||||
{
|
||||
SimpleComponent c(pass);
|
||||
c.SetTransparent(true);
|
||||
|
||||
float screen_width = g_base->graphics->screen_virtual_width();
|
||||
v = start_v;
|
||||
millisecs_t youngest_age = 9999;
|
||||
|
||||
for (auto i = screen_messages_.rbegin(); i != screen_messages_.rend();
|
||||
i++) {
|
||||
millisecs_t age = g_core->GetAppTimeMillisecs() - i->creation_time;
|
||||
youngest_age = std::min(youngest_age, age);
|
||||
float s_extra = 1.0f;
|
||||
if (age < 100) {
|
||||
s_extra = std::min(1.2f, 1.2f * (static_cast<float>(age) / 100.0f));
|
||||
} else if (age < 150) {
|
||||
s_extra =
|
||||
1.2f - 0.2f * ((150.0f - static_cast<float>(age)) / 50.0f);
|
||||
}
|
||||
float a;
|
||||
if (age > 3000) {
|
||||
a = 1.0f - static_cast<float>(age - 3000) / 2000;
|
||||
} else {
|
||||
a = 1;
|
||||
}
|
||||
if (i->translation_dirty) {
|
||||
BA_LOG_ONCE(
|
||||
LogLevel::kWarning,
|
||||
"Found dirty translation on screenmessage draw pass 2; raw="
|
||||
+ i->s_raw);
|
||||
}
|
||||
float str_height =
|
||||
g_base->text_graphics->GetStringHeight(i->s_translated.c_str());
|
||||
float str_width =
|
||||
g_base->text_graphics->GetStringWidth(i->s_translated.c_str());
|
||||
|
||||
if ((str_width * scale) > (screen_width - 40)) {
|
||||
s_extra *= ((screen_width - 40) / (str_width * scale));
|
||||
}
|
||||
float r = i->color.x;
|
||||
float g = i->color.y;
|
||||
float b = i->color.z;
|
||||
GetSafeColor(&r, &g, &b, 0.85f);
|
||||
|
||||
int elem_count = i->GetText().GetElementCount();
|
||||
for (int e = 0; e < elem_count; e++) {
|
||||
// Gracefully skip unloaded textures.
|
||||
TextureAsset* t = i->GetText().GetElementTexture(e);
|
||||
if (!t->preloaded()) {
|
||||
continue;
|
||||
}
|
||||
c.SetTexture(t);
|
||||
if (i->GetText().GetElementCanColor(e)) {
|
||||
c.SetColor(r, g, b, a);
|
||||
} else {
|
||||
c.SetColor(1, 1, 1, a);
|
||||
}
|
||||
c.SetFlatness(i->GetText().GetElementMaxFlatness(e));
|
||||
{
|
||||
auto xf = c.ScopedTransform();
|
||||
c.Translate(screen_width * 0.5f, i->v_smoothed,
|
||||
vr ? 150 : kScreenMessageZDepth);
|
||||
c.Scale(scale * s_extra, scale * s_extra);
|
||||
c.Translate(0, 20);
|
||||
c.DrawMesh(i->GetText().GetElementMesh(e));
|
||||
}
|
||||
}
|
||||
|
||||
v += scale * (36 + str_height);
|
||||
if (v > g_base->graphics->screen_virtual_height() + 30) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
c.Submit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Screen messages (top).
|
||||
{
|
||||
// Delete old ones.
|
||||
if (!screen_messages_top_.empty()) {
|
||||
millisecs_t cutoff;
|
||||
if (g_core->GetAppTimeMillisecs() > 5000) {
|
||||
cutoff = g_core->GetAppTimeMillisecs() - 5000;
|
||||
for (auto i = screen_messages_top_.begin();
|
||||
i != screen_messages_top_.end();) {
|
||||
if (i->creation_time < cutoff) {
|
||||
auto next = i;
|
||||
next++;
|
||||
screen_messages_top_.erase(i);
|
||||
i = next;
|
||||
} else {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Delete if we have too many.
|
||||
while ((screen_messages_top_.size()) > 6) {
|
||||
screen_messages_top_.erase(screen_messages_top_.begin());
|
||||
}
|
||||
|
||||
if (!screen_messages_top_.empty()) {
|
||||
SimpleComponent c(pass);
|
||||
c.SetTransparent(true);
|
||||
|
||||
// Draw all existing.
|
||||
float h = pass->virtual_width() - 300.0f;
|
||||
v = g_base->graphics->screen_virtual_height() - 50.0f;
|
||||
|
||||
float v_base = g_base->graphics->screen_virtual_height();
|
||||
float last_v = -999.0f;
|
||||
|
||||
float min_spacing = 25.0f;
|
||||
|
||||
for (auto i = screen_messages_top_.rbegin();
|
||||
i != screen_messages_top_.rend(); i++) {
|
||||
// Update the translation if need be.
|
||||
i->UpdateTranslation();
|
||||
|
||||
millisecs_t age = g_core->GetAppTimeMillisecs() - 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));
|
||||
} else if (age < 150) {
|
||||
s_extra = 1.1f - 0.1f * ((150.0f - static_cast<float>(age)) / 50.0f);
|
||||
}
|
||||
|
||||
float a;
|
||||
if (age > 3000) {
|
||||
a = 1.0f - static_cast<float>(age - 3000) / 2000;
|
||||
} else {
|
||||
a = 1;
|
||||
}
|
||||
|
||||
// This logic needs to run at a fixed hz or it breaks on high frame
|
||||
// rates.
|
||||
auto now_millisecs = pass->frame_def()->display_time_millisecs();
|
||||
i->smooth_time = std::max(i->smooth_time, now_millisecs - 100);
|
||||
while (i->smooth_time < now_millisecs) {
|
||||
i->smooth_time += 1000 / 60;
|
||||
i->v_smoothed += 0.1f;
|
||||
if (i->v_smoothed - last_v < min_spacing) {
|
||||
i->v_smoothed +=
|
||||
8.0f * (1.0f - ((i->v_smoothed - last_v) / min_spacing));
|
||||
}
|
||||
}
|
||||
last_v = i->v_smoothed;
|
||||
|
||||
// Draw the image if they provided one.
|
||||
if (i->texture.Exists()) {
|
||||
c.Submit();
|
||||
|
||||
SimpleComponent c2(pass);
|
||||
c2.SetTransparent(true);
|
||||
c2.SetTexture(i->texture);
|
||||
if (i->tint_texture.Exists()) {
|
||||
c2.SetColorizeTexture(i->tint_texture.Get());
|
||||
c2.SetColorizeColor(i->tint.x, i->tint.y, i->tint.z);
|
||||
c2.SetColorizeColor2(i->tint2.x, i->tint2.y, i->tint2.z);
|
||||
c2.SetMaskTexture(
|
||||
g_base->assets->SysTexture(SysTextureID::kCharacterIconMask));
|
||||
}
|
||||
c2.SetColor(1, 1, 1, a);
|
||||
{
|
||||
auto xf = c2.ScopedTransform();
|
||||
c2.Translate(h - 14, v_base + 10 + i->v_smoothed,
|
||||
kScreenMessageZDepth);
|
||||
c2.Scale(22.0f * s_extra, 22.0f * s_extra);
|
||||
c2.DrawMeshAsset(g_base->assets->SysMesh(SysMeshID::kImage1x1));
|
||||
}
|
||||
c2.Submit();
|
||||
}
|
||||
|
||||
float r = i->color.x;
|
||||
float g = i->color.y;
|
||||
float b = i->color.z;
|
||||
GetSafeColor(&r, &g, &b);
|
||||
|
||||
int elem_count = i->GetText().GetElementCount();
|
||||
for (int e = 0; e < elem_count; e++) {
|
||||
// Gracefully skip unloaded textures.
|
||||
TextureAsset* t = i->GetText().GetElementTexture(e);
|
||||
if (!t->preloaded()) {
|
||||
continue;
|
||||
}
|
||||
c.SetTexture(t);
|
||||
if (i->GetText().GetElementCanColor(e)) {
|
||||
c.SetColor(r, g, b, a);
|
||||
} else {
|
||||
c.SetColor(1, 1, 1, a);
|
||||
}
|
||||
c.SetShadow(-0.003f * i->GetText().GetElementUScale(e),
|
||||
-0.003f * i->GetText().GetElementVScale(e), 0.0f,
|
||||
1.0f * a);
|
||||
c.SetFlatness(i->GetText().GetElementMaxFlatness(e));
|
||||
c.SetMaskUV2Texture(i->GetText().GetElementMaskUV2Texture(e));
|
||||
{
|
||||
auto xf = c.ScopedTransform();
|
||||
c.Translate(h, v_base + 2 + i->v_smoothed, kScreenMessageZDepth);
|
||||
c.Scale(0.6f * s_extra, 0.6f * s_extra);
|
||||
c.DrawMesh(i->GetText().GetElementMesh(e));
|
||||
}
|
||||
}
|
||||
assert(!i->translation_dirty);
|
||||
v -= g_base->text_graphics->GetStringHeight(i->s_translated.c_str())
|
||||
* 0.6f
|
||||
+ 8.0f;
|
||||
}
|
||||
c.Submit();
|
||||
}
|
||||
}
|
||||
screenmessages->DrawMiscOverlays(frame_def);
|
||||
}
|
||||
|
||||
auto Graphics::GetDebugGraph(const std::string& name, bool smoothed)
|
||||
@ -949,32 +522,6 @@ void Graphics::GetSafeColor(float* red, float* green, float* blue,
|
||||
}
|
||||
}
|
||||
|
||||
void Graphics::AddScreenMessage(const std::string& msg, const Vector3f& color,
|
||||
bool top, TextureAsset* texture,
|
||||
TextureAsset* tint_texture,
|
||||
const Vector3f& tint, const Vector3f& tint2) {
|
||||
assert(g_base->InLogicThread());
|
||||
|
||||
// So we know we're always dealing with valid utf8.
|
||||
std::string m = Utils::GetValidUTF8(msg.c_str(), "ga9msg");
|
||||
|
||||
if (top) {
|
||||
float start_v = -40.0f;
|
||||
if (!screen_messages_top_.empty()) {
|
||||
start_v = std::min(
|
||||
start_v,
|
||||
std::max(-100.0f, screen_messages_top_.back().v_smoothed - 25.0f));
|
||||
}
|
||||
screen_messages_top_.emplace_back(m, true, g_core->GetAppTimeMillisecs(),
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
void Graphics::Reset() {
|
||||
assert(g_base->InLogicThread());
|
||||
fade_ = 0;
|
||||
@ -984,9 +531,7 @@ void Graphics::Reset() {
|
||||
camera_ = Object::New<Camera>();
|
||||
}
|
||||
|
||||
// Wipe out top screen messages since they might be using textures that are
|
||||
// being reset. Bottom ones are ok since they have no textures.
|
||||
screen_messages_top_.clear();
|
||||
screenmessages->Reset();
|
||||
}
|
||||
|
||||
void Graphics::InitInternalComponents(FrameDef* frame_def) {
|
||||
@ -1058,7 +603,7 @@ void Graphics::ClearFrameDefDeleteList() {
|
||||
}
|
||||
|
||||
void Graphics::FadeScreen(bool to, millisecs_t time, PyObject* endcall) {
|
||||
BA_PRECONDITION(g_base->InLogicThread());
|
||||
assert(g_base->InLogicThread());
|
||||
// If there's an ourstanding fade-end command, go ahead and run it.
|
||||
// (otherwise, overlapping fades can cause things to get lost)
|
||||
if (fade_end_call_.Exists()) {
|
||||
@ -1186,7 +731,7 @@ void Graphics::DrawDevUI(FrameDef* frame_def) {
|
||||
void Graphics::BuildAndPushFrameDef() {
|
||||
assert(g_base->InLogicThread());
|
||||
|
||||
BA_PRECONDITION_FATAL(g_base->logic->app_bootstrapping_complete());
|
||||
assert(g_base->logic->app_bootstrapping_complete());
|
||||
assert(camera_.Exists());
|
||||
assert(!g_core->HeadlessMode());
|
||||
|
||||
@ -1611,16 +1156,6 @@ void Graphics::DrawBlotches(FrameDef* frame_def) {
|
||||
}
|
||||
}
|
||||
|
||||
void Graphics::ClearScreenMessageTranslations() {
|
||||
assert(g_base && g_base->InLogicThread());
|
||||
for (auto&& i : screen_messages_) {
|
||||
i.translation_dirty = true;
|
||||
}
|
||||
for (auto&& i : screen_messages_top_) {
|
||||
i.translation_dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
void Graphics::ReturnCompletedFrameDef(FrameDef* frame_def) {
|
||||
std::scoped_lock lock(frame_def_delete_list_mutex_);
|
||||
g_base->graphics->frame_def_delete_list_.push_back(frame_def);
|
||||
@ -1951,26 +1486,6 @@ void Graphics::DrawRadialMeter(MeshIndexedSimpleFull* m, float amt) {
|
||||
}
|
||||
}
|
||||
|
||||
auto Graphics::ScreenMessageEntry::GetText() -> TextGroup& {
|
||||
if (translation_dirty) {
|
||||
BA_LOG_ONCE(
|
||||
LogLevel::kWarning,
|
||||
"Found dirty translation on screenmessage GetText; raw=" + s_raw);
|
||||
}
|
||||
if (!s_mesh_.Exists()) {
|
||||
s_mesh_ = Object::New<TextGroup>();
|
||||
mesh_dirty = true;
|
||||
}
|
||||
if (mesh_dirty) {
|
||||
s_mesh_->SetText(
|
||||
s_translated,
|
||||
align_left ? TextMesh::HAlign::kLeft : TextMesh::HAlign::kCenter,
|
||||
TextMesh::VAlign::kBottom);
|
||||
mesh_dirty = false;
|
||||
}
|
||||
return *s_mesh_;
|
||||
}
|
||||
|
||||
void Graphics::OnScreenSizeChange() {}
|
||||
|
||||
void Graphics::CalcVirtualRes_(float* x, float* y) {
|
||||
@ -2024,15 +1539,6 @@ void Graphics::SetScreenResolution(float x, float y) {
|
||||
UpdateInitialGraphicsSettingsSend_();
|
||||
}
|
||||
|
||||
void Graphics::ScreenMessageEntry::UpdateTranslation() {
|
||||
if (translation_dirty) {
|
||||
s_translated = g_base->assets->CompileResourceString(
|
||||
s_raw, "Graphics::ScreenMessageEntry::UpdateTranslation");
|
||||
translation_dirty = false;
|
||||
mesh_dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
auto Graphics::CubeMapFromReflectionType(ReflectionType reflection_type)
|
||||
-> SysCubeMapTextureID {
|
||||
switch (reflection_type) {
|
||||
@ -2112,8 +1618,8 @@ void Graphics::LanguageChanged() {
|
||||
Log(LogLevel::kWarning,
|
||||
"Graphics::LanguageChanged() called during draw; should not happen.");
|
||||
}
|
||||
// Also clear translations on all screen-messages.
|
||||
ClearScreenMessageTranslations();
|
||||
|
||||
screenmessages->ClearScreenMessageTranslations();
|
||||
}
|
||||
|
||||
auto Graphics::GraphicsQualityFromRequest(GraphicsQualityRequest request,
|
||||
@ -2136,6 +1642,7 @@ auto Graphics::GraphicsQualityFromRequest(GraphicsQualityRequest request,
|
||||
return GraphicsQuality::kLow;
|
||||
}
|
||||
}
|
||||
|
||||
auto Graphics::TextureQualityFromRequest(TextureQualityRequest request,
|
||||
TextureQuality auto_val)
|
||||
-> TextureQuality {
|
||||
|
||||
@ -53,8 +53,7 @@ const float kCursorZDepth{0.9f};
|
||||
// Client class for graphics operations (used from the logic thread).
|
||||
class Graphics {
|
||||
public:
|
||||
/// Instantiate the Graphics subclass for the current build.
|
||||
static auto Create() -> Graphics*;
|
||||
Graphics();
|
||||
|
||||
void OnAppStart();
|
||||
void OnAppPause();
|
||||
@ -119,8 +118,6 @@ class Graphics {
|
||||
return res_y_virtual_;
|
||||
}
|
||||
|
||||
void ClearScreenMessageTranslations();
|
||||
|
||||
// Given a point in space, returns the shadow density that should be drawn
|
||||
// into the shadow pass. Does this belong somewhere else?
|
||||
auto GetShadowDensity(float x, float y, float z) -> float;
|
||||
@ -128,21 +125,13 @@ class Graphics {
|
||||
static void GetSafeColor(float* r, float* g, float* b,
|
||||
float target_intensity = 0.6f);
|
||||
|
||||
// Print a message to the on-screen list.
|
||||
void AddScreenMessage(const std::string& msg,
|
||||
const Vector3f& color = {1, 1, 1}, bool top = false,
|
||||
TextureAsset* texture = nullptr,
|
||||
TextureAsset* tint_texture = nullptr,
|
||||
const Vector3f& tint = {1, 1, 1},
|
||||
const Vector3f& tint2 = {1, 1, 1});
|
||||
|
||||
// Fade the local screen in or out over the given time period.
|
||||
void FadeScreen(bool to, millisecs_t time, PyObject* endcall);
|
||||
|
||||
static void DrawRadialMeter(MeshIndexedSimpleFull* m, float amt);
|
||||
|
||||
// Ways to add a few simple component types quickly.
|
||||
// (uses particle rendering for efficient batches).
|
||||
// Ways to add a few simple component types quickly (uses particle
|
||||
// rendering for efficient batches).
|
||||
void DrawBlotch(const Vector3f& pos, float size, float r, float g, float b,
|
||||
float a) {
|
||||
DoDrawBlotch(&blotch_indices_, &blotch_verts_, pos, size, r, g, b, a);
|
||||
@ -240,13 +229,8 @@ class Graphics {
|
||||
float upper_top);
|
||||
void ReleaseFadeEndCommand();
|
||||
|
||||
// auto tv_border() const {
|
||||
// assert(g_base->InLogicThread());
|
||||
// return tv_border_;
|
||||
// }
|
||||
|
||||
// Nodes that draw flat stuff into the overlay pass should query this z value
|
||||
// for where to draw in z.
|
||||
// Nodes that draw flat stuff into the overlay pass should query this z
|
||||
// value for where to draw in z.
|
||||
auto overlay_node_z_depth() {
|
||||
fetched_overlay_node_z_depth_ = true;
|
||||
return overlay_node_z_depth_;
|
||||
@ -296,8 +280,8 @@ class Graphics {
|
||||
void AddMeshDataCreate(MeshData* d);
|
||||
void AddMeshDataDestroy(MeshData* d);
|
||||
|
||||
// For debugging: ensures that only transparent or opaque components
|
||||
// are submitted while enabled.
|
||||
// For debugging: ensures that only transparent or opaque components are
|
||||
// submitted while enabled.
|
||||
auto drawing_transparent_only() const { return drawing_transparent_only_; }
|
||||
void set_drawing_transparent_only(bool val) {
|
||||
drawing_transparent_only_ = val;
|
||||
@ -362,8 +346,8 @@ class Graphics {
|
||||
}
|
||||
|
||||
/// For temporary use in arbitrary threads. This should be removed when
|
||||
/// possible and replaced with proper safe thread-specific access
|
||||
/// patterns (so we can support switching renderers/etc.).
|
||||
/// possible and replaced with proper safe thread-specific access patterns
|
||||
/// (so we can support switching renderers/etc.).
|
||||
auto placeholder_client_context() const -> const GraphicsClientContext* {
|
||||
// Using this from arbitrary threads is currently ok currently since
|
||||
// context never changes once set. Will need to kill this call once that
|
||||
@ -372,10 +356,9 @@ class Graphics {
|
||||
return client_context_snapshot_.Get()->Get();
|
||||
}
|
||||
|
||||
protected:
|
||||
class ScreenMessageEntry;
|
||||
ScreenMessages* const screenmessages;
|
||||
|
||||
Graphics();
|
||||
protected:
|
||||
virtual ~Graphics();
|
||||
virtual void DoDrawFade(FrameDef* frame_def, float amt);
|
||||
static void CalcVirtualRes_(float* x, float* y);
|
||||
@ -453,8 +436,6 @@ class Graphics {
|
||||
std::map<std::string, Object::Ref<NetGraph>> debug_graphs_;
|
||||
std::mutex frame_def_delete_list_mutex_;
|
||||
std::list<Object::Ref<PythonContextCall>> clean_frame_commands_;
|
||||
std::list<ScreenMessageEntry> screen_messages_;
|
||||
std::list<ScreenMessageEntry> screen_messages_top_;
|
||||
std::vector<FrameDef*> recycle_frame_defs_;
|
||||
std::vector<uint16_t> blotch_indices_;
|
||||
std::vector<VertexSprite> blotch_verts_;
|
||||
@ -473,23 +454,22 @@ class Graphics {
|
||||
float gyro_mag_test_{};
|
||||
float overlay_node_z_depth_{};
|
||||
float progress_bar_progress_{};
|
||||
float screen_gamma_{1.0f};
|
||||
float shadow_lower_bottom_{-4.0f};
|
||||
float shadow_lower_top_{4.0f};
|
||||
float shadow_upper_bottom_{30.0f};
|
||||
float shadow_upper_top_{40.0f};
|
||||
seconds_t last_cursor_visibility_event_time_{};
|
||||
millisecs_t fade_start_{};
|
||||
millisecs_t fade_time_{};
|
||||
millisecs_t next_stat_update_time_{};
|
||||
millisecs_t progress_bar_end_time_{-9999};
|
||||
millisecs_t last_progress_bar_draw_time_{};
|
||||
millisecs_t last_progress_bar_start_time_{};
|
||||
microsecs_t last_suppress_gyro_time_{};
|
||||
seconds_t last_cursor_visibility_event_time_{};
|
||||
microsecs_t next_frame_number_filtered_increment_time_{};
|
||||
microsecs_t last_create_frame_def_time_microsecs_{};
|
||||
millisecs_t last_create_frame_def_time_millisecs_{};
|
||||
millisecs_t last_jitter_update_time_{};
|
||||
microsecs_t last_suppress_gyro_time_{};
|
||||
microsecs_t next_frame_number_filtered_increment_time_{};
|
||||
microsecs_t last_create_frame_def_time_microsecs_{};
|
||||
Object::Ref<ImageMesh> screen_mesh_;
|
||||
Object::Ref<ImageMesh> progress_bar_bottom_mesh_;
|
||||
Object::Ref<ImageMesh> progress_bar_top_mesh_;
|
||||
|
||||
@ -137,14 +137,10 @@ auto GraphicsServer::TryRender() -> bool {
|
||||
|
||||
auto GraphicsServer::WaitForRenderFrameDef_() -> FrameDef* {
|
||||
assert(g_base->app_adapter->InGraphicsContext());
|
||||
millisecs_t app_time = g_core->GetAppTimeMillisecs();
|
||||
millisecs_t start_time = g_core->GetAppTimeMillisecs();
|
||||
|
||||
if (!renderer_) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// If the app is paused, never render.
|
||||
if (g_base->app_adapter->app_suspended()) {
|
||||
// Don't bother waiting if we can't/shouldn't render anyway.
|
||||
if (!renderer_ || shutting_down_ || g_base->app_adapter->app_suspended()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@ -173,12 +169,12 @@ auto GraphicsServer::WaitForRenderFrameDef_() -> FrameDef* {
|
||||
// if we've been waiting for too long, give up. On some platforms such
|
||||
// as Android, this frame will still get flipped whether we draw in it
|
||||
// or not, so we *really* want to not skip drawing if we can help it.
|
||||
millisecs_t t = g_core->GetAppTimeMillisecs() - app_time;
|
||||
millisecs_t t = g_core->GetAppTimeMillisecs() - start_time;
|
||||
if (t >= 1000) {
|
||||
if (g_buildconfig.debug_build()) {
|
||||
Log(LogLevel::kWarning,
|
||||
"GraphicsServer: aborting GetRenderFrameDef after "
|
||||
+ std::to_string(t) + "ms.");
|
||||
"GraphicsServer: timed out at " + std::to_string(t)
|
||||
+ "ms waiting for logic thread to send us a FrameDef.");
|
||||
}
|
||||
break; // Fail.
|
||||
}
|
||||
@ -336,38 +332,6 @@ void GraphicsServer::LoadRenderer() {
|
||||
texture_quality_ = Graphics::TextureQualityFromRequest(
|
||||
texture_quality_requested_, renderer_->GetAutoTextureQuality());
|
||||
|
||||
// If we don't support high quality graphics, make sure we're no higher than
|
||||
// medium.
|
||||
// BA_PRECONDITION(g_base->graphics->has_supports_high_quality_graphics_value());
|
||||
// if (!g_base->graphics->supports_high_quality_graphics()
|
||||
// && graphics_quality_ > GraphicsQuality::kMedium) {
|
||||
// graphics_quality_ = GraphicsQuality::kMedium;
|
||||
// }
|
||||
// graphics_quality_set_ = true;
|
||||
|
||||
// Update texture quality based on request.
|
||||
// switch (texture_quality_requested_) {
|
||||
// case TextureQualityRequest::kLow:
|
||||
// texture_quality_ = TextureQuality::kLow;
|
||||
// break;
|
||||
// case TextureQualityRequest::kMedium:
|
||||
// texture_quality_ = TextureQuality::kMedium;
|
||||
// break;
|
||||
// case TextureQualityRequest::kHigh:
|
||||
// texture_quality_ = TextureQuality::kHigh;
|
||||
// break;
|
||||
// case TextureQualityRequest::kAuto:
|
||||
// texture_quality_ = renderer_->GetAutoTextureQuality();
|
||||
// break;
|
||||
// default:
|
||||
// Log(LogLevel::kError,
|
||||
// "Unhandled TextureQualityRequest value: "
|
||||
// +
|
||||
// std::to_string(static_cast<int>(texture_quality_requested_)));
|
||||
// texture_quality_ = TextureQuality::kLow;
|
||||
// }
|
||||
// texture_quality_set_ = true;
|
||||
|
||||
// Ok we've got our qualities figured out; now load/update the renderer.
|
||||
renderer_->Load();
|
||||
|
||||
@ -657,4 +621,15 @@ auto GraphicsServer::InGraphicsContext_() const -> bool {
|
||||
return g_base->app_adapter->InGraphicsContext();
|
||||
}
|
||||
|
||||
void GraphicsServer::Shutdown() {
|
||||
BA_PRECONDITION(!shutting_down_);
|
||||
BA_PRECONDITION(g_base->InGraphicsContext());
|
||||
shutting_down_ = true;
|
||||
|
||||
// We don't actually do anything here currently; just take note
|
||||
// that we're shutting down so we no longer wait for frames to come
|
||||
// in from the main thread.
|
||||
shutdown_completed_ = true;
|
||||
}
|
||||
|
||||
} // namespace ballistica::base
|
||||
|
||||
@ -231,14 +231,6 @@ class GraphicsServer {
|
||||
return tv_border_;
|
||||
}
|
||||
|
||||
// auto graphics_quality_set() const {
|
||||
// return graphics_quality_ != GraphicsQuality::kUnset;
|
||||
// }
|
||||
|
||||
// auto texture_quality_set() const {
|
||||
// return texture_quality_ != TextureQuality::kUnset;
|
||||
// }
|
||||
|
||||
auto SupportsTextureCompressionType(TextureCompressionType t) const -> bool {
|
||||
assert(InGraphicsContext_());
|
||||
assert(texture_compression_types_set_);
|
||||
@ -249,10 +241,6 @@ class GraphicsServer {
|
||||
void SetTextureCompressionTypes(
|
||||
const std::list<TextureCompressionType>& types);
|
||||
|
||||
// auto texture_compression_types_are_set() const {
|
||||
// return texture_compression_types_set_;
|
||||
// }
|
||||
|
||||
void set_renderer_context_lost(bool lost) { renderer_context_lost_ = lost; }
|
||||
|
||||
auto renderer_context_lost() const { return renderer_context_lost_; }
|
||||
@ -289,6 +277,11 @@ class GraphicsServer {
|
||||
return texture_compression_types_;
|
||||
}
|
||||
|
||||
/// Start spinning down the graphics server/etc.
|
||||
void Shutdown();
|
||||
|
||||
auto shutdown_completed() const { return shutdown_completed_; }
|
||||
|
||||
private:
|
||||
/// Pass a freshly allocated GraphicsContext instance, which the graphics
|
||||
/// system will take ownership of.
|
||||
@ -324,6 +317,10 @@ class GraphicsServer {
|
||||
}
|
||||
}
|
||||
|
||||
TextureQualityRequest texture_quality_requested_{};
|
||||
TextureQuality texture_quality_{};
|
||||
GraphicsQualityRequest graphics_quality_requested_{};
|
||||
GraphicsQuality graphics_quality_{};
|
||||
bool renderer_loaded_ : 1 {};
|
||||
bool model_view_projection_matrix_dirty_ : 1 {true};
|
||||
bool model_world_matrix_dirty_ : 1 {true};
|
||||
@ -331,11 +328,9 @@ class GraphicsServer {
|
||||
bool renderer_context_lost_ : 1 {};
|
||||
bool texture_compression_types_set_ : 1 {};
|
||||
bool cam_orient_matrix_dirty_ : 1 {true};
|
||||
bool shutting_down_ : 1 {};
|
||||
bool shutdown_completed_ : 1 {};
|
||||
Snapshot<GraphicsClientContext>* client_context_{};
|
||||
TextureQualityRequest texture_quality_requested_{};
|
||||
TextureQuality texture_quality_{};
|
||||
GraphicsQualityRequest graphics_quality_requested_{};
|
||||
GraphicsQuality graphics_quality_{};
|
||||
float res_x_{};
|
||||
float res_y_{};
|
||||
float res_x_virtual_{};
|
||||
|
||||
@ -18,7 +18,7 @@ class NinePatchMesh : public MeshIndexedSimpleFull {
|
||||
|
||||
/// Calculate a border value for a NinePatchMesh based on dimensions and a
|
||||
/// desired max corner radius. For calculating left or right borders,
|
||||
/// matching_dimension should be width and other_dimension should be
|
||||
/// `matching_dimension` should be width and `other_dimension` should be
|
||||
/// height. For top or bottom borders it is the opposite.
|
||||
static auto BorderForRadius(float corner_radius, float matching_dimension,
|
||||
float other_dimension) -> float {
|
||||
|
||||
@ -360,7 +360,7 @@ void TextMesh::SetText(const std::string& text_in, HAlign alignment_h,
|
||||
// compile it and add its final spans to our mesh.
|
||||
if (packer) {
|
||||
std::vector<TextPacker::Span> spans;
|
||||
packer->compile();
|
||||
packer->Compile();
|
||||
|
||||
// DEBUGGING - add a single quad above our first
|
||||
// span showing the entire texture for debugging purposes
|
||||
|
||||
@ -23,8 +23,7 @@ const float kPanMax = 9.0f;
|
||||
const float kPanMin = -9.0f;
|
||||
|
||||
Camera::Camera()
|
||||
: last_mode_set_time_(g_core->GetAppTimeMillisecs()),
|
||||
lock_panning_(g_core->IsVRMode()),
|
||||
: lock_panning_(g_core->IsVRMode()),
|
||||
pan_speed_scale_(g_core->IsVRMode() ? 0.3f : 1.0f) {}
|
||||
|
||||
Camera::~Camera() = default;
|
||||
@ -607,7 +606,8 @@ void Camera::Update(millisecs_t elapsed) {
|
||||
float damping = 0.006f;
|
||||
float damping2 = 0.006f;
|
||||
float xy_blend_speed = 0.0002f;
|
||||
millisecs_t real_time = g_core->GetAppTimeMillisecs();
|
||||
time_ += elapsed;
|
||||
// millisecs_t real_time = g_core->GetAppTimeMillisecs();
|
||||
|
||||
// Prevent camera "explosions" if we've been unable to update for a while.
|
||||
elapsed = std::min(millisecs_t{100}, elapsed);
|
||||
@ -653,18 +653,18 @@ void Camera::Update(millisecs_t elapsed) {
|
||||
}
|
||||
|
||||
if (!g_core->IsVRMode()) {
|
||||
smooth_speed_.x += elapsedf * rand_component
|
||||
* (-0.5f
|
||||
+ Utils::precalc_rand_1((real_time / rand_incr_1)
|
||||
% kPrecalcRandsCount));
|
||||
smooth_speed_.y += elapsedf * rand_component
|
||||
* (-0.5f
|
||||
+ Utils::precalc_rand_2((real_time / rand_incr_2)
|
||||
% kPrecalcRandsCount));
|
||||
smooth_speed_.z += elapsedf * rand_component
|
||||
* (-0.5f
|
||||
+ Utils::precalc_rand_3((real_time / rand_incr_3)
|
||||
% kPrecalcRandsCount));
|
||||
smooth_speed_.x +=
|
||||
elapsedf * rand_component
|
||||
* (-0.5f
|
||||
+ Utils::precalc_rand_1((time_ / rand_incr_1) % kPrecalcRandsCount));
|
||||
smooth_speed_.y +=
|
||||
elapsedf * rand_component
|
||||
* (-0.5f
|
||||
+ Utils::precalc_rand_2((time_ / rand_incr_2) % kPrecalcRandsCount));
|
||||
smooth_speed_.z +=
|
||||
elapsedf * rand_component
|
||||
* (-0.5f
|
||||
+ Utils::precalc_rand_3((time_ / rand_incr_3) % kPrecalcRandsCount));
|
||||
}
|
||||
|
||||
if (RandomFloat() < 0.1f && !g_core->IsVRMode()) {
|
||||
@ -714,15 +714,14 @@ void Camera::Update(millisecs_t elapsed) {
|
||||
shake_vel_.x +=
|
||||
0.05f * shake_amount_
|
||||
* (0.5f
|
||||
- Utils::precalc_rand_1(real_time % 122 * i % kPrecalcRandsCount));
|
||||
- Utils::precalc_rand_1(time_ % 122 * i % kPrecalcRandsCount));
|
||||
shake_vel_.y +=
|
||||
0.05f * shake_amount_
|
||||
* (0.5f
|
||||
- Utils::precalc_rand_2(real_time % 323 * i % kPrecalcRandsCount));
|
||||
- Utils::precalc_rand_2(time_ % 323 * i % kPrecalcRandsCount));
|
||||
shake_vel_.z +=
|
||||
0.05f * shake_amount_
|
||||
* (0.5f
|
||||
- Utils::precalc_rand_3(real_time % 76 * i % kPrecalcRandsCount));
|
||||
* (0.5f - Utils::precalc_rand_3(time_ % 76 * i % kPrecalcRandsCount));
|
||||
}
|
||||
|
||||
for (int j = 0; j < iterations; j++) {
|
||||
@ -740,8 +739,8 @@ void Camera::Update(millisecs_t elapsed) {
|
||||
uint32_t interval = g_core->IsVRMode() ? 50 : 100;
|
||||
|
||||
// Occasionally, update microphone position for audio.
|
||||
if (real_time - last_listener_update_time_ > interval) {
|
||||
last_listener_update_time_ = real_time;
|
||||
if (time_ - last_listener_update_time_ > interval) {
|
||||
last_listener_update_time_ = time_;
|
||||
bool do_regular_update = true;
|
||||
if (g_core->IsVRMode()) {
|
||||
#if BA_VR_MODE
|
||||
@ -891,7 +890,8 @@ 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->GetAppTimeMillisecs();
|
||||
// last_mode_set_time_ = time_;
|
||||
heading_ = kInitialHeading;
|
||||
}
|
||||
}
|
||||
|
||||
@ -92,37 +92,51 @@ class Camera : public Object {
|
||||
void set_pan_speed_scale(float val) { pan_speed_scale_ = val; }
|
||||
|
||||
private:
|
||||
float pan_speed_scale_{1.0f};
|
||||
bool lock_panning_{};
|
||||
Vector3f vr_offset_{0.0f, 0.0f, 0.0f};
|
||||
Vector3f vr_extra_offset_{0.0f, 0.0f, 0.0f};
|
||||
Vector3f vr_offset_smooth_{0.0f, 0.0f, 0.0f};
|
||||
millisecs_t last_mode_set_time_{};
|
||||
std::list<AreaOfInterest> areas_of_interest_;
|
||||
CameraMode mode_{CameraMode::kFollow};
|
||||
bool manual_{};
|
||||
bool smooth_next_frame_{};
|
||||
bool have_real_areas_of_interest_{};
|
||||
bool manual_ : 1 {};
|
||||
bool smooth_next_frame_ : 1 {};
|
||||
bool have_real_areas_of_interest_ : 1 {};
|
||||
bool lock_panning_ : 1 {};
|
||||
|
||||
// Manual stuff.
|
||||
bool panning_{};
|
||||
bool orbiting_{};
|
||||
bool rolling_{};
|
||||
bool trucking_{};
|
||||
bool alt_down_{};
|
||||
bool cmd_down_{};
|
||||
bool ctrl_down_{};
|
||||
bool mouse_left_down_{};
|
||||
bool mouse_middle_down_{};
|
||||
bool mouse_right_down_{};
|
||||
bool panning_ : 1 {};
|
||||
bool orbiting_ : 1 {};
|
||||
bool rolling_ : 1 {};
|
||||
bool trucking_ : 1 {};
|
||||
bool alt_down_ : 1 {};
|
||||
bool cmd_down_ : 1 {};
|
||||
bool ctrl_down_ : 1 {};
|
||||
bool mouse_left_down_ : 1 {};
|
||||
bool mouse_middle_down_ : 1 {};
|
||||
bool mouse_right_down_ : 1 {};
|
||||
|
||||
bool happy_thoughts_mode_ : 1 {};
|
||||
bool x_constrained_ : 1 {true};
|
||||
float pan_speed_scale_{1.0f};
|
||||
float heading_{kInitialHeading};
|
||||
Vector3f extra_pos_{0.0f, 0.0f, 0.0f};
|
||||
Vector3f extra_pos_2_{0.0f, 0.0f, 0.0f};
|
||||
float area_of_interest_bounds_[6]{-9999, -9999, -9999, 9999, 9999, 9999};
|
||||
float pan_pos_{};
|
||||
float pan_speed_{};
|
||||
float pan_target_{};
|
||||
float shake_amount_{};
|
||||
float target_radius_{2.0f};
|
||||
float target_radius_smoothed_{2.0f};
|
||||
float field_of_view_x_{5.0f};
|
||||
float field_of_view_y_{kCameraFOVY};
|
||||
float field_of_view_x_smoothed_{1.0f};
|
||||
float field_of_view_y_smoothed_{1.0f};
|
||||
float min_target_radius_{5.0f};
|
||||
float area_of_interest_near_{1.0f};
|
||||
float area_of_interest_far_{2.0f};
|
||||
float xy_constrain_blend_{0.5f};
|
||||
// millisecs_t last_mode_set_time_{};
|
||||
millisecs_t last_listener_update_time_{};
|
||||
millisecs_t time_{};
|
||||
Vector3f vr_offset_{0.0f, 0.0f, 0.0f};
|
||||
Vector3f vr_extra_offset_{0.0f, 0.0f, 0.0f};
|
||||
Vector3f vr_offset_smooth_{0.0f, 0.0f, 0.0f};
|
||||
Vector3f extra_pos_{0.0f, 0.0f, 0.0f};
|
||||
Vector3f extra_pos_2_{0.0f, 0.0f, 0.0f};
|
||||
Vector3f shake_pos_{0.0f, 0.0f, 0.0f};
|
||||
Vector3f shake_vel_{0.0f, 0.0f, 0.0f};
|
||||
Vector3f position_{0.0f, 1.0f, -1.0f};
|
||||
@ -131,20 +145,8 @@ class Camera : public Object {
|
||||
Vector3f position_offset_smoothed_{0.0f, 0.0f, 0.0f};
|
||||
Vector3f smooth_speed_{0.0f, 0.0f, 0.0f};
|
||||
Vector3f smooth_speed_2_{0.0f, 0.0f, 0.0f};
|
||||
float target_radius_{2.0f};
|
||||
float target_radius_smoothed_{2.0f};
|
||||
float field_of_view_x_{5.0f};
|
||||
float field_of_view_y_{kCameraFOVY};
|
||||
float field_of_view_x_smoothed_{1.0f};
|
||||
float field_of_view_y_smoothed_{1.0f};
|
||||
millisecs_t last_listener_update_time_{};
|
||||
bool happy_thoughts_mode_{};
|
||||
float min_target_radius_{5.0f};
|
||||
Vector3f up_{0.0f, 1.0f, 0.0f};
|
||||
float area_of_interest_near_{1.0f};
|
||||
float area_of_interest_far_{2.0f};
|
||||
bool x_constrained_{true};
|
||||
float xy_constrain_blend_{0.5f};
|
||||
std::list<AreaOfInterest> areas_of_interest_;
|
||||
std::vector<Vector3f> area_of_interest_points_{{0.0f, 0.0f, 0.0f}};
|
||||
};
|
||||
|
||||
|
||||
@ -6,7 +6,6 @@
|
||||
|
||||
#include "ballistica/base/graphics/graphics.h"
|
||||
#include "ballistica/base/support/app_config.h"
|
||||
#include "ballistica/shared/foundation/object.h"
|
||||
|
||||
namespace ballistica::base {
|
||||
|
||||
|
||||
538
src/ballistica/base/graphics/support/screen_messages.cc
Normal file
538
src/ballistica/base/graphics/support/screen_messages.cc
Normal file
@ -0,0 +1,538 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#include "ballistica/base/graphics/support/screen_messages.h"
|
||||
|
||||
#include "ballistica/base/graphics/component/simple_component.h"
|
||||
#include "ballistica/base/graphics/mesh/nine_patch_mesh.h"
|
||||
#include "ballistica/base/graphics/text/text_graphics.h"
|
||||
#include "ballistica/base/ui/ui.h"
|
||||
#include "ballistica/shared/generic/utils.h"
|
||||
|
||||
namespace ballistica::base {
|
||||
|
||||
const float kScreenMessageZDepth{-0.06f};
|
||||
|
||||
class ScreenMessages::ScreenMessageEntry {
|
||||
public:
|
||||
ScreenMessageEntry(std::string text, bool top_style, uint32_t c,
|
||||
const Vector3f& color, TextureAsset* texture,
|
||||
TextureAsset* tint_texture, const Vector3f& tint,
|
||||
const Vector3f& tint2)
|
||||
: top_style(top_style),
|
||||
creation_time(c),
|
||||
s_raw(std::move(text)),
|
||||
color(color),
|
||||
texture(texture),
|
||||
tint_texture(tint_texture),
|
||||
tint(tint),
|
||||
tint2(tint2) {}
|
||||
auto GetText() -> TextGroup&;
|
||||
void UpdateTranslation();
|
||||
bool top_style;
|
||||
uint32_t creation_time;
|
||||
Vector3f color;
|
||||
Vector3f tint;
|
||||
Vector3f tint2;
|
||||
std::string s_raw;
|
||||
std::string s_translated;
|
||||
float str_width{};
|
||||
float str_height{};
|
||||
Object::Ref<TextureAsset> texture;
|
||||
Object::Ref<TextureAsset> tint_texture;
|
||||
float v_smoothed{};
|
||||
bool translation_dirty{true};
|
||||
bool mesh_dirty{true};
|
||||
millisecs_t smooth_time{};
|
||||
Object::Ref<NinePatchMesh> shadow_mesh_;
|
||||
|
||||
private:
|
||||
Object::Ref<TextGroup> s_mesh_;
|
||||
};
|
||||
|
||||
ScreenMessages::ScreenMessages() = default;
|
||||
|
||||
void ScreenMessages::DrawMiscOverlays(FrameDef* frame_def) {
|
||||
RenderPass* pass = frame_def->overlay_pass();
|
||||
|
||||
// Screen messages (bottom).
|
||||
{
|
||||
// Delete old ones.
|
||||
if (!screen_messages_.empty()) {
|
||||
millisecs_t cutoff;
|
||||
if (g_core->GetAppTimeMillisecs() > 5000) {
|
||||
cutoff = g_core->GetAppTimeMillisecs() - 5000;
|
||||
for (auto i = screen_messages_.begin(); i != screen_messages_.end();) {
|
||||
if (i->creation_time < cutoff) {
|
||||
auto next = i;
|
||||
next++;
|
||||
screen_messages_.erase(i);
|
||||
i = next;
|
||||
} else {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Delete if we have too many.
|
||||
while ((screen_messages_.size()) > 4) {
|
||||
screen_messages_.erase(screen_messages_.begin());
|
||||
}
|
||||
|
||||
// Draw all existing.
|
||||
if (!screen_messages_.empty()) {
|
||||
bool vr = g_core->IsVRMode();
|
||||
|
||||
// These are less disruptive in the middle for menus but at the bottom
|
||||
// during gameplay.
|
||||
float start_v = g_base->graphics->screen_virtual_height() * 0.05f;
|
||||
float scale;
|
||||
switch (g_base->ui->scale()) {
|
||||
case UIScale::kSmall:
|
||||
scale = 1.5f;
|
||||
break;
|
||||
case UIScale::kMedium:
|
||||
scale = 1.2f;
|
||||
break;
|
||||
default:
|
||||
scale = 1.0f;
|
||||
break;
|
||||
}
|
||||
|
||||
// Shadows.
|
||||
{
|
||||
SimpleComponent c(pass);
|
||||
c.SetTransparent(true);
|
||||
c.SetTexture(
|
||||
// g_base->assets->SysTexture(SysTextureID::kSoftRectVertical));
|
||||
g_base->assets->SysTexture(SysTextureID::kShadowSharp));
|
||||
|
||||
float screen_width = g_base->graphics->screen_virtual_width();
|
||||
|
||||
float v = start_v;
|
||||
|
||||
millisecs_t youngest_age = 9999;
|
||||
|
||||
for (auto i = screen_messages_.rbegin(); i != screen_messages_.rend();
|
||||
i++) {
|
||||
// Update the translation if need be.
|
||||
i->UpdateTranslation();
|
||||
|
||||
// Don't actually need the text just yet but need shadow mesh
|
||||
// which is calculated as part of it.
|
||||
i->GetText();
|
||||
|
||||
millisecs_t age = g_core->GetAppTimeMillisecs() - i->creation_time;
|
||||
youngest_age = std::min(youngest_age, age);
|
||||
float s_extra = 1.0f;
|
||||
if (age < 100) {
|
||||
s_extra = std::min(1.2f, 1.2f * (static_cast<float>(age) / 100.0f));
|
||||
} else if (age < 150) {
|
||||
s_extra =
|
||||
1.2f - 0.2f * ((150.0f - static_cast<float>(age)) / 50.0f);
|
||||
}
|
||||
|
||||
float a;
|
||||
if (age > 3000) {
|
||||
a = 1.0f - static_cast<float>(age - 3000) / 2000;
|
||||
} else {
|
||||
a = 1;
|
||||
}
|
||||
a *= 0.7f;
|
||||
|
||||
// if (vr) {
|
||||
// a *= 0.8f;
|
||||
// }
|
||||
|
||||
if (i->translation_dirty) {
|
||||
BA_LOG_ONCE(
|
||||
LogLevel::kWarning,
|
||||
"Found dirty translation on screenmessage draw pass 1; raw="
|
||||
+ i->s_raw);
|
||||
}
|
||||
|
||||
float str_height = i->str_height;
|
||||
float str_width = i->str_width;
|
||||
|
||||
if ((str_width * scale) > (screen_width - 40)) {
|
||||
s_extra *= ((screen_width - 40) / (str_width * scale));
|
||||
}
|
||||
|
||||
float r = i->color.x;
|
||||
float g = i->color.y;
|
||||
float b = i->color.z;
|
||||
Graphics::GetSafeColor(&r, &g, &b);
|
||||
|
||||
float v_extra = scale * (static_cast<float>(youngest_age) * 0.01f);
|
||||
|
||||
float fade;
|
||||
if (age < 100) {
|
||||
fade = 1.0f;
|
||||
} else {
|
||||
// Don't fade ALL the way to black; leaves a tiny bit of color
|
||||
// showing which looks nice.
|
||||
fade = std::max(0.07f, (200.0f - static_cast<float>(age)) / 100.0f);
|
||||
}
|
||||
c.SetColor(r * fade, g * fade, b * fade, a);
|
||||
|
||||
{
|
||||
auto xf = c.ScopedTransform();
|
||||
|
||||
// This logic needs to run at a fixed hz or it breaks on high frame
|
||||
// rates.
|
||||
auto now_millisecs = pass->frame_def()->display_time_millisecs();
|
||||
i->smooth_time = std::max(i->smooth_time, now_millisecs - 100);
|
||||
while (i->smooth_time < now_millisecs) {
|
||||
i->smooth_time += 1000 / 60;
|
||||
if (i->v_smoothed == 0.0f) {
|
||||
i->v_smoothed = v + v_extra;
|
||||
} else {
|
||||
float smoothing = 0.8f;
|
||||
i->v_smoothed = smoothing * i->v_smoothed
|
||||
+ (1.0f - smoothing) * (v + v_extra);
|
||||
}
|
||||
}
|
||||
|
||||
c.Translate(screen_width * 0.5f, i->v_smoothed,
|
||||
vr ? 60 : kScreenMessageZDepth);
|
||||
|
||||
// if (vr) {
|
||||
// // Let's drop down a bit in vr mode.
|
||||
// // c.Translate(0, -10.0f, 0);
|
||||
// // c.Scale((str_width + 60) * scale * s_extra,
|
||||
// // (str_height + 20) * scale * s_extra);
|
||||
// c.Scale(scale * s_extra, scale * s_extra);
|
||||
|
||||
// // Align our bottom with where we just scaled from.
|
||||
// c.Translate(0, 0.5f, 0);
|
||||
{
|
||||
// c.Scale((str_width + 110) * scale * s_extra,
|
||||
// (str_height + 40) * scale * s_extra);
|
||||
c.Scale(scale * s_extra, scale * s_extra);
|
||||
c.Translate(0, 20);
|
||||
|
||||
// Align our bottom with where we just scaled from.
|
||||
c.Translate(0, 0.5f, 0);
|
||||
}
|
||||
// c.DrawMeshAsset(g_base->assets->SysMesh(SysMeshID::kImage1x1));
|
||||
assert(i->shadow_mesh_.Exists());
|
||||
c.DrawMesh(i->shadow_mesh_.Get());
|
||||
}
|
||||
|
||||
v += scale * (36 + str_height);
|
||||
if (v > g_base->graphics->screen_virtual_height() + 30) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
c.Submit();
|
||||
}
|
||||
|
||||
// Now the strings themselves.
|
||||
{
|
||||
SimpleComponent c(pass);
|
||||
c.SetTransparent(true);
|
||||
|
||||
float screen_width = g_base->graphics->screen_virtual_width();
|
||||
float v = start_v;
|
||||
millisecs_t youngest_age = 9999;
|
||||
|
||||
for (auto i = screen_messages_.rbegin(); i != screen_messages_.rend();
|
||||
i++) {
|
||||
millisecs_t age = g_core->GetAppTimeMillisecs() - i->creation_time;
|
||||
youngest_age = std::min(youngest_age, age);
|
||||
float s_extra = 1.0f;
|
||||
if (age < 100) {
|
||||
s_extra = std::min(1.2f, 1.2f * (static_cast<float>(age) / 100.0f));
|
||||
} else if (age < 150) {
|
||||
s_extra =
|
||||
1.2f - 0.2f * ((150.0f - static_cast<float>(age)) / 50.0f);
|
||||
}
|
||||
float a;
|
||||
if (age > 3000) {
|
||||
a = 1.0f - static_cast<float>(age - 3000) / 2000;
|
||||
} else {
|
||||
a = 1;
|
||||
}
|
||||
if (i->translation_dirty) {
|
||||
BA_LOG_ONCE(
|
||||
LogLevel::kWarning,
|
||||
"Found dirty translation on screenmessage draw pass 2; raw="
|
||||
+ i->s_raw);
|
||||
}
|
||||
float str_height = i->str_height;
|
||||
float str_width = i->str_width;
|
||||
|
||||
if ((str_width * scale) > (screen_width - 40)) {
|
||||
s_extra *= ((screen_width - 40) / (str_width * scale));
|
||||
}
|
||||
float r = i->color.x;
|
||||
float g = i->color.y;
|
||||
float b = i->color.z;
|
||||
Graphics::GetSafeColor(&r, &g, &b, 0.85f);
|
||||
|
||||
int elem_count = i->GetText().GetElementCount();
|
||||
for (int e = 0; e < elem_count; e++) {
|
||||
// Gracefully skip unloaded textures.
|
||||
TextureAsset* t = i->GetText().GetElementTexture(e);
|
||||
if (!t->preloaded()) {
|
||||
continue;
|
||||
}
|
||||
c.SetTexture(t);
|
||||
if (i->GetText().GetElementCanColor(e)) {
|
||||
c.SetColor(r, g, b, a);
|
||||
} else {
|
||||
c.SetColor(1, 1, 1, a);
|
||||
}
|
||||
c.SetFlatness(i->GetText().GetElementMaxFlatness(e));
|
||||
{
|
||||
auto xf = c.ScopedTransform();
|
||||
c.Translate(screen_width * 0.5f, i->v_smoothed,
|
||||
vr ? 150 : kScreenMessageZDepth);
|
||||
c.Scale(scale * s_extra, scale * s_extra);
|
||||
c.Translate(0, 20);
|
||||
c.DrawMesh(i->GetText().GetElementMesh(e));
|
||||
}
|
||||
}
|
||||
|
||||
v += scale * (36 + str_height);
|
||||
if (v > g_base->graphics->screen_virtual_height() + 30) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
c.Submit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Screen messages (top).
|
||||
{
|
||||
// Delete old ones.
|
||||
if (!screen_messages_top_.empty()) {
|
||||
millisecs_t cutoff;
|
||||
if (g_core->GetAppTimeMillisecs() > 5000) {
|
||||
cutoff = g_core->GetAppTimeMillisecs() - 5000;
|
||||
for (auto i = screen_messages_top_.begin();
|
||||
i != screen_messages_top_.end();) {
|
||||
if (i->creation_time < cutoff) {
|
||||
auto next = i;
|
||||
next++;
|
||||
screen_messages_top_.erase(i);
|
||||
i = next;
|
||||
} else {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Delete if we have too many.
|
||||
while ((screen_messages_top_.size()) > 6) {
|
||||
screen_messages_top_.erase(screen_messages_top_.begin());
|
||||
}
|
||||
|
||||
if (!screen_messages_top_.empty()) {
|
||||
SimpleComponent c(pass);
|
||||
c.SetTransparent(true);
|
||||
|
||||
// Draw all existing.
|
||||
float h = pass->virtual_width() - 300.0f;
|
||||
float v = g_base->graphics->screen_virtual_height() - 50.0f;
|
||||
|
||||
float v_base = g_base->graphics->screen_virtual_height();
|
||||
float last_v = -999.0f;
|
||||
|
||||
float min_spacing = 25.0f;
|
||||
|
||||
for (auto i = screen_messages_top_.rbegin();
|
||||
i != screen_messages_top_.rend(); i++) {
|
||||
// Update the translation if need be.
|
||||
i->UpdateTranslation();
|
||||
|
||||
millisecs_t age = g_core->GetAppTimeMillisecs() - 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));
|
||||
} else if (age < 150) {
|
||||
s_extra = 1.1f - 0.1f * ((150.0f - static_cast<float>(age)) / 50.0f);
|
||||
}
|
||||
|
||||
float a;
|
||||
if (age > 3000) {
|
||||
a = 1.0f - static_cast<float>(age - 3000) / 2000;
|
||||
} else {
|
||||
a = 1;
|
||||
}
|
||||
|
||||
// This logic needs to run at a fixed hz or it breaks on high frame
|
||||
// rates.
|
||||
auto now_millisecs = pass->frame_def()->display_time_millisecs();
|
||||
i->smooth_time = std::max(i->smooth_time, now_millisecs - 100);
|
||||
while (i->smooth_time < now_millisecs) {
|
||||
i->smooth_time += 1000 / 60;
|
||||
i->v_smoothed += 0.1f;
|
||||
if (i->v_smoothed - last_v < min_spacing) {
|
||||
i->v_smoothed +=
|
||||
8.0f * (1.0f - ((i->v_smoothed - last_v) / min_spacing));
|
||||
}
|
||||
}
|
||||
last_v = i->v_smoothed;
|
||||
|
||||
// Draw the image if they provided one.
|
||||
if (i->texture.Exists()) {
|
||||
c.Submit();
|
||||
|
||||
SimpleComponent c2(pass);
|
||||
c2.SetTransparent(true);
|
||||
c2.SetTexture(i->texture);
|
||||
if (i->tint_texture.Exists()) {
|
||||
c2.SetColorizeTexture(i->tint_texture.Get());
|
||||
c2.SetColorizeColor(i->tint.x, i->tint.y, i->tint.z);
|
||||
c2.SetColorizeColor2(i->tint2.x, i->tint2.y, i->tint2.z);
|
||||
c2.SetMaskTexture(
|
||||
g_base->assets->SysTexture(SysTextureID::kCharacterIconMask));
|
||||
}
|
||||
c2.SetColor(1, 1, 1, a);
|
||||
{
|
||||
auto xf = c2.ScopedTransform();
|
||||
c2.Translate(h - 14, v_base + 10 + i->v_smoothed,
|
||||
kScreenMessageZDepth);
|
||||
c2.Scale(22.0f * s_extra, 22.0f * s_extra);
|
||||
c2.DrawMeshAsset(g_base->assets->SysMesh(SysMeshID::kImage1x1));
|
||||
}
|
||||
c2.Submit();
|
||||
}
|
||||
|
||||
float r = i->color.x;
|
||||
float g = i->color.y;
|
||||
float b = i->color.z;
|
||||
Graphics::GetSafeColor(&r, &g, &b);
|
||||
|
||||
int elem_count = i->GetText().GetElementCount();
|
||||
for (int e = 0; e < elem_count; e++) {
|
||||
// Gracefully skip unloaded textures.
|
||||
TextureAsset* t = i->GetText().GetElementTexture(e);
|
||||
if (!t->preloaded()) {
|
||||
continue;
|
||||
}
|
||||
c.SetTexture(t);
|
||||
if (i->GetText().GetElementCanColor(e)) {
|
||||
c.SetColor(r, g, b, a);
|
||||
} else {
|
||||
c.SetColor(1, 1, 1, a);
|
||||
}
|
||||
c.SetShadow(-0.003f * i->GetText().GetElementUScale(e),
|
||||
-0.003f * i->GetText().GetElementVScale(e), 0.0f,
|
||||
1.0f * a);
|
||||
c.SetFlatness(i->GetText().GetElementMaxFlatness(e));
|
||||
c.SetMaskUV2Texture(i->GetText().GetElementMaskUV2Texture(e));
|
||||
{
|
||||
auto xf = c.ScopedTransform();
|
||||
c.Translate(h, v_base + 2 + i->v_smoothed, kScreenMessageZDepth);
|
||||
c.Scale(0.6f * s_extra, 0.6f * s_extra);
|
||||
c.DrawMesh(i->GetText().GetElementMesh(e));
|
||||
}
|
||||
}
|
||||
assert(!i->translation_dirty);
|
||||
v -= g_base->text_graphics->GetStringHeight(i->s_translated.c_str())
|
||||
* 0.6f
|
||||
+ 8.0f;
|
||||
}
|
||||
c.Submit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ScreenMessages::AddScreenMessage(const std::string& msg,
|
||||
const Vector3f& color, bool top,
|
||||
TextureAsset* texture,
|
||||
TextureAsset* tint_texture,
|
||||
const Vector3f& tint,
|
||||
const Vector3f& tint2) {
|
||||
assert(g_base->InLogicThread());
|
||||
|
||||
// So we know we're always dealing with valid utf8.
|
||||
std::string m = Utils::GetValidUTF8(msg.c_str(), "ga9msg");
|
||||
|
||||
if (top) {
|
||||
float start_v = -40.0f;
|
||||
if (!screen_messages_top_.empty()) {
|
||||
start_v = std::min(
|
||||
start_v,
|
||||
std::max(-100.0f, screen_messages_top_.back().v_smoothed - 25.0f));
|
||||
}
|
||||
screen_messages_top_.emplace_back(m, true, g_core->GetAppTimeMillisecs(),
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
void ScreenMessages::Reset() {
|
||||
// Wipe out top screen messages since they might be using textures that are
|
||||
// being reset. Bottom ones are ok since they have no textures.
|
||||
screen_messages_top_.clear();
|
||||
}
|
||||
|
||||
void ScreenMessages::ClearScreenMessageTranslations() {
|
||||
assert(g_base && g_base->InLogicThread());
|
||||
for (auto&& i : screen_messages_) {
|
||||
i.translation_dirty = true;
|
||||
}
|
||||
for (auto&& i : screen_messages_top_) {
|
||||
i.translation_dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
auto ScreenMessages::ScreenMessageEntry::GetText() -> TextGroup& {
|
||||
if (translation_dirty) {
|
||||
BA_LOG_ONCE(
|
||||
LogLevel::kWarning,
|
||||
"Found dirty translation on screenmessage GetText; raw=" + s_raw);
|
||||
}
|
||||
if (!s_mesh_.Exists()) {
|
||||
s_mesh_ = Object::New<TextGroup>();
|
||||
mesh_dirty = true;
|
||||
}
|
||||
if (mesh_dirty) {
|
||||
s_mesh_->SetText(
|
||||
s_translated,
|
||||
top_style ? TextMesh::HAlign::kLeft : TextMesh::HAlign::kCenter,
|
||||
TextMesh::VAlign::kBottom);
|
||||
|
||||
str_width = g_base->text_graphics->GetStringWidth(s_translated.c_str());
|
||||
str_height = g_base->text_graphics->GetStringHeight(s_translated.c_str());
|
||||
|
||||
if (!top_style) {
|
||||
float x_extend = 40.0f;
|
||||
float y_extend = 40.0f;
|
||||
float y_offset = -5.0f;
|
||||
float corner_radius = 60.0f;
|
||||
float width_fin = str_width + x_extend * 2.0f;
|
||||
float height_fin = str_height + y_extend * 2.0f;
|
||||
float x_border =
|
||||
NinePatchMesh::BorderForRadius(corner_radius, width_fin, height_fin);
|
||||
float y_border =
|
||||
NinePatchMesh::BorderForRadius(corner_radius, height_fin, width_fin);
|
||||
shadow_mesh_ = Object::New<NinePatchMesh>(
|
||||
-0.5f * width_fin, -y_extend + y_offset, 0.0f, width_fin, height_fin,
|
||||
x_border, y_border, x_border, y_border);
|
||||
}
|
||||
|
||||
mesh_dirty = false;
|
||||
}
|
||||
return *s_mesh_;
|
||||
}
|
||||
|
||||
void ScreenMessages::ScreenMessageEntry::UpdateTranslation() {
|
||||
if (translation_dirty) {
|
||||
s_translated = g_base->assets->CompileResourceString(
|
||||
s_raw, "Graphics::ScreenMessageEntry::UpdateTranslation");
|
||||
translation_dirty = false;
|
||||
mesh_dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ballistica::base
|
||||
37
src/ballistica/base/graphics/support/screen_messages.h
Normal file
37
src/ballistica/base/graphics/support/screen_messages.h
Normal file
@ -0,0 +1,37 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_BASE_GRAPHICS_SUPPORT_SCREEN_MESSAGES_H_
|
||||
#define BALLISTICA_BASE_GRAPHICS_SUPPORT_SCREEN_MESSAGES_H_
|
||||
|
||||
#include "ballistica/base/base.h"
|
||||
#include "ballistica/shared/math/vector3f.h"
|
||||
|
||||
namespace ballistica::base {
|
||||
|
||||
/// Wrangles a set of screen-messages.
|
||||
class ScreenMessages {
|
||||
public:
|
||||
ScreenMessages();
|
||||
|
||||
void ClearScreenMessageTranslations();
|
||||
|
||||
// Print a message to the on-screen list.
|
||||
void AddScreenMessage(const std::string& msg,
|
||||
const Vector3f& color = {1, 1, 1}, bool top = false,
|
||||
TextureAsset* texture = nullptr,
|
||||
TextureAsset* tint_texture = nullptr,
|
||||
const Vector3f& tint = {1, 1, 1},
|
||||
const Vector3f& tint2 = {1, 1, 1});
|
||||
|
||||
void DrawMiscOverlays(FrameDef* frame_def);
|
||||
void Reset();
|
||||
|
||||
private:
|
||||
class ScreenMessageEntry;
|
||||
std::list<ScreenMessageEntry> screen_messages_;
|
||||
std::list<ScreenMessageEntry> screen_messages_top_;
|
||||
};
|
||||
|
||||
} // namespace ballistica::base
|
||||
|
||||
#endif // BALLISTICA_BASE_GRAPHICS_SUPPORT_SCREEN_MESSAGES_H_
|
||||
@ -12,25 +12,25 @@
|
||||
#include "ballistica/base/graphics/text/text_graphics.h"
|
||||
|
||||
namespace ballistica::base {
|
||||
// the total number of glyph pages we have
|
||||
// The total number of glyph pages we have.
|
||||
#define BA_GLYPH_PAGE_COUNT 8
|
||||
|
||||
// the total number of glyphs we have
|
||||
// The total number of glyphs we have.
|
||||
const int kGlyphCount = 1280;
|
||||
|
||||
// the starting glyph index for each page
|
||||
// The starting glyph index for each page.
|
||||
uint32_t g_glyph_page_start_index_map[8] = {0, 258, 416, 546,
|
||||
698, 981, 1138, 1276};
|
||||
|
||||
// the number of glyphs on each page
|
||||
// The number of glyphs on each page.
|
||||
uint32_t g_glyph_page_glyph_counts[8] = {258, 158, 130, 152, 283, 157, 138, 4};
|
||||
|
||||
// our dynamically-loaded glyph structs for each page
|
||||
// Our dynamically-loaded glyph structs for each page.
|
||||
TextGraphics::Glyph* g_glyph_pages[8] = {nullptr, nullptr, nullptr, nullptr,
|
||||
nullptr, nullptr, nullptr, nullptr};
|
||||
|
||||
// the page index for each glyph
|
||||
uint16_t g_glyph_map[kGlyphCount] = {
|
||||
// The page index for each glyph.
|
||||
uint8_t g_glyph_map[kGlyphCount] = {
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
|
||||
@ -116,8 +116,7 @@ TextGraphics::TextGraphics() {
|
||||
}
|
||||
}
|
||||
|
||||
// init glyph values for our big font page
|
||||
// (a 8x8 array)
|
||||
// Init glyph values for our big font page (a 8x8 array).
|
||||
{
|
||||
float x_offs = 0.009f;
|
||||
float y_offs = 0.0059f;
|
||||
@ -284,7 +283,7 @@ TextGraphics::TextGraphics() {
|
||||
(1.0f / 8.0f) * static_cast<float>(x + 1) + x_offs + scale_extra;
|
||||
g.tex_max_y = (1.0f / 8.0f) * static_cast<float>(y) + y_offs;
|
||||
|
||||
// just scooted letters over.. account for that
|
||||
// Just scooted letters over; account for that.
|
||||
float foo_x = 0.0183f;
|
||||
float foo_y = 0.000f;
|
||||
g.tex_min_x += foo_x;
|
||||
@ -292,12 +291,12 @@ TextGraphics::TextGraphics() {
|
||||
g.tex_min_y += foo_y;
|
||||
g.tex_max_y += foo_y;
|
||||
|
||||
// clamp based on char width
|
||||
// Clamp based on char width.
|
||||
float scale = w * 1.32f;
|
||||
g.x_size *= scale;
|
||||
g.tex_max_x = g.tex_min_x + (g.tex_max_x - g.tex_min_x) * scale;
|
||||
|
||||
// add bot offset
|
||||
// Add bot offset.
|
||||
if (bot_offset != 0.0f) {
|
||||
g.tex_min_y = g.tex_max_y
|
||||
+ (g.tex_min_y - g.tex_max_y)
|
||||
@ -305,7 +304,7 @@ TextGraphics::TextGraphics() {
|
||||
g.pen_offset_y -= bot_offset;
|
||||
g.y_size += bot_offset;
|
||||
}
|
||||
// add left offset
|
||||
// Add left offset.
|
||||
if (left_offset != 0.0f) {
|
||||
g.tex_min_x = g.tex_max_x
|
||||
+ (g.tex_min_x - g.tex_max_x)
|
||||
@ -313,14 +312,14 @@ TextGraphics::TextGraphics() {
|
||||
g.pen_offset_x -= left_offset;
|
||||
g.x_size += left_offset;
|
||||
}
|
||||
// add right offset
|
||||
// Add right offset.
|
||||
if (right_offset != 0.0f) {
|
||||
g.tex_max_x = g.tex_min_x
|
||||
+ (g.tex_max_x - g.tex_min_x)
|
||||
* ((g.x_size + right_offset) / g.x_size);
|
||||
g.x_size += right_offset;
|
||||
}
|
||||
// add top offset
|
||||
// Add top offset.
|
||||
if (top_offset != 0.0f) {
|
||||
g.tex_max_y = g.tex_min_y
|
||||
+ (g.tex_max_y - g.tex_min_y)
|
||||
@ -844,13 +843,13 @@ void TextGraphics::GetFontPageCharRange(int page, uint32_t* first_char,
|
||||
// Our special pages:
|
||||
switch (page) {
|
||||
case static_cast<int>(FontPage::kOSRendered): {
|
||||
// we allow the OS to render anything not in one of our glyph textures
|
||||
// (technically this overlaps the private-use range which we use our own
|
||||
// textures for, but that's handled as a special-case by
|
||||
// TextGroup::setText
|
||||
// We allow the OS to render anything not in one of our glyph textures
|
||||
// (technically this overlaps the private-use range which we use our
|
||||
// own textures for, but that's handled as a special-case by
|
||||
// TextGroup::SetText.
|
||||
(*first_char) = kGlyphCount;
|
||||
(*last_char) = kTextMaxUnicodeVal; // hmm what's the max unicode value we
|
||||
// should ever see?..
|
||||
// hmm what's the max unicode value we should ever see?..
|
||||
(*last_char) = kTextMaxUnicodeVal;
|
||||
break;
|
||||
}
|
||||
case static_cast<int>(FontPage::kExtras1): {
|
||||
@ -887,52 +886,57 @@ void TextGraphics::GetFontPagesForText(const std::string& text,
|
||||
int last_page = -1;
|
||||
std::vector<uint32_t> unicode = Utils::UnicodeFromUTF8(text, "c03853");
|
||||
for (uint32_t val : unicode) {
|
||||
int page;
|
||||
int page{-1};
|
||||
|
||||
// Hack: allow showing euro even if we don't support unicode font rendering.
|
||||
if (g_buildconfig.enable_os_font_rendering()) {
|
||||
if (val == 8364) {
|
||||
val = 0xE000;
|
||||
}
|
||||
}
|
||||
// Hack: allow showing euro even if we don't support unicode font
|
||||
// rendering.
|
||||
// if (g_buildconfig.enable_os_font_rendering()) {
|
||||
// if (val == 8364) {
|
||||
// val = 0xE000;
|
||||
// }
|
||||
// }
|
||||
|
||||
// For values in the custom-char range (U+E000–U+F8FF) we point at our own
|
||||
// custom page(s)
|
||||
bool covered{};
|
||||
|
||||
// For values in the custom-char range (U+E000–U+F8FF) we point at our
|
||||
// own custom page(s)
|
||||
if (val >= 0xE000 && val <= 0xF8FF) {
|
||||
// The 25 chars after this are in our fontExtras sheet.
|
||||
if (val < 0xE000 + 25) {
|
||||
// Special value denoting our custom font page.
|
||||
page = static_cast<int>(FontPage::kExtras1);
|
||||
covered = true;
|
||||
} else if (val < 0xE000 + 50) {
|
||||
// Special value denoting our custom font page.
|
||||
page = static_cast<int>(FontPage::kExtras2);
|
||||
covered = true;
|
||||
} else if (val < 0xE000 + 75) {
|
||||
// Special value denoting our custom font page.
|
||||
page = static_cast<int>(FontPage::kExtras3);
|
||||
covered = true;
|
||||
} else if (val < 0xE000 + 100) {
|
||||
// Special value denoting our custom font page.
|
||||
page = static_cast<int>(FontPage::kExtras4);
|
||||
} else {
|
||||
// We dont cover this.. just go with '?'
|
||||
val = '?';
|
||||
page = g_glyph_map[val];
|
||||
covered = true;
|
||||
}
|
||||
} else if (val >= kGlyphCount) {
|
||||
// Otherwise if its outside of our texture-coverage area.
|
||||
} else if (val < kGlyphCount) {
|
||||
page = g_glyph_map[val];
|
||||
covered = true;
|
||||
}
|
||||
|
||||
if (!covered) {
|
||||
if (g_buildconfig.enable_os_font_rendering()) {
|
||||
page = static_cast<int>(FontPage::kOSRendered);
|
||||
} else {
|
||||
val = '?';
|
||||
page = g_glyph_map[val];
|
||||
}
|
||||
} else {
|
||||
// yay we cover it!
|
||||
page = g_glyph_map[val];
|
||||
}
|
||||
// compare to lastPage to avoid doing a set insert for *everything* since
|
||||
// most will be the same
|
||||
|
||||
// Compare to last_page to avoid doing a set insert for *everything*
|
||||
// since most will be the same.
|
||||
if (page != last_page) {
|
||||
(*font_pages).insert(page);
|
||||
font_pages->insert(page);
|
||||
last_page = page;
|
||||
}
|
||||
}
|
||||
@ -1009,12 +1013,8 @@ void TextGraphics::GetOSTextSpanBoundsAndWidth(const std::string& s, Rect* r,
|
||||
// Send this entry to the back of the list since we used it.
|
||||
text_span_bounds_cache_.erase(entry->list_iterator_);
|
||||
|
||||
// I guess inspection doesn't realize entry lives on after this?...
|
||||
#pragma clang diagnostic push
|
||||
#pragma ide diagnostic ignored "UnusedValue"
|
||||
entry->list_iterator_ =
|
||||
text_span_bounds_cache_.insert(text_span_bounds_cache_.end(), entry);
|
||||
#pragma clang diagnostic pop
|
||||
return;
|
||||
}
|
||||
auto entry(Object::New<TextSpanBoundsCacheEntry>());
|
||||
@ -1068,7 +1068,9 @@ auto TextGraphics::GetStringWidth(const char* text, bool big) -> float {
|
||||
line_length += GetOSTextSpanWidth(s);
|
||||
os_span.clear();
|
||||
}
|
||||
if (line_length > max_line_length) max_line_length = line_length;
|
||||
if (line_length > max_line_length) {
|
||||
max_line_length = line_length;
|
||||
}
|
||||
line_length = 0;
|
||||
t++;
|
||||
} else {
|
||||
@ -1145,7 +1147,9 @@ void TextGraphics::BreakUpString(const char* text, float width,
|
||||
s_begin = t;
|
||||
}
|
||||
} else {
|
||||
if (*t == 0) throw Exception();
|
||||
if (*t == 0) {
|
||||
throw Exception();
|
||||
}
|
||||
uint32_t val = Utils::GetUTF8Value(t);
|
||||
Utils::AdvanceUTF8(&t);
|
||||
|
||||
|
||||
@ -24,11 +24,11 @@ void TextPacker::AddSpan(const std::string& text, float x, float y,
|
||||
}
|
||||
|
||||
// FIXME - we currently run into minor problems because we measure our text
|
||||
// bounds at one size and then scale that linearly when trying to fit things
|
||||
// into the texture. However, fonts don't always scale linearly (and even when
|
||||
// that's an option it can be expensive).
|
||||
// bounds at one size and then scale that linearly when trying to fit
|
||||
// things into the texture. However, fonts don't always scale linearly (and
|
||||
// even when that's an option it can be expensive).
|
||||
|
||||
void TextPacker::compile() {
|
||||
void TextPacker::Compile() {
|
||||
assert(!compiled_);
|
||||
if (spans_.empty()) {
|
||||
compiled_ = true;
|
||||
@ -57,24 +57,23 @@ void TextPacker::compile() {
|
||||
width *= 2;
|
||||
}
|
||||
|
||||
// Alternately, if we're too big, crank our scale down so that our widest span
|
||||
// fits.
|
||||
// Alternately, if we're too big, crank our scale down so that our widest
|
||||
// span fits.
|
||||
if (widest_unscaled_span_width * scale > width * 0.9f) {
|
||||
scale *= ((width * 0.9f) / (widest_unscaled_span_width * scale));
|
||||
}
|
||||
float start_height = height;
|
||||
int mini_shrink_tries = 0;
|
||||
|
||||
// Ok; we've now locked in a width and scale.
|
||||
// Now we go through and position our spans.
|
||||
// We may need to do this more than once if our height comes out too big.
|
||||
// (hopefully this will never be a problem in practice)
|
||||
// Ok; we've now locked in a width and scale. Now we go through and
|
||||
// position our spans. We may need to do this more than once if our height
|
||||
// comes out too big. (hopefully this will never be a problem in practice)
|
||||
while (true) {
|
||||
height = start_height;
|
||||
|
||||
// We currently just lay out left-to-right, top-to-bottom.
|
||||
// This could be somewhat wasteful in particular configurations.
|
||||
// (leaving half-filled lines, etc) so it might be worth improving later.
|
||||
// We currently just lay out left-to-right, top-to-bottom. This could be
|
||||
// somewhat wasteful in particular configurations. (leaving half-filled
|
||||
// lines, etc) so it might be worth improving later.
|
||||
float widest_fill_right = 0.0f;
|
||||
float fill_right = 0.0f;
|
||||
float fill_bottom = 0.0f;
|
||||
@ -87,26 +86,27 @@ void TextPacker::compile() {
|
||||
// Start a new line if this would put us past the end.
|
||||
if (fill_right + span_width > width) {
|
||||
if (fill_right > widest_fill_right) {
|
||||
widest_fill_right = fill_right; // Keep track of how far over we go.
|
||||
// Keep track of how far over we go.
|
||||
widest_fill_right = fill_right;
|
||||
}
|
||||
fill_right = 0.0f;
|
||||
fill_bottom += line_height;
|
||||
line_height = 0.0f;
|
||||
}
|
||||
|
||||
// Position x such that x + left bound - buffer lines up with our current
|
||||
// right point.
|
||||
// Position x such that x + left bound - buffer lines up with our
|
||||
// current right point.
|
||||
float to_left = (i.bounds.l - span_buffer) * scale;
|
||||
i.tex_x = fill_right - to_left;
|
||||
fill_right += span_width;
|
||||
|
||||
// Position y such that y - top bound - buffer lines up with our current
|
||||
// bottom point.
|
||||
// Position y such that y - top bound - buffer lines up with our
|
||||
// current bottom point.
|
||||
float to_top = (-i.bounds.t - span_buffer) * scale;
|
||||
i.tex_y = fill_bottom - to_top;
|
||||
|
||||
// If our total height is greater than the current line height, expand the
|
||||
// line's.
|
||||
// If our total height is greater than the current line height, expand
|
||||
// the line's.
|
||||
if (span_height > line_height) {
|
||||
line_height = span_height;
|
||||
}
|
||||
@ -125,9 +125,9 @@ void TextPacker::compile() {
|
||||
// If it doesn't fit, repeat again with a smaller scale until it does.
|
||||
|
||||
// Dropping our scale has a disproportional effect on the final height
|
||||
// (since it opens up more relative horizontal space).
|
||||
// I'm not sure how to figure out how much to drop by other than
|
||||
// incrementally dropping values until we fit.
|
||||
// (since it opens up more relative horizontal space). I'm not sure
|
||||
// how to figure out how much to drop by other than incrementally
|
||||
// dropping values until we fit.
|
||||
scale *= 0.75f;
|
||||
|
||||
} else if (((widest_fill_right < (width * mini_shrink_threshold_h)
|
||||
@ -135,15 +135,15 @@ void TextPacker::compile() {
|
||||
|| fill_bottom + line_height
|
||||
< (height * mini_shrink_threshold_v))
|
||||
&& mini_shrink_tries < 3) {
|
||||
// If we're here it means we *barely* use more than half of the texture in
|
||||
// one direction or the other; let's shrink just a tiny bit and we should
|
||||
// be able to chop our texture size in half
|
||||
// If we're here it means we *barely* use more than half of the
|
||||
// texture in one direction or the other; let's shrink just a tiny bit
|
||||
// and we should be able to chop our texture size in half
|
||||
if (widest_fill_right < width * mini_shrink_threshold_h && width > 16) {
|
||||
float scale_val = 0.99f * (((width * 0.5f) / widest_fill_right));
|
||||
if (scale_val < 1.0f) {
|
||||
// FIXME - should think about a fixed multiplier here;
|
||||
// under the hood the system might be caching glyphs based on scale
|
||||
// and this would leave us with fewer different scales in the end and
|
||||
// FIXME - should think about a fixed multiplier here; under the
|
||||
// hood the system might be caching glyphs based on scale and
|
||||
// this would leave us with fewer different scales in the end and
|
||||
// thus better caching performance
|
||||
scale *= scale_val;
|
||||
}
|
||||
@ -151,9 +151,9 @@ void TextPacker::compile() {
|
||||
} else {
|
||||
float scale_val = 0.99f * (height * 0.5f) / (fill_bottom + line_height);
|
||||
if (scale_val < 1.0f) {
|
||||
// FIXME - should think about a fixed multiplier here;
|
||||
// under the hood the system might be caching glyphs based on scale
|
||||
// and this would leave us with fewer different scales in the end and
|
||||
// FIXME - should think about a fixed multiplier here; under the
|
||||
// hood the system might be caching glyphs based on scale and
|
||||
// this would leave us with fewer different scales in the end and
|
||||
// thus better caching performance
|
||||
scale *= scale_val;
|
||||
}
|
||||
@ -165,8 +165,8 @@ void TextPacker::compile() {
|
||||
}
|
||||
}
|
||||
|
||||
// Lastly, now that our texture width and height are completely finalized, we
|
||||
// can calculate UVs.
|
||||
// Lastly, now that our texture width and height are completely finalized,
|
||||
// we can calculate UVs.
|
||||
for (auto&& i : spans_) {
|
||||
// Now store uv coords for this span; they should include the buffer.
|
||||
i.u_min = (i.tex_x + (i.bounds.l - span_buffer) * scale) / width;
|
||||
@ -182,11 +182,11 @@ void TextPacker::compile() {
|
||||
}
|
||||
|
||||
// TODO(ericf): now we calculate a hash that's unique to this text
|
||||
// configuration; we'll use that as a key for the texture we'll generate/use.
|
||||
// ..this way multiple meshes can share the same generated texture.
|
||||
// *technically* we could calculate this hash and check for an existing
|
||||
// texture before we bother laying out our spans, but that might not save us
|
||||
// much time and would complicate things.
|
||||
// configuration; we'll use that as a key for the texture we'll
|
||||
// generate/use. ..this way multiple meshes can share the same generated
|
||||
// texture. *technically* we could calculate this hash and check for an
|
||||
// existing texture before we bother laying out our spans, but that might
|
||||
// not save us much time and would complicate things.
|
||||
hash_ = std::to_string(resolution_scale_);
|
||||
for (auto&& i : spans_) {
|
||||
char buffer[64];
|
||||
|
||||
@ -21,7 +21,7 @@ class TextPacker : public Object {
|
||||
// outside of here anyway so might as well recycle.
|
||||
void AddSpan(const std::string& text, float x, float y, const Rect& bounds);
|
||||
|
||||
auto hash() const -> const std::string& {
|
||||
const auto& hash() const {
|
||||
assert(compiled_);
|
||||
return hash_;
|
||||
}
|
||||
@ -51,32 +51,32 @@ class TextPacker : public Object {
|
||||
|
||||
// Once done adding spans, call this to calculate final span UV values,
|
||||
// texture configuration, and hash.
|
||||
void compile();
|
||||
void Compile();
|
||||
|
||||
auto spans() const -> const std::list<Span>& { return spans_; }
|
||||
const auto& spans() const { return spans_; }
|
||||
|
||||
auto texture_width() const -> int {
|
||||
auto texture_width() const {
|
||||
assert(compiled_);
|
||||
return texture_width_;
|
||||
}
|
||||
|
||||
auto texture_height() const -> int {
|
||||
auto texture_height() const {
|
||||
assert(compiled_);
|
||||
return texture_height_;
|
||||
}
|
||||
|
||||
auto text_scale() const -> float {
|
||||
auto text_scale() const {
|
||||
assert(compiled_);
|
||||
return text_scale_;
|
||||
}
|
||||
|
||||
private:
|
||||
bool compiled_{false};
|
||||
float resolution_scale_;
|
||||
float text_scale_{};
|
||||
int texture_width_{};
|
||||
int texture_height_{};
|
||||
float text_scale_{};
|
||||
std::string hash_;
|
||||
bool compiled_{false};
|
||||
std::list<Span> spans_;
|
||||
};
|
||||
|
||||
|
||||
@ -9,6 +9,7 @@
|
||||
#include "ballistica/base/input/input.h"
|
||||
#include "ballistica/base/python/base_python.h"
|
||||
#include "ballistica/base/support/classic_soft.h"
|
||||
#include "ballistica/base/support/repeater.h"
|
||||
#include "ballistica/base/ui/ui.h"
|
||||
#include "ballistica/core/core.h"
|
||||
#include "ballistica/shared/foundation/event_loop.h"
|
||||
@ -17,10 +18,6 @@
|
||||
|
||||
namespace ballistica::base {
|
||||
|
||||
const char* kMFiControllerName = "iOS/Mac Controller";
|
||||
|
||||
const int kJoystickRepeatDelay{500};
|
||||
|
||||
// Joy values below this are candidates for calibration.
|
||||
const float kJoystickCalibrationThreshold{6000.0f};
|
||||
|
||||
@ -68,7 +65,12 @@ JoystickInput::JoystickInput(int sdl_joystick_id,
|
||||
// that instead.
|
||||
// #if BA_SDL2_BUILD
|
||||
sdl_joystick_id_ = SDL_JoystickInstanceID(sdl_joystick_);
|
||||
raw_sdl_joystick_name_ = SDL_JoystickName(sdl_joystick_);
|
||||
if (auto* name = SDL_JoystickName(sdl_joystick_)) {
|
||||
raw_sdl_joystick_name_ = name;
|
||||
} else {
|
||||
// This can return nullptr if SDL can't find a name.
|
||||
raw_sdl_joystick_name_ = "Unknown Controller";
|
||||
}
|
||||
|
||||
// Special case: on windows, xinput stuff comes in with unique names
|
||||
// "XInput Controller #3", etc. Let's replace these with simply "XInput
|
||||
@ -78,20 +80,6 @@ JoystickInput::JoystickInput(int sdl_joystick_id,
|
||||
&& raw_sdl_joystick_name_.size() <= 22) {
|
||||
raw_sdl_joystick_name_ = "XInput Controller";
|
||||
}
|
||||
// #else
|
||||
// raw_sdl_joystick_name_ = SDL_JoystickName(sdl_joystick_id_);
|
||||
// #endif // BA_SDL2_BUILD
|
||||
|
||||
// If its an SDL joystick and we're using our custom sdl 1.2 build, ask it.
|
||||
// #if BA_XCODE_BUILD && BA_OSTYPE_MACOS && !BA_SDL2_BUILD
|
||||
// raw_sdl_joystick_identifier_ =
|
||||
// SDL_JoystickIdentifier(sdl_joystick_id_);
|
||||
// #endif
|
||||
|
||||
// Some special-cases on mac.
|
||||
if (strstr(raw_sdl_joystick_name_.c_str(), "PLAYSTATION") != nullptr) {
|
||||
is_mac_ps3_controller_ = true;
|
||||
}
|
||||
|
||||
#else // BA_ENABLE_SDL_JOYSTICKS
|
||||
throw Exception(); // Shouldn't happen.
|
||||
@ -101,8 +89,6 @@ JoystickInput::JoystickInput(int sdl_joystick_id,
|
||||
// Its a manual joystick.
|
||||
sdl_joystick_ = nullptr;
|
||||
|
||||
is_mfi_controller_ = (custom_device_name_ == kMFiControllerName);
|
||||
|
||||
// Hard code a few remote controls.
|
||||
// The newer way to do this is just set 'UI-Only' on the device config
|
||||
is_remote_control_ = ((custom_device_name_ == "Amazon Remote")
|
||||
@ -162,7 +148,17 @@ auto JoystickInput::HasMeaningfulButtonNames() -> bool {
|
||||
return g_buildconfig.ostype_android();
|
||||
}
|
||||
|
||||
void JoystickInput::SetButtonName(int button, const std::string& name) {
|
||||
button_names_[button] = name;
|
||||
}
|
||||
|
||||
auto JoystickInput::GetButtonName(int index) -> std::string {
|
||||
// First check any explicit ones we were passed.
|
||||
auto i = button_names_.find(index);
|
||||
if (i != button_names_.end()) {
|
||||
return i->second;
|
||||
}
|
||||
|
||||
// FIXME: Should get fancier here now that PS4 and XBone
|
||||
// controllers are supported through this.
|
||||
if (is_mfi_controller_) {
|
||||
@ -179,6 +175,7 @@ auto JoystickInput::GetButtonName(int index) -> std::string {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (g_buildconfig.ostype_android()) {
|
||||
// Special case: if this is a samsung controller, return the dice
|
||||
// button icons.
|
||||
@ -446,46 +443,6 @@ void JoystickInput::Update() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If a button's being held, potentially pass repeats along.
|
||||
if (up_held_ || down_held_ || left_held_ || right_held_) {
|
||||
// Don't ask for the widget unless we have something held.
|
||||
// (otherwise we prevent other inputs from getting at it)
|
||||
if (g_base->ui->GetWidgetForInput(this)) {
|
||||
millisecs_t repeat_delay = kJoystickRepeatDelay;
|
||||
|
||||
millisecs_t t = g_core->GetAppTimeMillisecs();
|
||||
auto c = WidgetMessage::Type::kEmptyMessage;
|
||||
if (t - last_hold_time_ < repeat_delay) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (t - last_hold_time_ >= repeat_delay) {
|
||||
bool pass = false;
|
||||
if (up_held_) {
|
||||
pass = true;
|
||||
c = WidgetMessage::Type::kMoveUp;
|
||||
} else if (down_held_) {
|
||||
pass = true;
|
||||
c = WidgetMessage::Type::kMoveDown;
|
||||
} else if (left_held_) {
|
||||
pass = true;
|
||||
c = WidgetMessage::Type::kMoveLeft;
|
||||
} else if (right_held_) {
|
||||
pass = true;
|
||||
c = WidgetMessage::Type::kMoveRight;
|
||||
}
|
||||
if (pass) {
|
||||
g_base->ui->SendWidgetMessage(WidgetMessage(c));
|
||||
}
|
||||
|
||||
// Set another repeat to happen sooner.
|
||||
last_hold_time_ =
|
||||
t
|
||||
- static_cast<millisecs_t>(static_cast<float>(repeat_delay) * 0.8f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void JoystickInput::SetStandardExtendedButtons() {
|
||||
@ -508,6 +465,8 @@ void JoystickInput::ResetHeldStates() {
|
||||
SDL_Event e;
|
||||
|
||||
dpad_right_held_ = dpad_left_held_ = dpad_up_held_ = dpad_down_held_ = false;
|
||||
ui_repeater_.Clear();
|
||||
|
||||
run_buttons_held_.clear();
|
||||
run_trigger1_value_ = run_trigger2_value_ = 0.0f;
|
||||
UpdateRunningState();
|
||||
@ -594,28 +553,28 @@ void JoystickInput::HandleSDLEvent(const SDL_Event* e) {
|
||||
|| dpad_down_held_))
|
||||
return;
|
||||
|
||||
bool isHoldPositionEvent = false;
|
||||
bool is_hold_position_event = false;
|
||||
|
||||
// Keep track of whether hold-position is being held. If so, we don't send
|
||||
// window events. (some joysticks always give us significant axis values but
|
||||
// window events (some joysticks always give us significant axis values but
|
||||
// rely on hold position to keep from doing stuff usually).
|
||||
if (e->type == SDL_JOYBUTTONDOWN
|
||||
&& e->jbutton.button == hold_position_button_) {
|
||||
need_to_send_held_state_ = true;
|
||||
hold_position_held_ = true;
|
||||
isHoldPositionEvent = true;
|
||||
is_hold_position_event = true;
|
||||
}
|
||||
if (e->type == SDL_JOYBUTTONUP
|
||||
&& e->jbutton.button == hold_position_button_) {
|
||||
need_to_send_held_state_ = true;
|
||||
hold_position_held_ = false;
|
||||
isHoldPositionEvent = true;
|
||||
is_hold_position_event = true;
|
||||
}
|
||||
|
||||
// Let's ignore events for just a moment after we're created.
|
||||
// (some joysticks seem to spit out erroneous button-pressed events when
|
||||
// first plugged in ).
|
||||
if (time - creation_time_ < 250 && !isHoldPositionEvent) {
|
||||
if (time - creation_time_ < 250 && !is_hold_position_event) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -700,7 +659,7 @@ void JoystickInput::HandleSDLEvent(const SDL_Event* e) {
|
||||
}
|
||||
}
|
||||
|
||||
// If its the ignore button, ignore it.
|
||||
// If its an ignored button, ignore it.
|
||||
if ((e->type == SDL_JOYBUTTONDOWN || e->type == SDL_JOYBUTTONUP)
|
||||
&& (e->jbutton.button == ignored_button_
|
||||
|| e->jbutton.button == ignored_button2_
|
||||
@ -709,49 +668,6 @@ void JoystickInput::HandleSDLEvent(const SDL_Event* e) {
|
||||
return;
|
||||
}
|
||||
|
||||
// A little pre-filtering on mac PS3 gamepads. (try to filter out some noise
|
||||
// we're seeing, etc).
|
||||
if (g_buildconfig.ostype_macos() && is_mac_ps3_controller_) {
|
||||
switch (e->type) {
|
||||
case SDL_JOYAXISMOTION: {
|
||||
// On my ps3 controller, I seem to be seeing occasional joy-axis-events
|
||||
// coming in with values of -32768 when nothing is being touched.
|
||||
// Filtering those out here.. Should look into this more and see if its
|
||||
// SDL's fault or else forward a bug to apple.
|
||||
if ((e->jaxis.axis == 0 || e->jaxis.axis == 1)
|
||||
&& e->jaxis.value == -32768
|
||||
&& (time - ps3_last_joy_press_time_ > 2000) && !ps3_jaxis1_pressed_
|
||||
&& !ps3_jaxis2_pressed_) {
|
||||
printf(
|
||||
"BAJoyStick notice: filtering out errand PS3 axis %d value of "
|
||||
"%d\n",
|
||||
static_cast<int>(e->jaxis.axis),
|
||||
static_cast<int>(e->jaxis.value));
|
||||
fflush(stdout);
|
||||
|
||||
// std::cout << "BSJoyStick notice: filtering out errant PS3 axis " <<
|
||||
// int(e->jaxis.axis) << " value of " << e->jaxis.value << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
if (abs(e->jaxis.value) >= kJoystickDiscreteThreshold) {
|
||||
ps3_last_joy_press_time_ = time;
|
||||
}
|
||||
|
||||
// Keep track of whether its pressed for next time.
|
||||
if (e->jaxis.axis == 0) {
|
||||
ps3_jaxis1_pressed_ = (abs(e->jaxis.value) > 3000);
|
||||
} else if (e->jaxis.axis == 1) {
|
||||
ps3_jaxis2_pressed_ = (abs(e->jaxis.value) > 3000);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// A few high level button press interceptions.
|
||||
if (e->type == SDL_JOYBUTTONDOWN) {
|
||||
if (e->jbutton.button == start_button_
|
||||
@ -806,111 +722,116 @@ void JoystickInput::HandleSDLEvent(const SDL_Event* e) {
|
||||
}
|
||||
}
|
||||
|
||||
// If we're in a dialog, send dialog events.
|
||||
// We keep track of special x/y values for dialog usage.
|
||||
// If we're in the ui, send ui events.
|
||||
// We keep track of special x/y values for ui usage.
|
||||
// These are formed as combinations of the actual joy value
|
||||
// and the hold-position state.
|
||||
// Think of hold-position as somewhat of a 'magnitude' to the joy event's
|
||||
// direction. They're really one and the same event. (we just need to store
|
||||
// their states ourselves since they don't both come through at once).
|
||||
bool isAnalogStickJAxisEvent = false;
|
||||
// FIXME: Ugh need to rip out this old hold-position stuff.
|
||||
bool is_analog_stick_jaxis_event = false;
|
||||
if (e->type == SDL_JOYAXISMOTION) {
|
||||
if (e->jaxis.axis == analog_lr_) {
|
||||
dialog_jaxis_x_ = e->jaxis.value;
|
||||
isAnalogStickJAxisEvent = true;
|
||||
is_analog_stick_jaxis_event = true;
|
||||
} else if (e->jaxis.axis == analog_ud_) {
|
||||
dialog_jaxis_y_ = e->jaxis.value;
|
||||
isAnalogStickJAxisEvent = true;
|
||||
is_analog_stick_jaxis_event = true;
|
||||
}
|
||||
}
|
||||
int dialogJaxisX = dialog_jaxis_x_;
|
||||
int ui_jaxis_x = dialog_jaxis_x_;
|
||||
if (hold_position_held_) {
|
||||
dialogJaxisX = 0; // Throttle is off.
|
||||
ui_jaxis_x = 0; // Throttle is off.
|
||||
}
|
||||
int dialogJaxisY = dialog_jaxis_y_;
|
||||
int ui_jaxis_y = dialog_jaxis_y_;
|
||||
if (hold_position_held_) {
|
||||
dialogJaxisY = 0; // Throttle is off.
|
||||
ui_jaxis_y = 0; // Throttle is off.
|
||||
}
|
||||
|
||||
// We might not wanna grab at the UI if we're a axis-motion event
|
||||
// below our 'pressed' threshold.. Otherwise fuzzy analog joystick
|
||||
// readings would cause rampant UI stealing even if no events are being sent.
|
||||
bool would_go_to_dialog = false;
|
||||
bool would_go_to_ui = false;
|
||||
auto wm = WidgetMessage::Type::kEmptyMessage;
|
||||
|
||||
if (isAnalogStickJAxisEvent || isHoldPositionEvent) {
|
||||
if (is_analog_stick_jaxis_event || is_hold_position_event) {
|
||||
// Even when we're not sending, clear out some 'held' states.
|
||||
if (left_held_ && dialogJaxisX >= -kJoystickDiscreteThreshold) {
|
||||
if (left_held_ && ui_jaxis_x >= -kJoystickDiscreteThreshold) {
|
||||
left_held_ = false;
|
||||
ui_repeater_.Clear();
|
||||
}
|
||||
if (right_held_ && dialogJaxisX <= kJoystickDiscreteThreshold) {
|
||||
if (right_held_ && ui_jaxis_x <= kJoystickDiscreteThreshold) {
|
||||
right_held_ = false;
|
||||
ui_repeater_.Clear();
|
||||
}
|
||||
if (up_held_ && dialogJaxisY >= -kJoystickDiscreteThreshold) {
|
||||
if (up_held_ && ui_jaxis_y >= -kJoystickDiscreteThreshold) {
|
||||
up_held_ = false;
|
||||
ui_repeater_.Clear();
|
||||
}
|
||||
if (down_held_ && dialogJaxisY <= kJoystickDiscreteThreshold) {
|
||||
if (down_held_ && ui_jaxis_y <= kJoystickDiscreteThreshold) {
|
||||
down_held_ = false;
|
||||
ui_repeater_.Clear();
|
||||
}
|
||||
if ((!right_held_) && ui_jaxis_x > kJoystickDiscreteThreshold) {
|
||||
would_go_to_ui = true;
|
||||
}
|
||||
if ((!left_held_) && ui_jaxis_x < -kJoystickDiscreteThreshold) {
|
||||
would_go_to_ui = true;
|
||||
}
|
||||
if ((!up_held_) && ui_jaxis_y < -kJoystickDiscreteThreshold) {
|
||||
would_go_to_ui = true;
|
||||
}
|
||||
if ((!down_held_) && ui_jaxis_y > kJoystickDiscreteThreshold) {
|
||||
would_go_to_ui = true;
|
||||
}
|
||||
if ((!right_held_) && dialogJaxisX > kJoystickDiscreteThreshold)
|
||||
would_go_to_dialog = true;
|
||||
if ((!left_held_) && dialogJaxisX < -kJoystickDiscreteThreshold)
|
||||
would_go_to_dialog = true;
|
||||
if ((!up_held_) && dialogJaxisY < -kJoystickDiscreteThreshold)
|
||||
would_go_to_dialog = true;
|
||||
if ((!down_held_) && dialogJaxisY > kJoystickDiscreteThreshold)
|
||||
would_go_to_dialog = true;
|
||||
} else if ((e->type == SDL_JOYHATMOTION && e->jhat.hat == hat_)
|
||||
|| (e->type == SDL_JOYBUTTONDOWN
|
||||
&& e->jbutton.button != hold_position_button_)) {
|
||||
// Other button-downs and hat motions always go.
|
||||
would_go_to_dialog = true;
|
||||
would_go_to_ui = true;
|
||||
}
|
||||
|
||||
// Resets always circumvent dialogs.
|
||||
if (resetting_) would_go_to_dialog = false;
|
||||
if (resetting_) {
|
||||
would_go_to_ui = false;
|
||||
}
|
||||
|
||||
// Anything that would go to a dialog also counts to mark us as
|
||||
// 'recently-used'.
|
||||
if (would_go_to_dialog) {
|
||||
// Anything that would go to ui also counts to mark us as 'recently-used'.
|
||||
if (would_go_to_ui) {
|
||||
UpdateLastInputTime();
|
||||
}
|
||||
|
||||
if (would_go_to_dialog && g_base->ui->GetWidgetForInput(this)) {
|
||||
bool pass = false;
|
||||
if (would_go_to_ui && g_base->ui->GetWidgetForInput(this)) {
|
||||
bool pass{};
|
||||
|
||||
// Special case.. either joy-axis-motion or hold-position events trigger
|
||||
// these.
|
||||
if (isAnalogStickJAxisEvent || isHoldPositionEvent) {
|
||||
if (dialogJaxisX > kJoystickDiscreteThreshold) {
|
||||
// To the right.
|
||||
if (is_analog_stick_jaxis_event || is_hold_position_event) {
|
||||
if (ui_jaxis_x > kJoystickDiscreteThreshold) {
|
||||
if (!right_held_ && !up_held_ && !down_held_) {
|
||||
last_hold_time_ = g_core->GetAppTimeMillisecs();
|
||||
right_held_ = true;
|
||||
pass = true;
|
||||
wm = WidgetMessage::Type::kMoveRight;
|
||||
pass = true;
|
||||
}
|
||||
} else if (dialogJaxisX < -kJoystickDiscreteThreshold) {
|
||||
} else if (ui_jaxis_x < -kJoystickDiscreteThreshold) {
|
||||
if (!left_held_ && !up_held_ && !down_held_) {
|
||||
last_hold_time_ = g_core->GetAppTimeMillisecs();
|
||||
wm = WidgetMessage::Type::kMoveLeft;
|
||||
pass = true;
|
||||
left_held_ = true;
|
||||
pass = true;
|
||||
wm = WidgetMessage::Type::kMoveLeft;
|
||||
}
|
||||
}
|
||||
if (dialogJaxisY > kJoystickDiscreteThreshold) {
|
||||
if (ui_jaxis_y > kJoystickDiscreteThreshold) {
|
||||
if (!down_held_ && !left_held_ && !right_held_) {
|
||||
last_hold_time_ = g_core->GetAppTimeMillisecs();
|
||||
wm = WidgetMessage::Type::kMoveDown;
|
||||
pass = true;
|
||||
down_held_ = true;
|
||||
}
|
||||
} else if (dialogJaxisY < -kJoystickDiscreteThreshold) {
|
||||
if (!up_held_ && !left_held_ && !right_held_) {
|
||||
last_hold_time_ = g_core->GetAppTimeMillisecs();
|
||||
wm = WidgetMessage::Type::kMoveUp;
|
||||
pass = true;
|
||||
wm = WidgetMessage::Type::kMoveDown;
|
||||
}
|
||||
} else if (ui_jaxis_y < -kJoystickDiscreteThreshold) {
|
||||
if (!up_held_ && !left_held_ && !right_held_) {
|
||||
up_held_ = true;
|
||||
pass = true;
|
||||
wm = WidgetMessage::Type::kMoveUp;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -924,7 +845,6 @@ void JoystickInput::HandleSDLEvent(const SDL_Event* e) {
|
||||
switch (e->jhat.value) {
|
||||
case SDL_HAT_LEFT: {
|
||||
if (!left_held_) {
|
||||
last_hold_time_ = g_core->GetAppTimeMillisecs();
|
||||
wm = WidgetMessage::Type::kMoveLeft;
|
||||
pass = true;
|
||||
left_held_ = true;
|
||||
@ -935,7 +855,6 @@ void JoystickInput::HandleSDLEvent(const SDL_Event* e) {
|
||||
|
||||
case SDL_HAT_RIGHT: {
|
||||
if (!right_held_) {
|
||||
last_hold_time_ = g_core->GetAppTimeMillisecs();
|
||||
wm = WidgetMessage::Type::kMoveRight;
|
||||
pass = true;
|
||||
right_held_ = true;
|
||||
@ -945,7 +864,6 @@ void JoystickInput::HandleSDLEvent(const SDL_Event* e) {
|
||||
}
|
||||
case SDL_HAT_UP: {
|
||||
if (!up_held_) {
|
||||
last_hold_time_ = g_core->GetAppTimeMillisecs();
|
||||
wm = WidgetMessage::Type::kMoveUp;
|
||||
pass = true;
|
||||
up_held_ = true;
|
||||
@ -955,7 +873,6 @@ void JoystickInput::HandleSDLEvent(const SDL_Event* e) {
|
||||
}
|
||||
case SDL_HAT_DOWN: {
|
||||
if (!down_held_) {
|
||||
last_hold_time_ = g_core->GetAppTimeMillisecs();
|
||||
wm = WidgetMessage::Type::kMoveDown;
|
||||
pass = true;
|
||||
down_held_ = true;
|
||||
@ -968,6 +885,7 @@ void JoystickInput::HandleSDLEvent(const SDL_Event* e) {
|
||||
down_held_ = false;
|
||||
left_held_ = false;
|
||||
right_held_ = false;
|
||||
ui_repeater_.Clear();
|
||||
}
|
||||
default:
|
||||
break;
|
||||
@ -1007,7 +925,20 @@ void JoystickInput::HandleSDLEvent(const SDL_Event* e) {
|
||||
break;
|
||||
}
|
||||
if (pass) {
|
||||
g_base->ui->SendWidgetMessage(WidgetMessage(wm));
|
||||
switch (wm) {
|
||||
case WidgetMessage::Type::kMoveUp:
|
||||
case WidgetMessage::Type::kMoveDown:
|
||||
case WidgetMessage::Type::kMoveLeft:
|
||||
case WidgetMessage::Type::kMoveRight:
|
||||
// For UI movement, set up a repeater so we can hold the button.
|
||||
ui_repeater_ = Repeater::New(
|
||||
kUINavigationRepeatDelay, kUINavigationRepeatInterval,
|
||||
[wm] { g_base->ui->SendWidgetMessage(WidgetMessage(wm)); });
|
||||
break;
|
||||
default:
|
||||
// Other messages are just one-shots.
|
||||
g_base->ui->SendWidgetMessage(WidgetMessage(wm));
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
@ -1502,22 +1433,7 @@ auto JoystickInput::GetRawDeviceName() -> std::string {
|
||||
auto JoystickInput::GetDeviceExtraDescription() -> std::string {
|
||||
std::string s;
|
||||
|
||||
// On mac, PS3 controllers can connect via USB or bluetooth,
|
||||
// and it can be confusing if one is doing both,
|
||||
// so lets specify here.
|
||||
if (GetDeviceName() == "PLAYSTATION(R)3 Controller") {
|
||||
// For bluetooth we get a serial in the form "04-76-6e-d1-17-90" while
|
||||
// on USB we get a simple int (the usb location id): "-9340234"
|
||||
// so lets consider it wireless if its got a dash not at the beginning.
|
||||
s = " (USB)";
|
||||
|
||||
auto dname = GetDeviceIdentifier();
|
||||
for (const char* tst = dname.c_str(); *tst; tst++) {
|
||||
if (*tst == '-' && tst != dname) {
|
||||
s = " (Bluetooth)";
|
||||
}
|
||||
}
|
||||
}
|
||||
// (no longer being used).
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
#ifndef BALLISTICA_BASE_INPUT_DEVICE_JOYSTICK_INPUT_H_
|
||||
#define BALLISTICA_BASE_INPUT_DEVICE_JOYSTICK_INPUT_H_
|
||||
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <string>
|
||||
|
||||
@ -16,7 +17,7 @@ namespace ballistica::base {
|
||||
const int kJoystickDiscreteThreshold{15000};
|
||||
const float kJoystickDiscreteThresholdFloat{0.46f};
|
||||
const int kJoystickAnalogCalibrationDivisions{20};
|
||||
extern const char* kMFiControllerName;
|
||||
// extern const char* kMFiControllerName;
|
||||
|
||||
/// A physical game controller.
|
||||
class JoystickInput : public InputDevice {
|
||||
@ -49,7 +50,6 @@ class JoystickInput : public InputDevice {
|
||||
|
||||
auto GetPartyButtonName() const -> std::string override;
|
||||
|
||||
auto GetButtonName(int index) -> std::string override;
|
||||
auto GetAxisName(int index) -> std::string override;
|
||||
|
||||
auto IsController() -> bool override { return true; }
|
||||
@ -73,6 +73,11 @@ class JoystickInput : public InputDevice {
|
||||
|
||||
auto HasMeaningfulButtonNames() -> bool override;
|
||||
|
||||
auto GetButtonName(int index) -> std::string override;
|
||||
|
||||
/// Custom controller types can pass in controller-specific button names.
|
||||
void SetButtonName(int button, const std::string& name);
|
||||
|
||||
protected:
|
||||
auto GetRawDeviceName() -> std::string override;
|
||||
auto GetDeviceExtraDescription() -> std::string override;
|
||||
@ -87,51 +92,51 @@ class JoystickInput : public InputDevice {
|
||||
void UpdateRunningState();
|
||||
auto GetCalibratedValue(float raw, float neutral) const -> int32_t;
|
||||
|
||||
std::string raw_sdl_joystick_name_;
|
||||
std::string raw_sdl_joystick_identifier_;
|
||||
float run_value_{};
|
||||
JoystickInput* child_joy_stick_{};
|
||||
JoystickInput* parent_joy_stick_{};
|
||||
millisecs_t last_ui_only_print_time_{};
|
||||
bool ui_only_{};
|
||||
bool unassigned_buttons_run_{true};
|
||||
bool start_button_activates_default_widget_{true};
|
||||
bool auto_recalibrate_analog_stick_{};
|
||||
millisecs_t creation_time_{};
|
||||
bool did_initial_reset_{};
|
||||
|
||||
// FIXME - should take this out and replace it with a bool
|
||||
// (we never actually access the sdl joystick directly outside of our
|
||||
// constructor)
|
||||
SDL_Joystick* sdl_joystick_{};
|
||||
|
||||
bool is_test_input_{};
|
||||
bool is_remote_control_{};
|
||||
bool is_remote_app_{};
|
||||
bool is_mfi_controller_{};
|
||||
bool is_mac_ps3_controller_{};
|
||||
|
||||
millisecs_t ps3_last_joy_press_time_{-10000};
|
||||
bool ui_only_ : 1 {};
|
||||
bool unassigned_buttons_run_ : 1 {true};
|
||||
bool start_button_activates_default_widget_ : 1 {true};
|
||||
bool auto_recalibrate_analog_stick_ : 1 {};
|
||||
bool did_initial_reset_ : 1 {};
|
||||
bool is_test_input_ : 1 {};
|
||||
bool is_remote_control_ : 1 {};
|
||||
bool is_remote_app_ : 1 {};
|
||||
bool is_mfi_controller_ : 1 {};
|
||||
|
||||
// For dialogs.
|
||||
bool left_held_{};
|
||||
bool right_held_{};
|
||||
bool up_held_{};
|
||||
bool down_held_{};
|
||||
bool hold_position_held_{};
|
||||
bool need_to_send_held_state_{};
|
||||
int hat_{};
|
||||
int analog_lr_{};
|
||||
bool left_held_ : 1 {};
|
||||
bool right_held_ : 1 {};
|
||||
bool up_held_ : 1 {};
|
||||
bool down_held_ : 1 {};
|
||||
bool hold_position_held_ : 1 {};
|
||||
bool need_to_send_held_state_ : 1 {};
|
||||
|
||||
bool hat_held_ : 1 {};
|
||||
bool dpad_right_held_ : 1 {};
|
||||
bool dpad_left_held_ : 1 {};
|
||||
bool dpad_up_held_ : 1 {};
|
||||
bool dpad_down_held_ : 1 {};
|
||||
|
||||
bool ignore_completely_ : 1 {};
|
||||
bool resetting_ : 1 {};
|
||||
bool calibrate_ : 1 {};
|
||||
bool can_configure_ : 1 {};
|
||||
|
||||
int hat_{0};
|
||||
int analog_lr_{0};
|
||||
int analog_ud_{1};
|
||||
millisecs_t last_hold_time_{};
|
||||
bool hat_held_{};
|
||||
bool dpad_right_held_{};
|
||||
bool dpad_left_held_{};
|
||||
bool dpad_up_held_{};
|
||||
bool dpad_down_held_{};
|
||||
|
||||
// Mappings of ba buttons to SDL buttons.
|
||||
int jump_button_{};
|
||||
int jump_button_{0};
|
||||
int punch_button_{1};
|
||||
int bomb_button_{2};
|
||||
int pickup_button_{3};
|
||||
@ -143,7 +148,6 @@ class JoystickInput : public InputDevice {
|
||||
// Used on rift build; we have one button which we disallow from joining but
|
||||
// the rest we allow. (all devices are treated as one and the same there).
|
||||
int remote_enter_button_{-1};
|
||||
bool ignore_completely_{};
|
||||
int ignored_button_{-1};
|
||||
int ignored_button2_{-1};
|
||||
int ignored_button3_{-1};
|
||||
@ -153,12 +157,6 @@ class JoystickInput : public InputDevice {
|
||||
int run_trigger1_{-1};
|
||||
int run_trigger2_{-1};
|
||||
int vr_reorient_button_{-1};
|
||||
float run_trigger1_min_{};
|
||||
float run_trigger1_max_{};
|
||||
float run_trigger2_min_{};
|
||||
float run_trigger2_max_{};
|
||||
float run_trigger1_value_{};
|
||||
float run_trigger2_value_{};
|
||||
int left_button_{-1};
|
||||
int right_button_{-1};
|
||||
int up_button_{-1};
|
||||
@ -167,15 +165,19 @@ class JoystickInput : public InputDevice {
|
||||
int right_button2_{-1};
|
||||
int up_button2_{-1};
|
||||
int down_button2_{-1};
|
||||
std::set<int> run_buttons_held_;
|
||||
int sdl_joystick_id_{};
|
||||
bool ps3_jaxis1_pressed_{};
|
||||
bool ps3_jaxis2_pressed_{};
|
||||
float run_value_{};
|
||||
float run_trigger1_min_{};
|
||||
float run_trigger1_max_{};
|
||||
float run_trigger2_min_{};
|
||||
float run_trigger2_max_{};
|
||||
float run_trigger1_value_{};
|
||||
float run_trigger2_value_{};
|
||||
float calibration_threshold_{};
|
||||
float calibration_break_threshold_{};
|
||||
float analog_calibration_vals_[kJoystickAnalogCalibrationDivisions]{};
|
||||
std::string custom_device_name_;
|
||||
bool can_configure_{};
|
||||
float calibrated_neutral_x_{};
|
||||
float calibrated_neutral_y_{};
|
||||
int32_t dialog_jaxis_x_{};
|
||||
int32_t dialog_jaxis_y_{};
|
||||
int32_t jaxis_raw_x_{};
|
||||
@ -183,11 +185,13 @@ class JoystickInput : public InputDevice {
|
||||
int32_t jaxis_x_{};
|
||||
int32_t jaxis_y_{};
|
||||
millisecs_t calibration_start_time_x_{};
|
||||
float calibrated_neutral_x_{};
|
||||
millisecs_t calibration_start_time_y_{};
|
||||
float calibrated_neutral_y_{};
|
||||
bool resetting_{};
|
||||
bool calibrate_{};
|
||||
std::set<int> run_buttons_held_;
|
||||
std::string custom_device_name_;
|
||||
std::string raw_sdl_joystick_name_;
|
||||
std::string raw_sdl_joystick_identifier_;
|
||||
std::map<int, std::string> button_names_;
|
||||
Object::Ref<Repeater> ui_repeater_;
|
||||
|
||||
BA_DISALLOW_CLASS_COPIES(JoystickInput);
|
||||
};
|
||||
|
||||
@ -2,8 +2,10 @@
|
||||
|
||||
#include "ballistica/base/input/device/keyboard_input.h"
|
||||
|
||||
#include "ballistica/base/app_adapter/app_adapter.h"
|
||||
#include "ballistica/base/platform/base_platform.h"
|
||||
#include "ballistica/base/support/classic_soft.h"
|
||||
#include "ballistica/base/support/repeater.h"
|
||||
#include "ballistica/base/ui/ui.h"
|
||||
|
||||
namespace ballistica::base {
|
||||
@ -42,10 +44,12 @@ KeyboardInput::KeyboardInput(KeyboardInput* parent_keyboard_input_in) {
|
||||
|
||||
KeyboardInput::~KeyboardInput() = default;
|
||||
|
||||
auto KeyboardInput::HandleKey(const SDL_Keysym* keysym, bool repeat, bool down)
|
||||
-> bool {
|
||||
auto KeyboardInput::HandleKey(const SDL_Keysym* keysym, bool down) -> bool {
|
||||
// Only allow the *main* keyboard to talk to the UI
|
||||
if (parent_keyboard_input_ == nullptr) {
|
||||
// Any new event coming in cancels repeats.
|
||||
ui_repeater_.Clear();
|
||||
|
||||
if (g_base->ui->GetWidgetForInput(this)) {
|
||||
bool pass = false;
|
||||
auto c = WidgetMessage::Type::kEmptyMessage;
|
||||
@ -78,10 +82,8 @@ auto KeyboardInput::HandleKey(const SDL_Keysym* keysym, bool repeat, bool down)
|
||||
case SDLK_SPACE:
|
||||
case SDLK_KP_ENTER:
|
||||
case SDLK_RETURN:
|
||||
if (!repeat) {
|
||||
c = WidgetMessage::Type::kActivate;
|
||||
pass = true;
|
||||
}
|
||||
c = WidgetMessage::Type::kActivate;
|
||||
pass = true;
|
||||
break;
|
||||
case SDLK_ESCAPE:
|
||||
// (limit to kb1 so we don't get double-beeps on failure)
|
||||
@ -123,32 +125,44 @@ auto KeyboardInput::HandleKey(const SDL_Keysym* keysym, bool repeat, bool down)
|
||||
}
|
||||
}
|
||||
if (pass) {
|
||||
g_base->ui->SendWidgetMessage(WidgetMessage(c, keysym));
|
||||
// For movement and key press widget events, set up repeats.
|
||||
// Otherwise run a single time immediately.
|
||||
switch (c) {
|
||||
case WidgetMessage::Type::kMoveUp:
|
||||
case WidgetMessage::Type::kMoveDown:
|
||||
case WidgetMessage::Type::kMoveLeft:
|
||||
case WidgetMessage::Type::kMoveRight:
|
||||
case WidgetMessage::Type::kKey:
|
||||
// Note: Need to pass keysym along as a value; not a pointer.
|
||||
ui_repeater_ = Repeater::New(
|
||||
g_base->app_adapter->GetKeyRepeatDelay(),
|
||||
g_base->app_adapter->GetKeyRepeatInterval(),
|
||||
[c, keysym = *keysym] {
|
||||
g_base->ui->SendWidgetMessage(WidgetMessage(c, &keysym));
|
||||
});
|
||||
break;
|
||||
default:
|
||||
g_base->ui->SendWidgetMessage(WidgetMessage(c, keysym));
|
||||
break;
|
||||
}
|
||||
}
|
||||
return (pass);
|
||||
}
|
||||
}
|
||||
|
||||
// Bring up menu if start is pressed.
|
||||
if (keysym->sym == start_key_ && !repeat && !g_base->ui->MainMenuVisible()) {
|
||||
if (keysym->sym == start_key_ && !g_base->ui->MainMenuVisible()) {
|
||||
g_base->ui->PushMainMenuPressCall(this);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Clion seems to think child_keyboard_input_ will never be set here (it will).
|
||||
#pragma clang diagnostic push
|
||||
#pragma ide diagnostic ignored "UnreachableCode"
|
||||
#pragma ide diagnostic ignored "ConstantConditionsOC"
|
||||
|
||||
// At this point, if we have a child input, let it try to handle things.
|
||||
if (child_keyboard_input_ && enable_child_) {
|
||||
if (child_keyboard_input_->HandleKey(keysym, repeat, down)) {
|
||||
if (child_keyboard_input_->HandleKey(keysym, down)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
if (!AttachedToPlayer()) {
|
||||
if (down
|
||||
&& ((keysym->sym == jump_key_) || (keysym->sym == punch_key_)
|
||||
@ -173,152 +187,151 @@ auto KeyboardInput::HandleKey(const SDL_Keysym* keysym, bool repeat, bool down)
|
||||
// (removing init values from input_type and input_type_2 gives a
|
||||
// 'possibly uninited value used' warning but leaving them gives a
|
||||
// 'values unused' warning. Grumble.)
|
||||
explicit_bool(input_type
|
||||
== (explicit_bool(false) ? input_type_2 : InputType::kLast));
|
||||
// explicit_bool(input_type
|
||||
// == (explicit_bool(false) ? input_type_2 :
|
||||
// InputType::kLast));
|
||||
|
||||
if (!repeat) {
|
||||
// Keyboard 1 supports assigned keys plus arrow keys if they're unused.
|
||||
if (keysym->sym == left_key_
|
||||
|| (device_number() == 1 && keysym->sym == SDLK_LEFT
|
||||
&& !left_key_assigned())) {
|
||||
player_input = true;
|
||||
input_type = InputType::kLeftRight;
|
||||
left_held_ = down;
|
||||
if (down) {
|
||||
if (right_held_) {
|
||||
input_value = 0;
|
||||
} else {
|
||||
input_value = -32767;
|
||||
}
|
||||
// Keyboard 1 supports assigned keys plus arrow keys if they're unused.
|
||||
if (keysym->sym == left_key_
|
||||
|| (device_number() == 1 && keysym->sym == SDLK_LEFT
|
||||
&& !left_key_assigned())) {
|
||||
player_input = true;
|
||||
input_type = InputType::kLeftRight;
|
||||
left_held_ = down;
|
||||
if (down) {
|
||||
if (right_held_) {
|
||||
input_value = 0;
|
||||
} else {
|
||||
if (right_held_) {
|
||||
input_value = 32767;
|
||||
}
|
||||
}
|
||||
} else if (keysym->sym == right_key_
|
||||
|| (device_number() == 1 && keysym->sym == SDLK_RIGHT
|
||||
&& !right_key_assigned())) {
|
||||
// Keyboard 1 supports assigned keys plus arrow keys if they're unused.
|
||||
player_input = true;
|
||||
input_type = InputType::kLeftRight;
|
||||
right_held_ = down;
|
||||
if (down) {
|
||||
if (left_held_) {
|
||||
input_value = 0;
|
||||
} else {
|
||||
input_value = 32767;
|
||||
}
|
||||
} else {
|
||||
if (left_held_) {
|
||||
input_value = -32767;
|
||||
}
|
||||
}
|
||||
} else if (keysym->sym == up_key_
|
||||
|| (device_number() == 1 && keysym->sym == SDLK_UP
|
||||
&& !up_key_assigned())) {
|
||||
player_input = true;
|
||||
input_type = InputType::kUpDown;
|
||||
up_held_ = down;
|
||||
if (down) {
|
||||
if (down_held_) {
|
||||
input_value = 0;
|
||||
} else {
|
||||
input_value = 32767;
|
||||
}
|
||||
} else {
|
||||
if (down_held_) input_value = -32767;
|
||||
}
|
||||
} else if (keysym->sym == down_key_
|
||||
|| (device_number() == 1 && keysym->sym == SDLK_DOWN
|
||||
&& !down_key_assigned())) {
|
||||
player_input = true;
|
||||
input_type = InputType::kUpDown;
|
||||
down_held_ = down;
|
||||
if (down) {
|
||||
if (up_held_) {
|
||||
input_value = 0;
|
||||
} else {
|
||||
input_value = -32767;
|
||||
}
|
||||
} else {
|
||||
if (up_held_) input_value = 32767;
|
||||
}
|
||||
} else if (keysym->sym == punch_key_) {
|
||||
player_input = true;
|
||||
UpdateRun(keysym->sym, down);
|
||||
if (down) {
|
||||
input_type = InputType::kPunchPress;
|
||||
} else {
|
||||
input_type = InputType::kPunchRelease;
|
||||
}
|
||||
} else if (keysym->sym == bomb_key_) {
|
||||
player_input = true;
|
||||
UpdateRun(keysym->sym, down);
|
||||
if (down)
|
||||
input_type = InputType::kBombPress;
|
||||
else
|
||||
input_type = InputType::kBombRelease;
|
||||
} else if (keysym->sym == hold_position_key_) {
|
||||
player_input = true;
|
||||
if (down) {
|
||||
input_type = InputType::kHoldPositionPress;
|
||||
} else {
|
||||
input_type = InputType::kHoldPositionRelease;
|
||||
}
|
||||
} else if (keysym->sym == pick_up_key_) {
|
||||
player_input = true;
|
||||
UpdateRun(keysym->sym, down);
|
||||
if (down) {
|
||||
input_type = InputType::kPickUpPress;
|
||||
} else {
|
||||
input_type = InputType::kPickUpRelease;
|
||||
}
|
||||
} else if ((device_number() == 1 && keysym->sym == SDLK_RETURN)
|
||||
|| (device_number() == 1 && keysym->sym == SDLK_KP_ENTER)
|
||||
|| keysym->sym == jump_key_) {
|
||||
// Keyboard 1 claims certain keys if they are otherwise unclaimed
|
||||
// (arrow keys, enter/return, etc).
|
||||
player_input = true;
|
||||
UpdateRun(keysym->sym, down);
|
||||
if (down) {
|
||||
input_type = InputType::kJumpPress;
|
||||
have_input_2 = true;
|
||||
input_type_2 = InputType::kFlyPress;
|
||||
} else {
|
||||
input_type = InputType::kJumpRelease;
|
||||
have_input_2 = true;
|
||||
input_type_2 = InputType::kFlyRelease;
|
||||
input_value = -32767;
|
||||
}
|
||||
} else {
|
||||
// Any other keys get processed as run keys.
|
||||
// keypad keys go to player 2 - anything else to player 1.
|
||||
switch (keysym->sym) {
|
||||
case SDLK_KP_0:
|
||||
case SDLK_KP_1:
|
||||
case SDLK_KP_2:
|
||||
case SDLK_KP_3:
|
||||
case SDLK_KP_4:
|
||||
case SDLK_KP_5:
|
||||
case SDLK_KP_6:
|
||||
case SDLK_KP_7:
|
||||
case SDLK_KP_8:
|
||||
case SDLK_KP_9:
|
||||
case SDLK_KP_PLUS:
|
||||
case SDLK_KP_MINUS:
|
||||
case SDLK_KP_ENTER:
|
||||
if (device_number() == 2) {
|
||||
UpdateRun(keysym->sym, down);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (device_number() == 1) {
|
||||
UpdateRun(keysym->sym, down);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
if (right_held_) {
|
||||
input_value = 32767;
|
||||
}
|
||||
}
|
||||
} else if (keysym->sym == right_key_
|
||||
|| (device_number() == 1 && keysym->sym == SDLK_RIGHT
|
||||
&& !right_key_assigned())) {
|
||||
// Keyboard 1 supports assigned keys plus arrow keys if they're unused.
|
||||
player_input = true;
|
||||
input_type = InputType::kLeftRight;
|
||||
right_held_ = down;
|
||||
if (down) {
|
||||
if (left_held_) {
|
||||
input_value = 0;
|
||||
} else {
|
||||
input_value = 32767;
|
||||
}
|
||||
} else {
|
||||
if (left_held_) {
|
||||
input_value = -32767;
|
||||
}
|
||||
}
|
||||
} else if (keysym->sym == up_key_
|
||||
|| (device_number() == 1 && keysym->sym == SDLK_UP
|
||||
&& !up_key_assigned())) {
|
||||
player_input = true;
|
||||
input_type = InputType::kUpDown;
|
||||
up_held_ = down;
|
||||
if (down) {
|
||||
if (down_held_) {
|
||||
input_value = 0;
|
||||
} else {
|
||||
input_value = 32767;
|
||||
}
|
||||
} else {
|
||||
if (down_held_) input_value = -32767;
|
||||
}
|
||||
} else if (keysym->sym == down_key_
|
||||
|| (device_number() == 1 && keysym->sym == SDLK_DOWN
|
||||
&& !down_key_assigned())) {
|
||||
player_input = true;
|
||||
input_type = InputType::kUpDown;
|
||||
down_held_ = down;
|
||||
if (down) {
|
||||
if (up_held_) {
|
||||
input_value = 0;
|
||||
} else {
|
||||
input_value = -32767;
|
||||
}
|
||||
} else {
|
||||
if (up_held_) input_value = 32767;
|
||||
}
|
||||
} else if (keysym->sym == punch_key_) {
|
||||
player_input = true;
|
||||
UpdateRun_(keysym->sym, down);
|
||||
if (down) {
|
||||
input_type = InputType::kPunchPress;
|
||||
} else {
|
||||
input_type = InputType::kPunchRelease;
|
||||
}
|
||||
} else if (keysym->sym == bomb_key_) {
|
||||
player_input = true;
|
||||
UpdateRun_(keysym->sym, down);
|
||||
if (down)
|
||||
input_type = InputType::kBombPress;
|
||||
else
|
||||
input_type = InputType::kBombRelease;
|
||||
} else if (keysym->sym == hold_position_key_) {
|
||||
player_input = true;
|
||||
if (down) {
|
||||
input_type = InputType::kHoldPositionPress;
|
||||
} else {
|
||||
input_type = InputType::kHoldPositionRelease;
|
||||
}
|
||||
} else if (keysym->sym == pick_up_key_) {
|
||||
player_input = true;
|
||||
UpdateRun_(keysym->sym, down);
|
||||
if (down) {
|
||||
input_type = InputType::kPickUpPress;
|
||||
} else {
|
||||
input_type = InputType::kPickUpRelease;
|
||||
}
|
||||
} else if ((device_number() == 1 && keysym->sym == SDLK_RETURN)
|
||||
|| (device_number() == 1 && keysym->sym == SDLK_KP_ENTER)
|
||||
|| keysym->sym == jump_key_) {
|
||||
// Keyboard 1 claims certain keys if they are otherwise unclaimed
|
||||
// (arrow keys, enter/return, etc).
|
||||
player_input = true;
|
||||
UpdateRun_(keysym->sym, down);
|
||||
if (down) {
|
||||
input_type = InputType::kJumpPress;
|
||||
have_input_2 = true;
|
||||
input_type_2 = InputType::kFlyPress;
|
||||
} else {
|
||||
input_type = InputType::kJumpRelease;
|
||||
have_input_2 = true;
|
||||
input_type_2 = InputType::kFlyRelease;
|
||||
}
|
||||
} else {
|
||||
// Any other keys get processed as run keys.
|
||||
// keypad keys go to player 2 - anything else to player 1.
|
||||
switch (keysym->sym) {
|
||||
case SDLK_KP_0:
|
||||
case SDLK_KP_1:
|
||||
case SDLK_KP_2:
|
||||
case SDLK_KP_3:
|
||||
case SDLK_KP_4:
|
||||
case SDLK_KP_5:
|
||||
case SDLK_KP_6:
|
||||
case SDLK_KP_7:
|
||||
case SDLK_KP_8:
|
||||
case SDLK_KP_9:
|
||||
case SDLK_KP_PLUS:
|
||||
case SDLK_KP_MINUS:
|
||||
case SDLK_KP_ENTER:
|
||||
if (device_number() == 2) {
|
||||
UpdateRun_(keysym->sym, down);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (device_number() == 1) {
|
||||
UpdateRun_(keysym->sym, down);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (player_input) {
|
||||
@ -344,7 +357,7 @@ void KeyboardInput::ResetHeldStates() {
|
||||
}
|
||||
}
|
||||
|
||||
void KeyboardInput::UpdateRun(SDL_Keycode key, bool down) {
|
||||
void KeyboardInput::UpdateRun_(SDL_Keycode key, bool down) {
|
||||
bool was_held = (!keys_held_.empty());
|
||||
if (down) {
|
||||
keys_held_.insert(key);
|
||||
@ -407,51 +420,51 @@ void KeyboardInput::UpdateMapping() {
|
||||
|
||||
int val = cl ? cl->GetControllerValue(this, "buttonJump") : -1;
|
||||
jump_key_ = (val == -1) ? jump_key_default : (SDL_Keycode)val;
|
||||
UpdateArrowKeys(jump_key_);
|
||||
UpdateArrowKeys_(jump_key_);
|
||||
|
||||
val = cl ? cl->GetControllerValue(this, "buttonPunch") : -1;
|
||||
punch_key_ = (val == -1) ? punch_key_default : (SDL_Keycode)val;
|
||||
UpdateArrowKeys(punch_key_);
|
||||
UpdateArrowKeys_(punch_key_);
|
||||
|
||||
val = cl ? cl->GetControllerValue(this, "buttonBomb") : -1;
|
||||
bomb_key_ = (val == -1) ? bomb_key_default : (SDL_Keycode)val;
|
||||
UpdateArrowKeys(bomb_key_);
|
||||
UpdateArrowKeys_(bomb_key_);
|
||||
|
||||
val = cl ? cl->GetControllerValue(this, "buttonPickUp") : -1;
|
||||
pick_up_key_ = (val == -1) ? pick_up_key_default : (SDL_Keycode)val;
|
||||
UpdateArrowKeys(pick_up_key_);
|
||||
UpdateArrowKeys_(pick_up_key_);
|
||||
|
||||
val = cl ? cl->GetControllerValue(this, "buttonHoldPosition") : -1;
|
||||
hold_position_key_ =
|
||||
(val == -1) ? hold_position_key_default : (SDL_Keycode)val;
|
||||
UpdateArrowKeys(hold_position_key_);
|
||||
UpdateArrowKeys_(hold_position_key_);
|
||||
|
||||
val = cl ? cl->GetControllerValue(this, "buttonStart") : -1;
|
||||
start_key_ = (val == -1) ? start_key_default : (SDL_Keycode)val;
|
||||
UpdateArrowKeys(start_key_);
|
||||
UpdateArrowKeys_(start_key_);
|
||||
|
||||
val = cl ? cl->GetControllerValue(this, "buttonUp") : -1;
|
||||
up_key_ = (val == -1) ? up_key_default : (SDL_Keycode)val;
|
||||
UpdateArrowKeys(up_key_);
|
||||
UpdateArrowKeys_(up_key_);
|
||||
|
||||
val = cl ? cl->GetControllerValue(this, "buttonDown") : -1;
|
||||
down_key_ = (val == -1) ? down_key_default : (SDL_Keycode)val;
|
||||
UpdateArrowKeys(down_key_);
|
||||
UpdateArrowKeys_(down_key_);
|
||||
|
||||
val = cl ? cl->GetControllerValue(this, "buttonLeft") : -1;
|
||||
left_key_ = (val == -1) ? left_key_default : (SDL_Keycode)val;
|
||||
UpdateArrowKeys(left_key_);
|
||||
UpdateArrowKeys_(left_key_);
|
||||
|
||||
val = cl ? cl->GetControllerValue(this, "buttonRight") : -1;
|
||||
right_key_ = (val == -1) ? right_key_default : (SDL_Keycode)val;
|
||||
UpdateArrowKeys(right_key_);
|
||||
UpdateArrowKeys_(right_key_);
|
||||
|
||||
enable_child_ = true;
|
||||
|
||||
up_held_ = down_held_ = left_held_ = right_held_ = false;
|
||||
}
|
||||
|
||||
void KeyboardInput::UpdateArrowKeys(SDL_Keycode key) {
|
||||
void KeyboardInput::UpdateArrowKeys_(SDL_Keycode key) {
|
||||
if (key == SDLK_UP) {
|
||||
up_key_assigned_ = true;
|
||||
} else if (key == SDLK_DOWN) {
|
||||
@ -464,8 +477,7 @@ void KeyboardInput::UpdateArrowKeys(SDL_Keycode key) {
|
||||
}
|
||||
|
||||
auto KeyboardInput::GetButtonName(int index) -> std::string {
|
||||
return g_base->platform->GetKeyName(index);
|
||||
// return InputDevice::GetButtonName(index);
|
||||
return g_base->app_adapter->GetKeyName(index);
|
||||
}
|
||||
|
||||
auto KeyboardInput::GetRawDeviceName() -> std::string { return "Keyboard"; }
|
||||
|
||||
@ -8,6 +8,7 @@
|
||||
|
||||
#include "ballistica/base/input/device/input_device.h"
|
||||
#include "ballistica/core/platform/support/min_sdl.h"
|
||||
#include "ballistica/shared/foundation/object.h"
|
||||
|
||||
namespace ballistica::base {
|
||||
|
||||
@ -15,7 +16,7 @@ class KeyboardInput : public InputDevice {
|
||||
public:
|
||||
explicit KeyboardInput(KeyboardInput* parent);
|
||||
~KeyboardInput() override;
|
||||
auto HandleKey(const SDL_Keysym* keysym, bool repeat, bool down) -> bool;
|
||||
auto HandleKey(const SDL_Keysym* keysym, bool down) -> bool;
|
||||
void UpdateMapping() override;
|
||||
auto GetRawDeviceName() -> std::string override;
|
||||
void ResetHeldStates() override;
|
||||
@ -29,8 +30,17 @@ class KeyboardInput : public InputDevice {
|
||||
auto GetButtonName(int index) -> std::string override;
|
||||
|
||||
private:
|
||||
void UpdateArrowKeys(SDL_Keycode key);
|
||||
void UpdateRun(SDL_Keycode key, bool down);
|
||||
void UpdateArrowKeys_(SDL_Keycode key);
|
||||
void UpdateRun_(SDL_Keycode key, bool down);
|
||||
bool down_held_ : 1 {};
|
||||
bool up_held_ : 1 {};
|
||||
bool left_held_ : 1 {};
|
||||
bool right_held_ : 1 {};
|
||||
bool enable_child_ : 1 {};
|
||||
bool left_key_assigned_ : 1 {};
|
||||
bool right_key_assigned_ : 1 {};
|
||||
bool up_key_assigned_ : 1 {};
|
||||
bool down_key_assigned_ : 1 {};
|
||||
SDL_Keycode up_key_{};
|
||||
SDL_Keycode down_key_{};
|
||||
SDL_Keycode left_key_{};
|
||||
@ -41,18 +51,10 @@ class KeyboardInput : public InputDevice {
|
||||
SDL_Keycode pick_up_key_{};
|
||||
SDL_Keycode hold_position_key_{};
|
||||
SDL_Keycode start_key_{};
|
||||
bool down_held_{};
|
||||
bool up_held_{};
|
||||
bool left_held_{};
|
||||
bool right_held_{};
|
||||
bool enable_child_{};
|
||||
bool left_key_assigned_{};
|
||||
bool right_key_assigned_{};
|
||||
bool up_key_assigned_{};
|
||||
bool down_key_assigned_{};
|
||||
KeyboardInput* parent_keyboard_input_{};
|
||||
KeyboardInput* child_keyboard_input_{};
|
||||
std::set<int> keys_held_;
|
||||
Object::Ref<Repeater> ui_repeater_;
|
||||
};
|
||||
|
||||
} // namespace ballistica::base
|
||||
|
||||
@ -22,7 +22,7 @@ TestInput::~TestInput() {
|
||||
}
|
||||
|
||||
void TestInput::Reset() {
|
||||
assert(g_core->InMainThread());
|
||||
assert(g_base->InLogicThread());
|
||||
reset_ = true;
|
||||
}
|
||||
|
||||
@ -33,7 +33,7 @@ void TestInput::HandleAlreadyPressedTwice() {
|
||||
}
|
||||
|
||||
void TestInput::Process(millisecs_t time) {
|
||||
assert(g_core->InMainThread());
|
||||
assert(g_base->InLogicThread());
|
||||
|
||||
if (reset_) {
|
||||
reset_ = false;
|
||||
|
||||
@ -16,20 +16,21 @@ class TestInput {
|
||||
|
||||
private:
|
||||
void HandleAlreadyPressedTwice();
|
||||
|
||||
int lr_{};
|
||||
int ud_{};
|
||||
bool jump_pressed_{};
|
||||
bool bomb_pressed_{};
|
||||
bool pickup_pressed_{};
|
||||
bool punch_pressed_{};
|
||||
int join_press_count_{};
|
||||
bool jump_pressed_ : 1 {};
|
||||
bool bomb_pressed_ : 1 {};
|
||||
bool pickup_pressed_ : 1 {};
|
||||
bool punch_pressed_ : 1 {};
|
||||
bool print_non_join_ : 1 {};
|
||||
bool print_already_did2_ : 1 {};
|
||||
bool reset_ : 1 {true};
|
||||
millisecs_t next_event_time_{};
|
||||
millisecs_t join_start_time_{};
|
||||
millisecs_t join_end_time_{9999};
|
||||
int join_press_count_{};
|
||||
bool reset_{true};
|
||||
JoystickInput* joystick_{};
|
||||
bool print_non_join_{};
|
||||
bool print_already_did2_{};
|
||||
};
|
||||
|
||||
} // namespace ballistica::base
|
||||
|
||||
@ -8,14 +8,11 @@
|
||||
#include "ballistica/base/graphics/support/camera.h"
|
||||
#include "ballistica/base/input/device/joystick_input.h"
|
||||
#include "ballistica/base/input/device/keyboard_input.h"
|
||||
#include "ballistica/base/input/device/test_input.h"
|
||||
#include "ballistica/base/input/device/touch_input.h"
|
||||
#include "ballistica/base/logic/logic.h"
|
||||
#include "ballistica/base/python/base_python.h"
|
||||
#include "ballistica/base/support/app_config.h"
|
||||
#include "ballistica/base/ui/dev_console.h"
|
||||
#include "ballistica/base/ui/ui.h"
|
||||
#include "ballistica/shared/buildconfig/buildconfig_common.h"
|
||||
#include "ballistica/shared/foundation/event_loop.h"
|
||||
#include "ballistica/shared/generic/utils.h"
|
||||
|
||||
@ -147,24 +144,22 @@ auto Input::GetNewNumberedIdentifier_(const std::string& name,
|
||||
return full_id;
|
||||
}
|
||||
|
||||
void Input::CreateTouchInput() {
|
||||
assert(g_core->InMainThread());
|
||||
assert(touch_input_ == nullptr);
|
||||
touch_input_ = Object::NewDeferred<TouchInput>();
|
||||
PushAddInputDeviceCall(touch_input_, false);
|
||||
}
|
||||
// void Input::CreateTouchInput() {
|
||||
// assert(g_core->InMainThread());
|
||||
// }
|
||||
|
||||
void Input::AnnounceConnects_() {
|
||||
assert(g_base->InLogicThread());
|
||||
|
||||
static bool first_print = true;
|
||||
|
||||
// For the first announcement just say "X controllers detected" and don't
|
||||
// have a sound.
|
||||
if (first_print && g_core->GetAppTimeSeconds() < 5.0) {
|
||||
if (first_print && g_core->GetAppTimeSeconds() < 3.0) {
|
||||
first_print = false;
|
||||
|
||||
// Disabling this completely on Android for now; we often get large
|
||||
// numbers of devices there that aren't actually devices.
|
||||
|
||||
bool do_print_initial_counts{!g_buildconfig.ostype_android()};
|
||||
|
||||
// If there's been several connected, just give a number.
|
||||
@ -238,12 +233,15 @@ void Input::ShowStandardInputDeviceConnectedMessage_(InputDevice* j) {
|
||||
}
|
||||
newly_connected_controllers_.push_back(j->GetDeviceName() + suffix);
|
||||
|
||||
// Set a timer to go off and announce the accumulated additions.
|
||||
// Set a timer to go off and announce controller additions. This allows
|
||||
// several connecting at (almost) the same time to be announced as a
|
||||
// single event.
|
||||
if (connect_print_timer_id_ != 0) {
|
||||
g_base->logic->DeleteAppTimer(connect_print_timer_id_);
|
||||
}
|
||||
connect_print_timer_id_ = g_base->logic->NewAppTimer(
|
||||
250, false, NewLambdaRunnable([this] { AnnounceConnects_(); }));
|
||||
500 * 1000, false,
|
||||
NewLambdaRunnable([this] { AnnounceConnects_(); }).Get());
|
||||
}
|
||||
|
||||
void Input::ShowStandardInputDeviceDisconnectedMessage_(InputDevice* j) {
|
||||
@ -258,7 +256,8 @@ void Input::ShowStandardInputDeviceDisconnectedMessage_(InputDevice* j) {
|
||||
g_base->logic->DeleteAppTimer(disconnect_print_timer_id_);
|
||||
}
|
||||
disconnect_print_timer_id_ = g_base->logic->NewAppTimer(
|
||||
250, false, NewLambdaRunnable([this] { AnnounceDisconnects_(); }));
|
||||
250 * 1000, false,
|
||||
NewLambdaRunnable([this] { AnnounceDisconnects_(); }).Get());
|
||||
}
|
||||
|
||||
void Input::PushAddInputDeviceCall(InputDevice* input_device,
|
||||
@ -557,7 +556,14 @@ void Input::UpdateEnabledControllerSubsystems_() {
|
||||
// }
|
||||
}
|
||||
|
||||
void Input::OnAppStart() { assert(g_base->InLogicThread()); }
|
||||
void Input::OnAppStart() {
|
||||
assert(g_base->InLogicThread());
|
||||
if (g_core->platform->HasTouchScreen()) {
|
||||
assert(touch_input_ == nullptr);
|
||||
touch_input_ = Object::NewDeferred<TouchInput>();
|
||||
PushAddInputDeviceCall(touch_input_, false);
|
||||
}
|
||||
}
|
||||
|
||||
void Input::OnAppPause() { assert(g_base->InLogicThread()); }
|
||||
|
||||
@ -759,63 +765,12 @@ void Input::PrintLockLabels_() {
|
||||
Log(LogLevel::kError, s);
|
||||
}
|
||||
|
||||
void Input::ProcessStressTesting(int player_count) {
|
||||
assert(g_core->InMainThread());
|
||||
assert(player_count >= 0);
|
||||
|
||||
millisecs_t time = g_core->GetAppTimeMillisecs();
|
||||
|
||||
// FIXME: If we don't check for stress_test_last_leave_time_ we totally
|
||||
// confuse the game.. need to be able to survive that.
|
||||
|
||||
// Kill some off if we have too many.
|
||||
while (static_cast<int>(test_inputs_.size()) > player_count) {
|
||||
delete test_inputs_.front();
|
||||
test_inputs_.pop_front();
|
||||
}
|
||||
|
||||
// If we have less than full test-inputs, add one randomly.
|
||||
if (static_cast<int>(test_inputs_.size()) < player_count
|
||||
&& ((rand() % 1000 < 10))) { // NOLINT
|
||||
test_inputs_.push_back(new TestInput());
|
||||
}
|
||||
|
||||
// Every so often lets kill the oldest one off.
|
||||
if (explicit_bool(true)) {
|
||||
if (test_inputs_.size() > 0 && (rand() % 2000 < 3)) { // NOLINT
|
||||
stress_test_last_leave_time_ = time;
|
||||
|
||||
// Usually do oldest; sometimes newest.
|
||||
if (rand() % 5 == 0) { // NOLINT
|
||||
delete test_inputs_.back();
|
||||
test_inputs_.pop_back();
|
||||
} else {
|
||||
delete test_inputs_.front();
|
||||
test_inputs_.pop_front();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (time - stress_test_time_ > 1000) {
|
||||
stress_test_time_ = time; // reset..
|
||||
for (auto& test_input : test_inputs_) {
|
||||
(*test_input).Reset();
|
||||
}
|
||||
}
|
||||
while (stress_test_time_ < time) {
|
||||
stress_test_time_++;
|
||||
for (auto& test_input : test_inputs_) {
|
||||
(*test_input).Process(stress_test_time_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Input::PushTextInputEvent(const std::string& text) {
|
||||
assert(g_base->logic->event_loop());
|
||||
g_base->logic->event_loop()->PushCall([this, text] {
|
||||
MarkInputActive();
|
||||
|
||||
// If if the app doesn't want direct text input right now.
|
||||
// If the app doesn't want direct text input right now, ignore.
|
||||
if (!g_base->app_adapter->HasDirectKeyboardInput()) {
|
||||
return;
|
||||
}
|
||||
@ -825,6 +780,22 @@ void Input::PushTextInputEvent(const std::string& text) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Also ignore if there are any mod keys being held.
|
||||
// We process some of our own keyboard shortcuts and don't
|
||||
// want text input to come through at the same time.
|
||||
if (keys_held_.contains(SDLK_LCTRL) || keys_held_.contains(SDLK_RCTRL)
|
||||
|| keys_held_.contains(SDLK_LALT) || keys_held_.contains(SDLK_RALT)
|
||||
|| keys_held_.contains(SDLK_LGUI) || keys_held_.contains(SDLK_RGUI)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ignore back-tick and tilde because we use that key to toggle the console.
|
||||
// FIXME: Perhaps should allow typing it if some control-character is
|
||||
// held?
|
||||
if (text == "`" || text == "~") {
|
||||
return;
|
||||
}
|
||||
|
||||
// We try to handle char filtering here (to keep it consistent across
|
||||
// platforms) but make a stink if they sent us something that we can't
|
||||
// at least translate to unicode.
|
||||
@ -848,6 +819,7 @@ void Input::PushTextInputEvent(const std::string& text) {
|
||||
&& g_base->ui->dev_console()->HandleTextEditing(text)) {
|
||||
return;
|
||||
}
|
||||
|
||||
g_base->ui->SendWidgetMessage(WidgetMessage(
|
||||
WidgetMessage::Type::kTextInput, nullptr, 0, 0, 0, 0, text.c_str()));
|
||||
});
|
||||
@ -986,6 +958,34 @@ void Input::HandleKeyPress_(const SDL_Keysym& keysym) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Nowadays we don't want the OS to deliver repeat events to us,
|
||||
// so filter out any that we get and make noise that they should stop. We
|
||||
// explicitly handle repeats for UI purposes at the InputDevice or Widget
|
||||
// level now.
|
||||
if (keys_held_.find(keysym.sym) != keys_held_.end()) {
|
||||
// Look out for several repeats coming in within the span of a few
|
||||
// seconds and complain if it happens. This should allow for the random
|
||||
// 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();
|
||||
if (now - last_count_reset_time > 2.0) {
|
||||
count = 0;
|
||||
last_count_reset_time = now;
|
||||
} else {
|
||||
count++;
|
||||
if (count > 10) {
|
||||
BA_LOG_ONCE(
|
||||
LogLevel::kWarning,
|
||||
"Input::HandleKeyPress_ seems to be getting passed repeat key"
|
||||
" press events. Only initial press events should be passed.");
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
keys_held_.insert(keysym.sym);
|
||||
|
||||
// If someone is capturing these events, give them a crack at it.
|
||||
if (keyboard_input_capture_press_) {
|
||||
if (keyboard_input_capture_press_(keysym)) {
|
||||
@ -998,52 +998,36 @@ void Input::HandleKeyPress_(const SDL_Keysym& keysym) {
|
||||
// ideally we should use the modifiers bundled with the key presses)
|
||||
UpdateModKeyStates_(&keysym, true);
|
||||
|
||||
bool repeat_press;
|
||||
if (keys_held_.count(keysym.sym) != 0) {
|
||||
repeat_press = true;
|
||||
} else {
|
||||
repeat_press = false;
|
||||
keys_held_.insert(keysym.sym);
|
||||
}
|
||||
|
||||
// Mobile-specific stuff.
|
||||
if (g_buildconfig.ostype_ios_tvos() || g_buildconfig.ostype_android()) {
|
||||
switch (keysym.sym) {
|
||||
// FIXME: See if this stuff is still necessary. Was this perhaps
|
||||
// specifically to support the console?
|
||||
case SDLK_DELETE:
|
||||
case SDLK_RETURN:
|
||||
case SDLK_KP_ENTER:
|
||||
case SDLK_BACKSPACE: {
|
||||
// FIXME: I don't remember what this was put here for, but now that
|
||||
// we have hardware keyboards it crashes text fields by sending
|
||||
// them a TEXT_INPUT message with no string.. I made them resistant
|
||||
// to that case but wondering if we can take this out?
|
||||
g_base->ui->SendWidgetMessage(
|
||||
WidgetMessage(WidgetMessage::Type::kTextInput, &keysym));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
// if (g_buildconfig.ostype_ios_tvos() || g_buildconfig.ostype_android()) {
|
||||
// switch (keysym.sym) {
|
||||
// // FIXME: See if this stuff is still necessary. Was this perhaps
|
||||
// // specifically to support the console?
|
||||
// case SDLK_DELETE:
|
||||
// case SDLK_RETURN:
|
||||
// case SDLK_KP_ENTER:
|
||||
// case SDLK_BACKSPACE: {
|
||||
// // FIXME: I don't remember what this was put here for, but now that
|
||||
// // we have hardware keyboards it crashes text fields by sending
|
||||
// // them a TEXT_INPUT message with no string.. I made them resistant
|
||||
// // to that case but wondering if we can take this out?
|
||||
// g_base->ui->SendWidgetMessage(
|
||||
// WidgetMessage(WidgetMessage::Type::kTextInput, &keysym));
|
||||
// break;
|
||||
// }
|
||||
// default:
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
|
||||
// Explicitly handle fullscreen-toggles in some cases.
|
||||
if (g_base->app_adapter->FullscreenControlAvailable()) {
|
||||
bool do_toggle{};
|
||||
// On our Mac SDL builds we support ctrl+F for toggling fullscreen.
|
||||
// On our nice Cocoa build, fullscreening happens magically through the
|
||||
// view menu fullscreen controls.
|
||||
if (g_buildconfig.ostype_macos() && !g_buildconfig.xcode_build()) {
|
||||
if (!repeat_press && keysym.sym == SDLK_f && ((keysym.mod & KMOD_CTRL))) {
|
||||
do_toggle = true;
|
||||
}
|
||||
}
|
||||
// On Windows we support both F11 and Alt+Enter for toggling fullscreen.
|
||||
if (g_buildconfig.ostype_windows()) {
|
||||
if (!repeat_press
|
||||
&& (keysym.sym == SDLK_F11
|
||||
|| (keysym.sym == SDLK_RETURN && ((keysym.mod & KMOD_ALT))))) {
|
||||
// On our SDL builds we support both F11 and Alt+Enter for toggling
|
||||
// fullscreen.
|
||||
if (g_buildconfig.sdl_build()) {
|
||||
if ((keysym.sym == SDLK_F11
|
||||
|| (keysym.sym == SDLK_RETURN && ((keysym.mod & KMOD_ALT))))) {
|
||||
do_toggle = true;
|
||||
}
|
||||
}
|
||||
@ -1055,120 +1039,117 @@ void Input::HandleKeyPress_(const SDL_Keysym& keysym) {
|
||||
}
|
||||
}
|
||||
|
||||
// Control-Q quits. On Mac, the usual Cmd-Q gets handled implicitly by the
|
||||
// app-adapter.
|
||||
// UPDATE: Disabling this for now. Looks like standard OS shortcuts like
|
||||
// Alt+F4 on windows or Cmd-Q on Mac are doing the right thing with SDL
|
||||
// builds these days so these are not needed.
|
||||
// if (!repeat_press && keysym.sym == SDLK_q && (keysym.mod & KMOD_CTRL)) {
|
||||
// g_base->QuitApp(true);
|
||||
// return;
|
||||
// }
|
||||
// Ctrl-V or Cmd-V sends paste commands to the console or any interested
|
||||
// text fields.
|
||||
if (keysym.sym == SDLK_v
|
||||
&& ((keysym.mod & KMOD_CTRL) || (keysym.mod & KMOD_GUI))) {
|
||||
if (auto* console = g_base->ui->dev_console()) {
|
||||
if (console->PasteFromClipboard()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
g_base->ui->SendWidgetMessage(WidgetMessage(WidgetMessage::Type::kPaste));
|
||||
return;
|
||||
}
|
||||
|
||||
// Let the console intercept stuff if it wants at this point.
|
||||
// Dev Console.
|
||||
if (auto* console = g_base->ui->dev_console()) {
|
||||
if (keysym.sym == SDLK_BACKQUOTE || keysym.sym == SDLK_F2) {
|
||||
// (reset input so characters don't continue walking and stuff)
|
||||
g_base->input->ResetHoldStates();
|
||||
console->ToggleState();
|
||||
return;
|
||||
}
|
||||
if (console->HandleKeyPress(&keysym)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Ctrl-V or Cmd-V sends paste commands to any interested text fields.
|
||||
// Command-Q or Control-Q quits.
|
||||
if (!repeat_press && keysym.sym == SDLK_v
|
||||
&& ((keysym.mod & KMOD_CTRL) || (keysym.mod & KMOD_GUI))) {
|
||||
g_base->ui->SendWidgetMessage(WidgetMessage(WidgetMessage::Type::kPaste));
|
||||
return;
|
||||
}
|
||||
|
||||
bool handled = false;
|
||||
|
||||
// None of the following stuff accepts key repeats.
|
||||
if (!repeat_press) {
|
||||
switch (keysym.sym) {
|
||||
// Menu button on android/etc. pops up the menu.
|
||||
case SDLK_MENU: {
|
||||
if (!g_base->ui->MainMenuVisible()) {
|
||||
g_base->ui->PushMainMenuPressCall(touch_input_);
|
||||
}
|
||||
handled = true;
|
||||
break;
|
||||
switch (keysym.sym) {
|
||||
// Menu button on android/etc. pops up the menu.
|
||||
case SDLK_MENU: {
|
||||
if (!g_base->ui->MainMenuVisible()) {
|
||||
g_base->ui->PushMainMenuPressCall(touch_input_);
|
||||
}
|
||||
|
||||
case SDLK_EQUALS:
|
||||
case SDLK_PLUS:
|
||||
if (keysym.mod & KMOD_CTRL) {
|
||||
g_base->app_mode()->ChangeGameSpeed(1);
|
||||
handled = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case SDLK_MINUS:
|
||||
if (keysym.mod & KMOD_CTRL) {
|
||||
g_base->app_mode()->ChangeGameSpeed(-1);
|
||||
handled = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case SDLK_F5: {
|
||||
if (g_base->ui->PartyIconVisible()) {
|
||||
g_base->ui->ActivatePartyIcon();
|
||||
}
|
||||
handled = true;
|
||||
break;
|
||||
}
|
||||
|
||||
case SDLK_F7:
|
||||
assert(g_base->logic->event_loop());
|
||||
g_base->logic->event_loop()->PushCall(
|
||||
[] { g_base->graphics->ToggleManualCamera(); });
|
||||
handled = true;
|
||||
break;
|
||||
|
||||
case SDLK_F8:
|
||||
assert(g_base->logic->event_loop());
|
||||
g_base->logic->event_loop()->PushCall(
|
||||
[] { g_base->graphics->ToggleNetworkDebugDisplay(); });
|
||||
handled = true;
|
||||
break;
|
||||
|
||||
case SDLK_F9:
|
||||
g_base->python->objs().PushCall(
|
||||
BasePython::ObjID::kLanguageTestToggleCall);
|
||||
handled = true;
|
||||
break;
|
||||
|
||||
case SDLK_F10:
|
||||
assert(g_base->logic->event_loop());
|
||||
g_base->logic->event_loop()->PushCall(
|
||||
[] { g_base->graphics->ToggleDebugDraw(); });
|
||||
handled = true;
|
||||
break;
|
||||
|
||||
case SDLK_ESCAPE:
|
||||
|
||||
if (!g_base->ui->MainMenuVisible()) {
|
||||
// There's no main menu up. Ask for one.
|
||||
|
||||
// Note: keyboard_input_ may be nullptr but escape key should
|
||||
// still function for menus; it just won't claim ownership.
|
||||
g_base->ui->PushMainMenuPressCall(keyboard_input_);
|
||||
} else {
|
||||
// Ok there *is* a main menu up. Send it a cancel message.
|
||||
g_base->ui->SendWidgetMessage(
|
||||
WidgetMessage(WidgetMessage::Type::kCancel));
|
||||
}
|
||||
handled = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
handled = true;
|
||||
break;
|
||||
}
|
||||
|
||||
case SDLK_EQUALS:
|
||||
case SDLK_PLUS:
|
||||
if (keysym.mod & KMOD_CTRL) {
|
||||
g_base->app_mode()->ChangeGameSpeed(1);
|
||||
handled = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case SDLK_MINUS:
|
||||
if (keysym.mod & KMOD_CTRL) {
|
||||
g_base->app_mode()->ChangeGameSpeed(-1);
|
||||
handled = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case SDLK_F5: {
|
||||
if (g_base->ui->PartyIconVisible()) {
|
||||
g_base->ui->ActivatePartyIcon();
|
||||
}
|
||||
handled = true;
|
||||
break;
|
||||
}
|
||||
|
||||
case SDLK_F7:
|
||||
assert(g_base->logic->event_loop());
|
||||
g_base->logic->event_loop()->PushCall(
|
||||
[] { g_base->graphics->ToggleManualCamera(); });
|
||||
handled = true;
|
||||
break;
|
||||
|
||||
case SDLK_F8:
|
||||
assert(g_base->logic->event_loop());
|
||||
g_base->logic->event_loop()->PushCall(
|
||||
[] { g_base->graphics->ToggleNetworkDebugDisplay(); });
|
||||
handled = true;
|
||||
break;
|
||||
|
||||
case SDLK_F9:
|
||||
g_base->python->objs().PushCall(
|
||||
BasePython::ObjID::kLanguageTestToggleCall);
|
||||
handled = true;
|
||||
break;
|
||||
|
||||
case SDLK_F10:
|
||||
assert(g_base->logic->event_loop());
|
||||
g_base->logic->event_loop()->PushCall(
|
||||
[] { g_base->graphics->ToggleDebugDraw(); });
|
||||
handled = true;
|
||||
break;
|
||||
|
||||
case SDLK_ESCAPE:
|
||||
if (!g_base->ui->MainMenuVisible()) {
|
||||
// There's no main menu up. Ask for one.
|
||||
|
||||
// Note: keyboard_input_ may be nullptr but escape key should
|
||||
// still function for menus; it just won't claim ownership.
|
||||
g_base->ui->PushMainMenuPressCall(keyboard_input_);
|
||||
} else {
|
||||
// Ok there *is* a main menu up. Send it a cancel message.
|
||||
g_base->ui->SendWidgetMessage(
|
||||
WidgetMessage(WidgetMessage::Type::kCancel));
|
||||
}
|
||||
handled = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// If we haven't claimed it, pass it along as potential player/widget input.
|
||||
// If we haven't handled this, pass it along as potential player/widget input.
|
||||
if (!handled) {
|
||||
if (keyboard_input_) {
|
||||
keyboard_input_->HandleKey(&keysym, repeat_press, true);
|
||||
keyboard_input_->HandleKey(&keysym, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1184,7 +1165,7 @@ void Input::HandleKeyRelease_(const SDL_Keysym& keysym) {
|
||||
// In some cases we may receive duplicate key-release events (if a
|
||||
// keyboard reset was run, it deals out key releases, but then the
|
||||
// keyboard driver issues them as well).
|
||||
if (keys_held_.count(keysym.sym) == 0) {
|
||||
if (keys_held_.find(keysym.sym) == keys_held_.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1205,7 +1186,7 @@ void Input::HandleKeyRelease_(const SDL_Keysym& keysym) {
|
||||
}
|
||||
|
||||
if (keyboard_input_) {
|
||||
keyboard_input_->HandleKey(&keysym, false, false);
|
||||
keyboard_input_->HandleKey(&keysym, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -114,10 +114,9 @@ class Input {
|
||||
// something.
|
||||
auto HaveControllerWithPlayer() -> bool;
|
||||
auto HaveRemoteAppController() -> bool;
|
||||
void ProcessStressTesting(int player_count);
|
||||
auto keyboard_input() const -> KeyboardInput* { return keyboard_input_; }
|
||||
auto keyboard_input_2() const -> KeyboardInput* { return keyboard_input_2_; }
|
||||
void CreateTouchInput();
|
||||
// void CreateTouchInput();
|
||||
|
||||
void PushTextInputEvent(const std::string& text);
|
||||
void PushKeyPressEventSimple(int keycode);
|
||||
@ -205,7 +204,6 @@ class Input {
|
||||
std::list<std::string> input_lock_permanent_labels_;
|
||||
std::list<std::string> input_unlock_permanent_labels_;
|
||||
std::list<std::string> recent_input_locks_unlocks_;
|
||||
std::list<TestInput*> test_inputs_;
|
||||
std::list<std::string> newly_connected_controllers_;
|
||||
std::list<std::string> newly_disconnected_controllers_;
|
||||
std::unordered_map<std::string, std::unordered_map<std::string, int> >
|
||||
@ -214,8 +212,6 @@ class Input {
|
||||
std::set<int> keys_held_;
|
||||
millisecs_t last_input_device_count_update_time_{};
|
||||
millisecs_t last_input_temp_lock_time_{};
|
||||
millisecs_t stress_test_time_{};
|
||||
millisecs_t stress_test_last_leave_time_{};
|
||||
void* single_touch_{};
|
||||
KeyboardInput* keyboard_input_{};
|
||||
KeyboardInput* keyboard_input_2_{};
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
#include "ballistica/base/assets/assets.h"
|
||||
#include "ballistica/base/audio/audio.h"
|
||||
#include "ballistica/base/graphics/graphics.h"
|
||||
#include "ballistica/base/graphics/support/screen_messages.h"
|
||||
#include "ballistica/base/input/input.h"
|
||||
#include "ballistica/base/logic/logic.h"
|
||||
#include "ballistica/base/networking/network_reader.h"
|
||||
@ -162,7 +163,8 @@ void RemoteAppServer::HandleData(int socket, uint8_t* buffer, size_t amt,
|
||||
g_base->assets->GetResourceString("controllerDisconnectedText");
|
||||
Utils::StringReplaceOne(&s, "${CONTROLLER}", m);
|
||||
g_base->logic->event_loop()->PushCall([s] {
|
||||
g_base->graphics->AddScreenMessage(s, Vector3f(1, 1, 1));
|
||||
g_base->graphics->screenmessages->AddScreenMessage(
|
||||
s, Vector3f(1, 1, 1));
|
||||
});
|
||||
g_base->logic->event_loop()->PushCall([] {
|
||||
g_base->audio->PlaySound(
|
||||
@ -369,8 +371,10 @@ auto RemoteAppServer::GetClient(int request_id, struct sockaddr* addr,
|
||||
std::string s =
|
||||
g_base->assets->GetResourceString("controllerReconnectedText");
|
||||
Utils::StringReplaceOne(&s, "${CONTROLLER}", m);
|
||||
g_base->logic->event_loop()->PushCall(
|
||||
[s] { g_base->graphics->AddScreenMessage(s, Vector3f(1, 1, 1)); });
|
||||
g_base->logic->event_loop()->PushCall([s] {
|
||||
g_base->graphics->screenmessages->AddScreenMessage(s,
|
||||
Vector3f(1, 1, 1));
|
||||
});
|
||||
g_base->logic->event_loop()->PushCall([] {
|
||||
g_base->audio->PlaySound(
|
||||
g_base->assets->SysSound(SysSoundID::kGunCock));
|
||||
@ -416,8 +420,10 @@ auto RemoteAppServer::GetClient(int request_id, struct sockaddr* addr,
|
||||
std::string s =
|
||||
g_base->assets->GetResourceString("controllerConnectedText");
|
||||
Utils::StringReplaceOne(&s, "${CONTROLLER}", m);
|
||||
g_base->logic->event_loop()->PushCall(
|
||||
[s] { g_base->graphics->AddScreenMessage(s, Vector3f(1, 1, 1)); });
|
||||
g_base->logic->event_loop()->PushCall([s] {
|
||||
g_base->graphics->screenmessages->AddScreenMessage(s,
|
||||
Vector3f(1, 1, 1));
|
||||
});
|
||||
|
||||
g_base->logic->event_loop()->PushCall([] {
|
||||
g_base->audio->PlaySound(
|
||||
|
||||
@ -99,8 +99,8 @@ void Logic::OnGraphicsReady() {
|
||||
// Anyone dealing in display-time should be able to handle a wide
|
||||
// variety of rates anyway. NOTE: This length is currently milliseconds.
|
||||
headless_display_time_step_timer_ = event_loop()->NewTimer(
|
||||
kHeadlessMinDisplayTimeStep / 1000, true,
|
||||
NewLambdaRunnable([this] { StepDisplayTime_(); }));
|
||||
kHeadlessMinDisplayTimeStep, true,
|
||||
NewLambdaRunnable([this] { StepDisplayTime_(); }).Get());
|
||||
} else {
|
||||
// In gui mode, push an initial frame to the graphics server. From this
|
||||
// point it will be self-sustaining, sending us a frame request each
|
||||
@ -133,9 +133,10 @@ void Logic::CompleteAppBootstrapping_() {
|
||||
|
||||
// Set up our timers.
|
||||
process_pending_work_timer_ = event_loop()->NewTimer(
|
||||
0, true, NewLambdaRunnable([this] { ProcessPendingWork_(); }));
|
||||
asset_prune_timer_ = event_loop()->NewTimer(
|
||||
2345, true, NewLambdaRunnable([] { g_base->assets->Prune(); }));
|
||||
0, true, NewLambdaRunnable([this] { ProcessPendingWork_(); }).Get());
|
||||
// asset_prune_timer_ = event_loop()->NewTimer(
|
||||
// 2345 * 1000, true, NewLambdaRunnable([] { g_base->assets->Prune();
|
||||
// }).Get());
|
||||
|
||||
// Let our initial dummy app-mode know it has become active.
|
||||
g_base->app_mode()->OnActivate();
|
||||
@ -381,9 +382,7 @@ void Logic::OnAppModeChanged() {
|
||||
"Resetting headless display step timer due to app-mode change.");
|
||||
}
|
||||
assert(headless_display_time_step_timer_);
|
||||
// NOTE: This is currently milliseconds.
|
||||
headless_display_time_step_timer_->SetLength(kHeadlessMinDisplayTimeStep
|
||||
/ 1000);
|
||||
headless_display_time_step_timer_->SetLength(kHeadlessMinDisplayTimeStep);
|
||||
}
|
||||
}
|
||||
|
||||
@ -439,8 +438,8 @@ void Logic::PostUpdateDisplayTimeForHeadlessMode_() {
|
||||
Log(LogLevel::kDebug, buffer);
|
||||
}
|
||||
|
||||
auto sleep_millisecs = headless_display_step_microsecs / 1000;
|
||||
headless_display_time_step_timer_->SetLength(sleep_millisecs);
|
||||
auto sleep_microsecs = headless_display_step_microsecs;
|
||||
headless_display_time_step_timer_->SetLength(sleep_microsecs);
|
||||
}
|
||||
|
||||
void Logic::UpdateDisplayTimeForFrameDraw_() {
|
||||
@ -581,7 +580,7 @@ void Logic::UpdatePendingWorkTimer_() {
|
||||
// If there's loading to do, keep at it rather vigorously.
|
||||
if (have_pending_loads_) {
|
||||
assert(process_pending_work_timer_);
|
||||
process_pending_work_timer_->SetLength(1);
|
||||
process_pending_work_timer_->SetLength(1 * 1000);
|
||||
} else {
|
||||
// Otherwise we've got nothing to do; go to sleep until something
|
||||
// changes.
|
||||
@ -635,8 +634,8 @@ void Logic::NotifyOfPendingAssetLoads() {
|
||||
UpdatePendingWorkTimer_();
|
||||
}
|
||||
|
||||
auto Logic::NewAppTimer(millisecs_t length, bool repeat,
|
||||
const Object::Ref<Runnable>& runnable) -> int {
|
||||
auto Logic::NewAppTimer(microsecs_t length, bool repeat, Runnable* runnable)
|
||||
-> int {
|
||||
// App-Timers simply get injected into our loop and run alongside our own
|
||||
// stuff.
|
||||
assert(g_base->InLogicThread());
|
||||
@ -649,7 +648,7 @@ void Logic::DeleteAppTimer(int timer_id) {
|
||||
event_loop()->DeleteTimer(timer_id);
|
||||
}
|
||||
|
||||
void Logic::SetAppTimerLength(int timer_id, millisecs_t length) {
|
||||
void Logic::SetAppTimerLength(int timer_id, microsecs_t length) {
|
||||
assert(g_base->InLogicThread());
|
||||
Timer* t = event_loop()->GetTimer(timer_id);
|
||||
if (t) {
|
||||
@ -660,14 +659,14 @@ void Logic::SetAppTimerLength(int timer_id, millisecs_t length) {
|
||||
}
|
||||
}
|
||||
|
||||
auto Logic::NewDisplayTimer(microsecs_t length, bool repeat,
|
||||
const Object::Ref<Runnable>& runnable) -> int {
|
||||
auto Logic::NewDisplayTimer(microsecs_t length, bool repeat, Runnable* runnable)
|
||||
-> int {
|
||||
// Display-Timers go into a timer-list that we exec explicitly when we
|
||||
// step display-time.
|
||||
assert(g_base->InLogicThread());
|
||||
int offset = 0;
|
||||
Timer* t = display_timers_->NewTimer(g_core->GetAppTimeMicrosecs(), length,
|
||||
offset, repeat ? -1 : 0, runnable);
|
||||
Timer* t = display_timers_->NewTimer(display_time_microsecs_, length, offset,
|
||||
repeat ? -1 : 0, runnable);
|
||||
return t->id();
|
||||
}
|
||||
|
||||
|
||||
@ -89,13 +89,12 @@ class Logic {
|
||||
void HandleInterruptSignal();
|
||||
void HandleTerminateSignal();
|
||||
|
||||
auto NewAppTimer(millisecs_t length, bool repeat,
|
||||
const Object::Ref<Runnable>& runnable) -> int;
|
||||
auto NewAppTimer(microsecs_t length, bool repeat, Runnable* runnable) -> int;
|
||||
void DeleteAppTimer(int timer_id);
|
||||
void SetAppTimerLength(int timer_id, millisecs_t length);
|
||||
void SetAppTimerLength(int timer_id, microsecs_t length);
|
||||
|
||||
auto NewDisplayTimer(microsecs_t length, bool repeat,
|
||||
const Object::Ref<Runnable>& runnable) -> int;
|
||||
auto NewDisplayTimer(microsecs_t length, bool repeat, Runnable* runnable)
|
||||
-> int;
|
||||
void DeleteDisplayTimer(int timer_id);
|
||||
void SetDisplayTimerLength(int timer_id, microsecs_t length);
|
||||
|
||||
@ -149,8 +148,6 @@ class Logic {
|
||||
bool shutdown_completed_ : 1 {};
|
||||
bool graphics_ready_ : 1 {};
|
||||
Timer* process_pending_work_timer_{};
|
||||
Timer* asset_prune_timer_{};
|
||||
Timer* debug_timer_{};
|
||||
EventLoop* event_loop_{};
|
||||
std::unique_ptr<TimerList> display_timers_;
|
||||
};
|
||||
|
||||
@ -4,11 +4,14 @@
|
||||
#include "ballistica/base/platform/apple/base_platform_apple.h"
|
||||
|
||||
#if BA_XCODE_BUILD
|
||||
#include <BallisticaKit-Swift.h>
|
||||
#include "ballistica/base/platform/apple/apple_utils.h"
|
||||
#include "ballistica/base/platform/apple/from_swift.h"
|
||||
#endif
|
||||
|
||||
#if BA_XCODE_BUILD
|
||||
#include "ballistica/base/platform/apple/apple_utils.h"
|
||||
// This needs to be below ballistica headers since it relies on
|
||||
// some types in them but does not include headers itself.
|
||||
#include <BallisticaKit-Swift.h>
|
||||
#endif
|
||||
|
||||
namespace ballistica::base {
|
||||
@ -16,13 +19,14 @@ namespace ballistica::base {
|
||||
BasePlatformApple::BasePlatformApple() {
|
||||
// On iOS, keep the device from falling asleep in our app
|
||||
#if BA_OSTYPE_IOS_TVOS
|
||||
AppleUtils::DisableIdleTimer();
|
||||
// AppleUtils::DisableIdleTimer();
|
||||
#endif
|
||||
}
|
||||
|
||||
void BasePlatformApple::DoPurchase(const std::string& item) {
|
||||
#if BA_USE_STORE_KIT
|
||||
AppleUtils::DoStoreKitPurchase(item);
|
||||
BallisticaKit::StoreKitContext::purchase(item);
|
||||
// AppleUtils::DoStoreKitPurchase(item);
|
||||
#else
|
||||
BasePlatform::DoPurchase(item);
|
||||
#endif
|
||||
@ -30,7 +34,8 @@ void BasePlatformApple::DoPurchase(const std::string& item) {
|
||||
|
||||
void BasePlatformApple::RestorePurchases() {
|
||||
#if BA_USE_STORE_KIT
|
||||
AppleUtils::DoStoreKitPurchaseRestore();
|
||||
BallisticaKit::StoreKitContext::restorePurchases();
|
||||
// AppleUtils::DoStoreKitPurchaseRestore();
|
||||
#else
|
||||
BasePlatform::RestorePurchases();
|
||||
#endif
|
||||
@ -38,8 +43,9 @@ void BasePlatformApple::RestorePurchases() {
|
||||
|
||||
void BasePlatformApple::PurchaseAck(const std::string& purchase,
|
||||
const std::string& order_id) {
|
||||
#if BA_XCODE_BUILD
|
||||
AppleUtils::PurchaseAck(purchase, order_id);
|
||||
#if BA_USE_STORE_KIT
|
||||
BallisticaKit::StoreKitContext::purchaseAck(purchase, order_id);
|
||||
// AppleUtils::PurchaseAck(purchase, order_id);
|
||||
#else
|
||||
BasePlatform::PurchaseAck(purchase, order_id);
|
||||
#endif
|
||||
@ -48,9 +54,9 @@ void BasePlatformApple::PurchaseAck(const std::string& purchase,
|
||||
void BasePlatformApple::DoOpenURL(const std::string& url) {
|
||||
#if BA_XCODE_BUILD
|
||||
#if BA_OSTYPE_MACOS
|
||||
BallisticaKit::CocoaFromCppOpenURL(url);
|
||||
BallisticaKit::CocoaFromCpp::openURL(url);
|
||||
#else
|
||||
BallisticaKit::UIKitFromCppOpenURL(url);
|
||||
BallisticaKit::UIKitFromCpp::openURL(url);
|
||||
#endif // BA_OSTYPE_MACOS
|
||||
|
||||
#else
|
||||
@ -59,6 +65,34 @@ void BasePlatformApple::DoOpenURL(const std::string& url) {
|
||||
#endif // BA_XCODE_BUILD
|
||||
}
|
||||
|
||||
void BasePlatformApple::LoginAdapterGetSignInToken(
|
||||
const std::string& login_type, int attempt_id) {
|
||||
#if BA_USE_GAME_CENTER
|
||||
if (login_type == "game_center") {
|
||||
BallisticaKit::GameCenterContext::getSignInToken(attempt_id);
|
||||
} else {
|
||||
Log(LogLevel::kError,
|
||||
"Got unexpected get-sign-in-token login-type: " + login_type);
|
||||
}
|
||||
#else
|
||||
BasePlatform::LoginAdapterGetSignInToken(login_type, attempt_id);
|
||||
#endif
|
||||
}
|
||||
|
||||
void BasePlatformApple::LoginAdapterBackEndActiveChange(
|
||||
const std::string& login_type, bool active) {
|
||||
#if BA_USE_GAME_CENTER
|
||||
if (login_type == "game_center") {
|
||||
BallisticaKit::GameCenterContext::backEndActiveChange(active);
|
||||
} else {
|
||||
Log(LogLevel::kError,
|
||||
"Got unexpected back-end-active-change login-type: " + login_type);
|
||||
}
|
||||
#else
|
||||
BasePlatform::LoginAdapterBackEndActiveChange(login_type, active);
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace ballistica::base
|
||||
|
||||
#endif // BA_OSTYPE_MACOS || BA_OSTYPE_IOS_TVOS
|
||||
|
||||
@ -16,6 +16,10 @@ class BasePlatformApple : public BasePlatform {
|
||||
void PurchaseAck(const std::string& purchase,
|
||||
const std::string& order_id) override;
|
||||
void DoOpenURL(const std::string& url) override;
|
||||
void LoginAdapterGetSignInToken(const std::string& login_type,
|
||||
int attempt_id) override;
|
||||
void LoginAdapterBackEndActiveChange(const std::string& login_type,
|
||||
bool active) override;
|
||||
};
|
||||
|
||||
} // namespace ballistica::base
|
||||
|
||||
@ -7,7 +7,6 @@
|
||||
#include "ballistica/base/base.h"
|
||||
#include "ballistica/base/input/input.h"
|
||||
#include "ballistica/base/logic/logic.h"
|
||||
#include "ballistica/base/platform/support/min_sdl_key_names.h"
|
||||
#include "ballistica/base/python/base_python.h"
|
||||
#include "ballistica/base/ui/ui.h"
|
||||
#include "ballistica/core/core.h"
|
||||
@ -16,73 +15,8 @@
|
||||
#include "ballistica/shared/python/python.h"
|
||||
#include "ballistica/shared/python/python_sys.h"
|
||||
|
||||
// ------------------------- PLATFORM SELECTION --------------------------------
|
||||
|
||||
// This ugly chunk of macros simply pulls in the correct platform class header
|
||||
// for each platform and defines the actual class g_base->platform will be.
|
||||
|
||||
// Android ---------------------------------------------------------------------
|
||||
|
||||
#if BA_OSTYPE_ANDROID
|
||||
#if BA_GOOGLE_BUILD
|
||||
#include "ballistica/base/platform/android/google/base_plat_andr_google.h"
|
||||
#define BA_PLATFORM_CLASS BasePlatformAndroidGoogle
|
||||
#elif BA_AMAZON_BUILD
|
||||
#include "ballistica/base/platform/android/amazon/base_plat_andr_amazon.h"
|
||||
#define BA_PLATFORM_CLASS BasePlatformAndroidAmazon
|
||||
#elif BA_CARDBOARD_BUILD
|
||||
#include "ballistica/base/platform/android/cardboard/base_pl_an_cardboard.h"
|
||||
#define BA_PLATFORM_CLASS BasePlatformAndroidCardboard
|
||||
#else // Generic android.
|
||||
#include "ballistica/base/platform/android/base_platform_android.h"
|
||||
#define BA_PLATFORM_CLASS BasePlatformAndroid
|
||||
#endif // (Android subplatform)
|
||||
|
||||
// Apple -----------------------------------------------------------------------
|
||||
|
||||
#elif BA_OSTYPE_MACOS || BA_OSTYPE_IOS_TVOS
|
||||
#include "ballistica/base/platform/apple/base_platform_apple.h"
|
||||
#define BA_PLATFORM_CLASS BasePlatformApple
|
||||
|
||||
// Windows ---------------------------------------------------------------------
|
||||
|
||||
#elif BA_OSTYPE_WINDOWS
|
||||
#if BA_RIFT_BUILD
|
||||
#include "ballistica/base/platform/windows/base_platform_windows_oculus.h"
|
||||
#define BA_PLATFORM_CLASS BasePlatformWindowsOculus
|
||||
#else // generic windows
|
||||
#include "ballistica/base/platform/windows/base_platform_windows.h"
|
||||
#define BA_PLATFORM_CLASS BasePlatformWindows
|
||||
#endif // windows subtype
|
||||
|
||||
// Linux -----------------------------------------------------------------------
|
||||
|
||||
#elif BA_OSTYPE_LINUX
|
||||
#include "ballistica/base/platform/linux/base_platform_linux.h"
|
||||
#define BA_PLATFORM_CLASS BasePlatformLinux
|
||||
#else
|
||||
|
||||
// Generic ---------------------------------------------------------------------
|
||||
|
||||
#define BA_PLATFORM_CLASS BasePlatform
|
||||
|
||||
#endif
|
||||
|
||||
// ----------------------- END PLATFORM SELECTION ------------------------------
|
||||
|
||||
#ifndef BA_PLATFORM_CLASS
|
||||
#error no BA_PLATFORM_CLASS defined for this platform
|
||||
#endif
|
||||
|
||||
namespace ballistica::base {
|
||||
|
||||
auto BasePlatform::Create() -> BasePlatform* {
|
||||
auto platform = new BA_PLATFORM_CLASS();
|
||||
platform->PostInit();
|
||||
assert(platform->ran_base_post_init_);
|
||||
return platform;
|
||||
}
|
||||
|
||||
BasePlatform::BasePlatform() = default;
|
||||
|
||||
void BasePlatform::PostInit() {
|
||||
@ -92,20 +26,6 @@ void BasePlatform::PostInit() {
|
||||
|
||||
BasePlatform::~BasePlatform() = default;
|
||||
|
||||
auto BasePlatform::GetKeyName(int keycode) -> std::string {
|
||||
// On our actual SDL platforms we're trying to be *pure* sdl so
|
||||
// call their function for this. Otherwise we call our own version
|
||||
// of it which is basically the same thing (at least for now).
|
||||
#if BA_MINSDL_BUILD
|
||||
return MinSDL_GetKeyName(keycode);
|
||||
#elif BA_SDL_BUILD
|
||||
return SDL_GetKeyName(static_cast<SDL_Keycode>(keycode));
|
||||
#else
|
||||
Log(LogLevel::kWarn, "CorePlatform::GetKeyName not implemented here.");
|
||||
return "?";
|
||||
#endif
|
||||
}
|
||||
|
||||
void BasePlatform::LoginAdapterGetSignInToken(const std::string& login_type,
|
||||
int attempt_id) {
|
||||
// Default implementation simply calls completion callback immediately.
|
||||
|
||||
@ -15,8 +15,12 @@ namespace ballistica::base {
|
||||
/// with a single platform (Windows, Mac, etc.).
|
||||
class BasePlatform {
|
||||
public:
|
||||
/// Instantiate the CorePlatform subclass for the current build.
|
||||
static auto Create() -> BasePlatform*;
|
||||
BasePlatform();
|
||||
|
||||
/// Called after our singleton has been instantiated. Any construction
|
||||
/// functionality requiring virtual functions resolving to their final
|
||||
/// class versions can go here.
|
||||
virtual void PostInit();
|
||||
|
||||
#pragma mark APP EVENTS / LIFECYCLE --------------------------------------------
|
||||
|
||||
@ -57,20 +61,18 @@ class BasePlatform {
|
||||
/// Called when the app should set itself up to intercept ctrl-c presses.
|
||||
virtual void SetupInterruptHandling();
|
||||
|
||||
#pragma mark INPUT DEVICES -----------------------------------------------------
|
||||
|
||||
// Return a name for a ballistica keycode.
|
||||
virtual auto GetKeyName(int keycode) -> std::string;
|
||||
|
||||
#pragma mark ACCOUNTS ----------------------------------------------------------
|
||||
|
||||
/// Called when a Python LoginAdapter is requesting an explicit sign-in.
|
||||
/// See the LoginAdapter class in Python for usage details.
|
||||
virtual void LoginAdapterGetSignInToken(const std::string& login_type,
|
||||
int attempt_id);
|
||||
/// Called when a Python LoginAdapter is informing us that a back-end is
|
||||
/// active/inactive.
|
||||
/// active/inactive. See the LoginAdapter class in Python for usage
|
||||
/// details.
|
||||
virtual void LoginAdapterBackEndActiveChange(const std::string& login_type,
|
||||
bool active);
|
||||
|
||||
#pragma mark MISC --------------------------------------------------------------
|
||||
|
||||
/// Do we define a platform-specific string editor? This is something like
|
||||
@ -94,6 +96,8 @@ class BasePlatform {
|
||||
/// Must be called in the logic thread.
|
||||
void StringEditorCancel();
|
||||
|
||||
auto ran_base_post_init() const { return ran_base_post_init_; }
|
||||
|
||||
protected:
|
||||
/// Pop up a text edit dialog.
|
||||
virtual void DoInvokeStringEditor(const std::string& title,
|
||||
@ -106,17 +110,11 @@ class BasePlatform {
|
||||
/// Make a purchase.
|
||||
virtual void DoPurchase(const std::string& item);
|
||||
|
||||
BasePlatform();
|
||||
virtual ~BasePlatform();
|
||||
|
||||
private:
|
||||
/// Called after our singleton has been instantiated. Any construction
|
||||
/// functionality requiring virtual functions resolving to their final
|
||||
/// class versions can go here.
|
||||
virtual void PostInit();
|
||||
|
||||
bool ran_base_post_init_ : 1 {};
|
||||
PythonRef string_edit_adapter_{};
|
||||
bool ran_base_post_init_{};
|
||||
std::string public_device_uuid_;
|
||||
};
|
||||
|
||||
|
||||
@ -367,6 +367,22 @@ auto MinSDL_GetKeyName(int keycode) -> std::string {
|
||||
static char name[8];
|
||||
char* end;
|
||||
|
||||
// Handle a few specially per platform.
|
||||
if (g_buildconfig.ostype_macos()) {
|
||||
switch (key) {
|
||||
case SDLK_LGUI:
|
||||
return "Left Command";
|
||||
case SDLK_RGUI:
|
||||
return "Right Command";
|
||||
case SDLK_LALT:
|
||||
return "Left Option";
|
||||
case SDLK_RALT:
|
||||
return "Right Option";
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (key & SDLK_SCANCODE_MASK) {
|
||||
return GetScancodeName((SDL_Scancode)(key & ~SDLK_SCANCODE_MASK));
|
||||
}
|
||||
|
||||
@ -48,6 +48,7 @@ class BasePython {
|
||||
kTemporarilyUnavailableMessageCall,
|
||||
kInProgressMessageCall,
|
||||
kErrorMessageCall,
|
||||
kSuccessMessageCall,
|
||||
kPurchaseNotValidErrorCall,
|
||||
kPurchaseAlreadyInProgressErrorCall,
|
||||
kVROrientationResetCBMessageCall,
|
||||
@ -111,6 +112,7 @@ class BasePython {
|
||||
kGetDevConsoleTabNamesCall,
|
||||
kAppDevConsoleDoRefreshTabCall,
|
||||
kUnsupportedControllerMessageCall,
|
||||
kGetV2AccountIdCall,
|
||||
kLast // Sentinel; must be at end.
|
||||
};
|
||||
|
||||
|
||||
@ -94,8 +94,8 @@ auto PythonClassAppTimer::tp_new(PyTypeObject* type, PyObject* args,
|
||||
auto runnable(Object::New<Runnable, PythonContextCallRunnable>(call_obj));
|
||||
|
||||
self->timer_id_ = g_base->logic->NewAppTimer(
|
||||
static_cast<millisecs_t>(length * 1000.0), repeat,
|
||||
Object::New<Runnable, PythonContextCallRunnable>(call_obj));
|
||||
static_cast<microsecs_t>(length * 1000000.0), repeat,
|
||||
Object::New<Runnable, PythonContextCallRunnable>(call_obj).Get());
|
||||
|
||||
self->have_timer_ = true;
|
||||
|
||||
|
||||
@ -104,7 +104,7 @@ auto PythonClassDisplayTimer::tp_new(PyTypeObject* type, PyObject* args,
|
||||
}
|
||||
self->timer_id_ = g_base->logic->NewDisplayTimer(
|
||||
static_cast<microsecs_t>(length * 1000000.0), repeat,
|
||||
Object::New<Runnable, PythonContextCallRunnable>(call_obj));
|
||||
Object::New<Runnable, PythonContextCallRunnable>(call_obj).Get());
|
||||
|
||||
self->have_timer_ = true;
|
||||
|
||||
|
||||
@ -9,7 +9,6 @@
|
||||
#include "ballistica/base/logic/logic.h"
|
||||
#include "ballistica/base/python/base_python.h"
|
||||
#include "ballistica/base/python/support/python_context_call_runnable.h"
|
||||
#include "ballistica/base/support/stress_test.h"
|
||||
#include "ballistica/base/ui/dev_console.h"
|
||||
#include "ballistica/base/ui/ui.h"
|
||||
#include "ballistica/shared/foundation/event_loop.h"
|
||||
@ -365,8 +364,8 @@ static auto PyAppTimer(PyObject* self, PyObject* args, PyObject* keywds)
|
||||
throw Exception("Timer length cannot be < 0.", PyExcType::kValue);
|
||||
}
|
||||
g_base->logic->NewAppTimer(
|
||||
static_cast<millisecs_t>(length * 1000.0), false,
|
||||
Object::New<Runnable, PythonContextCallRunnable>(call_obj));
|
||||
static_cast<microsecs_t>(length * 1000000.0), false,
|
||||
Object::New<Runnable, PythonContextCallRunnable>(call_obj).Get());
|
||||
Py_RETURN_NONE;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
@ -459,7 +458,7 @@ static auto PyDisplayTimer(PyObject* self, PyObject* args, PyObject* keywds)
|
||||
}
|
||||
g_base->logic->NewDisplayTimer(
|
||||
static_cast<microsecs_t>(length * 1000000.0), false,
|
||||
Object::New<Runnable, PythonContextCallRunnable>(call_obj));
|
||||
Object::New<Runnable, PythonContextCallRunnable>(call_obj).Get());
|
||||
Py_RETURN_NONE;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
@ -602,7 +601,7 @@ static auto PyCommitConfig(PyObject* self, PyObject* args, PyObject* keywds)
|
||||
}
|
||||
fclose(f_out);
|
||||
|
||||
// Now backup any existing config to .prev.
|
||||
// Now move any existing config to .prev.
|
||||
if (g_core->platform->FilePathExists(path)) {
|
||||
// On windows, rename doesn't overwrite existing files.. need to kill
|
||||
// the old explicitly.
|
||||
@ -784,32 +783,6 @@ static PyMethodDef PyEnvDef = {
|
||||
"This info is now exposed through babase.App; refer to those docs for\n"
|
||||
"info on specific elements."};
|
||||
|
||||
// -------------------------- set_stress_testing -------------------------------
|
||||
|
||||
static auto PySetStressTesting(PyObject* self, PyObject* args) -> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
int enable;
|
||||
int player_count;
|
||||
if (!PyArg_ParseTuple(args, "pi", &enable, &player_count)) {
|
||||
return nullptr;
|
||||
}
|
||||
g_base->app_adapter->PushMainThreadCall([enable, player_count] {
|
||||
g_base->stress_test()->Set(enable, player_count);
|
||||
});
|
||||
Py_RETURN_NONE;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
static PyMethodDef PySetStressTestingDef = {
|
||||
"set_stress_testing", // name
|
||||
PySetStressTesting, // method
|
||||
METH_VARARGS, // flags
|
||||
|
||||
"set_stress_testing(testing: bool, player_count: int) -> None\n"
|
||||
"\n"
|
||||
"(internal)",
|
||||
};
|
||||
|
||||
// -------------------------------- emit_log -----------------------------------
|
||||
|
||||
static auto PyEmitLog(PyObject* self, PyObject* args, PyObject* keywds)
|
||||
@ -1115,25 +1088,6 @@ static PyMethodDef PyMacMusicAppSetVolumeDef = {
|
||||
"(internal)",
|
||||
};
|
||||
|
||||
// ------------------------ mac_music_app_get_library --------------------------
|
||||
|
||||
static auto PyMacMusicAppGetLibrarySource(PyObject* self, PyObject* args,
|
||||
PyObject* keywds) -> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
g_core->platform->MacMusicAppGetLibrarySource();
|
||||
Py_RETURN_NONE;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
static PyMethodDef PyMacMusicAppGetLibrarySourceDef = {
|
||||
"mac_music_app_get_library_source", // name
|
||||
(PyCFunction)PyMacMusicAppGetLibrarySource, // method
|
||||
METH_VARARGS | METH_KEYWORDS, // flags
|
||||
|
||||
"mac_music_app_get_library_source() -> None\n"
|
||||
"\n"
|
||||
"(internal)"};
|
||||
|
||||
// --------------------------- mac_music_app_stop ------------------------------
|
||||
|
||||
static auto PyMacMusicAppStop(PyObject* self, PyObject* args, PyObject* keywds)
|
||||
@ -1645,6 +1599,52 @@ static PyMethodDef PyAudioShutdownIsCompleteDef = {
|
||||
"(internal)\n",
|
||||
};
|
||||
|
||||
// ----------------------- graphics_shutdown_begin -----------------------------
|
||||
|
||||
static auto PyGraphicsShutdownBegin(PyObject* self) -> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
|
||||
g_base->app_adapter->PushGraphicsContextCall(
|
||||
[] { g_base->graphics_server->Shutdown(); });
|
||||
|
||||
Py_RETURN_NONE;
|
||||
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
static PyMethodDef PyGraphicsShutdownBeginDef = {
|
||||
"graphics_shutdown_begin", // name
|
||||
(PyCFunction)PyGraphicsShutdownBegin, // method
|
||||
METH_NOARGS, // flags
|
||||
|
||||
"graphics_shutdown_begin() -> None\n"
|
||||
"\n"
|
||||
"(internal)\n",
|
||||
};
|
||||
|
||||
// -------------------- graphics_shutdown_is_complete --------------------------
|
||||
|
||||
static auto PyGraphicsShutdownIsComplete(PyObject* self) -> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
|
||||
if (g_base->graphics_server->shutdown_completed()) {
|
||||
Py_RETURN_TRUE;
|
||||
}
|
||||
Py_RETURN_FALSE;
|
||||
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
static PyMethodDef PyGraphicsShutdownIsCompleteDef = {
|
||||
"graphics_shutdown_is_complete", // name
|
||||
(PyCFunction)PyGraphicsShutdownIsComplete, // method
|
||||
METH_NOARGS, // flags
|
||||
|
||||
"graphics_shutdown_is_complete() -> bool\n"
|
||||
"\n"
|
||||
"(internal)\n",
|
||||
};
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
auto PythonMethodsApp::GetMethods() -> std::vector<PyMethodDef> {
|
||||
@ -1656,7 +1656,6 @@ auto PythonMethodsApp::GetMethods() -> std::vector<PyMethodDef> {
|
||||
PyCanDisplayFullUnicodeDef,
|
||||
PyEmitLogDef,
|
||||
PyV1CloudLogDef,
|
||||
PySetStressTestingDef,
|
||||
PyEnvDef,
|
||||
PyPreEnvDef,
|
||||
PyCommitConfigDef,
|
||||
@ -1677,7 +1676,6 @@ auto PythonMethodsApp::GetMethods() -> std::vector<PyMethodDef> {
|
||||
PyMacMusicAppInitDef,
|
||||
PyMacMusicAppGetVolumeDef,
|
||||
PyMacMusicAppSetVolumeDef,
|
||||
PyMacMusicAppGetLibrarySourceDef,
|
||||
PyMacMusicAppStopDef,
|
||||
PyMacMusicAppPlayPlaylistDef,
|
||||
PyMacMusicAppGetPlaylistsDef,
|
||||
@ -1702,6 +1700,8 @@ auto PythonMethodsApp::GetMethods() -> std::vector<PyMethodDef> {
|
||||
PyDevConsoleInputAdapterFinishDef,
|
||||
PyAudioShutdownBeginDef,
|
||||
PyAudioShutdownIsCompleteDef,
|
||||
PyGraphicsShutdownBeginDef,
|
||||
PyGraphicsShutdownIsCompleteDef,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
#include "ballistica/base/assets/assets.h"
|
||||
#include "ballistica/base/graphics/graphics.h"
|
||||
#include "ballistica/base/graphics/support/camera.h"
|
||||
#include "ballistica/base/graphics/support/screen_messages.h"
|
||||
#include "ballistica/base/graphics/text/text_graphics.h"
|
||||
#include "ballistica/base/logic/logic.h"
|
||||
#include "ballistica/base/python/base_python.h"
|
||||
@ -74,7 +75,7 @@ static auto PyScreenMessage(PyObject* self, PyObject* args, PyObject* keywds)
|
||||
}
|
||||
|
||||
// This version simply displays it locally.
|
||||
g_base->graphics->AddScreenMessage(message_str, color);
|
||||
g_base->graphics->screenmessages->AddScreenMessage(message_str, color);
|
||||
|
||||
Py_RETURN_NONE;
|
||||
BA_PYTHON_CATCH;
|
||||
@ -536,6 +537,7 @@ static auto PyFadeScreen(PyObject* self, PyObject* args, PyObject* keywds)
|
||||
&endcall)) {
|
||||
return nullptr;
|
||||
}
|
||||
BA_PRECONDITION(g_base->InLogicThread());
|
||||
g_base->graphics->FadeScreen(static_cast<bool>(fade),
|
||||
static_cast<int>(1000.0f * time), endcall);
|
||||
Py_RETURN_NONE;
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
#include <list>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "ballistica/base/app_adapter/app_adapter.h"
|
||||
#include "ballistica/base/assets/sound_asset.h"
|
||||
#include "ballistica/base/input/input.h"
|
||||
#include "ballistica/base/platform/base_platform.h"
|
||||
@ -128,7 +129,7 @@ static PyMethodDef PyHasTouchScreenDef = {
|
||||
|
||||
static auto PyClipboardIsSupported(PyObject* self) -> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
if (g_core->platform->ClipboardIsSupported()) {
|
||||
if (g_base->app_adapter->ClipboardIsSupported()) {
|
||||
Py_RETURN_TRUE;
|
||||
}
|
||||
Py_RETURN_FALSE;
|
||||
@ -154,7 +155,7 @@ static PyMethodDef PyClipboardIsSupportedDef = {
|
||||
|
||||
static auto PyClipboardHasText(PyObject* self) -> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
if (g_core->platform->ClipboardHasText()) {
|
||||
if (g_base->app_adapter->ClipboardHasText()) {
|
||||
Py_RETURN_TRUE;
|
||||
}
|
||||
Py_RETURN_FALSE;
|
||||
@ -187,7 +188,7 @@ static auto PyClipboardSetText(PyObject* self, PyObject* args, PyObject* keywds)
|
||||
const_cast<char**>(kwlist), &value)) {
|
||||
return nullptr;
|
||||
}
|
||||
g_core->platform->ClipboardSetText(value);
|
||||
g_base->app_adapter->ClipboardSetText(value);
|
||||
Py_RETURN_NONE;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
@ -211,7 +212,7 @@ static PyMethodDef PyClipboardSetTextDef = {
|
||||
|
||||
static auto PyClipboardGetText(PyObject* self) -> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
return PyUnicode_FromString(g_core->platform->ClipboardGetText().c_str());
|
||||
return PyUnicode_FromString(g_base->app_adapter->ClipboardGetText().c_str());
|
||||
Py_RETURN_FALSE;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
@ -1681,6 +1682,87 @@ static PyMethodDef PyAssetLoadsAllowedDef = {
|
||||
"(internal)",
|
||||
};
|
||||
|
||||
// -------------------- using_google_play_game_services ------------------------
|
||||
|
||||
static auto PyUsingGooglePlayGameServices(PyObject* self) -> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
if (g_buildconfig.use_google_play_game_services()) {
|
||||
Py_RETURN_TRUE;
|
||||
}
|
||||
Py_RETURN_FALSE;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
static PyMethodDef PyUsingGooglePlayGameServicesDef = {
|
||||
"using_google_play_game_services", // name
|
||||
(PyCFunction)PyUsingGooglePlayGameServices, // method
|
||||
METH_NOARGS, // flags
|
||||
|
||||
"using_google_play_game_services() -> bool\n"
|
||||
"\n"
|
||||
"(internal)",
|
||||
};
|
||||
|
||||
// ---------------------------- using_game_center ------------------------------
|
||||
|
||||
static auto PyUsingGameCenter(PyObject* self) -> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
if (g_buildconfig.use_game_center()) {
|
||||
Py_RETURN_TRUE;
|
||||
}
|
||||
Py_RETURN_FALSE;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
static PyMethodDef PyUsingGameCenterDef = {
|
||||
"using_game_center", // name
|
||||
(PyCFunction)PyUsingGameCenter, // method
|
||||
METH_NOARGS, // flags
|
||||
|
||||
"using_game_center() -> bool\n"
|
||||
"\n"
|
||||
"(internal)",
|
||||
};
|
||||
|
||||
// --------------------- native_review_request_supported -----------------------
|
||||
|
||||
static auto PyNativeReviewRequestSupported(PyObject* self) -> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
if (g_base->app_adapter->NativeReviewRequestSupported()) {
|
||||
Py_RETURN_TRUE;
|
||||
}
|
||||
Py_RETURN_FALSE;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
static PyMethodDef PyNativeReviewRequestSupportedDef = {
|
||||
"native_review_request_supported", // name
|
||||
(PyCFunction)PyNativeReviewRequestSupported, // method
|
||||
METH_NOARGS, // flags
|
||||
|
||||
"native_review_request_supported() -> bool\n"
|
||||
"\n"
|
||||
"(internal)",
|
||||
};
|
||||
|
||||
// -------------------------- native_review_request ----------------------------
|
||||
|
||||
static auto PyNativeReviewRequest(PyObject* self) -> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
g_base->app_adapter->NativeReviewRequest();
|
||||
Py_RETURN_NONE;
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
static PyMethodDef PyNativeReviewRequestDef = {
|
||||
"native_review_request", // name
|
||||
(PyCFunction)PyNativeReviewRequest, // method
|
||||
METH_NOARGS, // flags
|
||||
|
||||
"native_review_request() -> None\n"
|
||||
"\n"
|
||||
"(internal)",
|
||||
};
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
auto PythonMethodsMisc::GetMethods() -> std::vector<PyMethodDef> {
|
||||
@ -1744,6 +1826,10 @@ auto PythonMethodsMisc::GetMethods() -> std::vector<PyMethodDef> {
|
||||
PyDevConsoleBaseScaleDef,
|
||||
PyDevConsoleRequestRefreshDef,
|
||||
PyAssetLoadsAllowedDef,
|
||||
PyUsingGooglePlayGameServicesDef,
|
||||
PyUsingGameCenterDef,
|
||||
PyNativeReviewRequestSupportedDef,
|
||||
PyNativeReviewRequestDef,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -13,16 +13,24 @@ namespace ballistica::base {
|
||||
|
||||
class AppTimer : public Object {
|
||||
public:
|
||||
AppTimer(millisecs_t length, bool repeat,
|
||||
const Object::Ref<Runnable>& runnable) {
|
||||
AppTimer(seconds_t length, bool repeat, Runnable* runnable) {
|
||||
assert(g_base->InLogicThread());
|
||||
timer_id_ = base::g_base->logic->NewAppTimer(length, repeat, runnable);
|
||||
timer_id_ = base::g_base->logic->NewAppTimer(
|
||||
static_cast<microsecs_t>(length * 1000000.0), repeat, runnable);
|
||||
}
|
||||
|
||||
void SetLength(millisecs_t length) {
|
||||
assert(g_base->InLogicThread());
|
||||
base::g_base->logic->SetAppTimerLength(timer_id_, length);
|
||||
template <typename F>
|
||||
static auto New(seconds_t length, bool repeat, const F& lambda) {
|
||||
return Object::New<AppTimer>(length, repeat,
|
||||
NewLambdaRunnable<F>(lambda).Get());
|
||||
}
|
||||
|
||||
void SetLength(seconds_t length) {
|
||||
assert(g_base->InLogicThread());
|
||||
base::g_base->logic->SetAppTimerLength(
|
||||
timer_id_, static_cast<microsecs_t>(length * 1000000.0));
|
||||
}
|
||||
|
||||
~AppTimer() override {
|
||||
assert(g_base->InLogicThread());
|
||||
base::g_base->logic->DeleteAppTimer(timer_id_);
|
||||
@ -32,13 +40,6 @@ class AppTimer : public Object {
|
||||
int timer_id_;
|
||||
};
|
||||
|
||||
/// Create a AppTimer from a raw lambda.
|
||||
template <typename F>
|
||||
auto NewAppTimer(millisecs_t length, bool repeat, const F& lambda)
|
||||
-> Object::Ref<AppTimer> {
|
||||
return Object::New<AppTimer>(length, repeat, NewLambdaRunnable<F>(lambda));
|
||||
}
|
||||
|
||||
} // namespace ballistica::base
|
||||
|
||||
#endif // BALLISTICA_BASE_SUPPORT_APP_TIMER_H_
|
||||
|
||||
120
src/ballistica/base/support/base_build_switches.cc
Normal file
120
src/ballistica/base/support/base_build_switches.cc
Normal file
@ -0,0 +1,120 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#include "ballistica/base/support/base_build_switches.h"
|
||||
|
||||
#if BA_OSTYPE_ANDROID
|
||||
#include "ballistica/base/app_adapter/app_adapter_android.h"
|
||||
#endif
|
||||
#include "ballistica/base/app_adapter/app_adapter_apple.h"
|
||||
#include "ballistica/base/app_adapter/app_adapter_headless.h"
|
||||
#include "ballistica/base/app_adapter/app_adapter_sdl.h"
|
||||
#include "ballistica/base/app_adapter/app_adapter_vr.h"
|
||||
#include "ballistica/base/graphics/graphics.h"
|
||||
#include "ballistica/base/graphics/graphics_vr.h"
|
||||
|
||||
// ------------------------- PLATFORM SELECTION --------------------------------
|
||||
|
||||
// This ugly chunk of macros simply pulls in the correct platform class header
|
||||
// for each platform and defines the actual class g_base->platform will be.
|
||||
|
||||
// Android ---------------------------------------------------------------------
|
||||
|
||||
#if BA_OSTYPE_ANDROID
|
||||
#if BA_GOOGLE_BUILD
|
||||
#include "ballistica/base/platform/android/google/base_plat_andr_google.h"
|
||||
#define BA_PLATFORM_CLASS BasePlatformAndroidGoogle
|
||||
#elif BA_AMAZON_BUILD
|
||||
#include "ballistica/base/platform/android/amazon/base_plat_andr_amazon.h"
|
||||
#define BA_PLATFORM_CLASS BasePlatformAndroidAmazon
|
||||
#elif BA_CARDBOARD_BUILD
|
||||
#include "ballistica/base/platform/android/cardboard/base_pl_an_cardboard.h"
|
||||
#define BA_PLATFORM_CLASS BasePlatformAndroidCardboard
|
||||
#else // Generic android.
|
||||
#include "ballistica/base/platform/android/base_platform_android.h"
|
||||
#define BA_PLATFORM_CLASS BasePlatformAndroid
|
||||
#endif // (Android subplatform)
|
||||
|
||||
// Apple -----------------------------------------------------------------------
|
||||
|
||||
#elif BA_OSTYPE_MACOS || BA_OSTYPE_IOS_TVOS
|
||||
#include "ballistica/base/platform/apple/base_platform_apple.h"
|
||||
#define BA_PLATFORM_CLASS BasePlatformApple
|
||||
|
||||
// Windows ---------------------------------------------------------------------
|
||||
|
||||
#elif BA_OSTYPE_WINDOWS
|
||||
#if BA_RIFT_BUILD
|
||||
#include "ballistica/base/platform/windows/base_platform_windows_oculus.h"
|
||||
#define BA_PLATFORM_CLASS BasePlatformWindowsOculus
|
||||
#else // generic windows
|
||||
#include "ballistica/base/platform/windows/base_platform_windows.h"
|
||||
#define BA_PLATFORM_CLASS BasePlatformWindows
|
||||
#endif // windows subtype
|
||||
|
||||
// Linux -----------------------------------------------------------------------
|
||||
|
||||
#elif BA_OSTYPE_LINUX
|
||||
#include "ballistica/base/platform/linux/base_platform_linux.h"
|
||||
#define BA_PLATFORM_CLASS BasePlatformLinux
|
||||
#else
|
||||
|
||||
// Generic ---------------------------------------------------------------------
|
||||
|
||||
#define BA_PLATFORM_CLASS BasePlatform
|
||||
|
||||
#endif
|
||||
|
||||
// ----------------------- END PLATFORM SELECTION ------------------------------
|
||||
|
||||
#ifndef BA_PLATFORM_CLASS
|
||||
#error no BA_PLATFORM_CLASS defined for this platform
|
||||
#endif
|
||||
|
||||
namespace ballistica::base {
|
||||
|
||||
auto BaseBuildSwitches::CreatePlatform() -> BasePlatform* {
|
||||
auto platform = new BA_PLATFORM_CLASS();
|
||||
platform->PostInit();
|
||||
assert(platform->ran_base_post_init());
|
||||
return platform;
|
||||
}
|
||||
|
||||
auto BaseBuildSwitches::CreateGraphics() -> Graphics* {
|
||||
#if BA_VR_BUILD
|
||||
return new GraphicsVR();
|
||||
#else
|
||||
return new Graphics();
|
||||
#endif
|
||||
}
|
||||
|
||||
auto BaseBuildSwitches::CreateAppAdapter() -> AppAdapter* {
|
||||
assert(g_core);
|
||||
|
||||
AppAdapter* app_adapter{};
|
||||
|
||||
#if BA_HEADLESS_BUILD
|
||||
app_adapter = new AppAdapterHeadless();
|
||||
#elif BA_OSTYPE_ANDROID
|
||||
app_adapter = new AppAdapterAndroid();
|
||||
#elif BA_XCODE_BUILD
|
||||
app_adapter = new AppAdapterApple();
|
||||
#elif BA_RIFT_BUILD
|
||||
// Rift build can spin up in either VR or regular mode.
|
||||
if (g_core->vr_mode) {
|
||||
app_adapter = new AppAdapterVR();
|
||||
} else {
|
||||
app_adapter = new AppAdapterSDL();
|
||||
}
|
||||
#elif BA_CARDBOARD_BUILD
|
||||
app_adapter = new AppAdapterVR();
|
||||
#elif BA_SDL_BUILD
|
||||
app_adapter = new AppAdapterSDL();
|
||||
#else
|
||||
#error No app adapter defined for this build.
|
||||
#endif
|
||||
|
||||
assert(app_adapter);
|
||||
return app_adapter;
|
||||
}
|
||||
|
||||
} // namespace ballistica::base
|
||||
20
src/ballistica/base/support/base_build_switches.h
Normal file
20
src/ballistica/base/support/base_build_switches.h
Normal file
@ -0,0 +1,20 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_BASE_SUPPORT_BASE_BUILD_SWITCHES_H_
|
||||
#define BALLISTICA_BASE_SUPPORT_BASE_BUILD_SWITCHES_H_
|
||||
|
||||
#include "ballistica/base/base.h"
|
||||
|
||||
namespace ballistica::base {
|
||||
|
||||
/// Constructs various app components based on the current build config.
|
||||
class BaseBuildSwitches {
|
||||
public:
|
||||
static auto CreateGraphics() -> Graphics*;
|
||||
static auto CreatePlatform() -> BasePlatform*;
|
||||
static auto CreateAppAdapter() -> AppAdapter*;
|
||||
};
|
||||
|
||||
} // namespace ballistica::base
|
||||
|
||||
#endif // BALLISTICA_BASE_SUPPORT_BASE_BUILD_SWITCHES_H_
|
||||
45
src/ballistica/base/support/display_timer.h
Normal file
45
src/ballistica/base/support/display_timer.h
Normal file
@ -0,0 +1,45 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_BASE_SUPPORT_DISPLAY_TIMER_H_
|
||||
#define BALLISTICA_BASE_SUPPORT_DISPLAY_TIMER_H_
|
||||
|
||||
#include "ballistica/base/base.h"
|
||||
#include "ballistica/base/logic/logic.h"
|
||||
#include "ballistica/shared/ballistica.h"
|
||||
#include "ballistica/shared/foundation/object.h"
|
||||
#include "ballistica/shared/generic/lambda_runnable.h"
|
||||
|
||||
namespace ballistica::base {
|
||||
|
||||
class DisplayTimer : public Object {
|
||||
public:
|
||||
DisplayTimer(seconds_t length, bool repeat, Runnable* runnable) {
|
||||
assert(g_base->InLogicThread());
|
||||
timer_id_ = base::g_base->logic->NewDisplayTimer(
|
||||
static_cast<microsecs_t>(length * 1000000.0), repeat, runnable);
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
static auto New(seconds_t length, bool repeat, const F& lambda) {
|
||||
return Object::New<DisplayTimer>(length, repeat,
|
||||
NewLambdaRunnable<F>(lambda).Get());
|
||||
}
|
||||
|
||||
void SetLength(seconds_t length) {
|
||||
assert(g_base->InLogicThread());
|
||||
base::g_base->logic->SetDisplayTimerLength(
|
||||
timer_id_, static_cast<microsecs_t>(length * 1000000.0));
|
||||
}
|
||||
|
||||
~DisplayTimer() override {
|
||||
assert(g_base->InLogicThread());
|
||||
base::g_base->logic->DeleteDisplayTimer(timer_id_);
|
||||
}
|
||||
|
||||
private:
|
||||
int timer_id_;
|
||||
};
|
||||
|
||||
} // namespace ballistica::base
|
||||
|
||||
#endif // BALLISTICA_BASE_SUPPORT_DISPLAY_TIMER_H_
|
||||
@ -33,6 +33,7 @@ class PlusSoftInterface {
|
||||
virtual auto PutLog(bool fatal) -> bool = 0;
|
||||
virtual void AAT() = 0;
|
||||
virtual void AATE() = 0;
|
||||
virtual auto GAHU() -> std::optional<std::string> = 0;
|
||||
virtual void V1LoginDidChange() = 0;
|
||||
virtual void SetAdCompletionCall(PyObject* obj,
|
||||
bool pass_actually_showed) = 0;
|
||||
|
||||
55
src/ballistica/base/support/repeater.cc
Normal file
55
src/ballistica/base/support/repeater.cc
Normal file
@ -0,0 +1,55 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#include "ballistica/base/support/repeater.h"
|
||||
|
||||
#include "ballistica/base/support/display_timer.h"
|
||||
#include "ballistica/shared/foundation/event_loop.h"
|
||||
|
||||
namespace ballistica::base {
|
||||
Repeater::Repeater(seconds_t initial_delay, seconds_t repeat_delay,
|
||||
Runnable* runnable)
|
||||
: initial_delay_(initial_delay),
|
||||
repeat_delay_(repeat_delay),
|
||||
runnable_(runnable) {
|
||||
assert(g_base->InLogicThread());
|
||||
assert(initial_delay >= 0.0);
|
||||
assert(repeat_delay >= 0.0);
|
||||
|
||||
// Let's go ahead and run our initial time in a deferred call;
|
||||
// this is generally safer than running in the middle of whatever UI
|
||||
// code set this up.
|
||||
auto weak_this = Object::WeakRef<Repeater>(this);
|
||||
g_base->logic->event_loop()->PushCall([weak_this] {
|
||||
if (weak_this.Exists()) {
|
||||
weak_this->runnable_->RunAndLogErrors();
|
||||
if (!weak_this.Exists()) {
|
||||
// Runnable might have killed us.
|
||||
return;
|
||||
}
|
||||
// Kick off our initial delay timer (generally the longer one).
|
||||
weak_this->timer_ =
|
||||
DisplayTimer::New(weak_this->initial_delay_, false, [weak_this] {
|
||||
// Timer should not have fired if we died.
|
||||
assert(weak_this.Exists());
|
||||
weak_this->runnable_->RunAndLogErrors();
|
||||
if (!weak_this.Exists()) {
|
||||
// Runnable might have killed us.
|
||||
return;
|
||||
}
|
||||
// Kick off our repeat timer (generally the short one).
|
||||
weak_this->timer_ =
|
||||
DisplayTimer::New(weak_this->repeat_delay_, true, [weak_this] {
|
||||
// Timer should not have fired if we died.
|
||||
assert(weak_this.Exists());
|
||||
weak_this->runnable_->RunAndLogErrors();
|
||||
// Doesn't matter if Runnable killed us since we don't
|
||||
// touch anything for the remainder of this function.
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Repeater::~Repeater() { assert(g_base->InLogicThread()); }
|
||||
|
||||
} // namespace ballistica::base
|
||||
36
src/ballistica/base/support/repeater.h
Normal file
36
src/ballistica/base/support/repeater.h
Normal file
@ -0,0 +1,36 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_BASE_SUPPORT_REPEATER_H_
|
||||
#define BALLISTICA_BASE_SUPPORT_REPEATER_H_
|
||||
|
||||
#include "ballistica/base/base.h"
|
||||
#include "ballistica/shared/foundation/object.h"
|
||||
#include "ballistica/shared/generic/lambda_runnable.h"
|
||||
|
||||
namespace ballistica::base {
|
||||
|
||||
/// Runs some code immediately and then repeatedly after a delay. Useful for
|
||||
/// jobs such as selecting ui elements while keys or buttons are held.
|
||||
/// Uses display-time so emphasizes visual smoothness over accuracy.
|
||||
class Repeater : public Object {
|
||||
public:
|
||||
Repeater(seconds_t initial_delay, seconds_t repeat_delay, Runnable* runnable);
|
||||
~Repeater();
|
||||
|
||||
template <typename F>
|
||||
static auto New(seconds_t initial_delay, seconds_t repeat_delay,
|
||||
const F& lambda) {
|
||||
return Object::New<Repeater>(initial_delay, repeat_delay,
|
||||
NewLambdaRunnable<F>(lambda).Get());
|
||||
}
|
||||
|
||||
private:
|
||||
seconds_t initial_delay_;
|
||||
seconds_t repeat_delay_;
|
||||
Object::Ref<DisplayTimer> timer_;
|
||||
Object::Ref<Runnable> runnable_;
|
||||
};
|
||||
|
||||
} // namespace ballistica::base
|
||||
|
||||
#endif // BALLISTICA_BASE_SUPPORT_REPEATER_H_
|
||||
@ -1,25 +0,0 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_BASE_SUPPORT_STRESS_TEST_H_
|
||||
#define BALLISTICA_BASE_SUPPORT_STRESS_TEST_H_
|
||||
|
||||
#include "ballistica/shared/ballistica.h"
|
||||
|
||||
namespace ballistica::base {
|
||||
|
||||
class StressTest {
|
||||
public:
|
||||
void Set(bool enable, int player_count);
|
||||
void Update();
|
||||
|
||||
private:
|
||||
FILE* stress_test_stats_file_{};
|
||||
millisecs_t last_stress_test_update_time_{};
|
||||
bool stress_testing_{};
|
||||
int stress_test_player_count_{8};
|
||||
int last_total_frames_rendered_{};
|
||||
};
|
||||
|
||||
} // namespace ballistica::base
|
||||
|
||||
#endif // BALLISTICA_BASE_SUPPORT_STRESS_TEST_H_
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user