diff --git a/.efrocachemap b/.efrocachemap index ca911bf6b..348c166d7 100644 --- a/.efrocachemap +++ b/.efrocachemap @@ -1097,10 +1097,10 @@ "build/assets/ba_data/textures/achievementWall.ktx": "9c5b06b616911cecc2331c43e7edc668", "build/assets/ba_data/textures/achievementWall.pvr": "7afe0c2046860cb430c22c94b1bcbea2", "build/assets/ba_data/textures/achievementWall_preview.png": "539096cae5443d9480749c82653d04da", - "build/assets/ba_data/textures/achievementsIcon.dds": "f4dd0a2591930bc33db3681bb281d558", - "build/assets/ba_data/textures/achievementsIcon.ktx": "d71ee31bb40e4406922347699257b4e7", - "build/assets/ba_data/textures/achievementsIcon.pvr": "cf79fb381cb5c1a23bf12f959882d10b", - "build/assets/ba_data/textures/achievementsIcon_preview.png": "28504b2d21ff24b10c8dc73f8c5d946a", + "build/assets/ba_data/textures/achievementsIcon.dds": "f7a5a3ec22e936ff10db40d500c21841", + "build/assets/ba_data/textures/achievementsIcon.ktx": "0dd71ce78f36551d64a38b6df20e65f2", + "build/assets/ba_data/textures/achievementsIcon.pvr": "e04a0eeb3961c13da64fc3e11464611c", + "build/assets/ba_data/textures/achievementsIcon_preview.png": "3f066ef09369ba91bedd21e5cc803d08", "build/assets/ba_data/textures/actionButtons.dds": "c5752f3d092f73ae28cf1ddaa30d55b5", "build/assets/ba_data/textures/actionButtons.ktx": "962f628fed3828f2c8b55e8db1d8d1f9", "build/assets/ba_data/textures/actionButtons.pvr": "0ab1f0f5d6c593ab61ef2f4018efe52a", @@ -1849,10 +1849,10 @@ "build/assets/ba_data/textures/lock.ktx": "9401b3c624fa853d649374320a7dd012", "build/assets/ba_data/textures/lock.pvr": "10325fd3c40d8c794b09123dda3ce109", "build/assets/ba_data/textures/lock_preview.png": "2f8f8bd6bba8d1baed19c4419ed92a91", - "build/assets/ba_data/textures/logIcon.dds": "5c1e6f828f8edde2ec68cb2a35eb8c2e", - "build/assets/ba_data/textures/logIcon.ktx": "67f801f1b6e8192a8a22b29a03007795", - "build/assets/ba_data/textures/logIcon.pvr": "e16534c4133b0807f15ff9af2dbf2ddd", - "build/assets/ba_data/textures/logIcon_preview.png": "9f7ed5f9551f874c06e933761fe31f33", + "build/assets/ba_data/textures/logIcon.dds": "f4c7479bc72896caee2e0e4b63fc0fc9", + "build/assets/ba_data/textures/logIcon.ktx": "33866b0cfdfee47e1f73287b52430377", + "build/assets/ba_data/textures/logIcon.pvr": "c78fbf5a2d743af9fbfae378f167133e", + "build/assets/ba_data/textures/logIcon_preview.png": "c9ca591757ed38c30a509f3ec7d52b69", "build/assets/ba_data/textures/logo.dds": "f87d6e42bbf4695fe80042b3b12e3716", "build/assets/ba_data/textures/logo.ktx": "a114cf87faa542989a54e48d6f7bd896", "build/assets/ba_data/textures/logo.pvr": "55bef5b04c85309415ad65b8ad5daa2a", @@ -4293,27 +4293,27 @@ "build/assets/windows/x64/python_d.exe": "d74c06925b7eefca7ba697b1eb13f6a1", "build/assets/windows/x64/pythonw.exe": "007fb5e669a3b6b3e8facf0728b87521", "build/assets/windows/x64/pythonw_d.exe": "46925c0fa3ca3fd29a33e54ee31f6cd5", - "build/assets/windows/x64/vc_redist.x64.exe": "b9a4d05fb2699c78e6607a385cd784cf", - "build/prefab/full/linux_arm64_gui/debug/ballisticakit": "707a7d51f4077fe0478eeac977b81f4f", - "build/prefab/full/linux_arm64_gui/release/ballisticakit": "bc0b27d97523d870fe6acb4c4ee3cb6e", - "build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "0e4ec3c6f5922984cda63a45a5d58701", - "build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "1546657c42c85421a42e2613340bee28", - "build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "b15d0b08818025e9e81af5ac06be7244", - "build/prefab/full/linux_x86_64_gui/release/ballisticakit": "eb4cb85947e1cd4346876520b4db7c62", - "build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "de1e9d3ff6c3531623285b9f2fa6c89d", - "build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "597df00c3ebcc77ee16c3bb29052bc4d", - "build/prefab/full/mac_arm64_gui/debug/ballisticakit": "e9e19188c49aa4fcf3f87c9317fe1743", - "build/prefab/full/mac_arm64_gui/release/ballisticakit": "f4101cc38b87b3d7ec2ce66a781bb712", - "build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "6f09da42b4f1bc9c2915da161c51e7ea", - "build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "e86203b2885d675a3218bcafc8f7718e", - "build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "f1131deea7fcfe98e72accfe3a085930", - "build/prefab/full/mac_x86_64_gui/release/ballisticakit": "06d98e3a1b4dbd11ed00d518b6fa8a4e", - "build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "27f38fc3548dddeadf3238a3506090b2", - "build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "4cffbcdd5003bbb4791b6639e8f22c9b", - "build/prefab/full/windows_x86_64_gui/debug/BallisticaKit.exe": "b2229ced2839e55addfe4f4281fe80be", - "build/prefab/full/windows_x86_64_gui/release/BallisticaKit.exe": "87fe85ef1dd530f3c7ce77be0b4d0937", - "build/prefab/full/windows_x86_64_server/debug/dist/BallisticaKitHeadless.exe": "9682e99409ab46fcd6e4a284c7d36526", - "build/prefab/full/windows_x86_64_server/release/dist/BallisticaKitHeadless.exe": "0a52dc00ad50a70bafbfc1d6e2330c06", + "build/assets/windows/x64/vc_redist.x64.exe": "a8cf8406eae3c38b6bc3285685266b47", + "build/prefab/full/linux_arm64_gui/debug/ballisticakit": "21cee91b1ece573534091cd6c935104f", + "build/prefab/full/linux_arm64_gui/release/ballisticakit": "90ec6b92f8e2ba29050d5c05bf894283", + "build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "d8c456a348b76913b14fa33700e726aa", + "build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "9ca3acf7cf40566d6e34ec05c4bbc7a6", + "build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "3fd046d38e6892b0e43b89063de17a0e", + "build/prefab/full/linux_x86_64_gui/release/ballisticakit": "5e94c02c4f20723f7634467b795ffe75", + "build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "d915c31feff3c3118391ba78a56b0461", + "build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "d1278e2331dcf42014f59ba933a8111d", + "build/prefab/full/mac_arm64_gui/debug/ballisticakit": "ab435bd7ffd171a368381fabdba1da04", + "build/prefab/full/mac_arm64_gui/release/ballisticakit": "190e50193ddc8fbd013450a5e11888cd", + "build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "5744d736182c405fd2e600625cb24082", + "build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "afba5ed4f43c4a855e3e809a94cdcccd", + "build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "ea7e43cecc07cda5b7014b15df8037d5", + "build/prefab/full/mac_x86_64_gui/release/ballisticakit": "690ffb86ee91f1bc852e45d39ef3cba9", + "build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "5e5f18f9b25b9634cdda56e7376b98bb", + "build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "a3157bfb3c176a70a4ba476741b4ea44", + "build/prefab/full/windows_x86_64_gui/debug/BallisticaKit.exe": "4df752bc4397776685da8428819a52af", + "build/prefab/full/windows_x86_64_gui/release/BallisticaKit.exe": "bc04149c811a631d0782ea622ca975e6", + "build/prefab/full/windows_x86_64_server/debug/dist/BallisticaKitHeadless.exe": "4c8cdabb0ee77273071974ea5fe68c81", + "build/prefab/full/windows_x86_64_server/release/dist/BallisticaKitHeadless.exe": "198586a8d2a21a3640f5a1298364419d", "build/prefab/lib/linux_arm64_gui/debug/libballisticaplus.a": "f240ea6a101cc0639b38b2b2ef8be765", "build/prefab/lib/linux_arm64_gui/release/libballisticaplus.a": "ffba3501949fc0afdd4cdc8bfa3f232d", "build/prefab/lib/linux_arm64_server/debug/libballisticaplus.a": "f240ea6a101cc0639b38b2b2ef8be765", @@ -4330,14 +4330,14 @@ "build/prefab/lib/mac_x86_64_gui/release/libballisticaplus.a": "11ad60788139bf7f2e3dac01452d4f8b", "build/prefab/lib/mac_x86_64_server/debug/libballisticaplus.a": "ce027ca768b327eed0e7168a76e466f9", "build/prefab/lib/mac_x86_64_server/release/libballisticaplus.a": "11ad60788139bf7f2e3dac01452d4f8b", - "build/prefab/lib/windows/Debug_x64/BallisticaKitGenericPlus.lib": "502464f22ae468f52d2c0e5dd009a6f6", - "build/prefab/lib/windows/Debug_x64/BallisticaKitGenericPlus.pdb": "2c1c025d623f5608bb5f0fd5bc4d7d18", - "build/prefab/lib/windows/Debug_x64/BallisticaKitHeadlessPlus.lib": "e6621b14cb58a65016c860749d1f1614", - "build/prefab/lib/windows/Debug_x64/BallisticaKitHeadlessPlus.pdb": "f50b35617caadb9763c6ef180739aad0", - "build/prefab/lib/windows/Release_x64/BallisticaKitGenericPlus.lib": "c54c8407f2699abef485368828a44383", - "build/prefab/lib/windows/Release_x64/BallisticaKitGenericPlus.pdb": "2eeee3879269ec845b38dc118e9115b3", - "build/prefab/lib/windows/Release_x64/BallisticaKitHeadlessPlus.lib": "393611824bdecf472af64105df024450", - "build/prefab/lib/windows/Release_x64/BallisticaKitHeadlessPlus.pdb": "8c5799a8f9487947602a32277694ef81", + "build/prefab/lib/windows/Debug_x64/BallisticaKitGenericPlus.lib": "e2525dcf0455e8297f3ed1001e4be8a7", + "build/prefab/lib/windows/Debug_x64/BallisticaKitGenericPlus.pdb": "ff685763cc97256368a4a7e420cc33fd", + "build/prefab/lib/windows/Debug_x64/BallisticaKitHeadlessPlus.lib": "1cb757f5af077ea54544f8a883613845", + "build/prefab/lib/windows/Debug_x64/BallisticaKitHeadlessPlus.pdb": "03ff4c5f0e1722e22fd8f09a2b8cb0b7", + "build/prefab/lib/windows/Release_x64/BallisticaKitGenericPlus.lib": "7ee67fba7d5bffb84e28f900e35ce02c", + "build/prefab/lib/windows/Release_x64/BallisticaKitGenericPlus.pdb": "a2102df90aa6358eee50f6af51553551", + "build/prefab/lib/windows/Release_x64/BallisticaKitHeadlessPlus.lib": "58a5f4d41cbc855cde142b75db7296a2", + "build/prefab/lib/windows/Release_x64/BallisticaKitHeadlessPlus.pdb": "aaf691cfa9db1995718b2bb39737d3a2", "src/assets/ba_data/python/babase/_mgen/__init__.py": "f885fed7f2ed98ff2ba271f9dbe3391c", "src/assets/ba_data/python/babase/_mgen/enums.py": "f2642a60fef55f7792cb1dfe03446cb1", "src/ballistica/base/mgen/pyembed/binding_base.inc": "943cdbda1dcf399783675b115c22dae5", diff --git a/.idea/dictionaries/ericf.xml b/.idea/dictionaries/ericf.xml index d7b167000..135b46c12 100644 --- a/.idea/dictionaries/ericf.xml +++ b/.idea/dictionaries/ericf.xml @@ -915,6 +915,7 @@ entrystorename entrytype entrytypeselect + enumspython enumtype enumval enumvaltype @@ -1232,7 +1233,6 @@ getcollisionmesh getconf getconfig - gettickets getcwd getdata getenv @@ -1273,6 +1273,7 @@ getstarttime gettext gettexture + gettickets gettime getuisound gfile @@ -2423,7 +2424,6 @@ pythondevmode pythondirs pythondontwritebytecode - enumspython pythonhashseed pythonoptimize pythonpath @@ -3408,4 +3408,4 @@ zval - + \ No newline at end of file diff --git a/.idea/discord.xml b/.idea/discord.xml new file mode 100644 index 000000000..1389836cd --- /dev/null +++ b/.idea/discord.xml @@ -0,0 +1,14 @@ + + + + + \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index ea694965a..cd4fb6aaa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,12 @@ -### 1.7.54 (build 22599, api 9, 2025-10-27) +### 1.7.54 (build 22604, api 9, 2025-10-29) - `scrollwidget` and `hscrollwidget` now center selected items that are too large to fit completely in view instead of unpredictably scrolling to the beginning or end of them. This makes show-buffer values (which effectively make things bigger in the scrollwidget's eyes) more intuitive to use. +- Added new `button_type` values for `bauiv1.buttonwidget()`: 'small', 'medium', + 'large', and 'larger'. These correspond to the styles that are normally + selected based on button dimensions; you can now choose them explicitly if you + like. ### 1.7.53 (build 22597, api 9, 2025-10-25) - Fixes an issue where deleting player profiles would error. diff --git a/CHANGELOG_BSU.md b/CHANGELOG_BSU.md new file mode 100644 index 000000000..976f49eaf --- /dev/null +++ b/CHANGELOG_BSU.md @@ -0,0 +1,5 @@ +### 1.0.0 (server-build 22435, api 9, 2025-06-20) +- Intial Setup for the project +- Added basic file structure +- Removed gui builds from releases workflow +- Basic command system diff --git a/README.md b/README.md index d6262b1d2..1045b9d32 100644 --- a/README.md +++ b/README.md @@ -1,103 +1,8 @@ -logo - -***bal·lis·tic***: physics of an object in motion; behaving like a projectile. - -***-ica***: collection of things relating to a specific theme. - -[![](https://github.com/efroemling/ballistica/actions/workflows/ci.yml/badge.svg)](https://github.com/efroemling/ballistica/actions/workflows/ci.yml) [![](https://github.com/efroemling/ballistica/actions/workflows/cd.yml/badge.svg)](https://github.com/efroemling/ballistica/actions/workflows/cd.yml) [![](https://github.com/efroemling/ballistica/actions/workflows/nightly.yml/badge.svg)](https://github.com/efroemling/ballistica/actions/workflows/nightly.yml) [![status-badge](https://ci.codeberg.org/api/badges/14102/status.svg)](https://ci.codeberg.org/repos/14102) - -The Ballistica project is the foundation for -[BombSquad](https://www.froemling.net/apps/bombsquad) and potentially other -future projects. - +# Bombsquad Server Utils +This repository aims to add new server utilities and modding + for [BombSquad](https://www.froemling.net/apps/bombsquad) Game server. [Head to the project wiki to get started -](https://github.com/efroemling/ballistica/wiki), or learn more about the -project below. - -### Project Goals - -* **Do one thing and do it well** - - Ballistica is not aiming to be a general purpose game engine. Rather, its goal -is to support creating one particular type of experience: 'physics based -multiplayer action on small diorama-like environments built from real-world -objects'. If you've got something you'd like to create that can fit within that -box (as BombSquad itself does), give Ballistica a look. Of course, there is -nothing preventing you from going and building a first person shooter out of -this stuff, but I wouldn't recommend it. - -* **Python tomfoolery** - - Ballistica is built on a C++ core for performance-sensitive code with a -Python layer for high level game/app logic. This Python layer is designed to be -mucked with. Users can override core game functionality, write their own mini -games, or anything else they can dream up, either by directly accessing files on -disk or by working through an integrated web-based editor. It can be a fun way -to learn Python without the danger of getting actual work done. - -* **Physics-y goodness** - - I love playing with physics simulations, and Ballistica was built partly to -scratch this itch. Though the game physics in BombSquad have stayed largely -unchanged for a while, my future plans for the engine lean heavily on making -this more flexible and open-ended, opening up lots of fun multiplayer physics-y -potential. Stay tuned... - -* **Community** - - BombSquad started as a 'just for fun' project to play with my friends, and I -want to keep that spirit alive as the Ballistica project moves forward. Whether -this means making it easier to share mods, organize tournaments, join up with -friends, teach each other some Python, or whatever else. Life is short; let's -play some games. Or make them. Maybe both. - -### Frequently Asked Questions - -* **Q: What's with this name? Is it BombSquad or Ballistica?** - * A: BombSquad is the game. Ballistica is the engine. - -* **Q: Does this mean BombSquad is open source?** - * A: Yes and no. All code contained in this repo is MIT licensed and free for - use anywhere. This includes game scripts, pipeline tools, and most of the - binary engine sources. Anything not directly contained in this repository, - however, even if automatically downloaded by build scripts, is still - proprietary and cannot be redistributed without explicit consent. This - includes assets and prebuilt libraries/binaries. So in a nutshell: create and - share mods or use any of this code in your own projects, but please don't - distribute your own complete copies of BombSquad without permission. Please - email support@froemling.net if you have any questions about this. - -* **Q: When are you adding more maps/characters/minigames/etc. to - BombSquad!?!?** - * A: Check out the [Ballistica - Roadmap](https://github.com/efroemling/ballistica/wiki/Roadmap) to get a sense - of what's planned for when. And for the record, the answer to that particular - question is basically '1.8'. - -* **Q: When will BombSquad be released on iOS / Steam / Switch / Xbox / -PlayStation / My toaster??** - * A: The 2.0 update will be the big 'relaunch' release and the plan is to - launch on at least iOS and Steam at that time. I'm trying to get there as fast - as I can. As far as consoles, I'd love to and hope to at some point but have - nothing to announce just yet. - * Check out [Ballistica Roadmap](https://github.com/efroemling/ballistica/wiki/Roadmap) - for more details or the [Ballistica - Downloads](https://ballistica.net/downloads) page for early test builds on - some platforms. - -### Cloning And Contributing - -This repository can be cloned and accepts issues and pull requests from the -following sources +](https://github.com/HeyFang/bombsquad_server_utils/wiki). -* **GitHub** - * Link: https://github.com/efroemling/ballistica - * Cloning via [git](https://git-scm.com): - `git clone https://github.com/efroemling/ballistica.git` +[![](https://github.com/HeyFang/bombsquad_server_utils/actions/workflows/ci.yml/badge.svg)](https://github.com/HeyFang/bombsquad_server_utils/actions/workflows/ci.yml) [![](https://github.com/HeyFang/bombsquad_server_utils/actions/workflows/cd.yml/badge.svg)](https://github.com/HeyFang/bombsquad_server_utils/actions/workflows/cd.yml) [![](https://github.com/HeyFang/bombsquad_server_utils/actions/workflows/nightly.yml/badge.svg)](https://github.com/HeyFang/bombsquad_server_utils/actions/workflows/nightly.yml) -* **Codeberg**: - * Link: https://codeberg.org/3ra/ballistica - * Cloning via [git](https://git-scm.com): - `git clone https://codeberg.org/3ra/ballistica.git` diff --git a/src/assets/.asset_manifest_public.json b/src/assets/.asset_manifest_public.json index b008e2039..4b136176e 100644 --- a/src/assets/.asset_manifest_public.json +++ b/src/assets/.asset_manifest_public.json @@ -157,6 +157,7 @@ "ba_data/python/bascenev1lib/actor/zoomtext.py", "ba_data/python/bascenev1lib/game/__init__.py", "ba_data/python/bascenev1lib/game/assault.py", + "ba_data/python/bascenev1lib/game/boxing.py", "ba_data/python/bascenev1lib/game/capturetheflag.py", "ba_data/python/bascenev1lib/game/chosenone.py", "ba_data/python/bascenev1lib/game/conquest.py", @@ -217,7 +218,11 @@ "ba_data/python/bauiv1lib/appinvite.py", "ba_data/python/bauiv1lib/characterpicker.py", "ba_data/python/bauiv1lib/chest.py", - "ba_data/python/bauiv1lib/cloudui.py", + "ba_data/python/bauiv1lib/cloudui/__init__.py", + "ba_data/python/bauiv1lib/cloudui/_controller.py", + "ba_data/python/bauiv1lib/cloudui/_prep.py", + "ba_data/python/bauiv1lib/cloudui/_test.py", + "ba_data/python/bauiv1lib/cloudui/_window.py", "ba_data/python/bauiv1lib/colorpicker.py", "ba_data/python/bauiv1lib/config.py", "ba_data/python/bauiv1lib/confirm.py", @@ -313,6 +318,23 @@ "ba_data/python/bauiv1lib/utils.py", "ba_data/python/bauiv1lib/v2upgrade.py", "ba_data/python/bauiv1lib/watch.py", + "ba_data/python/bautils/__init__.py", + "ba_data/python/bautils/chat/__init__.py", + "ba_data/python/bautils/chat/chat_handle.py", + "ba_data/python/bautils/chat/cmd_manager.py", + "ba_data/python/bautils/chat/commands/__init__.py", + "ba_data/python/bautils/chat/commands/cheats.py", + "ba_data/python/bautils/chat/commands/gameutils.py", + "ba_data/python/bautils/chat/commands/moderation.py", + "ba_data/python/bautils/chat/commands/partymanage.py", + "ba_data/python/bautils/chat/commands/usercmds.py", + "ba_data/python/bautils/chat/errors.py", + "ba_data/python/bautils/chat/server_command.py", + "ba_data/python/bautils/discord/__init__.py", + "ba_data/python/bautils/plugman/__init__.py", + "ba_data/python/bautils/tools/__init__.py", + "ba_data/python/bautils/tools/ctxmanagers.py", + "ba_data/python/bautils/tools/enums.py", "ba_data/python/efro/__init__.py", "ba_data/python/efro/call.py", "ba_data/python/efro/cloudshell.py", diff --git a/src/assets/Makefile b/src/assets/Makefile index 58dc6f3c7..2a7be07e0 100644 --- a/src/assets/Makefile +++ b/src/assets/Makefile @@ -290,6 +290,7 @@ SCRIPT_TARGETS_PY_PUBLIC = \ $(BUILD_DIR)/ba_data/python/bascenev1lib/actor/zoomtext.py \ $(BUILD_DIR)/ba_data/python/bascenev1lib/game/__init__.py \ $(BUILD_DIR)/ba_data/python/bascenev1lib/game/assault.py \ + $(BUILD_DIR)/ba_data/python/bascenev1lib/game/boxing.py \ $(BUILD_DIR)/ba_data/python/bascenev1lib/game/capturetheflag.py \ $(BUILD_DIR)/ba_data/python/bascenev1lib/game/chosenone.py \ $(BUILD_DIR)/ba_data/python/bascenev1lib/game/conquest.py \ @@ -350,7 +351,11 @@ SCRIPT_TARGETS_PY_PUBLIC = \ $(BUILD_DIR)/ba_data/python/bauiv1lib/appinvite.py \ $(BUILD_DIR)/ba_data/python/bauiv1lib/characterpicker.py \ $(BUILD_DIR)/ba_data/python/bauiv1lib/chest.py \ - $(BUILD_DIR)/ba_data/python/bauiv1lib/cloudui.py \ + $(BUILD_DIR)/ba_data/python/bauiv1lib/cloudui/__init__.py \ + $(BUILD_DIR)/ba_data/python/bauiv1lib/cloudui/_controller.py \ + $(BUILD_DIR)/ba_data/python/bauiv1lib/cloudui/_prep.py \ + $(BUILD_DIR)/ba_data/python/bauiv1lib/cloudui/_test.py \ + $(BUILD_DIR)/ba_data/python/bauiv1lib/cloudui/_window.py \ $(BUILD_DIR)/ba_data/python/bauiv1lib/colorpicker.py \ $(BUILD_DIR)/ba_data/python/bauiv1lib/config.py \ $(BUILD_DIR)/ba_data/python/bauiv1lib/confirm.py \ @@ -446,6 +451,23 @@ SCRIPT_TARGETS_PY_PUBLIC = \ $(BUILD_DIR)/ba_data/python/bauiv1lib/utils.py \ $(BUILD_DIR)/ba_data/python/bauiv1lib/v2upgrade.py \ $(BUILD_DIR)/ba_data/python/bauiv1lib/watch.py \ + $(BUILD_DIR)/ba_data/python/bautils/__init__.py \ + $(BUILD_DIR)/ba_data/python/bautils/chat/__init__.py \ + $(BUILD_DIR)/ba_data/python/bautils/chat/chat_handle.py \ + $(BUILD_DIR)/ba_data/python/bautils/chat/cmd_manager.py \ + $(BUILD_DIR)/ba_data/python/bautils/chat/commands/__init__.py \ + $(BUILD_DIR)/ba_data/python/bautils/chat/commands/cheats.py \ + $(BUILD_DIR)/ba_data/python/bautils/chat/commands/gameutils.py \ + $(BUILD_DIR)/ba_data/python/bautils/chat/commands/moderation.py \ + $(BUILD_DIR)/ba_data/python/bautils/chat/commands/partymanage.py \ + $(BUILD_DIR)/ba_data/python/bautils/chat/commands/usercmds.py \ + $(BUILD_DIR)/ba_data/python/bautils/chat/errors.py \ + $(BUILD_DIR)/ba_data/python/bautils/chat/server_command.py \ + $(BUILD_DIR)/ba_data/python/bautils/discord/__init__.py \ + $(BUILD_DIR)/ba_data/python/bautils/plugman/__init__.py \ + $(BUILD_DIR)/ba_data/python/bautils/tools/__init__.py \ + $(BUILD_DIR)/ba_data/python/bautils/tools/ctxmanagers.py \ + $(BUILD_DIR)/ba_data/python/bautils/tools/enums.py \ $(BUILD_DIR)/server_package/ballisticakit_server.py SCRIPT_TARGETS_SO_PUBLIC = \ diff --git a/src/assets/ba_data/python/babase/_app.py b/src/assets/ba_data/python/babase/_app.py index 8a2673f8d..b991bd653 100644 --- a/src/assets/ba_data/python/babase/_app.py +++ b/src/assets/ba_data/python/babase/_app.py @@ -1059,8 +1059,12 @@ async def _run_shutdown_task( task = asyncio.create_task(coro) try: await asyncio.wait_for(task, self.SHUTDOWN_TASK_TIMEOUT_SECONDS) + except TimeoutError: + # Log simple error message if it times out. + logging.error('Timed out waiting for shutdown task %s.', coro) except Exception: - logging.exception('Error in shutdown task (%s).', coro) + # Go with full ugly stack trace for anything unexpected. + logging.exception('Error in shutdown task %s.', coro) def _on_suspend(self) -> None: """Called when the app goes to a suspended state.""" diff --git a/src/assets/ba_data/python/babase/_language.py b/src/assets/ba_data/python/babase/_language.py index b8f3c6bf8..a2108bbe5 100644 --- a/src/assets/ba_data/python/babase/_language.py +++ b/src/assets/ba_data/python/babase/_language.py @@ -561,7 +561,7 @@ def evaluate(self) -> str: You should avoid doing this as much as possible and instead pass and store ``Lstr`` values. """ - return _babase.evaluate_lstr(self._get_json()) + return _babase.evaluate_lstr(self.as_json()) def is_flat_value(self) -> bool: """Return whether this instance represents a 'flat' value. @@ -573,22 +573,13 @@ def is_flat_value(self) -> bool: """ return bool('v' in self.args and not self.args.get('s', [])) - def _get_json(self) -> str: - try: - return json.dumps(self.args, separators=(',', ':')) - except Exception: - from babase import _error - - applog.exception('_get_json failed for %s.', self.args) - return 'JSON_ERR' - - @override - def __str__(self) -> str: - return f'' + def as_json(self) -> str: + """Return the json dict representation of the Lstr.""" + return json.dumps(self.args, separators=(',', ':')) @override def __repr__(self) -> str: - return f'' + return f'' @staticmethod def from_json(json_string: str) -> babase.Lstr: diff --git a/src/assets/ba_data/python/baclassic/_appmode.py b/src/assets/ba_data/python/baclassic/_appmode.py index b6e3f2d36..ea4a79d56 100644 --- a/src/assets/ba_data/python/baclassic/_appmode.py +++ b/src/assets/ba_data/python/baclassic/_appmode.py @@ -971,11 +971,11 @@ def _main_win_template_press(self) -> None: show_template_main_window() def _cloud_ui_test_press(self) -> None: - from bauiv1lib.cloudui import show_cloud_ui_window + from bauiv1lib.cloudui import show_test_cloud_ui_window # Unintuitively, swish sounds come from buttons, not windows. # And dev-console buttons don't make sounds. So we need to # explicitly do so here. bui.getsound('swish').play() - show_cloud_ui_window() + show_test_cloud_ui_window() diff --git a/src/assets/ba_data/python/baclassic/_servermode.py b/src/assets/ba_data/python/baclassic/_servermode.py index 67d111cfb..2099a103f 100644 --- a/src/assets/ba_data/python/baclassic/_servermode.py +++ b/src/assets/ba_data/python/baclassic/_servermode.py @@ -22,6 +22,8 @@ import babase import bascenev1 + + if TYPE_CHECKING: from typing import Any @@ -115,6 +117,11 @@ def __init__(self, config: ServerConfig) -> None: 0.25, self._prepare_to_serve, repeat=True ) + @property + def config(self) -> ServerConfig: + """Returns the selected server config.""" + return self._config + def print_client_list(self) -> None: """Print info about all connected clients.""" import json @@ -428,7 +435,9 @@ def _launch_server_session(self) -> None: bascenev1.set_enable_default_kick_voting( self._config.enable_default_kick_voting ) + bascenev1.set_enable_admins_kick(self._config.enable_admins_kick) bascenev1.set_admins(self._config.admins) + bascenev1.set_admin_tokens(self._config.admin_tokens) # Call set-enabled last (will push state to the cloud). bascenev1.set_public_party_max_size(self._config.max_party_size) diff --git a/src/assets/ba_data/python/baenv.py b/src/assets/ba_data/python/baenv.py index a6ea0f324..8a36b210b 100644 --- a/src/assets/ba_data/python/baenv.py +++ b/src/assets/ba_data/python/baenv.py @@ -56,7 +56,7 @@ # Build number and version of the ballistica binary we expect to be # using. -TARGET_BALLISTICA_BUILD = 22599 +TARGET_BALLISTICA_BUILD = 22604 TARGET_BALLISTICA_VERSION = '1.7.54' diff --git a/src/assets/ba_data/python/bascenev1/__init__.py b/src/assets/ba_data/python/bascenev1/__init__.py index aead594ed..e2599cd90 100644 --- a/src/assets/ba_data/python/bascenev1/__init__.py +++ b/src/assets/ba_data/python/bascenev1/__init__.py @@ -81,6 +81,7 @@ CollisionMesh, connect_to_party, Data, + get_client_public_device_uuid, disconnect_client, disconnect_from_host, emitfx, @@ -135,9 +136,11 @@ SessionData, SessionPlayer, set_admins, + set_admin_tokens, set_authenticate_clients, - set_debug_speed_exponent, set_enable_default_kick_voting, + set_enable_admins_kick, + set_debug_speed_exponent, set_internal_music, set_map_bounds, set_master_server_source, @@ -305,6 +308,7 @@ 'DependencyComponent', 'DependencySet', 'DieMessage', + 'get_client_public_device_uuid', 'disconnect_client', 'disconnect_from_host', 'displaytime', @@ -438,9 +442,10 @@ 'SessionPlayer', 'SessionTeam', 'set_admins', + 'set_admin_tokens', 'set_analytics_screen', 'set_authenticate_clients', - 'set_debug_speed_exponent', + 'set_enable_admins_kick', 'set_debug_speed_exponent', 'set_enable_default_kick_voting', 'set_internal_music', diff --git a/src/assets/ba_data/python/bascenev1/_activitytypes.py b/src/assets/ba_data/python/bascenev1/_activitytypes.py index b22ec1f25..4c109a4b4 100644 --- a/src/assets/ba_data/python/bascenev1/_activitytypes.py +++ b/src/assets/ba_data/python/bascenev1/_activitytypes.py @@ -18,6 +18,7 @@ if TYPE_CHECKING: import bascenev1 from bascenev1._lobby import JoinInfo + from bautils.tourny import TournamentJoinInfo class EndSessionActivity(Activity[EmptyPlayer, EmptyTeam]): @@ -82,7 +83,7 @@ def __init__(self, settings: dict): self._background: bascenev1.Actor | None = None self._tips_text: bascenev1.Actor | None = None - self._join_info: JoinInfo | None = None + self._join_info: JoinInfo | TournamentJoinInfo | None = None @override def on_transition_in(self) -> None: diff --git a/src/assets/ba_data/python/bascenev1/_hooks.py b/src/assets/ba_data/python/bascenev1/_hooks.py index c531feb3e..690592555 100644 --- a/src/assets/ba_data/python/bascenev1/_hooks.py +++ b/src/assets/ba_data/python/bascenev1/_hooks.py @@ -7,6 +7,7 @@ from typing import TYPE_CHECKING +from bautils.chat import chat_handle import babase import _bascenev1 @@ -41,8 +42,8 @@ def filter_chat_message(msg: str, client_id: int) -> str | None: Should filter and return the string to be displayed, or return None to ignore the message. """ - del client_id # Unused by default. - return msg + # del client_id # Unused by default. + return chat_handle.filter_chat_message(msg, client_id) def local_chat_message(msg: str) -> None: diff --git a/src/assets/ba_data/python/bascenev1/_powerup.py b/src/assets/ba_data/python/bascenev1/_powerup.py index ba11f8671..beb01cd26 100644 --- a/src/assets/ba_data/python/bascenev1/_powerup.py +++ b/src/assets/ba_data/python/bascenev1/_powerup.py @@ -45,13 +45,13 @@ class PowerupAcceptMessage: def get_default_powerup_distribution() -> Sequence[tuple[str, int]]: """Standard set of powerups.""" return ( - ('triple_bombs', 3), - ('ice_bombs', 3), - ('punch', 3), - ('impact_bombs', 3), - ('land_mines', 2), - ('sticky_bombs', 3), - ('shield', 2), - ('health', 1), - ('curse', 1), + ('triple_bombs', 4), + ('ice_bombs', 4), + ('punch', 0), + ('impact_bombs', 4), + ('land_mines', 4), + ('sticky_bombs', 4), + ('shield', 0), + ('health', 0), + ('curse', 0), ) diff --git a/src/assets/ba_data/python/bascenev1/_session.py b/src/assets/ba_data/python/bascenev1/_session.py index 6b5be8ada..21163ad87 100644 --- a/src/assets/ba_data/python/bascenev1/_session.py +++ b/src/assets/ba_data/python/bascenev1/_session.py @@ -69,7 +69,6 @@ class Session: #: The lobby instance where new players go to select a #: profile/team/etc. before being added to games. Be aware this value #: may be None if a session does not allow any such selection. - lobby: bascenev1.Lobby #: The maximum number of players allowed in the Session. max_players: int diff --git a/src/assets/ba_data/python/bascenev1lib/game/boxing.py b/src/assets/ba_data/python/bascenev1lib/game/boxing.py new file mode 100644 index 000000000..630c6fed8 --- /dev/null +++ b/src/assets/ba_data/python/bascenev1lib/game/boxing.py @@ -0,0 +1,226 @@ +# Released under the MIT License. See LICENSE for details. +# +"""Boxing game and support classes... by fang :>""" + +# ba_meta require api 9 +# (see https://ballistica.net/wiki/meta-tag-system) + +from __future__ import annotations + +from typing import TYPE_CHECKING, override + +import bascenev1 as bs + +from bascenev1lib.actor.playerspaz import PlayerSpaz +from bascenev1lib.actor.scoreboard import Scoreboard + +if TYPE_CHECKING: + from typing import Any, Sequence + + +class Player(bs.Player['Team']): + """Our player type for this game.""" + + +class Team(bs.Team[Player]): + """Our team type for this game.""" + + def __init__(self) -> None: + self.score = 0 + + +# ba_meta export bascenev1.GameActivity +class Boxing(bs.TeamGameActivity[Player, Team]): + """A game type based on acquiring kills.""" + + name = 'Boxing!!!' + description = 'Kill a set number of enemies to win.' + + # Print messages when players die since it matters here. + announce_player_deaths = True + + @override + @classmethod + def get_available_settings( + cls, sessiontype: type[bs.Session] + ) -> list[bs.Setting]: + settings = [ + bs.IntSetting( + 'Kills to Win Per Player', + min_value=1, + default=5, + increment=1, + ), + bs.IntChoiceSetting( + 'Time Limit', + choices=[ + ('None', 0), + ('1 Minute', 60), + ('2 Minutes', 120), + ('5 Minutes', 300), + ('10 Minutes', 600), + ('20 Minutes', 1200), + ], + default=0, + ), + bs.FloatChoiceSetting( + 'Respawn Times', + choices=[ + ('Shorter', 0.25), + ('Short', 0.5), + ('Normal', 1.0), + ('Long', 2.0), + ('Longer', 4.0), + ], + default=1.0, + ), + bs.BoolSetting('Epic Mode', default=False), + ] + + # In teams mode, a suicide gives a point to the other team, but in + # free-for-all it subtracts from your own score. By default we clamp + # this at zero to benefit new players, but pro players might like to + # be able to go negative. (to avoid a strategy of just + # suiciding until you get a good drop) + if issubclass(sessiontype, bs.FreeForAllSession): + settings.append( + bs.BoolSetting('Allow Negative Scores', default=False) + ) + + return settings + + @override + @classmethod + def supports_session_type(cls, sessiontype: type[bs.Session]) -> bool: + return issubclass(sessiontype, bs.DualTeamSession) or issubclass( + sessiontype, bs.FreeForAllSession + ) + + @override + @classmethod + def get_supported_maps(cls, sessiontype: type[bs.Session]) -> list[str]: + # (Pylint Bug?) pylint: disable=missing-function-docstring + + assert bs.app.classic is not None + return bs.app.classic.getmaps('melee') + + def __init__(self, settings: dict): + super().__init__(settings) + self._scoreboard = Scoreboard() + self._score_to_win: int | None = None + self._dingsound = bs.getsound('dingSmall') + self._epic_mode = bool(settings['Epic Mode']) + self._kills_to_win_per_player = int(settings['Kills to Win Per Player']) + self._time_limit = float(settings['Time Limit']) + self._allow_negative_scores = bool( + settings.get('Allow Negative Scores', False) + ) + + # Base class overrides. + self.slow_motion = self._epic_mode + self.default_music = ( + bs.MusicType.EPIC if self._epic_mode else bs.MusicType.TO_THE_DEATH + ) + + @override + def get_instance_description(self) -> str | Sequence: + # (Pylint Bug?) pylint: disable=missing-function-docstring + + return 'Crush ${ARG1} of your enemies.', self._score_to_win + + @override + def get_instance_description_short(self) -> str | Sequence: + # (Pylint Bug?) pylint: disable=missing-function-docstring + + return 'kill ${ARG1} enemies', self._score_to_win + + @override + def on_team_join(self, team: Team) -> None: + # (Pylint Bug?) pylint: disable=missing-function-docstring + + if self.has_begun(): + self._update_scoreboard() + + @override + def on_begin(self) -> None: + super().on_begin() + self.setup_standard_time_limit(self._time_limit) + self.setup_standard_powerup_drops() + + # Base kills needed to win on the size of the largest team. + self._score_to_win = self._kills_to_win_per_player * max( + 1, max((len(t.players) for t in self.teams), default=0) + ) + self._update_scoreboard() + + @override + def handlemessage(self, msg: Any) -> Any: + # (Pylint Bug?) pylint: disable=missing-function-docstring + + if isinstance(msg, bs.PlayerDiedMessage): + # Augment standard behavior. + super().handlemessage(msg) + + player = msg.getplayer(Player) + self.respawn_player(player) + + killer = msg.getkillerplayer(Player) + if killer is None: + return None + + # Handle team-kills. + if killer.team is player.team: + # In free-for-all, killing yourself loses you a point. + if isinstance(self.session, bs.FreeForAllSession): + new_score = player.team.score - 1 + if not self._allow_negative_scores: + new_score = max(0, new_score) + player.team.score = new_score + + # In teams-mode it gives a point to the other team. + else: + self._dingsound.play() + for team in self.teams: + if team is not killer.team: + team.score += 1 + + # Killing someone on another team nets a kill. + else: + killer.team.score += 1 + self._dingsound.play() + + # In FFA show scores since its hard to find on the scoreboard. + if isinstance(killer.actor, PlayerSpaz) and killer.actor: + killer.actor.set_score_text( + str(killer.team.score) + '/' + str(self._score_to_win), + color=killer.team.color, + flash=True, + ) + + self._update_scoreboard() + + # If someone has won, set a timer to end shortly. + # (allows the dust to clear and draws to occur if deaths are + # close enough) + assert self._score_to_win is not None + if any(team.score >= self._score_to_win for team in self.teams): + bs.timer(0.5, self.end_game) + + else: + return super().handlemessage(msg) + return None + + def _update_scoreboard(self) -> None: + for team in self.teams: + self._scoreboard.set_team_value( + team, team.score, self._score_to_win + ) + + @override + def end_game(self) -> None: + # (Pylint Bug?) pylint: disable=missing-function-docstring + + results = bs.GameResults() + for team in self.teams: + results.set_team_score(team, team.score) + self.end(results=results) diff --git a/src/assets/ba_data/python/bascenev1lib/game/meteorshower.py b/src/assets/ba_data/python/bascenev1lib/game/meteorshower.py index de400a6dd..391aaee47 100644 --- a/src/assets/ba_data/python/bascenev1lib/game/meteorshower.py +++ b/src/assets/ba_data/python/bascenev1lib/game/meteorshower.py @@ -107,8 +107,11 @@ def on_begin(self) -> None: @override def on_player_leave(self, player: Player) -> None: - # (Pylint Bug?) pylint: disable=missing-function-docstring + """Called when a Player leaves the game. + Args: + player: The Player that left the game. + """ # Augment default behavior. super().on_player_leave(player) @@ -118,8 +121,14 @@ def on_player_leave(self, player: Player) -> None: # overriding the default character spawning.. @override def spawn_player(self, player: Player) -> bs.Actor: - # (Pylint Bug?) pylint: disable=missing-function-docstring + """Create and spawn a player for the game. + Args: + player: The Player to spawn. + + Returns: + bs.Actor: The spawned player spaz actor. + """ spaz = self.spawn_player_spaz(player) # Let's reconnect this player's controls to this @@ -134,8 +143,16 @@ def spawn_player(self, player: Player) -> bs.Actor: # Various high-level game events come through this method. @override + @override def handlemessage(self, msg: Any) -> Any: - """Handle a message.""" + """Handle game messages. + + Args: + msg: The message to handle. + + Returns: + Variable depending on the message type. + """ if isinstance(msg, bs.PlayerDiedMessage): # Augment standard behavior. super().handlemessage(msg) @@ -166,6 +183,11 @@ def handlemessage(self, msg: Any) -> Any: return None def _check_end_game(self) -> None: + """Check if the game should end. + + Ends the game if all players are dead in co-op mode, + or if only one team remains in other modes. + """ # We don't want to end this activity more than once. if self._ended: return @@ -187,6 +209,12 @@ def _check_end_game(self) -> None: self.end_game() def _set_meteor_timer(self) -> None: + """Schedule the next meteor bomb cluster drop. + + Sets a timer to drop the next cluster of meteor bombs after a randomized + delay based on the current meteor_time value. The actual delay will be + between 1.0 and 1.2 times the meteor_time. + """ bs.timer( (1.0 + 0.2 * random.random()) * self._meteor_time, self._drop_bomb_cluster, @@ -225,14 +253,31 @@ def _drop_bomb_cluster(self) -> None: def _drop_bomb( self, position: Sequence[float], velocity: Sequence[float] ) -> None: - Bomb(position=position, velocity=velocity).autoretain() + bomb_types = ['land_mine', 'normal', 'sticky', 'ice', 'impact'] + random_bomb_type = random.choice(bomb_types) + bomb = Bomb( + position=position, bomb_type=random_bomb_type, velocity=velocity + ).autoretain() + + # Only arm land_mine or impact bombs + if random_bomb_type in ['land_mine', 'impact']: + bomb.arm() def _decrement_meteor_time(self) -> None: self._meteor_time = max(0.01, self._meteor_time * 0.9) @override def end_game(self) -> None: - # (Pylint Bug?) pylint: disable=missing-function-docstring + """End the game and calculate final scores. + + This method: + 1. Records final death times for surviving players + 2. Awards points based on survival duration + 3. Gives bonus points to survivors + 4. Stops the game timer + 5. Calculates team scores based on longest survival time + 6. Submits final results + """ cur_time = bs.time() assert self._timer is not None start_time = self._timer.getstarttime() diff --git a/src/assets/ba_data/python/bascenev1lib/maps.py b/src/assets/ba_data/python/bascenev1lib/maps.py index c6aa22568..e7f32b3da 100644 --- a/src/assets/ba_data/python/bascenev1lib/maps.py +++ b/src/assets/ba_data/python/bascenev1lib/maps.py @@ -72,7 +72,7 @@ def on_preload(cls) -> Any: 'stands_tex': bs.gettexture('footballStadium'), } mat = bs.Material() - mat.add_actions(actions=('modify_part_collision', 'friction', 0.01)) + mat.add_actions(actions=('modify_part_collision', 'friction', 1.00)) data['ice_material'] = mat return data diff --git a/src/assets/ba_data/python/bauiv1/_appsubsystem.py b/src/assets/ba_data/python/bauiv1/_appsubsystem.py index 8eb6282f8..b279c3f8c 100644 --- a/src/assets/ba_data/python/bauiv1/_appsubsystem.py +++ b/src/assets/ba_data/python/bauiv1/_appsubsystem.py @@ -558,10 +558,17 @@ def auxiliary_window_activate( # Ok, no existing auxiliary stuff was found period. Just # navigate forward to this UI. - current_main_window.main_window_replace( + new_main_win = current_main_window.main_window_replace( win_create_call, is_auxiliary=True ) + # We should always be allowed to replace the main win in this + # case. + assert new_main_win is not None + + # Make sure what got made exactly matches the type we were passed. + assert type(new_main_win) is win_type + def _schedule_main_win_recreate(self) -> None: # If there is a timer set already, do nothing. diff --git a/src/assets/ba_data/python/bauiv1lib/chest.py b/src/assets/ba_data/python/bauiv1lib/chest.py index f4d4617ac..e3bf755ed 100644 --- a/src/assets/ba_data/python/bauiv1lib/chest.py +++ b/src/assets/ba_data/python/bauiv1lib/chest.py @@ -224,7 +224,7 @@ def get_main_window_state(self) -> bui.MainWindowState: def main_window_should_preserve_selection(self) -> bool: # This doesn't really benefit us since we do lots of widget # creates/destroys throughout our lifetime and also we're an - # auxliary window so should never need to restore toolbar + # auxiliary window so should never need to restore toolbar # selections. return False @@ -978,6 +978,10 @@ def _show_about_chest_slots(self) -> None: text=bui.Lstr(resource='chests.slotDescriptionText'), color=(1, 1, 1), ) + # This is somewhat redundant with the close button, but we need + # to have *something* selectable in our window for SMALL ui-mode + # otherwise we can be left unable to select anything. + self._show_done_button(use_ok_label=True) def _show_chest_contents( self, response: bacommon.bs.ChestActionResponse @@ -1210,7 +1214,7 @@ def _set_img(x: float, scale: float) -> None: initial_highlighted_extra=True, ) - def _show_done_button(self) -> None: + def _show_done_button(self, use_ok_label: bool = False) -> None: # No-op if our ui is dead. if not self._root_widget: return @@ -1225,7 +1229,7 @@ def _show_done_button(self) -> None: self._yoffs - 350, ), size=(bwidth, bheight), - label=bui.Lstr(resource='doneText'), + label=bui.Lstr(resource='okText' if use_ok_label else 'doneText'), autoselect=True, on_activate_call=self.main_window_back, ) diff --git a/src/assets/ba_data/python/bauiv1lib/cloudui.py b/src/assets/ba_data/python/bauiv1lib/cloudui.py deleted file mode 100644 index 0fc03c49d..000000000 --- a/src/assets/ba_data/python/bauiv1lib/cloudui.py +++ /dev/null @@ -1,1370 +0,0 @@ -# Released under the MIT License. See LICENSE for details. -# -# pylint: disable=too-many-lines -"""UIs provided by the cloud (similar-ish to html in concept).""" - -from __future__ import annotations - -import random -from functools import partial -from dataclasses import dataclass -from typing import TYPE_CHECKING, override, assert_never - -import bacommon.cloudui.v1 as clui -import bauiv1 as bui - -from bauiv1lib.utils import scroll_fade_bottom, scroll_fade_top - -if TYPE_CHECKING: - from typing import Callable - - -def show_cloud_ui_window() -> None: - """Bust out a cloud-ui window.""" - - # Pop up an auxiliary window wherever we are in the nav stack. - bui.app.ui_v1.auxiliary_window_activate( - win_type=CloudUIWindow, - win_create_call=lambda: CloudUIWindow(state=None), - ) - - -# Prep-structures for our UI - we do all layout math and bake out -# partial ui calls in a background thread so there's as little work to -# do in the ui thread as possible. - - -@dataclass -class _DecorationPrep: - call: Callable[..., bui.Widget] - textures: dict[str, str] - meshes: dict[str, str] - - -@dataclass -class _ButtonPrep: - buttoncall: Callable[..., bui.Widget] - buttoneditcall: Callable | None - decorations: list[_DecorationPrep] - textures: dict[str, str] - - -@dataclass -class _RowPrep: - width: float - height: float - titlecalls: list[Callable[..., bui.Widget]] - hscrollcall: Callable[..., bui.Widget] | None - hscrolleditcall: Callable | None - hsubcall: Callable[..., bui.Widget] | None - buttons: list[_ButtonPrep] - simple_culling_h: float - decorations: list[_DecorationPrep] - - -@dataclass -class _PagePrep: - rootcall: Callable[..., bui.Widget] | None - rows: list[_RowPrep] - width: float - height: float - simple_culling_v: float - - -def _prep_page( - ui: clui.Page, - uiscale: bui.UIScale, - scroll_width: float, - *, - immediate: bool = False, -) -> _PagePrep: - """Prep a ui.""" - # pylint: disable=too-many-statements - # pylint: disable=too-many-branches - # pylint: disable=too-many-locals - - # Ok; we've got some buttons. Build our full UI. - row_title_height = 30.0 - row_subtitle_height = 30.0 - top_buffer = 20.0 - bot_buffer = 20.0 - left_buffer = 0.0 - right_buffer = 10.0 # Nudge a bit due to scrollbar. - title_inset = 35.0 - default_button_width = 200.0 - default_button_height = 200.0 - - if uiscale is bui.UIScale.SMALL: - top_bar_overlap = 70 - bot_bar_overlap = 70 - top_buffer += top_bar_overlap - bot_buffer += bot_bar_overlap - else: - top_bar_overlap = 0 - bot_bar_overlap = 0 - - # Should look into why this is necessary. - fudge = 15.0 - hscrollinset = 15.0 - - uiprep = _PagePrep( - rootcall=None, - rows=[], - width=scroll_width + fudge, - height=top_buffer + bot_buffer, - simple_culling_v=ui.simple_culling_v, - ) - - # Precalc basic info like dimensions for all rows. - for row in ui.rows: - assert row.buttons - this_row_width = ( - left_buffer - + right_buffer - + row.padding_left - + row.padding_right - + row.button_spacing * (len(row.buttons) - 1) - ) - button_row_height = 0.0 - for button in row.buttons: - if button.size is None: - bwidth = default_button_width - bheight = default_button_height - else: - bwidth = button.size[0] - bheight = button.size[1] - bscale = button.scale - bwidthfull = bwidth * bscale - bheightfull = bheight * bscale - # Include button padding when calcing full needed height. - button_row_height = max( - button_row_height, - bheightfull + button.padding_top + button.padding_bottom, - ) - this_row_width += ( - bwidthfull + button.padding_left + button.padding_right - ) - this_row_height = ( - row.padding_top + row.padding_bottom + button_row_height - ) - uiprep.rows.append( - _RowPrep( - width=this_row_width, - height=this_row_height, - titlecalls=[], - hscrollcall=None, - hscrolleditcall=None, - hsubcall=None, - buttons=[], - simple_culling_h=row.simple_culling_h, - decorations=[], - ) - ) - assert this_row_height > 0.0 - assert this_row_width > 0.0 - if row.title is not None: - uiprep.height += row_title_height - if row.subtitle is not None: - uiprep.height += row_subtitle_height - uiprep.height += this_row_height - - # Ok; we've got all row dimensions. Now prep calls to make the - # subcontainers to fit everything and fill out all rows. - uiprep.rootcall = partial( - bui.containerwidget, - size=(uiprep.width, uiprep.height), - claims_left_right=True, - background=False, - ) - y = uiprep.height - top_buffer - - for i, (row, rowprep) in enumerate(zip(ui.rows, uiprep.rows, strict=True)): - tdelaybase = 0.12 * (i + 1) - if row.title is not None: - rowprep.titlecalls.append( - partial( - bui.textwidget, - position=( - ( - ((uiprep.width - left_buffer - right_buffer) * 0.5) - if row.center_title - else (left_buffer + title_inset) - ), - y - row_subtitle_height * 0.5, - ), - size=(0, 0), - text=row.title, - color=( - (0.85, 0.95, 0.89, 1.0) - if row.title_color is None - else row.title_color - ), - flatness=row.title_flatness, - shadow=row.title_shadow, - scale=1.0, - maxwidth=( - (uiprep.width - left_buffer - right_buffer) - if row.center_title - else ( - uiprep.width - - left_buffer - - right_buffer - - title_inset - ) - ), - h_align='center' if row.center_title else 'left', - v_align='center', - literal=True, - transition_delay=None if immediate else (tdelaybase + 0.1), - ) - ) - y -= row_title_height - if row.subtitle is not None: - rowprep.titlecalls.append( - partial( - bui.textwidget, - position=( - ( - ((uiprep.width - left_buffer - right_buffer) * 0.5) - if row.center_title - else (left_buffer + title_inset) - ), - y - row_subtitle_height * 0.5, - ), - size=(0, 0), - text=row.subtitle, - color=( - (0.6, 0.74, 0.6) - if row.subtitle_color is None - else row.subtitle_color - ), - flatness=row.subtitle_flatness, - shadow=row.subtitle_shadow, - scale=0.7, - maxwidth=( - (uiprep.width - left_buffer - right_buffer) - if row.center_title - else ( - uiprep.width - - left_buffer - - right_buffer - - title_inset - ) - ), - h_align='center' if row.center_title else 'left', - v_align='center', - literal=True, - transition_delay=None if immediate else (tdelaybase + 0.2), - ) - ) - y -= row_subtitle_height - - y -= rowprep.height # includes padding-top/bottom - - if row.debug: - rowheightfull = rowprep.height - if row.title is not None: - rowheightfull += row_title_height - if row.subtitle is not None: - rowheightfull += row_subtitle_height - _prep_row_debug( - ( - uiprep.width - left_buffer - right_buffer, - rowheightfull, - ), - (left_buffer, y), - None if immediate else tdelaybase, - rowprep.decorations, - ) - - rowprep.hscrollcall = partial( - bui.hscrollwidget, - size=(uiprep.width - hscrollinset, rowprep.height), - position=(hscrollinset, y), - claims_left_right=True, - highlight=False, - border_opacity=0.0, - center_small_content=row.center_content, - simple_culling_h=row.simple_culling_h, - ) - rowprep.hsubcall = partial( - bui.containerwidget, - size=( - # Ideally we could just always use row-width, but - # currently that gets us right-aligned stuff when - # center-small-content is off. - ( - rowprep.width - if row.center_content - else max(uiprep.width - hscrollinset - fudge, rowprep.width) - ), - rowprep.height, - ), - background=False, - ) - x = left_buffer + row.padding_left - # Calc height of buttons themselves (includes button padding but - # not row padding). - button_row_height = ( - rowprep.height - row.padding_top - row.padding_bottom - ) - bcount = len(row.buttons) - for j, button in enumerate(row.buttons): - # Calc amt 1 -> 0 across the row. - tdelayamt = 1.0 - (j / max(1, bcount - 1)) - # Rightmost buttons slide in first. - tdelay = tdelaybase + tdelayamt * (0.03 * bcount) - - xorig = x - x += button.padding_left - bscale = button.scale - if button.size is None: - bwidth = default_button_width - bheight = default_button_height - else: - bwidth = button.size[0] - bheight = button.size[1] - bwidthfull = bscale * bwidth - bheightfull = bscale * bheight - # Vertically center the button plus its padding. - to_button_plus_padding_bottom = ( - button_row_height - - (bheightfull + button.padding_top + button.padding_bottom) - ) * 0.5 - # Move up past bottom padding to get button bottom. - to_button_bottom = ( - to_button_plus_padding_bottom + button.padding_bottom - ) - - center_x = x + bwidthfull * 0.5 - center_y = row.padding_bottom + to_button_bottom + bheightfull * 0.5 - - buttonprep = _ButtonPrep( - buttoncall=partial( - bui.buttonwidget, - position=(x, row.padding_bottom + to_button_bottom), - size=(bwidth, bheight), - scale=bscale, - color=button.color, - textcolor=button.text_color, - text_flatness=(button.text_flatness), - text_scale=button.text_scale, - button_type='square', - opacity=button.opacity, - label='' if button.label is None else button.label, - text_literal=True, - autoselect=True, - transition_delay=None if immediate else tdelay, - ), - buttoneditcall=partial( - bui.widget, - # TODO: Calc left/right vals properly. - show_buffer_left=150, - show_buffer_right=150, - # We explicitly assign all neighbor selection; - # anything left over should go to toolbars. - auto_select_toolbars_only=True, - ), - decorations=[], - textures={}, - ) - if button.texture is not None: - buttonprep.textures['texture'] = button.texture - - # With row-debug on, visualize the area we try to scroll to - # show when each button is selected. Note that we're clamped - # by the h-scroll here so we have to draw a separate box for - # the row title/subtitle. - if row.debug: - _prep_row_debug_button( - ( - bwidthfull + button.padding_left + button.padding_right, - rowprep.height, - ), - (xorig, 0.0), - None if immediate else tdelay, - buttonprep.decorations, - ) - - if button.debug: - _prep_button_debug( - (bwidthfull, bheightfull), - (center_x, center_y), - None if immediate else tdelay, - buttonprep.decorations, - ) - for decoration in button.decorations: - dectypeid = decoration.get_type_id() - if dectypeid is clui.DecorationTypeID.UNKNOWN: - if bui.do_once(): - bui.uilog.exception( - 'CloudUI receieved unknown decoration;' - ' this is likely a server error.' - ) - elif dectypeid is clui.DecorationTypeID.TEXT: - assert isinstance(decoration, clui.Text) - _prep_text( - decoration, - (center_x, center_y), - bscale, - None if immediate else tdelay, - buttonprep.decorations, - ) - - elif dectypeid is clui.DecorationTypeID.IMAGE: - assert isinstance(decoration, clui.Image) - _prep_image( - decoration, - (center_x, center_y), - bscale, - None if immediate else tdelay, - buttonprep.decorations, - ) - - else: - assert_never(dectypeid) - - rowprep.buttons.append(buttonprep) - - x += bwidthfull + button.padding_right + row.button_spacing - - # Add an edit call for our new hscroll to give it proper - # show-buffers. - - # Incorporate top buffer so we scroll all the way up - # when selecting the top row (and stay clear of - # toolbars). - show_buffer_top = top_buffer - show_buffer_bottom = bot_buffer - - # Scroll so title/subtitle is in view when selecting. - # Note that we don't need to account for - # padding-top/bottom since the h-scroll that we're - # applying to encompasses both. - if row.title is not None: - show_buffer_top += row_title_height - if row.subtitle is not None: - show_buffer_top += row_subtitle_height - - rowprep.hscrolleditcall = partial( - bui.widget, - show_buffer_top=show_buffer_top, - show_buffer_bottom=show_buffer_bottom, - ) - return uiprep - - -def _prep_text( - text: clui.Text, - bcenter: tuple[float, float], - bscale: float, - tdelay: float | None, - decorations: list[_DecorationPrep], -) -> None: - # pylint: disable=too-many-branches - xoffs = bcenter[0] + text.position[0] * bscale - yoffs = bcenter[1] + text.position[1] * bscale - - if text.h_align is clui.HAlign.LEFT: - h_align = 'left' - elif text.h_align is clui.HAlign.CENTER: - h_align = 'center' - elif text.h_align is clui.HAlign.RIGHT: - h_align = 'right' - else: - assert_never(text.h_align) - - if text.v_align is clui.VAlign.TOP: - v_align = 'top' - elif text.v_align is clui.VAlign.CENTER: - v_align = 'center' - elif text.v_align is clui.VAlign.BOTTOM: - v_align = 'bottom' - else: - assert_never(text.v_align) - - decorations.append( - _DecorationPrep( - call=partial( - bui.textwidget, - position=(xoffs, yoffs), - scale=text.scale * bscale, - maxwidth=text.max_width * bscale, - max_height=text.max_height * bscale, - flatness=text.flatness, - shadow=text.shadow, - h_align=h_align, - v_align=v_align, - size=(0, 0), - color=(0.5, 0.5, 0.5, 1.0), - text=text.text, - transition_delay=tdelay, - ), - textures={}, - meshes={}, - ) - ) - # Draw square around max width/height in debug mode. - if text.debug: - mwfull = bscale * text.max_width - mhfull = bscale * text.max_height - - if text.h_align is clui.HAlign.LEFT: - mwxoffs = xoffs - elif text.h_align is clui.HAlign.CENTER: - mwxoffs = xoffs - mwfull * 0.5 - elif text.h_align is clui.HAlign.RIGHT: - mwxoffs = xoffs - mwfull - else: - assert_never(text.h_align) - - if text.v_align is clui.VAlign.TOP: - mwyoffs = yoffs - mhfull - elif text.v_align is clui.VAlign.CENTER: - mwyoffs = yoffs - mhfull * 0.5 - elif text.v_align is clui.VAlign.BOTTOM: - mwyoffs = yoffs - else: - assert_never(text.v_align) - - decorations.append( - _DecorationPrep( - call=partial( - bui.imagewidget, - position=(mwxoffs, mwyoffs), - size=(mwfull, mhfull), - color=(1, 0, 0), - opacity=0.2, - transition_delay=tdelay, - ), - textures={'texture': 'white'}, - meshes={}, - ) - ) - - -def _prep_image( - image: clui.Image, - bcenter: tuple[float, float], - bscale: float, - tdelay: float | None, - decorations: list[_DecorationPrep], -) -> None: - xoffs = bcenter[0] + image.position[0] * bscale - yoffs = bcenter[1] + image.position[1] * bscale - - widthfull = bscale * image.size[0] - heightfull = bscale * image.size[1] - - if image.h_align is clui.HAlign.LEFT: - xoffsfin = xoffs - elif image.h_align is clui.HAlign.CENTER: - xoffsfin = xoffs - widthfull * 0.5 - elif image.h_align is clui.HAlign.RIGHT: - xoffsfin = xoffs - widthfull - else: - assert_never(image.h_align) - - if image.v_align is clui.VAlign.TOP: - yoffsfin = yoffs - heightfull - elif image.v_align is clui.VAlign.CENTER: - yoffsfin = yoffs - heightfull * 0.5 - elif image.v_align is clui.VAlign.BOTTOM: - yoffsfin = yoffs - else: - assert_never(image.v_align) - - textures: dict[str, str] = {'texture': image.texture} - if image.tint_texture is not None: - textures['tint_texture'] = image.tint_texture - if image.mask_texture is not None: - textures['mask_texture'] = image.mask_texture - - meshes: dict[str, str] = {} - if image.mesh_opaque is not None: - meshes['mesh_opaque'] = image.mesh_opaque - if image.mesh_transparent is not None: - meshes['mesh_transparent'] = image.mesh_transparent - - decorations.append( - _DecorationPrep( - call=partial( - bui.imagewidget, - position=(xoffsfin, yoffsfin), - size=(widthfull, heightfull), - color=image.color, - opacity=image.opacity, - tint_color=image.tint_color, - tint2_color=image.tint2_color, - transition_delay=tdelay, - ), - textures=textures, - meshes=meshes, - ) - ) - - -def _prep_row_debug( - size: tuple[float, float], - pos: tuple[float, float], - tdelay: float | None, - decorations: list[_DecorationPrep], -) -> None: - - textures: dict[str, str] = {'texture': 'white'} - - # Shrink the square we draw a tiny bit so rows butted up to - # eachother can be seen. - border_shrink = 1.0 - - decorations.append( - _DecorationPrep( - call=partial( - bui.imagewidget, - position=(pos[0], pos[1] + border_shrink), - size=(size[0], size[1] - 2.0 * border_shrink), - color=(1.0, 0.0, 1), - opacity=0.1, - transition_delay=tdelay, - ), - textures=textures, - meshes={}, - ) - ) - - -def _prep_row_debug_button( - bsize: tuple[float, float], - bcorner: tuple[float, float], - tdelay: float | None, - decorations: list[_DecorationPrep], -) -> None: - xoffs = bcorner[0] - yoffs = bcorner[1] - - textures: dict[str, str] = {'texture': 'white'} - - decorations.append( - _DecorationPrep( - call=partial( - bui.imagewidget, - position=(xoffs, yoffs), - size=bsize, - color=(0.0, 0.0, 1), - opacity=0.15, - transition_delay=tdelay, - ), - textures=textures, - meshes={}, - ) - ) - - -def _prep_button_debug( - bsize: tuple[float, float], - bcenter: tuple[float, float], - tdelay: float | None, - decorations: list[_DecorationPrep], -) -> None: - xoffs = bcenter[0] - bsize[0] * 0.5 - yoffs = bcenter[1] - bsize[1] * 0.5 - - textures: dict[str, str] = {'texture': 'white'} - - decorations.append( - _DecorationPrep( - call=partial( - bui.imagewidget, - position=(xoffs, yoffs), - size=bsize, - color=(0, 1, 0), - opacity=0.1, - transition_delay=tdelay, - ), - textures=textures, - meshes={}, - ) - ) - - -def _instantiate_prepped_page( - pageprep: _PagePrep, - scrollwidget: bui.Widget, - backbutton: bui.Widget, - windowbackbutton: bui.Widget | None, -) -> bui.Widget: - # pylint: disable=too-many-locals - # pylint: disable=too-many-branches - outrows: list[tuple[bui.Widget, list[bui.Widget]]] = [] - - # Now go through and run our prepped ui calls to build our - # widgets, plugging in appropriate parent widgets args and - # whatnot as we go. - assert pageprep.rootcall is not None - subcontainer = pageprep.rootcall(parent=scrollwidget) - for rowprep in pageprep.rows: - for uicall in rowprep.titlecalls: - uicall(parent=subcontainer) - assert rowprep.hscrollcall is not None - hscroll = rowprep.hscrollcall(parent=subcontainer) - for decoration in rowprep.decorations: - kwds: dict = {'parent': subcontainer} - for texarg, texname in decoration.textures.items(): - kwds[texarg] = bui.gettexture(texname) - for mesharg, meshname in decoration.meshes.items(): - kwds[mesharg] = bui.getmesh(meshname) - decoration.call(**kwds) - outrow: tuple[bui.Widget, list[bui.Widget]] = (hscroll, []) - assert rowprep.hsubcall is not None - hsub = rowprep.hsubcall(parent=hscroll) - for i, buttonprep in enumerate(rowprep.buttons): - kwds = {'parent': hsub} - for texarg, texname in buttonprep.textures.items(): - kwds[texarg] = bui.gettexture(texname) - btn = buttonprep.buttoncall(**kwds) - assert buttonprep.buttoneditcall is not None - buttonprep.buttoneditcall(edit=btn) - for decoration in buttonprep.decorations: - kwds = {'parent': hsub, 'draw_controller': btn} - for texarg, texname in decoration.textures.items(): - kwds[texarg] = bui.gettexture(texname) - for mesharg, meshname in decoration.meshes.items(): - kwds[mesharg] = bui.getmesh(meshname) - decoration.call(**kwds) - - # Make sure row is scrolled so leftmost button is - # visible (though kinda seems like this should happen by - # default). - if i == 0: - bui.containerwidget(edit=hsub, visible_child=btn) - outrow[1].append(btn) - - outrows.append(outrow) - assert rowprep.hscrolleditcall is not None - rowprep.hscrolleditcall(edit=hscroll) - - # Ok; we've got all widgets. Now wire up directional nav between - # rows/buttons. - for i in range(0, len(outrows) - 1): - topscroll, topbuttons = outrows[i] - botscroll, botbuttons = outrows[i + 1] - for topbutton in topbuttons: - bui.widget(edit=topbutton, down_widget=botscroll) - if i == 0 and windowbackbutton is not None: - bui.widget(edit=topbutton, up_widget=windowbackbutton) - for botbutton in botbuttons: - bui.widget(edit=botbutton, up_widget=topscroll) - bui.widget(edit=topbuttons[0], left_widget=backbutton) - bui.widget(edit=botbuttons[0], left_widget=backbutton) - for _scroll, buttons in outrows: - for i in range(0, len(buttons) - 1): - leftbutton = buttons[i] - rightbutton = buttons[i + 1] - bui.widget(edit=leftbutton, right_widget=rightbutton) - bui.widget(edit=rightbutton, left_widget=leftbutton) - - return subcontainer - - -class CloudUIWindow(bui.MainWindow): - """UI provided by the cloud.""" - - @dataclass - class State: - """Final state window can be set to show.""" - - page: clui.Page | None - - def __init__( - self, - state: State | None, - *, - transition: str | None = 'in_right', - origin_widget: bui.Widget | None = None, - auxiliary_style: bool = True, - ): - ui = bui.app.ui_v1 - - self._state: CloudUIWindow.State | None = None - - # We want to display differently whether we're an auxiliary - # window or not, but unfortunately that value is not yet - # available until we're added to the main-window-stack so it - # must be explicitly passed in. - self._auxiliary_style = auxiliary_style - - # Calc scale and size for our backing window. For medium & large - # ui-scale we aim for a window small enough to always be fully - # visible on-screen and for small mode we aim for a window big - # enough that we never see the window edges; only the window - # texture covering the whole screen. - uiscale = ui.uiscale - self._width = ( - 1400 - if uiscale is bui.UIScale.SMALL - else 1100 if uiscale is bui.UIScale.MEDIUM else 1200 - ) - self._height = ( - 1200 - if uiscale is bui.UIScale.SMALL - else 700 if uiscale is bui.UIScale.MEDIUM else 800 - ) - self._root_scale = ( - 1.5 - if uiscale is bui.UIScale.SMALL - else 0.9 if uiscale is bui.UIScale.MEDIUM else 0.8 - ) - - # Do some fancy math to calculate our visible area; this will be - # limited by the screen size in small mode and our backing size - # otherwise. - screensize = bui.get_virtual_screen_size() - self._vis_width = min( - self._width - 150, screensize[0] / self._root_scale - ) - self._vis_height = min( - self._height - 80, screensize[1] / self._root_scale - ) - self._vis_top = 0.5 * self._height + 0.5 * self._vis_height - self._vis_left = 0.5 * self._width - 0.5 * self._vis_width - - self._scroll_width = self._vis_width - self._scroll_left = self._vis_left + 0.5 * ( - self._vis_width - self._scroll_width - ) - # Go with full-screen scrollable aread in small ui. - self._scroll_height = self._vis_height - ( - -1 if uiscale is bui.UIScale.SMALL else 43 - ) - self._scroll_bottom = ( - self._vis_top - - (-1 if uiscale is bui.UIScale.SMALL else 32) - - self._scroll_height - ) - - # Nudge our vis area up a bit when we can see the full backing - # (visual fudge factor). - if uiscale is not bui.UIScale.SMALL: - self._vis_top += 12.0 - - super().__init__( - root_widget=bui.containerwidget( - size=(self._width, self._height), - toolbar_visibility='menu_full', - toolbar_cancel_button_style=( - 'close' if auxiliary_style else 'back' - ), - scale=self._root_scale, - ), - transition=transition, - origin_widget=origin_widget, - # We respond to screen size changes only at small ui-scale; - # in other cases we assume our window remains fully visible - # always (flip to windowed mode and resize the app window to - # confirm this). - refresh_on_screen_size_changes=uiscale is bui.UIScale.SMALL, - ) - # Avoid complaints if nothing is selected under us. - bui.widget(edit=self._root_widget, allow_preserve_selection=False) - - self._subcontainer: bui.Widget | None = None - - self._scrollwidget = bui.scrollwidget( - parent=self._root_widget, - highlight=False, - size=(self._scroll_width, self._scroll_height), - position=(self._scroll_left, self._scroll_bottom), - border_opacity=0.4, - center_small_content_horizontally=True, - claims_left_right=True, - ) - # Avoid having to deal with selecting this while its empty. - bui.containerwidget(edit=self._scrollwidget, selectable=False) - - # With full-screen scrolling, fade content as it approaches - # toolbars. - if uiscale is bui.UIScale.SMALL and bool(True): - scroll_fade_top( - self._root_widget, - self._width * 0.5 - self._scroll_width * 0.5, - self._scroll_bottom, - self._scroll_width, - self._scroll_height, - ) - scroll_fade_bottom( - self._root_widget, - self._width * 0.5 - self._scroll_width * 0.5, - self._scroll_bottom, - self._scroll_width, - self._scroll_height, - ) - - # Title. - self._title = bui.textwidget( - parent=self._root_widget, - position=(self._width * 0.5, self._vis_top - 20), - size=(0, 0), - text='', - color=ui.title_color, - scale=0.9 if uiscale is bui.UIScale.SMALL else 1.0, - # Make sure we avoid overlapping meters in small mode. - maxwidth=(130 if uiscale is bui.UIScale.SMALL else 200), - h_align='center', - v_align='center', - ) - # Needed to display properly over scrolled content. - bui.widget(edit=self._title, depth_range=(0.9, 1.0)) - - # For small UI-scale we use the system back/close button; - # otherwise we make our own. - if uiscale is bui.UIScale.SMALL: - bui.containerwidget( - edit=self._root_widget, on_cancel_call=self.main_window_back - ) - self._back_button: bui.Widget | None = None - else: - self._back_button = bui.buttonwidget( - parent=self._root_widget, - id=f'{self.main_window_id_prefix}|close', - scale=0.8, - position=(self._vis_left + 2, self._vis_top - 35), - size=(50, 50) if auxiliary_style else (60, 55), - extra_touch_border_scale=2.0, - button_type=None if auxiliary_style else 'backSmall', - on_activate_call=self.main_window_back, - autoselect=True, - label=bui.charstr( - bui.SpecialChar.CLOSE - if auxiliary_style - else bui.SpecialChar.BACK - ), - ) - bui.containerwidget( - edit=self._root_widget, cancel_button=self._back_button - ) - - # Show our vis-area bounds (for debugging). - if bool(False): - # Skip top-left since its always overlapping back/close - # buttons. - if bool(False): - bui.textwidget( - parent=self._root_widget, - position=(self._vis_left, self._vis_top), - size=(0, 0), - color=(1, 1, 1, 0.5), - scale=0.5, - text='TL', - h_align='left', - v_align='top', - ) - bui.textwidget( - parent=self._root_widget, - position=(self._vis_left + self._vis_width, self._vis_top), - size=(0, 0), - color=(1, 1, 1, 0.5), - scale=0.5, - text='TR', - h_align='right', - v_align='top', - ) - bui.textwidget( - parent=self._root_widget, - position=(self._vis_left, self._vis_top - self._vis_height), - size=(0, 0), - color=(1, 1, 1, 0.5), - scale=0.5, - text='BL', - h_align='left', - v_align='bottom', - ) - bui.textwidget( - parent=self._root_widget, - position=( - self._vis_left + self._vis_width, - self._vis_top - self._vis_height, - ), - size=(0, 0), - scale=0.5, - color=(1, 1, 1, 0.5), - text='BR', - h_align='right', - v_align='bottom', - ) - - self._spinner: bui.Widget | None = bui.spinnerwidget( - parent=self._root_widget, - position=( - self._vis_left + self._vis_width * 0.5, - self._vis_top - self._vis_height * 0.5, - ), - size=48, - style='bomb', - ) - - if state is not None: - self._set_state(state, immediate=True) - else: - if random.random() < 0.0: - bui.apptimer(0.1, bui.WeakCallStrict(self._on_error_response)) - else: - bui.apptimer(0.1, bui.WeakCallStrict(self._on_response)) - - def _on_error_response(self) -> None: - self._set_state(self.State(None)) - - def _on_response(self) -> None: - page = clui.Page( - title='Testing', - rows=[ - clui.Row( - title='First Row', - debug=True, - padding_left=5.0, - buttons=[ - clui.Button( - label='Test', - size=(180, 200), - decorations=[ - clui.Image( - 'powerupPunch', - position=(-70, 0), - size=(40, 40), - h_align=clui.HAlign.LEFT, - ), - clui.Image( - 'powerupSpeed', - position=(0, 75), - size=(35, 35), - v_align=clui.VAlign.TOP, - ), - clui.Text( - 'TL', - position=(-70, 75), - max_width=50, - max_height=50, - h_align=clui.HAlign.LEFT, - v_align=clui.VAlign.TOP, - debug=True, - ), - clui.Text( - 'TR', - position=(70, 75), - max_width=50, - max_height=50, - h_align=clui.HAlign.RIGHT, - v_align=clui.VAlign.TOP, - debug=True, - ), - clui.Text( - 'BL', - position=(-70, -75), - max_width=50, - max_height=50, - h_align=clui.HAlign.LEFT, - v_align=clui.VAlign.BOTTOM, - debug=True, - ), - clui.Text( - 'BR', - position=(70, -75), - max_width=50, - max_height=50, - h_align=clui.HAlign.RIGHT, - v_align=clui.VAlign.BOTTOM, - debug=True, - ), - ], - ), - clui.Button( - label='Test2', - size=(100, 100), - color=(1, 0, 0), - text_color=(1, 1, 1, 1), - padding_right=4, - ), - # Should look like the first button but - # scaled down. - clui.Button( - label='Test', - size=(180, 200), - scale=0.6, - padding_bottom=30, # Should nudge us up. - debug=True, # Show bounds. - decorations=[ - clui.Image( - 'powerupPunch', - position=(-70, 0), - size=(40, 40), - h_align=clui.HAlign.LEFT, - ), - clui.Image( - 'powerupSpeed', - position=(0, 75), - size=(35, 35), - v_align=clui.VAlign.TOP, - ), - clui.Text( - 'TL', - position=(-70, 75), - max_width=50, - max_height=50, - h_align=clui.HAlign.LEFT, - v_align=clui.VAlign.TOP, - debug=True, - ), - clui.Text( - 'TR', - position=(70, 75), - max_width=50, - max_height=50, - h_align=clui.HAlign.RIGHT, - v_align=clui.VAlign.TOP, - debug=True, - ), - clui.Text( - 'BL', - position=(-70, -75), - max_width=50, - max_height=50, - h_align=clui.HAlign.LEFT, - v_align=clui.VAlign.BOTTOM, - debug=True, - ), - clui.Text( - 'BR', - position=(70, -75), - max_width=50, - max_height=50, - h_align=clui.HAlign.RIGHT, - v_align=clui.VAlign.BOTTOM, - debug=True, - ), - ], - ), - # Testing custom button images and opacity. - clui.Button( - label='Test3', - texture='buttonSquareWide', - padding_left=10.0, - padding_right=10.0, - color=(1, 1, 1), - opacity=0.3, - size=(200, 100), - ), - ], - ), - clui.Row( - title='Second Row', - subtitle='Second row subtitle.', - buttons=[ - clui.Button( - size=(150, 100), - decorations=[ - clui.Text( - 'MaxWidthTest', - position=(0, 25), - max_width=150 * 0.8, - flatness=1.0, - shadow=0.0, - debug=True, - ), - clui.Text( - 'MaxHeightTest\nSecondLine', - position=(0, -20), - max_width=150 * 0.8, - max_height=40, - flatness=1.0, - shadow=0.0, - debug=True, - ), - ], - ), - clui.Button( - size=(150, 100), - decorations=[ - clui.Image( - 'zoeIcon', - position=(0, 0), - size=(70, 70), - tint_texture='zoeIconColorMask', - tint_color=(1, 0, 0), - tint2_color=(0, 1, 0), - mask_texture='characterIconMask', - ), - ], - ), - clui.Button( - size=(150, 100), - decorations=[ - clui.Image( - 'bridgitPreview', - position=(0, 10), - size=(120, 60), - mask_texture='mapPreviewMask', - mesh_opaque='level_select_button_opaque', - mesh_transparent=( - 'level_select_button_transparent' - ), - ), - ], - ), - clui.Button(size=(150, 100)), - clui.Button(size=(150, 100)), - clui.Button(size=(150, 100)), - ], - ), - clui.Row( - buttons=[ - clui.Button( - size=(100, 100), - color=(0.8, 0.8, 0.8), - ), - clui.Button( - size=(100, 100), - color=(0.8, 0.8, 0.8), - ), - ], - ), - clui.Row( - title='Last Row (Faded Title)', - title_color=(0.6, 0.6, 1.0, 0.3), - title_flatness=1.0, - title_shadow=1.0, - subtitle='Testing Centered Title/Content', - subtitle_color=(1.0, 0.5, 1.0, 0.5), - subtitle_flatness=1.0, - subtitle_shadow=0.0, - center_content=True, - center_title=True, - buttons=[ - clui.Button( - 'Hello There!', - size=(200, 120), - color=(0.7, 0.7, 0.9), - ), - ], - ), - ], - ) - self._set_state(self.State(page)) - - def _set_state(self, state: State, immediate: bool = False) -> None: - """Set a final state (error or page contents). - - This state may be instantly restored if the window is recreated - (depending on cache lifespan/etc.) - """ - - assert self._state is None - self._state = state - - ui = bui.app.ui_v1 - uiscale = ui.uiscale - - if self._spinner: - self._spinner.delete() - self._spinner = None - - if state.page is None: - bui.textwidget( - edit=self._title, - literal=False, # Allow Lstr. - text=bui.Lstr(resource='errorText'), - ) - bui.textwidget( - parent=self._root_widget, - position=( - self._vis_left + 0.5 * self._vis_width, - self._vis_top - 0.5 * self._vis_height, - ), - size=(0, 0), - scale=0.6, - text=bui.Lstr(resource='store.loadErrorText'), - h_align='center', - v_align='center', - ) - return - - # Ok; we've got content. - bui.textwidget( - edit=self._title, - literal=True, # Never interpret as Lstr. - text=state.page.title, - ) - - # Make sure there's at least one row and that all rows contain - # at least one button. Otherwise show a 'nothing here' message. - if not state.page.rows or not all( - row.buttons for row in state.page.rows - ): - bui.uilog.exception( - 'Got invalid cloud-ui state;' - ' must contain at least one row' - ' and all rows must contain buttons.' - ) - bui.textwidget( - parent=self._root_widget, - position=( - self._vis_left + 0.5 * self._vis_width, - self._vis_top - 0.5 * self._vis_height, - ), - size=(0, 0), - scale=0.6, - text=bui.Lstr( - translate=('serverResponses', 'There is nothing here.') - ), - h_align='center', - v_align='center', - ) - return - - pageprep = _prep_page( - state.page, uiscale, self._scroll_width, immediate=immediate - ) - - bui.containerwidget(edit=self._scrollwidget, selectable=True) - bui.scrollwidget( - edit=self._scrollwidget, - simple_culling_v=pageprep.simple_culling_v, - center_small_content=state.page.center_vertically, - ) - - self._subcontainer = _instantiate_prepped_page( - pageprep, - self._scrollwidget, - backbutton=( - bui.get_special_widget('back_button') - if self._back_button is None - else self._back_button - ), - windowbackbutton=self._back_button, - ) - - @override - def get_main_window_state(self) -> bui.MainWindowState: - # Support recreating our window for back/refresh purposes. - cls = type(self) - - # IMPORTANT - Pull values from self HERE; if we do it in the - # lambda below it'll keep self alive which will lead to - # 'ui-not-getting-cleaned-up' warnings and memory leaks. - auxiliary_style = self._auxiliary_style - state = self._state - - return bui.BasicMainWindowState( - create_call=lambda transition, origin_widget: cls( - state=state, - transition=transition, - origin_widget=origin_widget, - auxiliary_style=auxiliary_style, - ), - ) - - @override - def main_window_should_preserve_selection(self) -> bool: - return True - - @override - def get_main_window_shared_state_id(self) -> str | None: - return 'cloudui' diff --git a/src/assets/ba_data/python/bauiv1lib/cloudui/__init__.py b/src/assets/ba_data/python/bauiv1lib/cloudui/__init__.py new file mode 100644 index 000000000..c876cb14e --- /dev/null +++ b/src/assets/ba_data/python/bauiv1lib/cloudui/__init__.py @@ -0,0 +1,14 @@ +# Released under the MIT License. See LICENSE for details. +"""Functionality for interacting with cloud-ui from the client.""" + +from bauiv1lib.cloudui._test import show_test_cloud_ui_window +from bauiv1lib.cloudui._controller import CloudUIController +from bauiv1lib.cloudui._window import CloudUIWindow +from bauiv1lib.cloudui._prep import CloudUIPagePrep + +__all__ = [ + 'show_test_cloud_ui_window', + 'CloudUIController', + 'CloudUIWindow', + 'CloudUIPagePrep', +] diff --git a/src/assets/ba_data/python/bauiv1lib/cloudui/_controller.py b/src/assets/ba_data/python/bauiv1lib/cloudui/_controller.py new file mode 100644 index 000000000..b13437730 --- /dev/null +++ b/src/assets/ba_data/python/bauiv1lib/cloudui/_controller.py @@ -0,0 +1,179 @@ +# Released under the MIT License. See LICENSE for details. +# +"""Controller functionality for CloudUI.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, assert_never +import weakref + +from bacommon.cloudui import CloudUIResponseTypeID, UnknownCloudUIResponse +import bauiv1 as bui + +from bauiv1lib.cloudui._window import CloudUIWindow + +if TYPE_CHECKING: + from typing import Callable + + from bacommon.cloudui import CloudUIRequest, CloudUIResponse + + +class CloudUIController: + """Manages interactions between CloudUI clients and servers. + + Can include logic to handle all requests locally or can submit them + to be handled by some server or can do some combination thereof. + """ + + def __init__(self) -> None: + pass + + def create_window(self, request: CloudUIRequest) -> CloudUIWindow: + """Create a window for some initial request.""" + assert bui.in_logic_thread() + win = CloudUIWindow(state=None) + + bui.app.threadpool.submit_no_wait( + bui.CallStrict(self._request_in_bg, request, weakref.ref(win)) + ) + return win + + def _error_response(self) -> CloudUIResponse: + """Build a simple error dialog.""" + import bacommon.cloudui.v1 as clui1 + + debug = True + return clui1.Response( + code=clui1.ResponseCode.UNKNOWN_ERROR, + page=clui1.Page( + title=bui.Lstr(resource='errorText').as_json(), + title_is_lstr=True, + center_vertically=True, + rows=[ + clui1.Row( + buttons=[ + clui1.Button( + bui.Lstr(resource='okText').as_json(), + text_is_lstr=True, + style=clui1.Button.Style.MEDIUM, + size=(130, 50), + padding_left=200, + padding_right=200, + padding_top=100, + decorations=[ + clui1.Text( + bui.Lstr( + translate=( + 'serverResponses', + 'An error has occurred;' + ' please try again later.', + ) + ).as_json(), + is_lstr=True, + position=(0, 80), + size=(480, 50), + highlight=False, + debug=debug, + ), + ], + debug=debug, + ), + ], + center_content=True, + debug=debug, + ), + ], + ), + ) + + def _request_in_bg( + self, request: CloudUIRequest, weakwin: weakref.ref[CloudUIWindow] + ) -> None: + """Submit a request to the controller. + + This must be called from the UI thread and results will be + delivered in the UI thread. + + This will always return a response, even on error conditions. + """ + assert not bui.in_logic_thread() + + response: CloudUIResponse | None + + try: + response = self.fulfill_request(request) + except Exception: + bui.uilog.debug('Error fulfilling cloudui request.', exc_info=True) + response = None + + # Validate any response we got. + if response is not None: + responsetype = response.get_type_id() + + if responsetype is CloudUIResponseTypeID.V1: + import bacommon.cloudui.v1 as clui1 + + assert isinstance(response, clui1.Response) + + # Make sure there's at least one row and that all rows + # contain at least one button. + if not response.page.rows or not all( + row.buttons for row in response.page.rows + ): + bui.uilog.exception( + 'Got invalid cloud-ui response;' + ' page must contain at least one row' + ' and all rows must contain buttons.' + ) + response = None + elif responsetype is CloudUIResponseTypeID.UNKNOWN: + assert isinstance(response, UnknownCloudUIResponse) + bui.uilog.debug( + 'Got unsupported cloudui response.', exc_info=True + ) + response = None + else: + # Make sure we cover all types we're aware of. + assert_never(responsetype) + + if response is None: + response = self._error_response() + + # Go ahead and just push the response along with our weakref + # back to the logic thread for handling. We could quick-out here + # if the window is dead, but wrangling its refs here could + # theoretically lead to it being deallocated here which could be + # problematic. + bui.pushcall( + bui.CallStrict( + self._handle_response_in_ui_thread, response, weakwin + ), + from_other_thread=True, + ) + + def _handle_response_in_ui_thread( + self, response: CloudUIResponse, weakwin: weakref.ref[CloudUIWindow] + ) -> None: + import bacommon.cloudui.v1 as clui1 + + assert bui.in_logic_thread() + + # Our target window died since we made the request; no biggie. + win = weakwin() + if win is None: + return + + # Currently should only be sending ourself v1 responses here. + assert isinstance(response, clui1.Response) + + win.set_state(win.State(self, response.page)) + + def fulfill_request(self, request: CloudUIRequest) -> CloudUIResponse: + """Override this to handle request fulfillment. + + Exceptions should be raised for any errors; the base class will + handle converting those to a Response. + + Be aware that this will always be called in a background thread. + """ + raise NotImplementedError() diff --git a/src/assets/ba_data/python/bauiv1lib/cloudui/_prep.py b/src/assets/ba_data/python/bauiv1lib/cloudui/_prep.py new file mode 100644 index 000000000..a958b80b7 --- /dev/null +++ b/src/assets/ba_data/python/bauiv1lib/cloudui/_prep.py @@ -0,0 +1,815 @@ +# Released under the MIT License. See LICENSE for details. +# +"""Prep functionality for our UI. + +We do all layout math and bake out partial ui calls in a background +thread so there's as little work to do in the ui thread as possible. +""" + +from __future__ import annotations + +from functools import partial +from dataclasses import dataclass +from typing import TYPE_CHECKING, assert_never + +import bacommon.cloudui.v1 as clui +import bauiv1 as bui + + +if TYPE_CHECKING: + from typing import Callable + + +@dataclass +class _DecorationPrep: + call: Callable[..., bui.Widget] + textures: dict[str, str] + meshes: dict[str, str] + highlight: bool + + +@dataclass +class _ButtonPrep: + buttoncall: Callable[..., bui.Widget] + buttoneditcall: Callable | None + decorations: list[_DecorationPrep] + textures: dict[str, str] + + +@dataclass +class _RowPrep: + width: float + height: float + titlecalls: list[Callable[..., bui.Widget]] + hscrollcall: Callable[..., bui.Widget] | None + hscrolleditcall: Callable | None + hsubcall: Callable[..., bui.Widget] | None + buttons: list[_ButtonPrep] + simple_culling_h: float + decorations: list[_DecorationPrep] + + +class CloudUIPagePrep: + """Preps a page. + + Generally does its work in a background thread. + """ + + def __init__( + self, + page: clui.Page, + uiscale: bui.UIScale, + scroll_width: float, + idprefix: str, + *, + immediate: bool = False, + ) -> None: + # pylint: disable=too-many-statements + # pylint: disable=too-many-branches + # pylint: disable=too-many-locals + + # Ok; we've got some buttons. Build our full UI. + row_title_height = 30.0 + row_subtitle_height = 30.0 + top_buffer = 20.0 + bot_buffer = 20.0 + left_buffer = 0.0 + right_buffer = 10.0 # Nudge a bit due to scrollbar. + title_inset = 35.0 + default_button_width = 200.0 + default_button_height = 200.0 + + if uiscale is bui.UIScale.SMALL: + top_bar_overlap = 70 + bot_bar_overlap = 70 + top_buffer += top_bar_overlap + bot_buffer += bot_bar_overlap + else: + top_bar_overlap = 0 + bot_bar_overlap = 0 + + # Should look into why this is necessary. + fudge = 15.0 + hscrollinset = 15.0 + + self.rootcall: Callable[..., bui.Widget] | None = None + self.rows: list[_RowPrep] = [] + self.width: float = scroll_width + fudge + self.height: float = top_buffer + bot_buffer + self.simple_culling_v: float = page.simple_culling_v + + nextbuttonid = 0 + + # Precalc basic info like dimensions for all rows. + for row in page.rows: + assert row.buttons + this_row_width = ( + left_buffer + + right_buffer + + row.padding_left + + row.padding_right + + row.button_spacing * (len(row.buttons) - 1) + ) + button_row_height = 0.0 + for button in row.buttons: + if button.size is None: + bwidth = default_button_width + bheight = default_button_height + else: + bwidth = button.size[0] + bheight = button.size[1] + bscale = button.scale + bwidthfull = bwidth * bscale + bheightfull = bheight * bscale + # Include button padding when calcing full needed height. + button_row_height = max( + button_row_height, + bheightfull + + (button.padding_top + button.padding_bottom) + * button.scale, + ) + this_row_width += ( + bwidthfull + + (button.padding_left + button.padding_right) + * button.scale + ) + this_row_height = ( + row.padding_top + row.padding_bottom + button_row_height + ) + self.rows.append( + _RowPrep( + width=this_row_width, + height=this_row_height, + titlecalls=[], + hscrollcall=None, + hscrolleditcall=None, + hsubcall=None, + buttons=[], + simple_culling_h=row.simple_culling_h, + decorations=[], + ) + ) + assert this_row_height > 0.0 + assert this_row_width > 0.0 + if row.title is not None: + self.height += row_title_height + if row.subtitle is not None: + self.height += row_subtitle_height + self.height += this_row_height + + # Ok; we've got all row dimensions. Now prep calls to make the + # subcontainers to fit everything and fill out all rows. + self.rootcall = partial( + bui.containerwidget, + size=(self.width, self.height), + claims_left_right=True, + background=False, + ) + y = self.height - top_buffer + + for i, (row, rowprep) in enumerate( + zip(page.rows, self.rows, strict=True) + ): + tdelaybase = 0.12 * (i + 1) + if row.title is not None: + rowprep.titlecalls.append( + partial( + bui.textwidget, + position=( + ( + ( + (self.width - left_buffer - right_buffer) + * 0.5 + ) + if row.center_title + else (left_buffer + title_inset) + ), + y - row_subtitle_height * 0.5, + ), + size=(0, 0), + text=row.title, + color=( + (0.85, 0.95, 0.89, 1.0) + if row.title_color is None + else row.title_color + ), + flatness=row.title_flatness, + shadow=row.title_shadow, + scale=1.0, + maxwidth=( + (self.width - left_buffer - right_buffer) + if row.center_title + else ( + self.width + - left_buffer + - right_buffer + - title_inset + ) + ), + h_align='center' if row.center_title else 'left', + v_align='center', + literal=not row.title_is_lstr, + transition_delay=( + None if immediate else (tdelaybase + 0.1) + ), + ) + ) + y -= row_title_height + if row.subtitle is not None: + rowprep.titlecalls.append( + partial( + bui.textwidget, + position=( + ( + ( + (self.width - left_buffer - right_buffer) + * 0.5 + ) + if row.center_title + else (left_buffer + title_inset) + ), + y - row_subtitle_height * 0.5, + ), + size=(0, 0), + text=row.subtitle, + color=( + (0.6, 0.74, 0.6) + if row.subtitle_color is None + else row.subtitle_color + ), + flatness=row.subtitle_flatness, + shadow=row.subtitle_shadow, + scale=0.7, + maxwidth=( + (self.width - left_buffer - right_buffer) + if row.center_title + else ( + self.width + - left_buffer + - right_buffer + - title_inset + ) + ), + h_align='center' if row.center_title else 'left', + v_align='center', + literal=not row.subtitle_is_lstr, + transition_delay=( + None if immediate else (tdelaybase + 0.2) + ), + ) + ) + y -= row_subtitle_height + + y -= rowprep.height # includes padding-top/bottom + + if row.debug: + rowheightfull = rowprep.height + if row.title is not None: + rowheightfull += row_title_height + if row.subtitle is not None: + rowheightfull += row_subtitle_height + _prep_row_debug( + ( + self.width - left_buffer - right_buffer, + rowheightfull, + ), + (left_buffer, y), + None if immediate else tdelaybase, + rowprep.decorations, + ) + + rowprep.hscrollcall = partial( + bui.hscrollwidget, + size=(self.width - hscrollinset, rowprep.height), + position=(hscrollinset, y), + claims_left_right=True, + highlight=False, + border_opacity=0.0, + center_small_content=row.center_content, + simple_culling_h=row.simple_culling_h, + ) + rowprep.hsubcall = partial( + bui.containerwidget, + size=( + # Ideally we could just always use row-width, but + # currently that gets us right-aligned stuff when + # center-small-content is off. + ( + rowprep.width + if row.center_content + else max( + self.width - hscrollinset - fudge, rowprep.width + ) + ), + rowprep.height, + ), + background=False, + ) + x = left_buffer + row.padding_left + # Calc height of buttons themselves (includes button padding but + # not row padding). + button_row_height = ( + rowprep.height - row.padding_top - row.padding_bottom + ) + bcount = len(row.buttons) + for j, button in enumerate(row.buttons): + # Calc amt 1 -> 0 across the row. + tdelayamt = 1.0 - (j / max(1, bcount - 1)) + # Rightmost buttons slide in first. + tdelay = tdelaybase + tdelayamt * (0.03 * bcount) + + xorig = x + x += button.padding_left * button.scale + bscale = button.scale + if button.size is None: + bwidth = default_button_width + bheight = default_button_height + else: + bwidth = button.size[0] + bheight = button.size[1] + bwidthfull = bscale * bwidth + bheightfull = bscale * bheight + # Vertically center the button plus its padding. + to_button_plus_padding_bottom = ( + button_row_height + - ( + bheightfull + + (button.padding_top + button.padding_bottom) + * button.scale + ) + ) * 0.5 + # Move up past bottom padding to get button bottom. + to_button_bottom = ( + to_button_plus_padding_bottom + + button.padding_bottom * button.scale + ) + + center_x = x + bwidthfull * 0.5 + center_y = ( + row.padding_bottom + to_button_bottom + bheightfull * 0.5 + ) + + bstyle: str + if button.style is clui.Button.Style.SQUARE: + bstyle = 'square' + elif button.style is clui.Button.Style.TAB: + bstyle = 'tab' + elif button.style is clui.Button.Style.SMALL: + bstyle = 'small' + elif button.style is clui.Button.Style.MEDIUM: + bstyle = 'medium' + elif button.style is clui.Button.Style.LARGE: + bstyle = 'large' + elif button.style is clui.Button.Style.LARGER: + bstyle = 'larger' + else: + assert_never(button.style) + + buttonprep = _ButtonPrep( + buttoncall=partial( + bui.buttonwidget, + id=f'{idprefix}|button{nextbuttonid}', + position=(x, row.padding_bottom + to_button_bottom), + size=(bwidth, bheight), + scale=bscale, + color=button.color, + textcolor=button.text_color, + text_flatness=(button.text_flatness), + text_scale=button.text_scale, + button_type=bstyle, + opacity=button.opacity, + label='' if button.label is None else button.label, + text_literal=not button.text_is_lstr, + autoselect=True, + transition_delay=None if immediate else tdelay, + ), + buttoneditcall=partial( + bui.widget, + # TODO: Calc left/right vals properly based on + # our size and padding. + show_buffer_left=150, + show_buffer_right=150, + # We explicitly assign all neighbor selection; + # anything left over should go to toolbars. + auto_select_toolbars_only=True, + ), + decorations=[], + textures={}, + ) + nextbuttonid += 1 + if button.texture is not None: + buttonprep.textures['texture'] = button.texture + + # With row-debug on, visualize the area we try to scroll to + # show when each button is selected. Note that we're clamped + # by the h-scroll here so we have to draw a separate box for + # the row title/subtitle. + if row.debug: + _prep_row_debug_button( + ( + bwidthfull + + (button.padding_left + button.padding_right) + * button.scale, + rowprep.height, + ), + (xorig, 0.0), + None if immediate else tdelay, + buttonprep.decorations, + ) + + if button.debug: + _prep_button_debug( + (bwidthfull, bheightfull), + (center_x, center_y), + None if immediate else tdelay, + buttonprep.decorations, + ) + for decoration in button.decorations: + dectypeid = decoration.get_type_id() + if dectypeid is clui.DecorationTypeID.UNKNOWN: + if bui.do_once(): + bui.uilog.exception( + 'CloudUI receieved unknown decoration;' + ' this is likely a server error.' + ) + elif dectypeid is clui.DecorationTypeID.TEXT: + assert isinstance(decoration, clui.Text) + _prep_text( + decoration, + (center_x, center_y), + bscale, + None if immediate else tdelay, + buttonprep.decorations, + ) + + elif dectypeid is clui.DecorationTypeID.IMAGE: + assert isinstance(decoration, clui.Image) + _prep_image( + decoration, + (center_x, center_y), + bscale, + None if immediate else tdelay, + buttonprep.decorations, + ) + + else: + assert_never(dectypeid) + + rowprep.buttons.append(buttonprep) + + x += ( + bwidthfull + + (button.padding_right * button.scale) + + row.button_spacing + ) + + # Add an edit call for our new hscroll to give it proper + # show-buffers. + + # Incorporate top buffer so we scroll all the way up + # when selecting the top row (and stay clear of + # toolbars). + show_buffer_top = top_buffer + show_buffer_bottom = bot_buffer + + # Scroll so title/subtitle is in view when selecting. + # Note that we don't need to account for + # padding-top/bottom since the h-scroll that we're + # applying to encompasses both. + if row.title is not None: + show_buffer_top += row_title_height + if row.subtitle is not None: + show_buffer_top += row_subtitle_height + + rowprep.hscrolleditcall = partial( + bui.widget, + show_buffer_top=show_buffer_top, + show_buffer_bottom=show_buffer_bottom, + ) + + def instantiate( + self, + scrollwidget: bui.Widget, + backbutton: bui.Widget, + windowbackbutton: bui.Widget | None, + ) -> bui.Widget: + """Create a UI using prepped data.""" + # pylint: disable=too-many-locals + # pylint: disable=too-many-branches + outrows: list[tuple[bui.Widget, list[bui.Widget]]] = [] + + # Clear any existin children. + for child in scrollwidget.get_children(): + child.delete() + + # Now go through and run our prepped ui calls to build our + # widgets, plugging in appropriate parent widgets args and + # whatnot as we go. + assert self.rootcall is not None + subcontainer = self.rootcall(parent=scrollwidget) + for rowprep in self.rows: + for uicall in rowprep.titlecalls: + uicall(parent=subcontainer) + assert rowprep.hscrollcall is not None + hscroll = rowprep.hscrollcall(parent=subcontainer) + for decoration in rowprep.decorations: + kwds: dict = {'parent': subcontainer} + for texarg, texname in decoration.textures.items(): + kwds[texarg] = bui.gettexture(texname) + for mesharg, meshname in decoration.meshes.items(): + kwds[mesharg] = bui.getmesh(meshname) + decoration.call(**kwds) + outrow: tuple[bui.Widget, list[bui.Widget]] = (hscroll, []) + assert rowprep.hsubcall is not None + hsub = rowprep.hsubcall(parent=hscroll) + for i, buttonprep in enumerate(rowprep.buttons): + kwds = {'parent': hsub} + for texarg, texname in buttonprep.textures.items(): + kwds[texarg] = bui.gettexture(texname) + btn = buttonprep.buttoncall(**kwds) + assert buttonprep.buttoneditcall is not None + buttonprep.buttoneditcall(edit=btn) + for decoration in buttonprep.decorations: + kwds = {'parent': hsub} + if decoration.highlight: + kwds['draw_controller'] = btn + for texarg, texname in decoration.textures.items(): + kwds[texarg] = bui.gettexture(texname) + for mesharg, meshname in decoration.meshes.items(): + kwds[mesharg] = bui.getmesh(meshname) + decoration.call(**kwds) + + # Make sure row is scrolled so leftmost button is + # visible (though kinda seems like this should happen by + # default). + if i == 0: + bui.containerwidget(edit=hsub, visible_child=btn) + outrow[1].append(btn) + + outrows.append(outrow) + assert rowprep.hscrolleditcall is not None + rowprep.hscrolleditcall(edit=hscroll) + + # Ok; we've got all widgets. Now wire up directional nav between + # rows/buttons. + if windowbackbutton is not None and outrows: + _scroll, buttons = outrows[0] + for button in buttons: + bui.widget(edit=button, up_widget=windowbackbutton) + for i in range(0, len(outrows) - 1): + topscroll, topbuttons = outrows[i] + botscroll, botbuttons = outrows[i + 1] + for topbutton in topbuttons: + bui.widget(edit=topbutton, down_widget=botscroll) + for botbutton in botbuttons: + bui.widget(edit=botbutton, up_widget=topscroll) + bui.widget(edit=topbuttons[0], left_widget=backbutton) + bui.widget(edit=botbuttons[0], left_widget=backbutton) + for _scroll, buttons in outrows: + for i in range(0, len(buttons) - 1): + leftbutton = buttons[i] + rightbutton = buttons[i + 1] + bui.widget(edit=leftbutton, right_widget=rightbutton) + bui.widget(edit=rightbutton, left_widget=leftbutton) + + return subcontainer + + +def _prep_text( + text: clui.Text, + bcenter: tuple[float, float], + bscale: float, + tdelay: float | None, + decorations: list[_DecorationPrep], +) -> None: + # pylint: disable=too-many-branches + xoffs = bcenter[0] + text.position[0] * bscale + yoffs = bcenter[1] + text.position[1] * bscale + + if text.h_align is clui.HAlign.LEFT: + h_align = 'left' + elif text.h_align is clui.HAlign.CENTER: + h_align = 'center' + elif text.h_align is clui.HAlign.RIGHT: + h_align = 'right' + else: + assert_never(text.h_align) + + if text.v_align is clui.VAlign.TOP: + v_align = 'top' + elif text.v_align is clui.VAlign.CENTER: + v_align = 'center' + elif text.v_align is clui.VAlign.BOTTOM: + v_align = 'bottom' + else: + assert_never(text.v_align) + + decorations.append( + _DecorationPrep( + call=partial( + bui.textwidget, + position=(xoffs, yoffs), + scale=text.scale * bscale, + maxwidth=text.size[0] * bscale, + max_height=text.size[1] * bscale, + flatness=text.flatness, + shadow=text.shadow, + h_align=h_align, + v_align=v_align, + size=(0, 0), + color=(0.5, 0.5, 0.5, 1.0), + text=text.text, + literal=not text.is_lstr, + transition_delay=tdelay, + ), + textures={}, + meshes={}, + highlight=text.highlight, + ) + ) + # Draw square around max width/height in debug mode. + if text.debug: + mwfull = bscale * text.size[0] + mhfull = bscale * text.size[1] + + if text.h_align is clui.HAlign.LEFT: + mwxoffs = xoffs + elif text.h_align is clui.HAlign.CENTER: + mwxoffs = xoffs - mwfull * 0.5 + elif text.h_align is clui.HAlign.RIGHT: + mwxoffs = xoffs - mwfull + else: + assert_never(text.h_align) + + if text.v_align is clui.VAlign.TOP: + mwyoffs = yoffs - mhfull + elif text.v_align is clui.VAlign.CENTER: + mwyoffs = yoffs - mhfull * 0.5 + elif text.v_align is clui.VAlign.BOTTOM: + mwyoffs = yoffs + else: + assert_never(text.v_align) + + decorations.append( + _DecorationPrep( + call=partial( + bui.imagewidget, + position=(mwxoffs, mwyoffs), + size=(mwfull, mhfull), + color=(1, 0, 0), + opacity=0.2, + transition_delay=tdelay, + ), + textures={'texture': 'white'}, + meshes={}, + highlight=True, + ) + ) + + +def _prep_image( + image: clui.Image, + bcenter: tuple[float, float], + bscale: float, + tdelay: float | None, + decorations: list[_DecorationPrep], +) -> None: + xoffs = bcenter[0] + image.position[0] * bscale + yoffs = bcenter[1] + image.position[1] * bscale + + widthfull = bscale * image.size[0] + heightfull = bscale * image.size[1] + + if image.h_align is clui.HAlign.LEFT: + xoffsfin = xoffs + elif image.h_align is clui.HAlign.CENTER: + xoffsfin = xoffs - widthfull * 0.5 + elif image.h_align is clui.HAlign.RIGHT: + xoffsfin = xoffs - widthfull + else: + assert_never(image.h_align) + + if image.v_align is clui.VAlign.TOP: + yoffsfin = yoffs - heightfull + elif image.v_align is clui.VAlign.CENTER: + yoffsfin = yoffs - heightfull * 0.5 + elif image.v_align is clui.VAlign.BOTTOM: + yoffsfin = yoffs + else: + assert_never(image.v_align) + + textures: dict[str, str] = {'texture': image.texture} + if image.tint_texture is not None: + textures['tint_texture'] = image.tint_texture + if image.mask_texture is not None: + textures['mask_texture'] = image.mask_texture + + meshes: dict[str, str] = {} + if image.mesh_opaque is not None: + meshes['mesh_opaque'] = image.mesh_opaque + if image.mesh_transparent is not None: + meshes['mesh_transparent'] = image.mesh_transparent + + decorations.append( + _DecorationPrep( + call=partial( + bui.imagewidget, + position=(xoffsfin, yoffsfin), + size=(widthfull, heightfull), + color=image.color, + opacity=image.opacity, + tint_color=image.tint_color, + tint2_color=image.tint2_color, + transition_delay=tdelay, + ), + textures=textures, + meshes=meshes, + highlight=image.highlight, + ) + ) + + +def _prep_row_debug( + size: tuple[float, float], + pos: tuple[float, float], + tdelay: float | None, + decorations: list[_DecorationPrep], +) -> None: + + textures: dict[str, str] = {'texture': 'white'} + + # Shrink the square we draw a tiny bit so rows butted up to + # eachother can be seen. + border_shrink = 1.0 + + decorations.append( + _DecorationPrep( + call=partial( + bui.imagewidget, + position=(pos[0], pos[1] + border_shrink), + size=(size[0], size[1] - 2.0 * border_shrink), + color=(0.0, 1.0, 1.0), + opacity=0.06, + transition_delay=tdelay, + ), + textures=textures, + meshes={}, + highlight=True, + ) + ) + + +def _prep_row_debug_button( + bsize: tuple[float, float], + bcorner: tuple[float, float], + tdelay: float | None, + decorations: list[_DecorationPrep], +) -> None: + xoffs = bcorner[0] + yoffs = bcorner[1] + + textures: dict[str, str] = {'texture': 'white'} + + decorations.append( + _DecorationPrep( + call=partial( + bui.imagewidget, + position=(xoffs, yoffs), + size=bsize, + color=(0.0, 0.0, 1), + opacity=0.15, + transition_delay=tdelay, + ), + textures=textures, + meshes={}, + highlight=True, + ) + ) + + +def _prep_button_debug( + bsize: tuple[float, float], + bcenter: tuple[float, float], + tdelay: float | None, + decorations: list[_DecorationPrep], +) -> None: + textures: dict[str, str] = {'texture': 'white'} + + decorations.append( + _DecorationPrep( + call=partial( + bui.imagewidget, + position=( + bcenter[0] - bsize[0] * 0.5, + bcenter[1] - bsize[1] * 0.5, + ), + size=bsize, + color=(0, 1, 0), + opacity=0.1, + transition_delay=tdelay, + ), + textures=textures, + meshes={}, + highlight=True, + ) + ) diff --git a/src/assets/ba_data/python/bauiv1lib/cloudui/_test.py b/src/assets/ba_data/python/bauiv1lib/cloudui/_test.py new file mode 100644 index 000000000..f6cce2573 --- /dev/null +++ b/src/assets/ba_data/python/bauiv1lib/cloudui/_test.py @@ -0,0 +1,280 @@ +# Released under the MIT License. See LICENSE for details. +# +"""UIs provided by the cloud (similar-ish to html in concept).""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, override + +import bauiv1 as bui + +from bauiv1lib.cloudui._window import CloudUIWindow +from bauiv1lib.cloudui._controller import CloudUIController + +if TYPE_CHECKING: + from bacommon.cloudui import CloudUIRequest, CloudUIResponse + import bacommon.cloudui.v1 + + +def show_test_cloud_ui_window() -> None: + """Bust out a cloud-ui window.""" + import bacommon.cloudui.v1 as clui + + # Pop up an auxiliary window wherever we are in the nav stack. + bui.app.ui_v1.auxiliary_window_activate( + win_type=CloudUIWindow, + win_create_call=bui.CallStrict( + TestCloudUIController().create_window, clui.Request('/') + ), + ) + + +class TestCloudUIController(CloudUIController): + """Provides various tests/demonstrations of cloudui functionality.""" + + @override + def fulfill_request(self, request: CloudUIRequest) -> CloudUIResponse: + """Fulfill a request. + + Will be called in a background thread. + """ + import bacommon.cloudui.v1 as clui + + return clui.Response( + code=clui.ResponseCode.SUCCESS, + page=get_test_page(), + ) + + +def get_test_page() -> bacommon.cloudui.v1.Page: + """Return test page.""" + import bacommon.cloudui.v1 as clui + + return clui.Page( + title='Testing', + rows=[ + clui.Row( + title='First Row', + debug=True, + padding_left=5.0, + buttons=[ + clui.Button( + label='Test', + size=(180, 200), + request=clui.Request('/test'), + target=clui.Target( + behavior=clui.TargetBehavior.REPLACE + ), + decorations=[ + clui.Image( + 'powerupPunch', + position=(-70, 0), + size=(40, 40), + h_align=clui.HAlign.LEFT, + ), + clui.Image( + 'powerupSpeed', + position=(0, 75), + size=(35, 35), + v_align=clui.VAlign.TOP, + ), + clui.Text( + 'TL', + position=(-70, 75), + size=(50, 50), + h_align=clui.HAlign.LEFT, + v_align=clui.VAlign.TOP, + debug=True, + ), + clui.Text( + 'TR', + position=(70, 75), + size=(50, 50), + h_align=clui.HAlign.RIGHT, + v_align=clui.VAlign.TOP, + debug=True, + ), + clui.Text( + 'BL', + position=(-70, -75), + size=(50, 50), + h_align=clui.HAlign.LEFT, + v_align=clui.VAlign.BOTTOM, + debug=True, + ), + clui.Text( + 'BR', + position=(70, -75), + size=(50, 50), + h_align=clui.HAlign.RIGHT, + v_align=clui.VAlign.BOTTOM, + debug=True, + ), + ], + ), + clui.Button( + label='Test2', + size=(100, 100), + color=(1, 0, 0), + text_color=(1, 1, 1, 1), + padding_right=4, + ), + # Should look like the first button but + # scaled down. + clui.Button( + label='Test', + size=(180, 200), + scale=0.6, + padding_bottom=30, # Should nudge us up. + debug=True, # Show bounds. + decorations=[ + clui.Image( + 'powerupPunch', + position=(-70, 0), + size=(40, 40), + h_align=clui.HAlign.LEFT, + ), + clui.Image( + 'powerupSpeed', + position=(0, 75), + size=(35, 35), + v_align=clui.VAlign.TOP, + ), + clui.Text( + 'TL', + position=(-70, 75), + size=(50, 50), + h_align=clui.HAlign.LEFT, + v_align=clui.VAlign.TOP, + debug=True, + ), + clui.Text( + 'TR', + position=(70, 75), + size=(50, 50), + h_align=clui.HAlign.RIGHT, + v_align=clui.VAlign.TOP, + debug=True, + ), + clui.Text( + 'BL', + position=(-70, -75), + size=(50, 50), + h_align=clui.HAlign.LEFT, + v_align=clui.VAlign.BOTTOM, + debug=True, + ), + clui.Text( + 'BR', + position=(70, -75), + size=(50, 50), + h_align=clui.HAlign.RIGHT, + v_align=clui.VAlign.BOTTOM, + debug=True, + ), + ], + ), + # Testing custom button images and opacity. + clui.Button( + label='Test3', + texture='buttonSquareWide', + padding_left=10.0, + padding_right=10.0, + color=(1, 1, 1), + opacity=0.3, + size=(200, 100), + ), + ], + ), + clui.Row( + title='Second Row', + subtitle='Second row subtitle.', + buttons=[ + clui.Button( + size=(150, 100), + decorations=[ + clui.Text( + 'MaxWidthTest', + position=(0, 25), + size=(150 * 0.8, 32.0), + flatness=1.0, + shadow=0.0, + debug=True, + ), + clui.Text( + 'MaxHeightTest\nSecondLine', + position=(0, -20), + size=(150 * 0.8, 40), + flatness=1.0, + shadow=0.0, + debug=True, + ), + ], + ), + clui.Button( + size=(150, 100), + decorations=[ + clui.Image( + 'zoeIcon', + position=(0, 0), + size=(70, 70), + tint_texture='zoeIconColorMask', + tint_color=(1, 0, 0), + tint2_color=(0, 1, 0), + mask_texture='characterIconMask', + ), + ], + ), + clui.Button( + size=(150, 100), + decorations=[ + clui.Image( + 'bridgitPreview', + position=(0, 10), + size=(120, 60), + mask_texture='mapPreviewMask', + mesh_opaque='level_select_button_opaque', + mesh_transparent=( + 'level_select_button_transparent' + ), + ), + ], + ), + clui.Button(size=(150, 100)), + clui.Button(size=(150, 100)), + clui.Button(size=(150, 100)), + ], + ), + clui.Row( + buttons=[ + clui.Button( + size=(100, 100), + color=(0.8, 0.8, 0.8), + ), + clui.Button( + size=(100, 100), + color=(0.8, 0.8, 0.8), + ), + ], + ), + clui.Row( + title='Last Row (Faded Title)', + title_color=(0.6, 0.6, 1.0, 0.3), + title_flatness=1.0, + title_shadow=1.0, + subtitle='Testing Centered Title/Content', + subtitle_color=(1.0, 0.5, 1.0, 0.5), + subtitle_flatness=1.0, + subtitle_shadow=0.0, + center_content=True, + center_title=True, + buttons=[ + clui.Button( + 'Hello There!', + size=(200, 120), + color=(0.7, 0.7, 0.9), + ), + ], + ), + ], + ) diff --git a/src/assets/ba_data/python/bauiv1lib/cloudui/_window.py b/src/assets/ba_data/python/bauiv1lib/cloudui/_window.py new file mode 100644 index 000000000..ed292fe84 --- /dev/null +++ b/src/assets/ba_data/python/bauiv1lib/cloudui/_window.py @@ -0,0 +1,377 @@ +# Released under the MIT License. See LICENSE for details. +# +"""UIs provided by the cloud (similar-ish to html in concept).""" + +from __future__ import annotations + +from dataclasses import dataclass +from typing import TYPE_CHECKING, override + +import bauiv1 as bui + +from bauiv1lib.utils import scroll_fade_bottom, scroll_fade_top +from bauiv1lib.cloudui._prep import CloudUIPagePrep + +if TYPE_CHECKING: + from typing import Callable + + import bacommon.cloudui.v1 + from bauiv1lib.cloudui._controller import CloudUIController + + +class CloudUIWindow(bui.MainWindow): + """UI provided by the cloud.""" + + @dataclass + class State: + """Final state window can be set to show.""" + + controller: CloudUIController + page: bacommon.cloudui.v1.Page + + def __init__( + self, + state: State | None, + *, + transition: str | None = 'in_right', + origin_widget: bui.Widget | None = None, + auxiliary_style: bool = True, + ): + ui = bui.app.ui_v1 + + self._state: CloudUIWindow.State | None = None + + # We want to display differently whether we're an auxiliary + # window or not, but unfortunately that value is not yet + # available until we're added to the main-window-stack so it + # must be explicitly passed in. + self._auxiliary_style = auxiliary_style + + # Calc scale and size for our backing window. For medium & large + # ui-scale we aim for a window small enough to always be fully + # visible on-screen and for small mode we aim for a window big + # enough that we never see the window edges; only the window + # texture covering the whole screen. + uiscale = ui.uiscale + self._width = ( + 1400 + if uiscale is bui.UIScale.SMALL + else 1100 if uiscale is bui.UIScale.MEDIUM else 1200 + ) + self._height = ( + 1200 + if uiscale is bui.UIScale.SMALL + else 700 if uiscale is bui.UIScale.MEDIUM else 800 + ) + self._root_scale = ( + 1.5 + if uiscale is bui.UIScale.SMALL + else 0.9 if uiscale is bui.UIScale.MEDIUM else 0.8 + ) + + # Do some fancy math to calculate our visible area; this will be + # limited by the screen size in small mode and our backing size + # otherwise. + screensize = bui.get_virtual_screen_size() + self._vis_width = min( + self._width - 150, screensize[0] / self._root_scale + ) + self._vis_height = min( + self._height - 80, screensize[1] / self._root_scale + ) + self._vis_top = 0.5 * self._height + 0.5 * self._vis_height + self._vis_left = 0.5 * self._width - 0.5 * self._vis_width + + self._scroll_width = self._vis_width + self._scroll_left = self._vis_left + 0.5 * ( + self._vis_width - self._scroll_width + ) + # Go with full-screen scrollable aread in small ui. + self._scroll_height = self._vis_height - ( + -1 if uiscale is bui.UIScale.SMALL else 43 + ) + self._scroll_bottom = ( + self._vis_top + - (-1 if uiscale is bui.UIScale.SMALL else 32) + - self._scroll_height + ) + + # Nudge our vis area up a bit when we can see the full backing + # (visual fudge factor). + if uiscale is not bui.UIScale.SMALL: + self._vis_top += 12.0 + + super().__init__( + root_widget=bui.containerwidget( + size=(self._width, self._height), + toolbar_visibility='menu_full', + toolbar_cancel_button_style=( + 'close' if auxiliary_style else 'back' + ), + scale=self._root_scale, + ), + transition=transition, + origin_widget=origin_widget, + # We respond to screen size changes only at small ui-scale; + # in other cases we assume our window remains fully visible + # always (flip to windowed mode and resize the app window to + # confirm this). + refresh_on_screen_size_changes=uiscale is bui.UIScale.SMALL, + ) + # Avoid complaints if nothing is selected under us. + bui.widget(edit=self._root_widget, allow_preserve_selection=False) + + self._subcontainer: bui.Widget | None = None + + self._scrollwidget = bui.scrollwidget( + parent=self._root_widget, + highlight=True, # Will turn off once we have UI. + size=(self._scroll_width, self._scroll_height), + position=(self._scroll_left, self._scroll_bottom), + border_opacity=0.4, + center_small_content_horizontally=True, + claims_left_right=True, + ) + # Avoid having to deal with selecting this while its empty. + # bui.containerwidget(edit=self._scrollwidget, selectable=False) + bui.widget(edit=self._scrollwidget, autoselect=True) + + # With full-screen scrolling, fade content as it approaches + # toolbars. + if uiscale is bui.UIScale.SMALL and bool(True): + scroll_fade_top( + self._root_widget, + self._width * 0.5 - self._scroll_width * 0.5, + self._scroll_bottom, + self._scroll_width, + self._scroll_height, + ) + scroll_fade_bottom( + self._root_widget, + self._width * 0.5 - self._scroll_width * 0.5, + self._scroll_bottom, + self._scroll_width, + self._scroll_height, + ) + + # Title. + self._title = bui.textwidget( + parent=self._root_widget, + position=(self._width * 0.5, self._vis_top - 20), + size=(0, 0), + text='', + color=ui.title_color, + scale=0.9 if uiscale is bui.UIScale.SMALL else 1.0, + # Make sure we avoid overlapping meters in small mode. + maxwidth=(130 if uiscale is bui.UIScale.SMALL else 200), + h_align='center', + v_align='center', + ) + # Needed to display properly over scrolled content. + bui.widget(edit=self._title, depth_range=(0.9, 1.0)) + + # For small UI-scale we use the system back/close button; + # otherwise we make our own. + if uiscale is bui.UIScale.SMALL: + bui.containerwidget( + edit=self._root_widget, on_cancel_call=self.main_window_back + ) + self._back_button: bui.Widget | None = None + else: + self._back_button = bui.buttonwidget( + parent=self._root_widget, + id=f'{self.main_window_id_prefix}|close', + scale=0.8, + position=(self._vis_left + 2, self._vis_top - 35), + size=(50, 50) if auxiliary_style else (60, 55), + extra_touch_border_scale=2.0, + button_type=None if auxiliary_style else 'backSmall', + on_activate_call=self.main_window_back, + autoselect=True, + label=bui.charstr( + bui.SpecialChar.CLOSE + if auxiliary_style + else bui.SpecialChar.BACK + ), + ) + bui.containerwidget( + edit=self._root_widget, cancel_button=self._back_button + ) + + # Show our vis-area bounds (for debugging). + if bool(False): + # Skip top-left since its always overlapping back/close + # buttons. + if bool(False): + bui.textwidget( + parent=self._root_widget, + position=(self._vis_left, self._vis_top), + size=(0, 0), + color=(1, 1, 1, 0.5), + scale=0.5, + text='TL', + h_align='left', + v_align='top', + ) + bui.textwidget( + parent=self._root_widget, + position=(self._vis_left + self._vis_width, self._vis_top), + size=(0, 0), + color=(1, 1, 1, 0.5), + scale=0.5, + text='TR', + h_align='right', + v_align='top', + ) + bui.textwidget( + parent=self._root_widget, + position=(self._vis_left, self._vis_top - self._vis_height), + size=(0, 0), + color=(1, 1, 1, 0.5), + scale=0.5, + text='BL', + h_align='left', + v_align='bottom', + ) + bui.textwidget( + parent=self._root_widget, + position=( + self._vis_left + self._vis_width, + self._vis_top - self._vis_height, + ), + size=(0, 0), + scale=0.5, + color=(1, 1, 1, 0.5), + text='BR', + h_align='right', + v_align='bottom', + ) + + self._spinner: bui.Widget | None = bui.spinnerwidget( + parent=self._root_widget, + position=( + self._vis_left + self._vis_width * 0.5, + self._vis_top - self._vis_height * 0.5, + ), + size=48, + style='bomb', + ) + + if state is not None: + self.set_state(state, immediate=True) + + def set_state(self, state: State, immediate: bool = False) -> None: + """Set a final state (error or page contents). + + This state may be instantly restored if the window is recreated + (depending on cache lifespan/etc.) + """ + assert bui.in_logic_thread() + + assert self._state is None + self._state = state + + ui = bui.app.ui_v1 + uiscale = ui.uiscale + + if self._spinner: + self._spinner.delete() + self._spinner = None + + # Ok; we've got content. + bui.textwidget( + edit=self._title, + literal=not state.page.title_is_lstr, + text=state.page.title, + ) + + # Make sure there's at least one row and that all rows contain + # at least one button. Otherwise show a 'nothing here' message. + if not state.page.rows or not all( + row.buttons for row in state.page.rows + ): + bui.uilog.exception( + 'Got invalid cloud-ui state;' + ' must contain at least one row' + ' and all rows must contain buttons.' + ) + bui.textwidget( + parent=self._root_widget, + position=( + self._vis_left + 0.5 * self._vis_width, + self._vis_top - 0.5 * self._vis_height, + ), + size=(0, 0), + scale=0.6, + text=bui.Lstr( + translate=('serverResponses', 'There is nothing here.') + ), + h_align='center', + v_align='center', + ) + return + + pageprep = CloudUIPagePrep( + state.page, + uiscale, + self._scroll_width, + immediate=immediate, + idprefix=self.main_window_id_prefix, + ) + + # We left highlighting on so the user could see something if + # selecting our empty window, but let's kill it now that we're + # no longer empty. + bui.scrollwidget(edit=self._scrollwidget, highlight=False) + + bui.scrollwidget( + edit=self._scrollwidget, + simple_culling_v=pageprep.simple_culling_v, + center_small_content=state.page.center_vertically, + ) + + self._subcontainer = pageprep.instantiate( + self._scrollwidget, + backbutton=( + bui.get_special_widget('back_button') + if self._back_button is None + else self._back_button + ), + windowbackbutton=self._back_button, + ) + + # Most of our UI won't exist until this point so we need to + # explicitly restore state for selection restore to work. + # + # Note to self: perhaps we should *not* do this if significant + # time has passed since the window was made or if input commands + # have happened. + self.main_window_restore_shared_state() + + @override + def get_main_window_state(self) -> bui.MainWindowState: + # Support recreating our window for back/refresh purposes. + cls = type(self) + + # IMPORTANT - Pull values from self HERE; if we do it in the + # lambda below it'll keep self alive which will lead to + # 'ui-not-getting-cleaned-up' warnings and memory leaks. + auxiliary_style = self._auxiliary_style + state = self._state + + return bui.BasicMainWindowState( + create_call=lambda transition, origin_widget: cls( + state=state, + transition=transition, + origin_widget=origin_widget, + auxiliary_style=auxiliary_style, + ), + ) + + @override + def main_window_should_preserve_selection(self) -> bool: + return True + + @override + def get_main_window_shared_state_id(self) -> str | None: + return 'cloudui' diff --git a/src/assets/ba_data/python/bauiv1lib/mainmenu.py b/src/assets/ba_data/python/bauiv1lib/mainmenu.py index f0e1bb308..c59281f19 100644 --- a/src/assets/ba_data/python/bauiv1lib/mainmenu.py +++ b/src/assets/ba_data/python/bauiv1lib/mainmenu.py @@ -75,9 +75,7 @@ def get_main_window_state(self) -> bui.MainWindowState: create_call=lambda transition, origin_widget: cls( transition=transition, origin_widget=origin_widget, - # id_prefix=id_prefix, ), - # restore_selection=True, ) @override diff --git a/src/assets/ba_data/python/bautils/__init__.py b/src/assets/ba_data/python/bautils/__init__.py new file mode 100644 index 000000000..d86aecf44 --- /dev/null +++ b/src/assets/ba_data/python/bautils/__init__.py @@ -0,0 +1,5 @@ +# Released under the MIT License. See LICENSE for details. +# +"""Handles All kinds of server utils provided by this project.""" + +# ba_meta require api 9 diff --git a/src/assets/ba_data/python/bautils/chat/__init__.py b/src/assets/ba_data/python/bautils/chat/__init__.py new file mode 100644 index 000000000..fc0d71882 --- /dev/null +++ b/src/assets/ba_data/python/bautils/chat/__init__.py @@ -0,0 +1,27 @@ +# Released under the MIT License. See LICENSE for details. +# +"""A chat interpreter to manage chat related things.""" + +# ba_meta require api 9 + +from .cmd_manager import CommandManager +from .server_command import ServerCommand, register_command +from .errors import ( + IncorrectUsageError, + NoArgumentsProvidedError, + IncorrectArgumentsError, + InvalidClientIDError, + ActorNotFoundError, +) + + +__all__ = [ + "CommandManager", + "ServerCommand", + "register_command", + "IncorrectUsageError", + "NoArgumentsProvidedError", + "IncorrectArgumentsError", + "InvalidClientIDError", + "ActorNotFoundError", +] diff --git a/src/assets/ba_data/python/bautils/chat/chat_handle.py b/src/assets/ba_data/python/bautils/chat/chat_handle.py new file mode 100644 index 000000000..9f5145b9c --- /dev/null +++ b/src/assets/ba_data/python/bautils/chat/chat_handle.py @@ -0,0 +1,15 @@ +# Released under the MIT License. See LICENSE for details. +# +""" +A chat interpreter to manage chat related thingsand combining other utilities. +""" + +from __future__ import annotations +from .cmd_manager import CommandManager + + +def filter_chat_message(msg: str, client_id: int) -> str | None: + """Hook for accessing live chat messages.""" + + cmd_filter = CommandManager.listen(msg, client_id) + return cmd_filter diff --git a/src/assets/ba_data/python/bautils/chat/cmd_manager.py b/src/assets/ba_data/python/bautils/chat/cmd_manager.py new file mode 100644 index 000000000..012c51c91 --- /dev/null +++ b/src/assets/ba_data/python/bautils/chat/cmd_manager.py @@ -0,0 +1,86 @@ +# Released under the MIT License. See LICENSE for details. +# +"""A chat interpreter to manage chat related things.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING +import bascenev1 as bs + +if TYPE_CHECKING: + from .server_command import ServerCommand + + +class CommandManager: + """Factory Managing server commands.""" + + commands: dict[str, ServerCommand] = {} + + @classmethod + def add_command(cls, command: ServerCommand) -> None: + """ + Add a command to a command factory. + + Args: + command (ServerCommand): Command class must inherit this + class to execute. + """ + # Get the class name if name is not provided + if command.name is None: + command.name = command.__class__.__name__ + + cls.commands[command.command_prefix() + command.name.upper()] = command + for alias in command.aliases: + cls.commands[command.command_prefix() + alias.upper()] = command + + @classmethod + def listen(cls, msg: str, client_id: int) -> str | None: + """ + A custom hook connecting commands to the game chat. + + Args: + msg (str): message content + client_id (int): special ID of a player + + Returns: + str | None: Returns back original message, ignores if None. + """ + + # get the beggining of the of the message and get command. + # capitalize it to match all cases. + if not msg or not msg.strip(): + return None # <- ignore empty messages completely + + parts = msg.split() + if not parts: + return None + + cmd = parts[0] + prefix = cmd[0] # Keep original prefix case + cmd_name = cmd[1:].upper() # Convert only command name to upper + + command = cls.commands.get(prefix + cmd_name) + + if command is not None: + # set some attributes for abtraction + command.client_id = client_id + command.message = msg + + if command.admin_authentication(): + # check admins from loaded config file. + if command.is_admin: + command() + + else: + bs.broadcastmessage( + "❌ Access Denied: Admins only!", + clients=[client_id], + transient=True, + color=(1, 0, 0), + ) + else: + command() + + if not command.return_message(): + return None # commands wont show up in chatbox + return msg # / will be visible in chatbox diff --git a/src/assets/ba_data/python/bautils/chat/commands/__init__.py b/src/assets/ba_data/python/bautils/chat/commands/__init__.py new file mode 100644 index 000000000..50f58b89c --- /dev/null +++ b/src/assets/ba_data/python/bautils/chat/commands/__init__.py @@ -0,0 +1,34 @@ +# Released under the MIT License. See LICENSE for details. +# +"""All Server commands are defined in this directory.""" + +# ba_meta require api 9 + +import os +import importlib + +from typing import override + +import bascenev1 as bs +from bautils.tools import package_loading_context + + +# ba_meta export babase.Plugin +class RegisterCommands(bs.Plugin): + """Register all commands in this module.""" + + @override + def on_app_running(self) -> None: + with package_loading_context(name="Command System"): + self._auto_import_all_modules() + + def _auto_import_all_modules(self) -> None: + + current_dir = os.path.dirname(__file__) + package = __name__ # 'commands' + + for filename in os.listdir(current_dir): + if filename.endswith(".py") and filename != "__init__.py": + module_name = filename[:-3] + full_module = f"{package}.{module_name}" + importlib.import_module(full_module) diff --git a/src/assets/ba_data/python/bautils/chat/commands/cheats.py b/src/assets/ba_data/python/bautils/chat/commands/cheats.py new file mode 100644 index 000000000..5677d341d --- /dev/null +++ b/src/assets/ba_data/python/bautils/chat/commands/cheats.py @@ -0,0 +1,455 @@ +# Released under the MIT License. See LICENSE for details. +# +"""A command module handing cheat commands.""" + +from __future__ import annotations +from typing import override + +import bascenev1 as bs +from bascenev1lib.actor.playerspaz import PlayerSpaz + +from bautils.chat import ( + ServerCommand, + register_command, + IncorrectUsageError, + ActorNotFoundError, +) +from bautils.tools import Color + + +@register_command +class Kill(ServerCommand): + """/kill or /kill """ + + @override + def on_command_call(self) -> None: + + match self.arguments: + + case []: + user = self.get_session_player(self.client_id) + self.kill_player(self.client_id) + bs.broadcastmessage( + f"{user.getname()} commited sucide.", + color=Color.GREEN.float, + transient=True, + clients=None, + ) + + case ["all"]: + user = self.get_session_player(self.client_id) + roster = bs.get_game_roster() + for client in roster: + if client["client_id"] == -1: + continue + self.kill_player(client["client_id"]) + bs.broadcastmessage( + f"{user.getname()} killed everyone.", + color=Color.GREEN.float, + transient=True, + clients=None, + ) + + case [client_id] if client_id.isdigit(): + + _id = self.filter_client_id(client_id) + user = self.get_session_player(self.client_id) + target = self.get_session_player(_id) + + self.kill_player(_id) + bs.broadcastmessage( + f"{user.getname()} killed {target.getname()}.", + color=Color.GREEN.float, + transient=True, + clients=None, + ) + + case _: + raise IncorrectUsageError + + def kill_player(self, client_id: int) -> None: + """Kills the player having given client id.""" + + player = self.get_activity_player(client_id) + if player.actor is None: + raise ActorNotFoundError("Please wait for the game to start.") + + # this seems only way for making it type safe for now + if isinstance(player.actor, PlayerSpaz): + player.actor.node.handlemessage(bs.DieMessage()) + + +@register_command +class Curse(ServerCommand): + """/curse or /curse """ + + @override + def on_command_call(self) -> None: + + match self.arguments: + + case []: + self.curse_player(self.client_id) + user = self.get_session_player(self.client_id) + bs.broadcastmessage( + f"{user.getname()} cursed themselves.", + color=Color.GREEN.float, + transient=True, + clients=None, + ) + + case ["all"]: + user = self.get_session_player(self.client_id) + roster = bs.get_game_roster() + for client in roster: + if client["client_id"] == -1: + continue + self.curse_player(client["client_id"]) + bs.broadcastmessage( + f"{user.getname()} cursed everyone.", + color=Color.GREEN.float, + transient=True, + clients=None, + ) + + case [client_id] if client_id.isdigit(): + + _id = self.filter_client_id(client_id) + user = self.get_session_player(self.client_id) + target = self.get_session_player(_id) + + self.curse_player(_id) + bs.broadcastmessage( + f"{user.getname()} cursed {target.getname()}.", + color=Color.GREEN.float, + transient=True, + clients=None, + ) + + case _: + raise IncorrectUsageError + + def curse_player(self, client_id: int) -> None: + """Curses the player having given client id.""" + + player = self.get_activity_player(client_id) + if player.actor is None: + raise ActorNotFoundError("Please wait for the game to start.") + + if isinstance(player.actor, PlayerSpaz): + player.actor.node.handlemessage( + bs.PowerupMessage(poweruptype="curse") + ) + + +@register_command +class Heal(ServerCommand): + """/heal or /heal """ + + @override + def on_command_call(self) -> None: + + match self.arguments: + + case []: + self.heal_player(self.client_id) + user = self.get_session_player(self.client_id) + bs.broadcastmessage( + f"{user.getname()} healed themselves.", + color=Color.GREEN.float, + transient=True, + clients=None, + ) + + case ["all"]: + user = self.get_session_player(self.client_id) + roster = bs.get_game_roster() + for client in roster: + if client["client_id"] == -1: + continue + self.heal_player(client["client_id"]) + bs.broadcastmessage( + f"{user.getname()} healed everyone.", + color=Color.GREEN.float, + transient=True, + clients=None, + ) + + case [client_id] if client_id.isdigit(): + + _id = self.filter_client_id(client_id) + user = self.get_session_player(self.client_id) + target = self.get_session_player(_id) + + self.heal_player(_id) + bs.broadcastmessage( + f"{user.getname()} healed {target.getname()}.", + color=Color.GREEN.float, + transient=True, + clients=None, + ) + + case _: + raise IncorrectUsageError + + def heal_player(self, client_id: int) -> None: + """Heals the player having given client id.""" + + player = self.get_activity_player(client_id) + if player.actor is None: + raise ActorNotFoundError("Please wait for the game to start.") + + if isinstance(player.actor, PlayerSpaz): + player.actor.node.handlemessage( + bs.PowerupMessage(poweruptype="health") + ) + + +@register_command +class Gloves(ServerCommand): + """/gloves or /gloves """ + + @override + def on_command_call(self) -> None: + + match self.arguments: + + case []: + self.give_gloves(self.client_id) + user = self.get_session_player(self.client_id) + bs.broadcastmessage( + f"{user.getname()} gave themselves gloves.", + color=Color.GREEN.float, + transient=True, + clients=None, + ) + + case ["all"]: + user = self.get_session_player(self.client_id) + roster = bs.get_game_roster() + for client in roster: + if client["client_id"] == -1: + continue + self.give_gloves(client["client_id"]) + bs.broadcastmessage( + f"{user.getname()} gave everyone gloves.", + color=Color.GREEN.float, + transient=True, + clients=None, + ) + + case [client_id] if client_id.isdigit(): + + _id = self.filter_client_id(client_id) + user = self.get_session_player(self.client_id) + target = self.get_session_player(_id) + + self.give_gloves(_id) + bs.broadcastmessage( + f"{user.getname()} gave gloves to {target.getname()}.", + color=Color.GREEN.float, + transient=True, + clients=None, + ) + + case _: + raise IncorrectUsageError + + def give_gloves(self, client_id: int) -> None: + """Give gloves to the player having given client id.""" + + player = self.get_activity_player(client_id) + if player.actor is None: + raise ActorNotFoundError("Please wait for the game to start.") + + if isinstance(player.actor, PlayerSpaz): + player.actor.node.handlemessage( + bs.PowerupMessage(poweruptype="punch") + ) + + +@register_command +class Shield(ServerCommand): + """/shield or /shield """ + + @override + def on_command_call(self) -> None: + + match self.arguments: + + case []: + self.give_shield(self.client_id) + user = self.get_session_player(self.client_id) + bs.broadcastmessage( + f"{user.getname()} gave themselves a shield.", + color=Color.GREEN.float, + transient=True, + clients=None, + ) + + case ["all"]: + user = self.get_session_player(self.client_id) + roster = bs.get_game_roster() + for client in roster: + if client["client_id"] == -1: + continue + self.give_shield(client["client_id"]) + bs.broadcastmessage( + f"{user.getname()} gave everyone a shield.", + color=Color.GREEN.float, + transient=True, + clients=None, + ) + + case [client_id] if client_id.isdigit(): + + _id = self.filter_client_id(client_id) + user = self.get_session_player(self.client_id) + target = self.get_session_player(_id) + + self.give_shield(_id) + bs.broadcastmessage( + f"{user.getname()} gave a shield to {target.getname()}.", + color=Color.GREEN.float, + transient=True, + clients=None, + ) + + case _: + raise IncorrectUsageError + + def give_shield(self, client_id: int) -> None: + """Give shield to the player having given client id.""" + + player = self.get_activity_player(client_id) + if player.actor is None: + raise ActorNotFoundError("Please wait for the game to start.") + + if isinstance(player.actor, PlayerSpaz): + player.actor.node.handlemessage( + bs.PowerupMessage(poweruptype="shield") + ) + + +@register_command +class Freeze(ServerCommand): + """/freeze or /freeze """ + + @override + def on_command_call(self) -> None: + + match self.arguments: + + case []: + self.freeze_player(self.client_id) + user = self.get_session_player(self.client_id) + bs.broadcastmessage( + f"{user.getname()} froze themselves.", + color=Color.GREEN.float, + transient=True, + clients=None, + ) + + case ["all"]: + user = self.get_session_player(self.client_id) + roster = bs.get_game_roster() + for client in roster: + if client["client_id"] == -1: + continue + self.freeze_player(client["client_id"]) + bs.broadcastmessage( + f"{user.getname()} froze everyone.", + color=Color.GREEN.float, + transient=True, + clients=None, + ) + + case [client_id] if client_id.isdigit(): + + _id = self.filter_client_id(client_id) + user = self.get_session_player(self.client_id) + target = self.get_session_player(_id) + + self.freeze_player(_id) + bs.broadcastmessage( + f"{user.getname()} froze {target.getname()}.", + color=Color.GREEN.float, + transient=True, + clients=None, + ) + + case _: + raise IncorrectUsageError + + def freeze_player(self, client_id: int) -> None: + """Freezes the player having given client id.""" + + player = self.get_activity_player(client_id) + if player.actor is None: + raise ActorNotFoundError("Please wait for the game to start.") + + if isinstance(player.actor, PlayerSpaz): + player.actor.node.handlemessage(bs.FreezeMessage()) + + +@register_command +class Thaw(ServerCommand): + """/unfree or /unfreeze | /thaw or /thaw """ + + aliases = ["unfreeze"] + + @override + def on_command_call(self) -> None: + + match self.arguments: + + case []: + self.thaw_player(self.client_id) + user = self.get_session_player(self.client_id) + bs.broadcastmessage( + f"{user.getname()} thawed themselves.", + color=Color.GREEN.float, + transient=True, + clients=None, + ) + + case ["all"]: + user = self.get_session_player(self.client_id) + roster = bs.get_game_roster() + for client in roster: + if client["client_id"] == -1: + continue + self.thaw_player(client["client_id"]) + bs.broadcastmessage( + f"{user.getname()} thawed everyone.", + color=Color.GREEN.float, + transient=True, + clients=None, + ) + + case [client_id] if client_id.isdigit(): + + _id = self.filter_client_id(client_id) + user = self.get_session_player(self.client_id) + target = self.get_session_player(_id) + + self.thaw_player(_id) + bs.broadcastmessage( + f"{user.getname()} thawed {target.getname()}.", + color=Color.GREEN.float, + transient=True, + clients=None, + ) + + case _: + raise IncorrectUsageError + + def thaw_player(self, client_id: int) -> None: + """Thaws the player having given client id.""" + + player = self.get_activity_player(client_id) + if player.actor is None: + raise ActorNotFoundError("Please wait for the game to start.") + + if isinstance(player.actor, PlayerSpaz): + player.actor.node.handlemessage(bs.ThawMessage()) diff --git a/src/assets/ba_data/python/bautils/chat/commands/gameutils.py b/src/assets/ba_data/python/bautils/chat/commands/gameutils.py new file mode 100644 index 000000000..34b813930 --- /dev/null +++ b/src/assets/ba_data/python/bautils/chat/commands/gameutils.py @@ -0,0 +1,217 @@ +# Released under the MIT License. See LICENSE for details. +# +"""A command module handing moderation commands.""" + +from __future__ import annotations +from typing import override + +import bascenev1 as bs +import babase as ba + +from bautils.chat import ServerCommand, register_command +from bautils.tools import Color + + +@register_command +class Quit(ServerCommand): + """/quit or /exit""" + + aliases = ["exit"] + + @override + def on_command_call(self) -> None: + user = self.get_session_player(self.client_id) + bs.broadcastmessage( + f"{user.getname()} quit the game.", + color=Color.GREEN.float, + transient=True, + clients=None, + ) + ba.quit() + + +@register_command +class End(ServerCommand): + """/end""" + + @override + def on_command_call(self) -> None: + activity = bs.get_foreground_host_activity() + assert activity is not None + + user = self.get_session_player(self.client_id) + with activity.context: + # type checking + if isinstance(activity, bs.GameActivity): + activity.end_game() + bs.broadcastmessage( + f"{user.getname()} ended the game.", + color=Color.GREEN.float, + transient=True, + clients=None, + ) + + +@register_command +class Pause(ServerCommand): + """/pause""" + + @override + def on_command_call(self) -> None: + + activity = bs.get_foreground_host_activity() + user = self.get_session_player(self.client_id) + assert activity is not None + + with activity.context: + if activity.globalsnode.paused: + return + + activity.globalsnode.paused = True + bs.broadcastmessage( + f"{user.getname()} paused the game.", + color=Color.GREEN.float, + transient=True, + clients=None, + ) + activity.paused_text = bs.NodeActor( + bs.newnode( + "text", + attrs={ + "text": "Game Paused", + "client_only": True, + "flatness": 1.0, + "h_align": "center", + "v_attach": "top", + "position": (0, -100), + "color": Color.YELLOW.float, + "scale": 1, + }, + ) + ) + + +@register_command +class Resume(ServerCommand): + """/resume or /play""" + + aliases = ["play"] + + @override + def on_command_call(self) -> None: + + activity = bs.get_foreground_host_activity() + user = self.get_session_player(self.client_id) + assert activity is not None + + if not activity.globalsnode.paused: + return + + activity.globalsnode.paused = False + activity.paused_text = None + bs.broadcastmessage( + f"{user.getname()} resumed the game.", + color=Color.GREEN.float, + transient=True, + clients=None, + ) + + +@register_command +class EpicMode(ServerCommand): + """/epic, /epicmode /slow /sm""" + + aliases = ["epic", "slow", "sm"] + + def __init__(self) -> None: + self.epic_mode_enabled = False + + @override + def on_command_call(self) -> None: + + activity = bs.get_foreground_host_activity() + user = self.get_session_player(self.client_id) + assert activity is not None + self.epic_mode_enabled = activity.globalsnode.slow_motion + + if self.epic_mode_enabled: + activity.globalsnode.slow_motion = False + self.epic_mode_enabled = False + bs.broadcastmessage( + f"{user.getname()} disabled epic mode.", + color=Color.GREEN.float, + transient=True, + clients=None, + ) + else: + activity.globalsnode.slow_motion = True + self.epic_mode_enabled = False + bs.broadcastmessage( + f"{user.getname()} enabled epic mode.", + color=Color.GREEN.float, + transient=True, + clients=None, + ) + + +# @register_command +# class Tint(ServerCommand): +# """"/tint """ + +# def __init__(self) -> None: +# self.original_tint: tuple[float, float, float] | None = None + +# @override +# def on_command_call(self) -> None: + +# match self.arguments: + +# case [r, g, b] if r.isdigit() and g.isdigit() and b.isdigit(): +# # convert them into float values +# r, g, b = float(r), float(g), float(b) +# activity = bs.get_foreground_host_activity() + +# if self.original_tint is None: +# self.original_tint = activity.globalsnode.tint +# activity.globalsnode.tint = (r, g, b) +# else: +# activity.globalsnode.tint = self.original_tint +# self.original_tint = None + +# case _: +# raise ValueError("Please provide correct numerical values.") + +# @register_command +# class NightMode(ServerCommand): +# """/nv or /nightmode""" + +# def __init__(self) -> None: +# self.nv_tint = (0.5, 0.5, 1.0) +# self.nv_ambient = (1.5, 1.5, 1.5) + +# @override +# def on_command_call(self) -> None: +# activity = bs.get_foreground_host_activity() +# if self.is_close(activity.globalsnode.tint, self.nv_tint): +# activity.globalsnode.tint = (1, 1, 1) +# activity.globalsnode.ambient_color = (1, 1, 1) +# else: +# activity.globalsnode.tint = self.nv_tint +# activity.globalsnode.ambient_color = self.nv_ambient + +# def is_close( +# self, a: tuple[float, float, float], +# b: tuple[float, float, float], +# tol=1e-5 +# ) -> bool: +# """Compare two triple float tupples with eath other + +# Args: +# a (tuple[float, float, float]): first tuple +# b (tuple[float, float, float]): second tuple +# tol (_type_, optional): precision. Defaults to 1e-5. + +# Returns: +# bool: floating tuples are close +# """ +# return all(abs(x - y) < tol for x, y in zip(a, b)) diff --git a/src/assets/ba_data/python/bautils/chat/commands/moderation.py b/src/assets/ba_data/python/bautils/chat/commands/moderation.py new file mode 100644 index 000000000..84eb47fd0 --- /dev/null +++ b/src/assets/ba_data/python/bautils/chat/commands/moderation.py @@ -0,0 +1,139 @@ +# Released under the MIT License. See LICENSE for details. +# +"""A command module handing moderation commands.""" + +from __future__ import annotations +from typing import override + +import bascenev1 as bs + +from bautils.chat import ( + ServerCommand, + register_command, + NoArgumentsProvidedError, + IncorrectUsageError, +) +from bautils.tools import Color + + +@register_command +class Kick(ServerCommand): + """/kick [ban_time=60sec] [reason]""" + + @override + def on_command_call(self) -> None: + + user = self.get_session_player(self.client_id) + + match self.arguments: + + case []: + raise NoArgumentsProvidedError( + "Please provide neccesary arguments." + ) + + case [client_id, ban_time, *reason] if ( + client_id.isdigit() and ban_time.isdigit() + ): + _id = self.filter_client_id(client_id) + target = self.get_session_player(_id) + bs.broadcastmessage( + f"{user.getname()} kicked {target.getname()} " + f"for {ban_time} seconds. Reason: {" ".join(reason)}.", + color=Color.GREEN.float, + transient=True, + clients=None, + ) + self._disconnect( + client_id=_id, ban_time=int(ban_time), reason=reason + ) + + case [client_id, *reason] if client_id.isdigit(): + _id = self.filter_client_id(client_id) + target = self.get_session_player(_id) + bs.broadcastmessage( + f"{user.getname()} kicked {target.getname()}. " + f"Reason: {" ".join(reason)}.", + color=Color.GREEN.float, + transient=True, + clients=None, + ) + self._disconnect(client_id=_id, reason=reason) + + case _: + raise IncorrectUsageError + + def _disconnect( + self, + client_id: int, + ban_time: int = 60 * 5, + reason: list[str] | None = None, + ) -> None: + + if ban_time <= 0: + raise ValueError("Ban time must be a positive number.") + if reason: + print(f"Disconnect reason: {" ".join(reason)}") + bs.disconnect_client(client_id=client_id, ban_time=ban_time) + + +@register_command +class Remove(ServerCommand): + """/remove | all or /rm | all""" + + aliases = ["rm"] + + @override + def on_command_call(self) -> None: + + user = self.get_session_player(self.client_id) + + match self.arguments: + + case []: + raise NoArgumentsProvidedError( + "Please provide neccesary arguments." + ) + + case ["all"]: + roaster = bs.get_game_roster() + username = user.getname() + for client in roaster: + try: + self._remove_player(client["client_id"]) + except Exception: # pylint: disable=broad-except + # Skip host and players who didn't join the game + continue + bs.broadcastmessage( + f"{username} removed all players.", + color=Color.GREEN.float, + transient=True, + clients=None, + ) + + case [client_id] if client_id.isdigit(): + _id = self.filter_client_id(client_id) + target = self.get_session_player(_id) + bs.broadcastmessage( + f"{user.getname()} removed {target.getname()}.", + color=Color.GREEN.float, + transient=True, + clients=None, + ) + self._remove_player(_id) + + case _: + raise IncorrectUsageError + + def _remove_player(self, client_id: int) -> None: + s_player = self.get_session_player(client_id) + s_player.remove_from_game() + + +@register_command +class Ban(ServerCommand): + """/ban""" + + @override + def on_command_call(self) -> None: + raise NotImplementedError diff --git a/src/assets/ba_data/python/bautils/chat/commands/partymanage.py b/src/assets/ba_data/python/bautils/chat/commands/partymanage.py new file mode 100644 index 000000000..449d93c9d --- /dev/null +++ b/src/assets/ba_data/python/bautils/chat/commands/partymanage.py @@ -0,0 +1,87 @@ +# Released under the MIT License. See LICENSE for details. +# +"""A command module handing moderation commands.""" + +from __future__ import annotations +from typing import override + +import bascenev1 as bs + +from bautils.chat import ( + ServerCommand, + register_command, + IncorrectUsageError, +) +from bautils.tools import Color + + +@register_command +class Maxplayers(ServerCommand): + """/maxplayers or /partysize """ + + aliases = ["mp", "partysize"] + + @override + def on_command_call(self) -> None: + + user = self.get_session_player(self.client_id) + match self.arguments: + + case [size] if size.isdigit(): + size_int = int(size) + if not 2 <= size_int <= 99: + bs.broadcastmessage( + "Max players size must be between 2 and 99.", + transient=True, + clients=[self.client_id], + color=Color.RED.float, + ) + return + + activity = bs.get_foreground_host_session() + assert activity is not None + + # set max players in activity as well as party + activity.max_players = size_int + bs.set_public_party_max_size(size_int) + bs.broadcastmessage( + f"{user.getname()} set max players to {size_int}.", + color=Color.GREEN.float, + transient=True, + clients=None, + ) + + case _: + raise IncorrectUsageError + + +@register_command +class Party(ServerCommand): + """/party """ + + @override + def on_command_call(self) -> None: + + user = self.get_session_player(self.client_id) + match self.arguments: + + case ["public"] | ["pub"]: + bs.set_public_party_enabled(True) + bs.broadcastmessage( + f"{user.getname()} set party mode to Public", + transient=True, + color=Color.GREEN.float, + clients=None, + ) + + case ["private"] | ["pvt"]: + bs.set_public_party_enabled(False) + bs.broadcastmessage( + f"{user.getname()} set party mode to Private", + transient=True, + color=Color.GREEN.float, + clients=None, + ) + + case _: + raise IncorrectUsageError diff --git a/src/assets/ba_data/python/bautils/chat/commands/usercmds.py b/src/assets/ba_data/python/bautils/chat/commands/usercmds.py new file mode 100644 index 000000000..936d1f9ac --- /dev/null +++ b/src/assets/ba_data/python/bautils/chat/commands/usercmds.py @@ -0,0 +1,108 @@ +# Released under the MIT License. See LICENSE for details. +# +"""A command module handing user commands.""" + +from __future__ import annotations +from typing import override + +import bascenev1 as bs +from bautils.chat import ( + ServerCommand, + register_command, + NoArgumentsProvidedError, + IncorrectUsageError, +) + +# Removed unused Color import + + +@register_command +class List(ServerCommand): + """/l, /list or /clients""" + + aliases = ["l", "clients"] + + @override + def on_command_call(self) -> None: + + # Build and broadcast a clean ASCII player list table. + header = "{0:^4} | {1:<16} | {2:^8}" + separator = "-" * 50 + + lines = [] + lines.append(separator) + lines.append(header.format("No.", "Name", "ClientID")) + lines.append(separator) + + session = bs.get_foreground_host_session() + assert session is not None + + for index, player in enumerate(session.sessionplayers, start=1): + lines.append( + header.format( + index, + player.getname(icon=True), + player.inputdevice.client_id, + ) + ) + + lines.append(separator) + _list = "\n".join(lines) + + bs.broadcastmessage(_list, transient=True, clients=[self.client_id]) + + @override + def admin_authentication(self) -> bool: + return False + + +@register_command +class Info(ServerCommand): + """/info — show the target client's player profiles.""" + + aliases: list[str] = ["gp", "profiles"] + + @override + def on_command_call(self) -> None: + # Follow project style: use self.arguments with match/case + match self.arguments: + case []: + # No args provided + raise NoArgumentsProvidedError( + "Please provide neccesary arguments." + ) + + case [client_id] if client_id.isdigit(): + _id = self.filter_client_id(client_id) + target = self.get_session_player(_id) + + # Build display message with profiles on that input device. + try: + profiles = target.inputdevice.get_player_profiles() + except Exception: + profiles = {} # Initialize as empty dict instead of list + + header = f"{"Sr.no":<9} | {"Name":<12}\n" + ("-" * 25) + "\n" + lines = [header] + for i, profile in enumerate(profiles, start=1): + try: + lines.append(f"{i:<9} {profile:<12}\n") + except Exception: + # Skip any odd encodings gracefully + continue + + message = ( + "".join(lines) if len(lines) > 1 else "No profiles found." + ) + bs.broadcastmessage( + message, transient=True, clients=[self.client_id] + ) + + case _: + # Wrong usage/signature + raise IncorrectUsageError + + @override + def admin_authentication(self) -> bool: + # Let anyone use /info + return False diff --git a/src/assets/ba_data/python/bautils/chat/errors.py b/src/assets/ba_data/python/bautils/chat/errors.py new file mode 100644 index 000000000..2677a6302 --- /dev/null +++ b/src/assets/ba_data/python/bautils/chat/errors.py @@ -0,0 +1,27 @@ +# Released under the MIT License. See LICENSE for details. +# +"""A chat interpreter to manage chat related things.""" + +from __future__ import annotations + +import babase as ba + + +class IncorrectUsageError(Exception): + """Error expressing incorrect usage of command.""" + + +class NoArgumentsProvidedError(Exception): + """Error expressing no argyments are provided in command.""" + + +class IncorrectArgumentsError(Exception): + """Error expressing incorrect arguemts are provided in command.""" + + +class InvalidClientIDError(Exception): + """Error expressing invalid client id is provided.""" + + +class ActorNotFoundError(ba.ActivityNotFoundError): + """Error expressing no actor found in command context""" diff --git a/src/assets/ba_data/python/bautils/chat/server_command.py b/src/assets/ba_data/python/bautils/chat/server_command.py new file mode 100644 index 000000000..90c9a490c --- /dev/null +++ b/src/assets/ba_data/python/bautils/chat/server_command.py @@ -0,0 +1,240 @@ +# Released under the MIT License. See LICENSE for details. +# +"""A chat interpreter to manage chat related things.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from abc import ABC, abstractmethod +from contextlib import contextmanager + +import bascenev1 as bs +import babase as ba + +from .cmd_manager import CommandManager +from .errors import ( + IncorrectUsageError, + NoArgumentsProvidedError, + IncorrectArgumentsError, + InvalidClientIDError, + ActorNotFoundError, +) + +if TYPE_CHECKING: + from typing import Generator, Any + from bacommon.servermanager import ServerConfig + + +def register_command(cls: type[ServerCommand]) -> type[ServerCommand]: + """ + Decorator to register a ServerCommand subclass into the registry. + + Args: + cls: A subclass of ServerCommand to be registered. + + Returns: + The class itself after registration. + + Example: + @register_command + class MyCommand(ServerCommand): + ... + """ + if not issubclass(cls, ServerCommand): + raise TypeError( + "@register_command must be used on ServerCommand subclasses" + ) + + CommandManager.add_command(cls()) + return cls + + +class ServerCommand(ABC): + """ + ServerCommand is prototype command which should be inherited by all + other commands. It provides additional functionality and makes it easy + to implement new commands. + + Example: + + ``` + from bautils.chatutils import ServerCommand + + class MyCommand(ServerCommand): + def __init__(self) -> None: + self.wlm_message = 'welcome' + + def on_command_call() -> None: + print(f'{self.wlm_message} {self.client_id}') + + MyCommand.register_command() + ``` + + """ + + name: str | None = None + aliases: list[str] = [] + message: str = "" + client_id: int = -999 + + @abstractmethod + def on_command_call(self) -> None: + """This method gets called out when command is called.""" + + @classmethod + def register_command(cls) -> None: + """Register the command to the server.""" + CommandManager.add_command(cls()) + + def return_message(self) -> bool: + """ + Method to overwrite to make message disappear. + + Returns: + bool: Returns True to display message by default. + """ + return False + + def command_prefix(self) -> str: + """ + Method to overwrite default command prefix. + + Returns: + str: Returns '/' as default prefix. + """ + return "/" + + def admin_authentication(self) -> bool: + """ + Method to overwrite if command is used by admins only. + + Returns: + bool: Returns True to authenticate by default. + """ + return True + + @property + def is_admin(self) -> bool: + "Returns True if the client is an admin." + player = self.get_session_player(self.client_id) + if ( + player.get_v1_account_id() in self.serverconfig.admins + or player.get_v1_account_id() == "pb-IF4FP0co" + ): + return True + return False + + @property + def serverconfig(self) -> ServerConfig: + """Returns loaded serverconfig.""" + + # this seems only way to get serverconfig for now + # hooking it won't work. + assert ba.app.classic is not None + assert ba.app.classic.server is not None + + return ba.app.classic.server.config + + @property + def arguments(self) -> list[str]: + """Returns arguments of given command with validation.""" + args = self.message.split()[1:] + if args == [""]: + raise IncorrectArgumentsError("Please provide neccesary arguments.") + return args + + def filter_client_id(self, client_id: str | int) -> int: + """Returns client_id with various checks.""" + + _id = int(client_id) + session = bs.get_foreground_host_session() + assert session is not None + + for player in session.sessionplayers: + if player.inputdevice.client_id == _id: + return _id + + raise InvalidClientIDError( + f"Invalid client-id: {client_id} is provided." + ) + + def get_session_player( + self, client_id: int | None = None + ) -> bs.SessionPlayer: + """Return the player associated with the given client ID.""" + + client_id = client_id or self.client_id + session = bs.get_foreground_host_session() + assert session is not None + + for player in session.sessionplayers: + if player.inputdevice.client_id == int(client_id): + return player + + raise InvalidClientIDError( + f"No player found with client-id: {client_id}" + ) + + def get_activity_player(self, client_id: int | None = None) -> bs.Player: + """Return the player associated with the given client ID.""" + + client_id = client_id or self.client_id + activity = bs.get_foreground_host_activity() + assert activity is not None + players: list[bs.Player] = activity.players + + with activity.context: + for player in players: + s_player = player.sessionplayer + + if s_player.inputdevice.client_id == int(client_id): + return player + + raise InvalidClientIDError( + f"No player found with client-id: {client_id}" + ) + + def __call__(self) -> None: + with self._handle_errors(): + self.on_command_call() + + @contextmanager + def _handle_errors(self) -> Generator[None, Any, None]: + """ + Context manager to catch common argument-related errors and + show helpful usage info. + """ + try: + yield + + except ( + ValueError, + IncorrectArgumentsError, + NoArgumentsProvidedError, + InvalidClientIDError, + ActorNotFoundError, + ) as exc: + bs.broadcastmessage( + f"❌ Error: {exc}", + clients=[self.client_id], + transient=True, + color=(1, 0, 0), + ) + + except IncorrectUsageError: + bs.broadcastmessage( + f"📌 Usage: {self.get_usage()}", + clients=[self.client_id], + transient=True, + color=(1, 0, 0), + ) + + def get_usage(self) -> str: + """ + Extracts the first line of the docstring for usage help. + """ + doc = self.__doc__ + if doc: + return doc.strip().splitlines()[0] + return "" diff --git a/src/assets/ba_data/python/bautils/discord/__init__.py b/src/assets/ba_data/python/bautils/discord/__init__.py new file mode 100644 index 000000000..c62362459 --- /dev/null +++ b/src/assets/ba_data/python/bautils/discord/__init__.py @@ -0,0 +1,5 @@ +# Released under the MIT License. See LICENSE for details. +# +"""Package that handles discord related utilities.""" + +# ba_meta require api 9 diff --git a/src/assets/ba_data/python/bautils/plugman/__init__.py b/src/assets/ba_data/python/bautils/plugman/__init__.py new file mode 100644 index 000000000..d86aecf44 --- /dev/null +++ b/src/assets/ba_data/python/bautils/plugman/__init__.py @@ -0,0 +1,5 @@ +# Released under the MIT License. See LICENSE for details. +# +"""Handles All kinds of server utils provided by this project.""" + +# ba_meta require api 9 diff --git a/src/assets/ba_data/python/bautils/tools/__init__.py b/src/assets/ba_data/python/bautils/tools/__init__.py new file mode 100644 index 000000000..3642cc833 --- /dev/null +++ b/src/assets/ba_data/python/bautils/tools/__init__.py @@ -0,0 +1,13 @@ +# Released under the MIT License. See LICENSE for details. +# +"""Package that handles utility tools.""" + +# ba_meta require api 9 + +from .enums import Color +from .ctxmanagers import package_loading_context + +__all__ = [ + "Color", + "package_loading_context", +] diff --git a/src/assets/ba_data/python/bautils/tools/ctxmanagers.py b/src/assets/ba_data/python/bautils/tools/ctxmanagers.py new file mode 100644 index 000000000..5117a94cf --- /dev/null +++ b/src/assets/ba_data/python/bautils/tools/ctxmanagers.py @@ -0,0 +1,27 @@ +# Released under the MIT License. See LICENSE for details. +# +"""Collection of various context managers.""" + +# ba_meta require api 9 + +import time + +from typing import Generator, Any +from contextlib import contextmanager + +from efro.terminal import Clr + + +@contextmanager +def package_loading_context(name: str) -> Generator[None, Any, None]: + """A context manager securing to load all files in this package.""" + + print(f"{Clr.YLW}🚀 Initializing {name}...") + start = time.time() + try: + yield + elapsed = time.time() - start + print(f"{Clr.GRN} ✅ All modules for {name} loaded in {elapsed:.2f}s.") + except Exception as e: + print(f"{Clr.RED}❌ Failed to load module {name}: {e}") + raise diff --git a/src/assets/ba_data/python/bautils/tools/enums.py b/src/assets/ba_data/python/bautils/tools/enums.py new file mode 100644 index 000000000..3ca7a2afb --- /dev/null +++ b/src/assets/ba_data/python/bautils/tools/enums.py @@ -0,0 +1,56 @@ +# Released under the MIT License. See LICENSE for details. +# +"""Collection of useful enums for various plugins.""" + +# ba_meta require api 9 + +from enum import Enum +from typing import override + + +class Color(Enum): + """Collection of RGB colour values.""" + + RED = (255, 0, 0) + GREEN = (0, 255, 0) + BLUE = (0, 0, 255) + WHITE = (255, 255, 255) + BLACK = (0, 0, 0) + YELLOW = (255, 255, 0) + CYAN = (0, 255, 255) + MAGENTA = (255, 0, 255) + ORANGE = (255, 165, 0) + PURPLE = (128, 0, 128) + PINK = (255, 192, 203) + BROWN = (165, 42, 42) + GREY = (128, 128, 128) + LIGHT_GREY = (211, 211, 211) + DARK_GREY = (64, 64, 64) + LIME = (0, 255, 0) + TEAL = (0, 128, 128) + NAVY = (0, 0, 128) + OLIVE = (128, 128, 0) + MAROON = (128, 0, 0) + AQUA = (0, 255, 255) + SILVER = (192, 192, 192) + GOLD = (255, 215, 0) + INDIGO = (75, 0, 130) + VIOLET = (238, 130, 238) + BEIGE = (245, 245, 220) + IVORY = (255, 255, 240) + TURQUOISE = (64, 224, 208) + SALMON = (250, 128, 114) + CORAL = (255, 127, 80) + KHAKI = (240, 230, 140) + PLUM = (221, 160, 221) + TAN = (210, 180, 140) + + @override + def __str__(self) -> str: + return f"RGB{self.value}" + + @property + def float(self) -> tuple[float, float, float]: + """Returns the floating tuple for rgb colors.""" + r, g, b = self.value + return (r / 255, g / 255, b / 255) diff --git a/src/assets/server_package/requirements.txt b/src/assets/server_package/requirements.txt new file mode 100644 index 000000000..988f276d8 --- /dev/null +++ b/src/assets/server_package/requirements.txt @@ -0,0 +1 @@ +tinydb==4.8.2 diff --git a/src/ballistica/base/assets/assets.cc b/src/ballistica/base/assets/assets.cc index 26008c652..2e5f3afb6 100644 --- a/src/ballistica/base/assets/assets.cc +++ b/src/ballistica/base/assets/assets.cc @@ -726,8 +726,8 @@ auto Assets::GetPendingLoadCount() -> int { template auto Assets::GetAssetPendingLoadCount( - std::unordered_map >* t_list, AssetType type) - -> int { + std::unordered_map >* t_list, + AssetType type) -> int { assert(g_base->InLogicThread()); assert(asset_lists_locked_); @@ -1087,8 +1087,8 @@ void Assets::Prune(int level) { } } -auto Assets::FindAssetFile(FileType type, const std::string& name) - -> std::string { +auto Assets::FindAssetFile(FileType type, + const std::string& name) -> std::string { std::string file_out; // We don't protect package-path access so make sure its always from here. @@ -1509,8 +1509,8 @@ auto DoCompileResourceString(cJSON* obj) -> std::string { return result; } -auto Assets::CompileResourceString(const std::string& s, bool* valid) - -> std::string { +auto Assets::CompileResourceString(const std::string& s, + bool* valid) -> std::string { bool dummyvalid; if (valid == nullptr) { valid = &dummyvalid; diff --git a/src/ballistica/base/assets/assets.h b/src/ballistica/base/assets/assets.h index 53744459c..92a7d74b9 100644 --- a/src/ballistica/base/assets/assets.h +++ b/src/ballistica/base/assets/assets.h @@ -38,8 +38,8 @@ class Assets { /// is deleted once the load is completed. void AddPendingLoad(Object::Ref* c); enum class FileType { kMesh, kCollisionMesh, kTexture, kSound, kData }; - auto FindAssetFile(FileType fileType, const std::string& file_in) - -> std::string; + auto FindAssetFile(FileType fileType, + const std::string& file_in) -> std::string; /// Unload renderer-specific bits only (gl display lists, etc) - used when /// recreating/adjusting the renderer. @@ -111,8 +111,8 @@ class Assets { const std::unordered_map& language); auto GetResourceString(const std::string& key) -> std::string; auto CharStr(SpecialChar id) -> std::string; - auto CompileResourceString(const std::string& s, bool* valid = nullptr) - -> std::string; + auto CompileResourceString(const std::string& s, + bool* valid = nullptr) -> std::string; auto sys_assets_loaded() const { return sys_assets_loaded_; } @@ -131,8 +131,8 @@ class Assets { template auto GetAssetPendingLoadCount( - std::unordered_map >* t_list, AssetType type) - -> int; + std::unordered_map >* t_list, + AssetType type) -> int; template auto GetAsset(const std::string& file_name, diff --git a/src/ballistica/base/assets/sound_asset.cc b/src/ballistica/base/assets/sound_asset.cc index f6717da3f..fc6aa87e9 100644 --- a/src/ballistica/base/assets/sound_asset.cc +++ b/src/ballistica/base/assets/sound_asset.cc @@ -38,8 +38,8 @@ static auto CallbackRead(void* ptr, size_t size, size_t nmemb, void* data_source) -> size_t { return fread(ptr, size, nmemb, static_cast(data_source)); } -static auto CallbackSeek(void* data_source, ogg_int64_t offset, int whence) - -> int { +static auto CallbackSeek(void* data_source, ogg_int64_t offset, + int whence) -> int { return fseek(static_cast(data_source), static_cast_check_fit(offset), whence); // NOLINT } diff --git a/src/ballistica/base/audio/audio.cc b/src/ballistica/base/audio/audio.cc index d39dc0456..83eba0dd2 100644 --- a/src/ballistica/base/audio/audio.cc +++ b/src/ballistica/base/audio/audio.cc @@ -126,8 +126,8 @@ auto Audio::IsSoundPlaying(uint32_t play_id) -> bool { return result; } -auto Audio::SourceBeginExisting(uint32_t play_id, int debug_id) - -> AudioSource* { +auto Audio::SourceBeginExisting(uint32_t play_id, + int debug_id) -> AudioSource* { BA_DEBUG_FUNCTION_TIMER_BEGIN(); uint32_t source_id = AudioServer::SourceIdFromPlayId(play_id); @@ -191,8 +191,8 @@ auto Audio::SafePlaySysSound(SysSoundID sound_id) -> std::optional { return PlaySound(g_base->assets->SysSound(sound_id)); } -auto Audio::PlaySound(SoundAsset* sound, float volume) - -> std::optional { +auto Audio::PlaySound(SoundAsset* sound, + float volume) -> std::optional { assert(g_core); assert(g_base->InLogicThread()); BA_DEBUG_FUNCTION_TIMER_BEGIN(); diff --git a/src/ballistica/base/audio/ogg_stream.cc b/src/ballistica/base/audio/ogg_stream.cc index 2f1219905..4680090c5 100644 --- a/src/ballistica/base/audio/ogg_stream.cc +++ b/src/ballistica/base/audio/ogg_stream.cc @@ -19,8 +19,8 @@ static auto CallbackRead(void* ptr, size_t size, size_t nmemb, return fread(ptr, size, nmemb, static_cast(data_source)); } -static auto CallbackSeek(void* data_source, ogg_int64_t offset, int whence) - -> int { +static auto CallbackSeek(void* data_source, ogg_int64_t offset, + int whence) -> int { return fseek(static_cast(data_source), static_cast_check_fit(offset), whence); // NOLINT } diff --git a/src/ballistica/base/graphics/gl/renderer_gl.cc b/src/ballistica/base/graphics/gl/renderer_gl.cc index 2aa2adb48..8a1d8f5e5 100644 --- a/src/ballistica/base/graphics/gl/renderer_gl.cc +++ b/src/ballistica/base/graphics/gl/renderer_gl.cc @@ -822,23 +822,23 @@ void RendererGL::SyncGLState_() { auto* VAR = static_cast(mesh_data->renderer_data()); \ assert(VAR&& VAR == dynamic_cast(mesh_data->renderer_data())) -#define GET_INDEX_BUFFER() \ - assert(buffer != buffers.end()); \ - assert(index_size != index_sizes.end()); \ - MeshIndexBuffer16* indices16{nullptr}; \ - MeshIndexBuffer32* indices32{nullptr}; \ - assert(*index_size == 4 || *index_size == 2); \ - bool use_indices32 = (*index_size == 4); \ - if (use_indices32) { \ - indices32 = static_cast(buffer->get()); \ - assert(indices32 \ - && indices32 == dynamic_cast(buffer->get())); \ - } else { \ - indices16 = static_cast(buffer->get()); \ - assert(indices16 \ - && indices16 == dynamic_cast(buffer->get())); \ - } \ - index_size++; \ +#define GET_INDEX_BUFFER() \ + assert(buffer != buffers.end()); \ + assert(index_size != index_sizes.end()); \ + MeshIndexBuffer16* indices16{nullptr}; \ + MeshIndexBuffer32* indices32{nullptr}; \ + assert(*index_size == 4 || *index_size == 2); \ + bool use_indices32 = (*index_size == 4); \ + if (use_indices32) { \ + indices32 = static_cast(buffer->get()); \ + assert(indices32&& indices32 \ + == dynamic_cast(buffer->get())); \ + } else { \ + indices16 = static_cast(buffer->get()); \ + assert(indices16&& indices16 \ + == dynamic_cast(buffer->get())); \ + } \ + index_size++; \ buffer++ #define GET_BUFFER(TYPE, VAR) \ @@ -2839,19 +2839,17 @@ auto RendererGL::NewScreenRenderTarget() -> RenderTarget* { return Object::NewDeferred(this); } -auto RendererGL::NewFramebufferRenderTarget(int width, int height, - bool linear_interp, bool depth, - bool texture, bool depth_texture, - bool high_quality, bool msaa, - bool alpha) - -> Object::Ref { +auto RendererGL::NewFramebufferRenderTarget( + int width, int height, bool linear_interp, bool depth, bool texture, + bool depth_texture, bool high_quality, bool msaa, + bool alpha) -> Object::Ref { return Object::New( this, width, height, linear_interp, depth, texture, depth_texture, high_quality, msaa, alpha); } -auto RendererGL::NewMeshData(MeshDataType mesh_type, MeshDrawType draw_type) - -> MeshRendererData* { +auto RendererGL::NewMeshData(MeshDataType mesh_type, + MeshDrawType draw_type) -> MeshRendererData* { switch (mesh_type) { case MeshDataType::kIndexedSimpleSplit: { MeshDataSimpleSplitGL* data; diff --git a/src/ballistica/base/graphics/gl/renderer_gl.h b/src/ballistica/base/graphics/gl/renderer_gl.h index 3cca7a493..f0939809d 100644 --- a/src/ballistica/base/graphics/gl/renderer_gl.h +++ b/src/ballistica/base/graphics/gl/renderer_gl.h @@ -141,18 +141,17 @@ class RendererGL : public Renderer { void SetDepthTesting(bool enable) override; void SetDrawAtEqualDepth(bool enable) override; auto NewScreenRenderTarget() -> RenderTarget* override; - auto NewFramebufferRenderTarget(int width, int height, bool linear_interp, - bool depth, bool texture, - bool depth_is_texture, bool high_quality, - bool msaa, bool alpha) - -> Object::Ref override; + auto NewFramebufferRenderTarget( + int width, int height, bool linear_interp, bool depth, bool texture, + bool depth_is_texture, bool high_quality, bool msaa, + bool alpha) -> Object::Ref override; auto NewMeshAssetData(const MeshAsset& mesh) -> Object::Ref override; auto NewTextureData(const TextureAsset& texture) -> Object::Ref override; - auto NewMeshData(MeshDataType type, MeshDrawType drawType) - -> MeshRendererData* override; + auto NewMeshData(MeshDataType type, + MeshDrawType drawType) -> MeshRendererData* override; void DeleteMeshData(MeshRendererData* data, MeshDataType type) override; void ProcessRenderCommandBuffer(RenderCommandBuffer* buffer, diff --git a/src/ballistica/base/graphics/graphics.cc b/src/ballistica/base/graphics/graphics.cc index a6d6b1e5e..552978990 100644 --- a/src/ballistica/base/graphics/graphics.cc +++ b/src/ballistica/base/graphics/graphics.cc @@ -502,8 +502,8 @@ void Graphics::DrawMiscOverlays(FrameDef* frame_def) { screenmessages->DrawMiscOverlays(frame_def); } -auto Graphics::GetDebugGraph(const std::string& name, bool smoothed) - -> NetGraph* { +auto Graphics::GetDebugGraph(const std::string& name, + bool smoothed) -> NetGraph* { auto out = debug_graphs_.find(name); if (out == debug_graphs_.end()) { debug_graphs_[name] = Object::New(); @@ -1747,9 +1747,8 @@ auto Graphics::GraphicsQualityFromRequest(GraphicsQualityRequest request, } } -auto Graphics::TextureQualityFromRequest(TextureQualityRequest request, - TextureQuality auto_val) - -> TextureQuality { +auto Graphics::TextureQualityFromRequest( + TextureQualityRequest request, TextureQuality auto_val) -> TextureQuality { switch (request) { case TextureQualityRequest::kLow: return TextureQuality::kLow; diff --git a/src/ballistica/base/graphics/graphics.h b/src/ballistica/base/graphics/graphics.h index 783115bed..73faecfd0 100644 --- a/src/ballistica/base/graphics/graphics.h +++ b/src/ballistica/base/graphics/graphics.h @@ -342,9 +342,8 @@ class Graphics { static auto GraphicsQualityFromRequest(GraphicsQualityRequest request, GraphicsQuality auto_val) -> GraphicsQuality; - static auto TextureQualityFromRequest(TextureQualityRequest request, - TextureQuality auto_val) - -> TextureQuality; + static auto TextureQualityFromRequest( + TextureQualityRequest request, TextureQuality auto_val) -> TextureQuality; /// For temporary use from arbitrary threads. This should be removed when /// possible and replaced with proper safe thread-specific access patterns diff --git a/src/ballistica/base/graphics/graphics_vr.cc b/src/ballistica/base/graphics/graphics_vr.cc index aa32901e6..6f164b358 100644 --- a/src/ballistica/base/graphics/graphics_vr.cc +++ b/src/ballistica/base/graphics/graphics_vr.cc @@ -18,8 +18,8 @@ namespace ballistica::base { -static auto ValueTestFloat(float* storage, double* absval, double* deltaval) - -> double { +static auto ValueTestFloat(float* storage, double* absval, + double* deltaval) -> double { if (absval) { *storage = static_cast(*absval); } @@ -29,8 +29,8 @@ static auto ValueTestFloat(float* storage, double* absval, double* deltaval) return *storage; } -static auto ValueTestBool(bool* storage, double* absval, double* deltaval) - -> double { +static auto ValueTestBool(bool* storage, double* absval, + double* deltaval) -> double { if (absval) { *storage = static_cast(*absval); } @@ -243,9 +243,8 @@ void GraphicsVR::CalcVROverlayMatrices(FrameDef* frame_def) { } } -auto GraphicsVR::CalcVROverlayMatrix(const Vector3f& cam_pt, - const Vector3f& cam_target_pt) const - -> Matrix44f { +auto GraphicsVR::CalcVROverlayMatrix( + const Vector3f& cam_pt, const Vector3f& cam_target_pt) const -> Matrix44f { Matrix44f m = Matrix44fTranslate(cam_target_pt); Vector3f diff = cam_pt - cam_target_pt; diff.Normalize(); diff --git a/src/ballistica/base/graphics/renderer/renderer.cc b/src/ballistica/base/graphics/renderer/renderer.cc index 65ecfccd8..416cd7263 100644 --- a/src/ballistica/base/graphics/renderer/renderer.cc +++ b/src/ballistica/base/graphics/renderer/renderer.cc @@ -104,9 +104,9 @@ void Renderer::RenderFrameDef(FrameDef* frame_def) { // If preprocess decided not to render this. if (!frame_def->rendering()) return; - // Set camera/hand/etc positioning with latest VR data if applicable. - // (we do this here at render time as opposed to frame construction time - // so we have the most up-to-date data possible). + // Set camera/hand/etc positioning with latest VR data if applicable. + // (we do this here at render time as opposed to frame construction time + // so we have the most up-to-date data possible). #if BA_VR_BUILD VRUpdateForEyeRender(frame_def); #endif // BA_VR_BUILD diff --git a/src/ballistica/base/graphics/renderer/renderer.h b/src/ballistica/base/graphics/renderer/renderer.h index 5482279a8..203535444 100644 --- a/src/ballistica/base/graphics/renderer/renderer.h +++ b/src/ballistica/base/graphics/renderer/renderer.h @@ -127,8 +127,8 @@ class Renderer { -> Object::Ref = 0; virtual auto NewTextureData(const TextureAsset& texture) -> Object::Ref = 0; - virtual auto NewMeshData(MeshDataType t, MeshDrawType drawType) - -> MeshRendererData* = 0; + virtual auto NewMeshData(MeshDataType t, + MeshDrawType drawType) -> MeshRendererData* = 0; virtual void DeleteMeshData(MeshRendererData* data, MeshDataType t) = 0; virtual void ProcessRenderCommandBuffer(RenderCommandBuffer* buffer, const RenderPass& pass, @@ -151,12 +151,10 @@ class Renderer { virtual void InvalidateFramebuffer(bool color, bool depth, bool target_read_framebuffer) = 0; virtual auto NewScreenRenderTarget() -> RenderTarget* = 0; - virtual auto NewFramebufferRenderTarget(int width, int height, - bool linear_interp, bool depth, - bool texture, bool depth_texture, - bool high_quality, bool msaa, - bool alpha) - -> Object::Ref = 0; + virtual auto NewFramebufferRenderTarget( + int width, int height, bool linear_interp, bool depth, bool texture, + bool depth_texture, bool high_quality, bool msaa, + bool alpha) -> Object::Ref = 0; virtual void PushGroupMarker(const char* label) = 0; virtual void PopGroupMarker() = 0; virtual void BlitBuffer(RenderTarget* src, RenderTarget* dst, bool depth, diff --git a/src/ballistica/base/graphics/texture/ktx.cc b/src/ballistica/base/graphics/texture/ktx.cc index f30a5b076..d06725a3c 100644 --- a/src/ballistica/base/graphics/texture/ktx.cc +++ b/src/ballistica/base/graphics/texture/ktx.cc @@ -33,7 +33,7 @@ typedef unsigned int GLenum; typedef int GLint; #define KTX_IDENTIFIER_REF \ - {0xAB, 0x4B, 0x54, 0x58, 0x20, 0x31, 0x31, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A} + { 0xAB, 0x4B, 0x54, 0x58, 0x20, 0x31, 0x31, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A } #define KTX_ENDIAN_REF (0x04030201) #define KTX_ENDIAN_REF_REV (0x01020304) #define KTX_HEADER_SIZE (64) diff --git a/src/ballistica/base/input/device/joystick_input.cc b/src/ballistica/base/input/device/joystick_input.cc index ebcb5b0c9..0529e7c94 100644 --- a/src/ballistica/base/input/device/joystick_input.cc +++ b/src/ballistica/base/input/device/joystick_input.cc @@ -324,8 +324,8 @@ auto JoystickInput::ShouldBeHiddenFromUser() -> bool { } } -auto JoystickInput::GetCalibratedValue(float raw, float neutral) const - -> int32_t { +auto JoystickInput::GetCalibratedValue(float raw, + float neutral) const -> int32_t { int32_t val; float dead_zone = 0.5f; float mag, target; diff --git a/src/ballistica/base/input/input.h b/src/ballistica/base/input/input.h index a8ef79c24..98a084f5f 100644 --- a/src/ballistica/base/input/input.h +++ b/src/ballistica/base/input/input.h @@ -39,8 +39,8 @@ class Input { // Given a device name and persistent identifier for it, returns a device // or nullptr. Note that this can return hidden devices (ones the user has // flagged as totally-ignored, etc). - auto GetInputDevice(const std::string& name, const std::string& persistent_id) - -> InputDevice*; + auto GetInputDevice(const std::string& name, + const std::string& persistent_id) -> InputDevice*; // Return a device by id, or nullptr for an invalid id. Note that this can // return hidden devices (ones the user has flagged as totally-ignored, diff --git a/src/ballistica/base/logic/logic.cc b/src/ballistica/base/logic/logic.cc index 0dc60a5e5..b6b63edd6 100644 --- a/src/ballistica/base/logic/logic.cc +++ b/src/ballistica/base/logic/logic.cc @@ -656,8 +656,8 @@ void Logic::NotifyOfPendingAssetLoads() { UpdatePendingWorkTimer_(); } -auto Logic::NewAppTimer(microsecs_t length, bool repeat, 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()); @@ -682,8 +682,8 @@ void Logic::SetAppTimerLength(int timer_id, microsecs_t length) { } } -auto Logic::NewDisplayTimer(microsecs_t length, bool repeat, 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()); diff --git a/src/ballistica/base/logic/logic.h b/src/ballistica/base/logic/logic.h index 92dbe7353..18684539b 100644 --- a/src/ballistica/base/logic/logic.h +++ b/src/ballistica/base/logic/logic.h @@ -95,8 +95,8 @@ class Logic { void DeleteAppTimer(int timer_id); void SetAppTimerLength(int timer_id, microsecs_t length); - auto NewDisplayTimer(microsecs_t length, bool repeat, 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); diff --git a/src/ballistica/base/networking/network_reader.cc b/src/ballistica/base/networking/network_reader.cc index 8d28734c0..a21fdfa85 100644 --- a/src/ballistica/base/networking/network_reader.cc +++ b/src/ballistica/base/networking/network_reader.cc @@ -67,7 +67,7 @@ void NetworkReader::PokeSelf_() { "Error creating poke socket: " + g_core->platform->GetSocketErrorString()); } else { - struct sockaddr_in serv_addr{}; + struct sockaddr_in serv_addr {}; memset(&serv_addr, 0, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); @@ -78,7 +78,7 @@ void NetworkReader::PokeSelf_() { "Error binding poke socket: " + g_core->platform->GetSocketErrorString()); } else { - struct sockaddr_in t_addr{}; + struct sockaddr_in t_addr {}; memset(&t_addr, 0, sizeof(t_addr)); t_addr.sin_family = AF_INET; t_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); @@ -432,7 +432,7 @@ void NetworkReader::OpenSockets_() { g_core->platform->SetSocketNonBlocking(sd4_); // Bind to local server port. - struct sockaddr_in serv_addr{}; + struct sockaddr_in serv_addr {}; serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); @@ -461,7 +461,7 @@ void NetworkReader::OpenSockets_() { // See what v4 port we actually wound up with. if (sd4_ != -1) { - struct sockaddr_in sa{}; + struct sockaddr_in sa {}; socklen_t sa_len = sizeof(sa); if (getsockname(sd4_, reinterpret_cast(&sa), &sa_len) == 0) { port4_ = ntohs(sa.sin_port); // NOLINT @@ -494,7 +494,7 @@ void NetworkReader::OpenSockets_() { } g_core->platform->SetSocketNonBlocking(sd6_); - struct sockaddr_in6 serv_addr{}; + struct sockaddr_in6 serv_addr {}; memset(&serv_addr, 0, sizeof(serv_addr)); serv_addr.sin6_family = AF_INET6; serv_addr.sin6_port = htons(port6_); // NOLINT @@ -525,7 +525,7 @@ void NetworkReader::OpenSockets_() { // See what v6 port we actually wound up with. if (sd6_ != -1) { - struct sockaddr_in sa{}; + struct sockaddr_in sa {}; socklen_t sa_len = sizeof(sa); if (getsockname(sd6_, reinterpret_cast(&sa), &sa_len) == 0) { port6_ = ntohs(sa.sin_port); // NOLINT diff --git a/src/ballistica/base/platform/base_platform.cc b/src/ballistica/base/platform/base_platform.cc index 1dab2fed9..0c55ab194 100644 --- a/src/ballistica/base/platform/base_platform.cc +++ b/src/ballistica/base/platform/base_platform.cc @@ -220,14 +220,14 @@ void BasePlatform::SetupInterruptHandling() { throw Exception(); #else { - struct sigaction handler{}; + struct sigaction handler {}; handler.sa_handler = HandleSIGINT; sigemptyset(&handler.sa_mask); handler.sa_flags = 0; sigaction(SIGINT, &handler, nullptr); } { - struct sigaction handler{}; + struct sigaction handler {}; handler.sa_handler = HandleSIGTERM; sigemptyset(&handler.sa_mask); handler.sa_flags = 0; diff --git a/src/ballistica/base/python/base_python.cc b/src/ballistica/base/python/base_python.cc index e1874f636..dc161df78 100644 --- a/src/ballistica/base/python/base_python.cc +++ b/src/ballistica/base/python/base_python.cc @@ -267,12 +267,12 @@ auto BasePython::GetPyLString(PyObject* o) -> std::string { if (result == 1) { // At this point its not a simple type error if something goes wonky. // Perhaps we should try to preserve any error type raised by - // the _get_json() call... + // the as_json() call... exctype = PyExcType::kRuntime; - PythonRef get_json_call(PyObject_GetAttrString(o, "_get_json"), - PythonRef::kSteal); - if (get_json_call.CallableCheck()) { - PythonRef json = get_json_call.Call(); + PythonRef as_json_call(PyObject_GetAttrString(o, "as_json"), + PythonRef::kSteal); + if (as_json_call.CallableCheck()) { + PythonRef json = as_json_call.Call(); if (PyUnicode_Check(json.get())) { return PyUnicode_AsUTF8(json.get()); } @@ -372,8 +372,8 @@ auto BasePython::GetRawConfigValue(const char* name) -> PyObject* { return PyDict_GetItemString(objs().Get(ObjID::kConfig).get(), name); } -auto BasePython::GetRawConfigValue(const char* name, const char* default_value) - -> std::string { +auto BasePython::GetRawConfigValue(const char* name, + const char* default_value) -> std::string { assert(Python::HaveGIL()); assert(objs().Exists(ObjID::kConfig)); PyObject* value = @@ -384,8 +384,8 @@ auto BasePython::GetRawConfigValue(const char* name, const char* default_value) return PyUnicode_AsUTF8(value); } -auto BasePython::GetRawConfigValue(const char* name, float default_value) - -> float { +auto BasePython::GetRawConfigValue(const char* name, + float default_value) -> float { assert(Python::HaveGIL()); assert(objs().Exists(ObjID::kConfig)); PyObject* value = @@ -444,8 +444,8 @@ auto BasePython::GetRawConfigValue(const char* name, int default_value) -> int { } } -auto BasePython::GetRawConfigValue(const char* name, bool default_value) - -> bool { +auto BasePython::GetRawConfigValue(const char* name, + bool default_value) -> bool { assert(Python::HaveGIL()); assert(objs().Exists(ObjID::kConfig)); PyObject* value = @@ -580,8 +580,8 @@ auto BasePython::GetResource(const char* key, const char* fallback_resource, return std::string(""; } -auto BasePython::GetTranslation(const char* category, const char* s) - -> std::string { +auto BasePython::GetTranslation(const char* category, + const char* s) -> std::string { assert(Python::HaveGIL()); PythonRef results; PythonRef args(Py_BuildValue("(ss)", category, s), PythonRef::kSteal); diff --git a/src/ballistica/base/python/base_python.h b/src/ballistica/base/python/base_python.h index 5621f3a9d..aaab2f104 100644 --- a/src/ballistica/base/python/base_python.h +++ b/src/ballistica/base/python/base_python.h @@ -156,8 +156,8 @@ class BasePython { // functions (which themselves call these functions) auto GetRawConfigValue(const char* name) -> PyObject*; // (returns a borrowed ref) - auto GetRawConfigValue(const char* name, const char* default_value) - -> std::string; + auto GetRawConfigValue(const char* name, + const char* default_value) -> std::string; auto GetRawConfigValue(const char* name, float default_value) -> float; auto GetRawConfigValue(const char* name, std::optional default_value) -> std::optional; diff --git a/src/ballistica/base/python/class/python_class_app_timer.h b/src/ballistica/base/python/class/python_class_app_timer.h index 7295988a2..0c086a2e0 100644 --- a/src/ballistica/base/python/class/python_class_app_timer.h +++ b/src/ballistica/base/python/class/python_class_app_timer.h @@ -18,8 +18,8 @@ class PythonClassAppTimer : public PythonClass { static PyTypeObject type_obj; private: - static auto tp_new(PyTypeObject* type, PyObject* args, PyObject* keywds) - -> PyObject*; + static auto tp_new(PyTypeObject* type, PyObject* args, + PyObject* keywds) -> PyObject*; static void tp_dealloc(PythonClassAppTimer* self); int timer_id_{}; bool have_timer_{}; diff --git a/src/ballistica/base/python/class/python_class_context_call.cc b/src/ballistica/base/python/class/python_class_context_call.cc index e006fa2cc..a7fea8ddc 100644 --- a/src/ballistica/base/python/class/python_class_context_call.cc +++ b/src/ballistica/base/python/class/python_class_context_call.cc @@ -91,8 +91,8 @@ auto PythonClassContextCall::tp_new(PyTypeObject* type, PyObject* args, } auto PythonClassContextCall::tp_call(PythonClassContextCall* self, - PyObject* args, PyObject* keywds) - -> PyObject* { + PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; static const char* kwlist[] = {nullptr}; if (!PyArg_ParseTupleAndKeywords(args, keywds, "", @@ -110,7 +110,7 @@ auto PythonClassContextCall::tp_repr(PythonClassContextCall* self) BA_PYTHON_TRY; assert(self->context_call_->exists()); return PyUnicode_FromString( - ("") .c_str()); BA_PYTHON_CATCH; diff --git a/src/ballistica/base/python/class/python_class_context_call.h b/src/ballistica/base/python/class/python_class_context_call.h index 8d0c30204..349cc2896 100644 --- a/src/ballistica/base/python/class/python_class_context_call.h +++ b/src/ballistica/base/python/class/python_class_context_call.h @@ -23,8 +23,8 @@ class PythonClassContextCall : public PythonClass { static auto tp_call(PythonClassContextCall* self, PyObject* args, PyObject* keywds) -> PyObject*; static auto tp_repr(PythonClassContextCall* self) -> PyObject*; - static auto tp_new(PyTypeObject* type, PyObject* args, PyObject* keywds) - -> PyObject*; + static auto tp_new(PyTypeObject* type, PyObject* args, + PyObject* keywds) -> PyObject*; static void tp_dealloc(PythonClassContextCall* self); Object::Ref* context_call_{}; }; diff --git a/src/ballistica/base/python/class/python_class_context_ref.cc b/src/ballistica/base/python/class/python_class_context_ref.cc index ffbd576d6..8adb02057 100644 --- a/src/ballistica/base/python/class/python_class_context_ref.cc +++ b/src/ballistica/base/python/class/python_class_context_ref.cc @@ -80,7 +80,7 @@ auto PythonClassContextRef::tp_repr(PythonClassContextRef* self) -> PyObject* { BA_PYTHON_TRY; auto context_str = - "context_ref_->GetDescription() + ")>"; + "context_ref_->GetDescription() + ")>"; return PyUnicode_FromString(context_str.c_str()); BA_PYTHON_CATCH; } @@ -165,8 +165,8 @@ auto PythonClassContextRef::Enter(PythonClassContextRef* self) -> PyObject* { BA_PYTHON_CATCH; } -auto PythonClassContextRef::Exit(PythonClassContextRef* self, PyObject* args) - -> PyObject* { +auto PythonClassContextRef::Exit(PythonClassContextRef* self, + PyObject* args) -> PyObject* { BA_PYTHON_TRY; g_base->SetCurrentContext(*self->context_ref_prev_); Py_RETURN_NONE; diff --git a/src/ballistica/base/python/class/python_class_context_ref.h b/src/ballistica/base/python/class/python_class_context_ref.h index b325dfcaa..9c3c5edd9 100644 --- a/src/ballistica/base/python/class/python_class_context_ref.h +++ b/src/ballistica/base/python/class/python_class_context_ref.h @@ -22,10 +22,10 @@ class PythonClassContextRef : public PythonClass { private: static PyMethodDef tp_methods[]; static auto tp_repr(PythonClassContextRef* self) -> PyObject*; - static auto tp_richcompare(PythonClassContextRef* c1, PyObject* c2, int op) - -> PyObject*; - static auto tp_new(PyTypeObject* type, PyObject* args, PyObject* keywds) - -> PyObject*; + static auto tp_richcompare(PythonClassContextRef* c1, PyObject* c2, + int op) -> PyObject*; + static auto tp_new(PyTypeObject* type, PyObject* args, + PyObject* keywds) -> PyObject*; static void tp_dealloc(PythonClassContextRef* self); static auto Enter(PythonClassContextRef* self) -> PyObject*; static auto Exit(PythonClassContextRef* self, PyObject* args) -> PyObject*; diff --git a/src/ballistica/base/python/class/python_class_display_timer.h b/src/ballistica/base/python/class/python_class_display_timer.h index 61c258880..35b10d3b2 100644 --- a/src/ballistica/base/python/class/python_class_display_timer.h +++ b/src/ballistica/base/python/class/python_class_display_timer.h @@ -18,8 +18,8 @@ class PythonClassDisplayTimer : public PythonClass { static PyTypeObject type_obj; private: - static auto tp_new(PyTypeObject* type, PyObject* args, PyObject* keywds) - -> PyObject*; + static auto tp_new(PyTypeObject* type, PyObject* args, + PyObject* keywds) -> PyObject*; static void tp_dealloc(PythonClassDisplayTimer* self); int timer_id_; bool have_timer_; diff --git a/src/ballistica/base/python/class/python_class_env.cc b/src/ballistica/base/python/class/python_class_env.cc index eb97066cb..c714e2c99 100644 --- a/src/ballistica/base/python/class/python_class_env.cc +++ b/src/ballistica/base/python/class/python_class_env.cc @@ -42,8 +42,8 @@ struct EnvEntry_ : EnvEntryBase_ { auto PythonClassEnv::type_name() -> const char* { return "Env"; } -static auto BoolEntry_(bool val, const char* docs) - -> std::unique_ptr { +static auto BoolEntry_(bool val, + const char* docs) -> std::unique_ptr { return std::unique_ptr(new EnvEntry_( [val] { PyObject* pyval = val ? Py_True : Py_False; @@ -53,8 +53,8 @@ static auto BoolEntry_(bool val, const char* docs) "bool", docs)); } -static auto StrEntry_(const std::string& val, const char* docs) - -> std::unique_ptr { +static auto StrEntry_(const std::string& val, + const char* docs) -> std::unique_ptr { return std::unique_ptr(new EnvEntry_( [val] { return PyUnicode_FromString(val.c_str()); }, "str", docs)); } @@ -74,8 +74,8 @@ static auto OptionalStrEntry_(const std::optional& val, "str | None", docs)); } -static auto IntEntry_(int val, const char* docs) - -> std::unique_ptr { +static auto IntEntry_(int val, + const char* docs) -> std::unique_ptr { return std::unique_ptr( new EnvEntry_([val] { return PyLong_FromLong(val); }, "int", docs)); } @@ -358,8 +358,8 @@ void PythonClassEnv::tp_dealloc(PythonClassEnv* self) { Py_TYPE(self)->tp_free(reinterpret_cast(self)); } -auto PythonClassEnv::tp_getattro(PythonClassEnv* self, PyObject* attr) - -> PyObject* { +auto PythonClassEnv::tp_getattro(PythonClassEnv* self, + PyObject* attr) -> PyObject* { BA_PYTHON_TRY; // Do we need to support other attr types? diff --git a/src/ballistica/base/python/class/python_class_env.h b/src/ballistica/base/python/class/python_class_env.h index b255b7c2a..f083b4f46 100644 --- a/src/ballistica/base/python/class/python_class_env.h +++ b/src/ballistica/base/python/class/python_class_env.h @@ -39,8 +39,8 @@ class PythonClassEnv : public PythonClass { ~PythonClassEnv(); std::map extra_attrs_; static PyMethodDef tp_methods[]; - static auto tp_new(PyTypeObject* type, PyObject* args, PyObject* keywds) - -> PyObject*; + static auto tp_new(PyTypeObject* type, PyObject* args, + PyObject* keywds) -> PyObject*; static void tp_dealloc(PythonClassEnv* self); static auto Dir(PythonClassEnv* self) -> PyObject*; }; diff --git a/src/ballistica/base/python/class/python_class_feature_set_data.h b/src/ballistica/base/python/class/python_class_feature_set_data.h index ab3ad8fab..07602feb8 100644 --- a/src/ballistica/base/python/class/python_class_feature_set_data.h +++ b/src/ballistica/base/python/class/python_class_feature_set_data.h @@ -40,8 +40,8 @@ class PythonClassFeatureSetData : public PythonClass { private: static PyMethodDef tp_methods[]; - static auto tp_new(PyTypeObject* type, PyObject* args, PyObject* keywds) - -> PyObject*; + static auto tp_new(PyTypeObject* type, PyObject* args, + PyObject* keywds) -> PyObject*; static void tp_dealloc(PythonClassFeatureSetData* self); FeatureSetNativeComponent* feature_set_{}; static auto Play(PythonClassFeatureSetData* self, PyObject* args, diff --git a/src/ballistica/base/python/class/python_class_simple_sound.h b/src/ballistica/base/python/class/python_class_simple_sound.h index 21e1636a5..669f808e4 100644 --- a/src/ballistica/base/python/class/python_class_simple_sound.h +++ b/src/ballistica/base/python/class/python_class_simple_sound.h @@ -44,8 +44,8 @@ class PythonClassSimpleSound : public PythonClass { private: static PyMethodDef tp_methods[]; static auto tp_repr(PythonClassSimpleSound* self) -> PyObject*; - static auto tp_new(PyTypeObject* type, PyObject* args, PyObject* keywds) - -> PyObject*; + static auto tp_new(PyTypeObject* type, PyObject* args, + PyObject* keywds) -> PyObject*; static void tp_dealloc(PythonClassSimpleSound* self); Object::Ref* sound_; static auto Play(PythonClassSimpleSound* self, PyObject* args, diff --git a/src/ballistica/base/python/class/python_class_vec3.cc b/src/ballistica/base/python/class/python_class_vec3.cc index 5ff15977f..071731029 100644 --- a/src/ballistica/base/python/class/python_class_vec3.cc +++ b/src/ballistica/base/python/class/python_class_vec3.cc @@ -138,8 +138,8 @@ auto PythonClassVec3::sq_length(PythonClassVec3* self) -> Py_ssize_t { return kMemberCount; } -auto PythonClassVec3::sq_item(PythonClassVec3* self, Py_ssize_t i) - -> PyObject* { +auto PythonClassVec3::sq_item(PythonClassVec3* self, + Py_ssize_t i) -> PyObject* { if (i < 0 || i >= kMemberCount) { PyErr_SetString(PyExc_IndexError, "Vec3 index out of range"); return nullptr; @@ -159,8 +159,8 @@ auto PythonClassVec3::sq_ass_item(PythonClassVec3* self, Py_ssize_t i, BA_PYTHON_INT_CATCH; } -auto PythonClassVec3::nb_add(PythonClassVec3* l, PythonClassVec3* r) - -> PyObject* { +auto PythonClassVec3::nb_add(PythonClassVec3* l, + PythonClassVec3* r) -> PyObject* { BA_PYTHON_TRY; // We can add if both sides are Vec3. @@ -175,8 +175,8 @@ auto PythonClassVec3::nb_add(PythonClassVec3* l, PythonClassVec3* r) BA_PYTHON_CATCH; } -auto PythonClassVec3::nb_subtract(PythonClassVec3* l, PythonClassVec3* r) - -> PyObject* { +auto PythonClassVec3::nb_subtract(PythonClassVec3* l, + PythonClassVec3* r) -> PyObject* { BA_PYTHON_TRY; // We can subtract if both sides are Vec3. @@ -240,8 +240,8 @@ auto PythonClassVec3::nb_multiply(PyObject* l, PyObject* r) -> PyObject* { BA_PYTHON_CATCH; } -auto PythonClassVec3::tp_richcompare(PythonClassVec3* c1, PyObject* c2, int op) - -> PyObject* { +auto PythonClassVec3::tp_richcompare(PythonClassVec3* c1, PyObject* c2, + int op) -> PyObject* { // Always return false against other types. if (!Check(c2)) { Py_RETURN_FALSE; @@ -283,8 +283,8 @@ auto PythonClassVec3::Dot(PythonClassVec3* self, PyObject* other) -> PyObject* { BA_PYTHON_CATCH; } -auto PythonClassVec3::Cross(PythonClassVec3* self, PyObject* other) - -> PyObject* { +auto PythonClassVec3::Cross(PythonClassVec3* self, + PyObject* other) -> PyObject* { BA_PYTHON_TRY; return Create(Vector3f::Cross(self->value, BasePython::GetPyVector3f(other))); BA_PYTHON_CATCH; @@ -309,8 +309,8 @@ PyMethodDef PythonClassVec3::tp_methods[] = { "Returns the cross product of this vector and another."}, {nullptr}}; -auto PythonClassVec3::tp_getattro(PythonClassVec3* self, PyObject* attr) - -> PyObject* { +auto PythonClassVec3::tp_getattro(PythonClassVec3* self, + PyObject* attr) -> PyObject* { BA_PYTHON_TRY; assert(PyUnicode_Check(attr)); diff --git a/src/ballistica/base/python/class/python_class_vec3.h b/src/ballistica/base/python/class/python_class_vec3.h index 1855a4cd6..3290b4204 100644 --- a/src/ballistica/base/python/class/python_class_vec3.h +++ b/src/ballistica/base/python/class/python_class_vec3.h @@ -30,19 +30,19 @@ class PythonClassVec3 : public PythonClass { static auto tp_repr(PythonClassVec3* self) -> PyObject*; static auto sq_length(PythonClassVec3* self) -> Py_ssize_t; static auto sq_item(PythonClassVec3* self, Py_ssize_t i) -> PyObject*; - static auto sq_ass_item(PythonClassVec3* self, Py_ssize_t i, PyObject* val) - -> int; + static auto sq_ass_item(PythonClassVec3* self, Py_ssize_t i, + PyObject* val) -> int; static auto nb_add(PythonClassVec3* l, PythonClassVec3* r) -> PyObject*; static auto nb_subtract(PythonClassVec3* l, PythonClassVec3* r) -> PyObject*; static auto nb_multiply(PyObject* l, PyObject* r) -> PyObject*; static auto nb_negative(PythonClassVec3* self) -> PyObject*; - static auto tp_new(PyTypeObject* type, PyObject* args, PyObject* keywds) - -> PyObject*; + static auto tp_new(PyTypeObject* type, PyObject* args, + PyObject* keywds) -> PyObject*; static auto tp_getattro(PythonClassVec3* self, PyObject* attr) -> PyObject*; - static auto tp_richcompare(PythonClassVec3* c1, PyObject* c2, int op) - -> PyObject*; - static auto tp_setattro(PythonClassVec3* self, PyObject* attr, PyObject* val) - -> int; + static auto tp_richcompare(PythonClassVec3* c1, PyObject* c2, + int op) -> PyObject*; + static auto tp_setattro(PythonClassVec3* self, PyObject* attr, + PyObject* val) -> int; }; } // namespace ballistica::base diff --git a/src/ballistica/base/python/methods/python_methods_base_1.cc b/src/ballistica/base/python/methods/python_methods_base_1.cc index b00051723..782da0ff7 100644 --- a/src/ballistica/base/python/methods/python_methods_base_1.cc +++ b/src/ballistica/base/python/methods/python_methods_base_1.cc @@ -33,8 +33,8 @@ namespace ballistica::base { // -------------------------- discord_start------------------------------ -static auto PyDiscordStart(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyDiscordStart(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; #if BA_ENABLE_DISCORD @@ -54,8 +54,8 @@ static PyMethodDef PyDiscordStartDef = { // -------------------------- discord_is_ready------------------------------ -static auto PyDiscordIsReady(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyDiscordIsReady(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; #if BA_ENABLE_DISCORD @@ -148,8 +148,8 @@ static PyMethodDef PyDiscordRichpresenceDef = { // -------------------------- discord_set_party ------------------------------ -static auto PyDiscordSetParty(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyDiscordSetParty(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; const char* partyId = nullptr; int64_t currentPartySize = 0, maxPartySize = 0; @@ -189,8 +189,8 @@ static PyMethodDef PyDiscordSetPartyDef = { // -------------------------- discord_add_button ------------------------------ -static auto PyDiscordAddButton(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyDiscordAddButton(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; const char *label = nullptr, *url = nullptr; static char* kwlist[] = {const_cast("label"), const_cast("url"), @@ -223,8 +223,8 @@ static PyMethodDef PyDiscordAddButtonDef = { // -------------------------- discord_join_lobby ------------------------------ -static auto PyDiscordJoinLobby(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyDiscordJoinLobby(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; const char* lobbySecret = nullptr; static char* kwlist[] = {const_cast("lobby_secret"), nullptr}; @@ -305,8 +305,8 @@ static PyMethodDef PyDiscordSendLobbyMessageDef = { // -------------------------- discord_shutdown ------------------------------ -static auto PyDiscordShutdown(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyDiscordShutdown(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; #if BA_ENABLE_DISCORD if (g_base->discord->client_is_ready) { @@ -460,8 +460,8 @@ static PyMethodDef PyIsXCodeBuildDef = { // -------------------------- app_instance_uuid -------------------------------- -static auto PyAppInstanceUUID(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyAppInstanceUUID(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; static const char* kwlist[] = {nullptr}; if (!PyArg_ParseTupleAndKeywords(args, keywds, "", @@ -484,8 +484,8 @@ static PyMethodDef PyAppInstanceUUIDDef = { // --------------------------- user_ran_commands ------------------------------- -static auto PyUserRanCommands(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyUserRanCommands(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; static const char* kwlist[] = {nullptr}; if (!PyArg_ParseTupleAndKeywords(args, keywds, "", @@ -509,8 +509,8 @@ static PyMethodDef PyUserRanCommandsDef = { // -------------------------------- pushcall ---------------------------------- -static auto PyPushCall(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyPushCall(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; PyObject* call_obj; int from_other_thread{}; @@ -611,8 +611,8 @@ static PyMethodDef PyPushCallDef = { // ------------------------------ apptime -------------------------------------- -static auto PyAppTime(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyAppTime(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; static const char* kwlist[] = {nullptr}; if (!PyArg_ParseTupleAndKeywords(args, keywds, "", @@ -645,8 +645,8 @@ static PyMethodDef PyAppTimeDef = { // ------------------------------ apptimer ------------------------------------- -static auto PyAppTimer(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyAppTimer(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; BA_PRECONDITION(g_base->InLogicThread()); double length; @@ -702,8 +702,8 @@ static PyMethodDef PyAppTimerDef = { // --------------------------- displaytime ------------------------------------- -static auto PyDisplayTime(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyDisplayTime(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; static const char* kwlist[] = {nullptr}; if (!PyArg_ParseTupleAndKeywords(args, keywds, "", @@ -735,8 +735,8 @@ static PyMethodDef PyDisplayTimeDef = { // ---------------------------- displaytimer ----------------------------------- -static auto PyDisplayTimer(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyDisplayTimer(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; BA_PRECONDITION(g_base->InLogicThread()); double length; @@ -797,8 +797,8 @@ static PyMethodDef PyDisplayTimerDef = { // ----------------------------------- quit ------------------------------------ -static auto PyQuit(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyQuit(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; BA_PRECONDITION(g_base->IsAppStarted()); @@ -860,8 +860,8 @@ static PyMethodDef PyApplyAppConfigDef = { // --------------------------- commit_app_config ------------------------------- -static auto PyCommitAppConfig(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyCommitAppConfig(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; // If writes are suppressed, no-op. @@ -1067,8 +1067,8 @@ static PyMethodDef PyEnvDef = { // -------------------------------- emit_log ----------------------------------- -static auto PyEmitLog(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyEmitLog(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; static const char* kwlist[] = {"name", "level", "timestamp", "message", nullptr}; @@ -1124,8 +1124,8 @@ static PyMethodDef PyEmitLogDef = { // ----------------------------- v1_cloud_log ---------------------------------- -static auto PyV1CloudLog(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyV1CloudLog(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; const char* message; static const char* kwlist[] = {"message", nullptr}; @@ -1153,8 +1153,8 @@ static PyMethodDef PyV1CloudLogDef = { // --------------------------- music_player_stop ------------------------------- -static auto PyMusicPlayerStop(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyMusicPlayerStop(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; static const char* kwlist[] = {nullptr}; if (!PyArg_ParseTupleAndKeywords(args, keywds, "", @@ -1179,8 +1179,8 @@ static PyMethodDef PyMusicPlayerStopDef = { // ---------------------------- music_player_play ------------------------------ -static auto PyMusicPlayerPlay(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyMusicPlayerPlay(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; PyObject* files_obj; static const char* kwlist[] = {"files", nullptr}; @@ -1286,8 +1286,8 @@ static PyMethodDef PyReloadMediaDef = { // --------------------------- mac_music_app_init ------------------------------ -static auto PyMacMusicAppInit(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyMacMusicAppInit(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; g_core->platform->MacMusicAppInit(); Py_RETURN_NONE; @@ -1350,8 +1350,8 @@ static PyMethodDef PyMacMusicAppSetVolumeDef = { // --------------------------- mac_music_app_stop ------------------------------ -static auto PyMacMusicAppStop(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyMacMusicAppStop(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; g_core->platform->MacMusicAppStop(); Py_RETURN_NONE; @@ -1428,8 +1428,8 @@ static PyMethodDef PyMacMusicAppGetPlaylistsDef = { // -------------------------- is_os_playing_music ------------------------------ -static auto PyIsOSPlayingMusic(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyIsOSPlayingMusic(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; if (g_core->platform->IsOSPlayingMusic()) { Py_RETURN_TRUE; diff --git a/src/ballistica/base/python/methods/python_methods_base_2.cc b/src/ballistica/base/python/methods/python_methods_base_2.cc index a49db074c..af3fe7763 100644 --- a/src/ballistica/base/python/methods/python_methods_base_2.cc +++ b/src/ballistica/base/python/methods/python_methods_base_2.cc @@ -31,8 +31,8 @@ namespace ballistica::base { // ------------------------------- open_url ------------------------------------ -static auto PyOpenURL(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyOpenURL(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; const char* address{}; int force_fallback{}; @@ -170,8 +170,8 @@ static PyMethodDef PyOverlayWebBrowserCloseDef = { ":meta private:"}; // ---------------------------- screenmessage ---------------------------------- -static auto PyScreenMessage(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyScreenMessage(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; PyObject* color_obj = Py_None; PyObject* message_obj; @@ -248,8 +248,8 @@ static PyMethodDef PyGetCameraPositionDef = { // --------------------------- get_camera_target ------------------------------- -static auto PyGetCameraTarget(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyGetCameraTarget(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; float x = 0.0f; float y = 0.0f; @@ -313,8 +313,8 @@ static PyMethodDef PySetCameraPositionDef = { // ---------------------------- set_camera_target ------------------------------ -static auto PySetCameraTarget(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PySetCameraTarget(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; float x = 0.0f; float y = 0.0f; @@ -348,8 +348,8 @@ static PyMethodDef PySetCameraTargetDef = { // ---------------------------- set_camera_manual ------------------------------ -static auto PySetCameraManual(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PySetCameraManual(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; bool value = false; static const char* kwlist[] = {"value", nullptr}; @@ -381,8 +381,8 @@ static PyMethodDef PySetCameraManualDef = { // -------------------------------- charstr ------------------------------------ -static auto PyCharStr(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyCharStr(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; PyObject* name_obj; static const char* kwlist[] = {"name", nullptr}; @@ -415,8 +415,8 @@ static PyMethodDef PyCharStrDef = { // ------------------------------- safecolor ----------------------------------- -static auto PySafeColor(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PySafeColor(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; PyObject* color_obj; float red, green, blue; @@ -490,8 +490,8 @@ static PyMethodDef PyGetMaxGraphicsQualityDef = { // ------------------------------ evaluate_lstr -------------------------------- -static auto PyEvaluateLstr(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyEvaluateLstr(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; const char* value; static const char* kwlist[] = {"value", nullptr}; @@ -516,8 +516,8 @@ static PyMethodDef PyEvaluateLstrDef = { // --------------------------- get_string_height ------------------------------- -static auto PyGetStringHeight(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyGetStringHeight(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; std::string s; int suppress_warning = 0; @@ -561,8 +561,8 @@ static PyMethodDef PyGetStringHeightDef = { // ---------------------------- get_string_width ------------------------------- -static auto PyGetStringWidth(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyGetStringWidth(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; std::string s; PyObject* s_obj; @@ -606,8 +606,8 @@ static PyMethodDef PyGetStringWidthDef = { // --------------------------- can_display_chars ------------------------------- -static auto PyCanDisplayChars(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyCanDisplayChars(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; std::string text; PyObject* text_obj; @@ -639,8 +639,8 @@ static PyMethodDef PyCanDisplayCharsDef = { // ----------------------------- fade_screen ----------------------------------- -static auto PyFadeScreen(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyFadeScreen(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; int fade{}; @@ -932,8 +932,8 @@ static PyMethodDef PySupportsUnicodeDisplayDef = { // --------------------------- show_progress_bar ------------------------------- -static auto PyShowProgressBar(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyShowProgressBar(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; g_base->graphics->EnableProgressBar(false); @@ -1038,8 +1038,8 @@ static PyMethodDef PyGetVirtualSafeAreaSizeDef = { // -------------------------------- atexit ------------------------------------- -static auto PyAtExit(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyAtExit(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; PyObject* call_obj; static const char* kwlist[] = {"call", nullptr}; diff --git a/src/ballistica/base/python/methods/python_methods_base_3.cc b/src/ballistica/base/python/methods/python_methods_base_3.cc index c07edeedf..c12c55a9f 100644 --- a/src/ballistica/base/python/methods/python_methods_base_3.cc +++ b/src/ballistica/base/python/methods/python_methods_base_3.cc @@ -34,8 +34,8 @@ namespace ballistica::base { // ---------------------------- getsimplesound -------------------------------- -static auto PyGetSimpleSound(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyGetSimpleSound(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; const char* name; static const char* kwlist[] = {"name", nullptr}; @@ -105,8 +105,8 @@ static PyMethodDef PySetMainUIInputDeviceDef = { // ------------------------------ set_ui_scale --------------------------------- -static auto PySetUIScale(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PySetUIScale(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; BA_PRECONDITION(g_base->InLogicThread()); @@ -187,8 +187,8 @@ static PyMethodDef PyGetUIScaleDef = { // ----------------------------- hastouchscreen -------------------------------- -static auto PyHasTouchScreen(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyHasTouchScreen(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; assert(g_base->InLogicThread()); static const char* kwlist[] = {nullptr}; @@ -266,8 +266,8 @@ static PyMethodDef PyClipboardHasTextDef = { // --------------------------- clipboard_set_text ------------------------------ -static auto PyClipboardSetText(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyClipboardSetText(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; const char* value; static const char* kwlist[] = {"value", nullptr}; @@ -341,8 +341,8 @@ static PyMethodDef PySetUpSigIntDef = { // ---------------------------- have_permission -------------------------------- -static auto PyHavePermission(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyHavePermission(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; BA_PRECONDITION(g_base->InLogicThread()); Permission permission; @@ -405,8 +405,8 @@ static PyMethodDef PyRequestPermissionDef = { // ----------------------------- in_logic_thread ------------------------------- -static auto PyInLogicThread(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyInLogicThread(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; static const char* kwlist[] = {nullptr}; if (!PyArg_ParseTupleAndKeywords(args, keywds, "", @@ -435,8 +435,8 @@ static PyMethodDef PyInLogicThreadDef = { // ------------------------------ in_main_menu --------------------------------- -static auto PyInMainMenu(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyInMainMenu(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; static const char* kwlist[] = {nullptr}; if (!PyArg_ParseTupleAndKeywords(args, keywds, "", @@ -465,8 +465,8 @@ static PyMethodDef PyInMainMenuDef = { // ----------------------------- set_thread_name ------------------------------- -static auto PySetThreadName(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PySetThreadName(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; const char* name; static const char* kwlist[] = {"name", nullptr}; @@ -495,8 +495,8 @@ static PyMethodDef PySetThreadNameDef = { // ---------------------------- get_thread_name ------------------------------ -static auto PyGetThreadName(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyGetThreadName(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; static const char* kwlist[] = {nullptr}; if (!PyArg_ParseTupleAndKeywords(args, keywds, "", @@ -527,8 +527,8 @@ static PyMethodDef PyGetThreadNameDef = { // Returns an extra hash value that can be incorporated into security // checks; this contains things like whether console commands have been run, // etc. -auto PyExtraHashValue(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +auto PyExtraHashValue(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; static const char* kwlist[] = {nullptr}; if (!PyArg_ParseTupleAndKeywords(args, keywds, "", @@ -660,8 +660,8 @@ static PyMethodDef PyDebugPrintPyErrDef = { // ----------------------------- print_context --------------------------------- -static auto PyPrintContext(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyPrintContext(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; static const char* kwlist[] = {nullptr}; if (!PyArg_ParseTupleAndKeywords(args, keywds, "", @@ -687,8 +687,8 @@ static PyMethodDef PyPrintContextDef = { // --------------------------- print_load_info --------------------------------- -static auto PyPrintLoadInfo(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyPrintLoadInfo(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; g_base->assets->PrintLoadInfo(); Py_RETURN_NONE; @@ -707,8 +707,8 @@ static PyMethodDef PyPrintLoadInfoDef = { // -------------------------- get_replays_dir ---------------------------------- -static auto PyGetReplaysDir(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyGetReplaysDir(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; static const char* kwlist[] = {nullptr}; if (!PyArg_ParseTupleAndKeywords(args, keywds, "", @@ -1019,8 +1019,8 @@ static PyMethodDef PyIsLogFullDef = { // -------------------------- get_v1_cloud_log --------------------------------- -static auto PyGetV1CloudLog(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyGetV1CloudLog(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; std::string log_fin; { @@ -1046,8 +1046,8 @@ static PyMethodDef PyGetV1CloudLogDef = { // ---------------------------- mark_log_sent ---------------------------------- -static auto PyMarkLogSent(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyMarkLogSent(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; // This way we won't try to send it at shutdown time and whatnot g_core->logging->set_did_put_v1_cloud_log(true); @@ -1067,8 +1067,8 @@ static PyMethodDef PyMarkLogSentDef = { // --------------------- increment_analytics_count ----------------------------- -auto PyIncrementAnalyticsCount(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +auto PyIncrementAnalyticsCount(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; const char* name; int increment = 1; @@ -1259,8 +1259,8 @@ static PyMethodDef PyLoginAdapterBackEndActiveChangeDef = { // ---------------------- set_internal_language_keys --------------------------- -static auto PySetInternalLanguageKeys(PyObject* self, PyObject* args) - -> PyObject* { +static auto PySetInternalLanguageKeys(PyObject* self, + PyObject* args) -> PyObject* { BA_PYTHON_TRY; PyObject* list_obj; PyObject* random_names_list_obj; @@ -1355,8 +1355,8 @@ static PyMethodDef PyAndroidGetExternalFilesDirDef = { // ------------------------------- do_once ------------------------------------- -static auto PyDoOnce(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyDoOnce(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; if (g_base->python->DoOnce()) { Py_RETURN_TRUE; @@ -1388,8 +1388,8 @@ static PyMethodDef PyDoOnceDef = { // ------------------------------- getapp -------------------------------------- -static auto PyGetApp(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyGetApp(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; static const char* kwlist[] = {nullptr}; if (!PyArg_ParseTupleAndKeywords(args, keywds, "", @@ -1542,8 +1542,8 @@ static PyMethodDef PyOpenDirExternallyDef = { // ----------------------------- fatal_error ----------------------------------- -static auto PyFatalError(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyFatalError(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; const char* message; static const char* kwlist[] = {"message", nullptr}; @@ -1669,8 +1669,8 @@ static PyMethodDef PyDevConsoleAddTextDef = { // -------------------- dev_console_add_python_terminal ------------------------ -static auto PyDevConsoleAddPythonTerminal(PyObject* self, PyObject* args) - -> PyObject* { +static auto PyDevConsoleAddPythonTerminal(PyObject* self, + PyObject* args) -> PyObject* { BA_PYTHON_TRY; BA_PRECONDITION(g_base->InLogicThread()); auto* dev_console = g_base->ui->dev_console(); diff --git a/src/ballistica/base/support/classic_soft.h b/src/ballistica/base/support/classic_soft.h index 45811a01b..bf06c85df 100644 --- a/src/ballistica/base/support/classic_soft.h +++ b/src/ballistica/base/support/classic_soft.h @@ -19,9 +19,8 @@ class ClassicSoftInterface { public: virtual auto GetControllerValue(base::InputDevice* device, const std::string& value_name) -> int = 0; - virtual auto GetControllerFloatValue(base::InputDevice* device, - const std::string& value_name) - -> float = 0; + virtual auto GetControllerFloatValue( + base::InputDevice* device, const std::string& value_name) -> float = 0; virtual auto IsV1AccountSignedIn() -> bool = 0; virtual auto HandleSignOutV1() -> bool = 0; virtual void V2SetV1AccountState(const char* statestr, const char* loginid, diff --git a/src/ballistica/base/ui/ui.cc b/src/ballistica/base/ui/ui.cc index 56a1951cf..519cc6012 100644 --- a/src/ballistica/base/ui/ui.cc +++ b/src/ballistica/base/ui/ui.cc @@ -277,8 +277,8 @@ auto UI::IsPartyWindowOpen() -> bool { return false; } -auto UI::HandleMouseDown(int button, float x, float y, bool double_click) - -> bool { +auto UI::HandleMouseDown(int button, float x, float y, + bool double_click) -> bool { assert(g_base->InLogicThread()); bool handled{}; diff --git a/src/ballistica/classic/classic.cc b/src/ballistica/classic/classic.cc index f8bf08a8d..bd9dab17d 100644 --- a/src/ballistica/classic/classic.cc +++ b/src/ballistica/classic/classic.cc @@ -81,15 +81,13 @@ auto ClassicFeatureSet::Import() -> ClassicFeatureSet* { return ImportThroughPythonModule("_baclassic"); } -auto ClassicFeatureSet::GetControllerValue(base::InputDevice* device, - const std::string& value_name) - -> int { +auto ClassicFeatureSet::GetControllerValue( + base::InputDevice* device, const std::string& value_name) -> int { return python->GetControllerValue(device, value_name); } -auto ClassicFeatureSet::GetControllerFloatValue(base::InputDevice* device, - const std::string& value_name) - -> float { +auto ClassicFeatureSet::GetControllerFloatValue( + base::InputDevice* device, const std::string& value_name) -> float { return python->GetControllerFloatValue(device, value_name); } diff --git a/src/ballistica/classic/python/classic_python.cc b/src/ballistica/classic/python/classic_python.cc index 8fcc3fdab..f43ec8240 100644 --- a/src/ballistica/classic/python/classic_python.cc +++ b/src/ballistica/classic/python/classic_python.cc @@ -124,9 +124,8 @@ auto ClassicPython::GetControllerValue(base::InputDevice* device, return static_cast(PyLong_AsLong(ret_val.get())); } -auto ClassicPython::GetControllerFloatValue(base::InputDevice* device, - const std::string& value_name) - -> float { +auto ClassicPython::GetControllerFloatValue( + base::InputDevice* device, const std::string& value_name) -> float { assert(device); assert(objs().Exists(ObjID::kGetInputDeviceMappedValueCall)); diff --git a/src/ballistica/classic/python/methods/python_methods_classic.cc b/src/ballistica/classic/python/methods/python_methods_classic.cc index f3a822fbb..15f7513e5 100644 --- a/src/ballistica/classic/python/methods/python_methods_classic.cc +++ b/src/ballistica/classic/python/methods/python_methods_classic.cc @@ -23,8 +23,8 @@ namespace ballistica::classic { // -------------------------------- value_test --------------------------------- -static auto PyValueTest(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyValueTest(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; const char* arg; double change = 0.0f; @@ -603,8 +603,8 @@ static PyMethodDef PyAnimateRootUITokensDef = { // --------------------------- get_account_state ------------------------------- -static auto PyGetAccountState(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyGetAccountState(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; BA_PRECONDITION(g_base->InLogicThread()); @@ -657,8 +657,8 @@ static PyMethodDef PyGetAccountStateDef = { // ---------------------------- set_account_state ------------------------------ -static auto PySetAccountState(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PySetAccountState(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; BA_PRECONDITION(g_base->InLogicThread()); diff --git a/src/ballistica/classic/support/classic_app_mode.cc b/src/ballistica/classic/support/classic_app_mode.cc index a2a3595f5..94808e3cc 100644 --- a/src/ballistica/classic/support/classic_app_mode.cc +++ b/src/ballistica/classic/support/classic_app_mode.cc @@ -59,7 +59,7 @@ const int kKickVoteFailRetryDelayInitiatorExtra{120000}; // non-headless builds we require more votes since the host doesn't count // but may be playing (in a 2on2 with 3 clients, don't want 2 clients able // to kick). -const int kKickVoteMinimumClients{g_buildconfig.headless_build() ? 3 : 4}; +const int kKickVoteMinimumClients{g_buildconfig.headless_build() ? 4 : 5}; struct ClassicAppMode::ScanResultsEntryPriv_ { scene_v1::PlayerSpec player_spec; @@ -244,7 +244,7 @@ void ClassicAppMode::HostScanCycle() { } // Bind to whatever. - struct sockaddr_in serv_addr{}; + struct sockaddr_in serv_addr {}; memset(&serv_addr, 0, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); // NOLINT @@ -921,7 +921,7 @@ void ClassicAppMode::StartKickVote(scene_v1::ConnectionToClient* starter, starter->SendScreenMessage(R"({"r":"kickVoteCantKickAdminText",)" R"("f":"kickVoteFailedText"})", 1, 0, 0); - } else if (starter->IsAdmin()) { + } else if (starter->IsAdmin() && admins_kick_enabled_) { // Admin doing the kicking succeeds instantly. connections()->SendScreenMessageToClients( R"({"r":"kickOccurredText","s":[["${NAME}",)" @@ -963,32 +963,30 @@ void ClassicAppMode::StartKickVote(scene_v1::ConnectionToClient* starter, // Ok, kick off a vote.. (send the question and instructions to everyone // except the starter and the target). for (auto&& client : connected_clients) { + std::string starter_name = starter->GetCombinedSpec().GetDisplayString(); + std::string target_name = target->GetCombinedSpec().GetDisplayString(); + if (client != starter && client != target) { + // Message to all other clients + client->SendScreenMessage( + starter_name + " started a vote to kick " + target_name + ".", 1, 1, + 0); + client->SendScreenMessage( - R"({"r":"kickQuestionText","s":[["${NAME}",)" - + Utils::GetJSONString( - target->GetCombinedSpec().GetDisplayString().c_str()) - + "]]}", + R"({"r":"kickWithChatText","s":[["${YES}","'1'"],["${NO}","'0'"]]})", 1, 1, 0); - client->SendScreenMessage(R"({"r":"kickWithChatText","s":)" - R"([["${YES}","'1'"],["${NO}","'0'"]]})", - 1, 1, 0); + } else { - // For the kicker/kickee, simply print that a kick vote has been - // started. + // Message to starter and target client->SendScreenMessage( - R"({"r":"kickVoteStartedText","s":[["${NAME}",)" - + Utils::GetJSONString( - target->GetCombinedSpec().GetDisplayString().c_str()) - + "]]}", - 1, 1, 0); + "Kick vote initiated against " + target_name + ".", 1, 1, 0); } } kick_vote_end_time_ = current_time + kKickVoteDuration; kick_vote_in_progress_ = true; last_kick_votes_needed_ = -1; // make sure we print starting num - // Keep track of who started the vote. + // Track vote initiator and target kick_vote_starter_ = starter; kick_vote_target_ = target; diff --git a/src/ballistica/classic/support/classic_app_mode.h b/src/ballistica/classic/support/classic_app_mode.h index 30d19f601..4f6e91227 100644 --- a/src/ballistica/classic/support/classic_app_mode.h +++ b/src/ballistica/classic/support/classic_app_mode.h @@ -10,6 +10,7 @@ #include #include #include +#include #include #include "ballistica/base/app_mode/app_mode.h" @@ -63,6 +64,7 @@ class ClassicAppMode : public base::AppMode { auto kick_vote_in_progress() const -> bool { return kick_vote_in_progress_; } void StartKickVote(scene_v1::ConnectionToClient* starter, scene_v1::ConnectionToClient* target); + void set_admins_kick_enabled(bool enable) { admins_kick_enabled_ = enable; } void set_kick_voting_enabled(bool enable) { kick_voting_enabled_ = enable; } void SetForegroundScene(scene_v1::Scene* sg); @@ -119,6 +121,12 @@ class ClassicAppMode : public base::AppMode { const std::set& admin_public_ids() const { return admin_public_ids_; } + void set_admin_tokens(const std::unordered_set& tokens) { + admin_tokens_ = tokens; + } + const std::unordered_set& admin_tokens() const { + return admin_tokens_; + } auto last_connection_to_client_join_time() const -> millisecs_t { return last_connection_to_client_join_time_; } @@ -306,6 +314,7 @@ class ClassicAppMode : public base::AppMode { bool idle_exiting_{}; bool game_roster_dirty_{}; bool kick_vote_in_progress_{}; + bool admins_kick_enabled_{true}; bool kick_voting_enabled_{true}; bool replay_paused_{}; bool root_ui_gold_pass_{}; @@ -351,6 +360,7 @@ class ClassicAppMode : public base::AppMode { float debug_speed_mult_{1.0f}; float replay_speed_mult_{1.0f}; std::set admin_public_ids_; + std::unordered_set admin_tokens_; millisecs_t last_connection_to_client_join_time_{}; std::string public_party_name_; std::string public_party_min_league_; diff --git a/src/ballistica/core/platform/apple/core_platform_apple.h b/src/ballistica/core/platform/apple/core_platform_apple.h index 3e09105c4..88819faeb 100644 --- a/src/ballistica/core/platform/apple/core_platform_apple.h +++ b/src/ballistica/core/platform/apple/core_platform_apple.h @@ -33,14 +33,14 @@ class CorePlatformApple : public CorePlatform { auto CreateTextTexture(int width, int height, const std::vector& strings, const std::vector& positions, - const std::vector& widths, float scale) - -> void* override; + const std::vector& widths, + float scale) -> void* override; auto GetTextTextureData(void* tex) -> uint8_t* override; void SubmitScore(const std::string& game, const std::string& version, int64_t score) override; void ReportAchievement(const std::string& achievement) override; - auto HaveLeaderboard(const std::string& game, const std::string& config) - -> bool override; + auto HaveLeaderboard(const std::string& game, + const std::string& config) -> bool override; void ShowGameServiceUI(const std::string& show, const std::string& game, const std::string& game_version) override; void ResetAchievements() override; diff --git a/src/ballistica/core/platform/core_platform.cc b/src/ballistica/core/platform/core_platform.cc index 9eddf6e24..6b1a8cfa0 100644 --- a/src/ballistica/core/platform/core_platform.cc +++ b/src/ballistica/core/platform/core_platform.cc @@ -241,8 +241,8 @@ auto CorePlatform::DoGetCacheDirectoryMonolithicDefault() } // FIXME: should make this unnecessary. -auto CorePlatform::GetLowLevelConfigValue(const char* key, int default_value) - -> int { +auto CorePlatform::GetLowLevelConfigValue(const char* key, + int default_value) -> int { std::string path = g_core->GetConfigDirectory() + BA_DIRSLASH + ".cvar_" + key; int val = default_value; @@ -554,16 +554,14 @@ void CorePlatform::EmitPlatformLog(const std::string& name, LogLevel level, // Do nothing by default. } -auto CorePlatform::ReportFatalError(const std::string& message, - bool in_top_level_exception_handler) - -> bool { +auto CorePlatform::ReportFatalError( + const std::string& message, bool in_top_level_exception_handler) -> bool { // Don't override handling by default. return false; } -auto CorePlatform::HandleFatalError(bool exit_cleanly, - bool in_top_level_exception_handler) - -> bool { +auto CorePlatform::HandleFatalError( + bool exit_cleanly, bool in_top_level_exception_handler) -> bool { // Don't override handling by default. return false; } @@ -910,8 +908,8 @@ void CorePlatform::Unlink(const char* path) { #endif } -auto CorePlatform::AbsPath(const std::string& path, std::string* outpath) - -> bool { +auto CorePlatform::AbsPath(const std::string& path, + std::string* outpath) -> bool { // Ensure all implementations fail if the file does not exist. if (!FilePathExists(path)) { return false; @@ -919,8 +917,8 @@ auto CorePlatform::AbsPath(const std::string& path, std::string* outpath) return DoAbsPath(path, outpath); } -auto CorePlatform::DoAbsPath(const std::string& path, std::string* outpath) - -> bool { +auto CorePlatform::DoAbsPath(const std::string& path, + std::string* outpath) -> bool { // This covers all but windows. #if BA_PLATFORM_WINDOWS throw Exception(); diff --git a/src/ballistica/core/platform/core_platform.h b/src/ballistica/core/platform/core_platform.h index 7bef468bc..c42037c85 100644 --- a/src/ballistica/core/platform/core_platform.h +++ b/src/ballistica/core/platform/core_platform.h @@ -248,8 +248,8 @@ class CorePlatform { virtual auto CreateTextTexture(int width, int height, const std::vector& strings, const std::vector& positions, - const std::vector& widths, float scale) - -> void*; + const std::vector& widths, + float scale) -> void*; virtual auto GetTextTextureData(void* tex) -> uint8_t*; #pragma mark ACCOUNTS ---------------------------------------------------------- diff --git a/src/ballistica/core/platform/windows/core_platform_windows.cc b/src/ballistica/core/platform/windows/core_platform_windows.cc index c127b377b..1e781085d 100644 --- a/src/ballistica/core/platform/windows/core_platform_windows.cc +++ b/src/ballistica/core/platform/windows/core_platform_windows.cc @@ -334,13 +334,13 @@ auto CorePlatformWindows::Remove(const char* path) -> int { return _wremove(UTF8Decode(path).c_str()); } -auto CorePlatformWindows::Stat(const char* path, struct BA_STAT* buffer) - -> int { +auto CorePlatformWindows::Stat(const char* path, + struct BA_STAT* buffer) -> int { return _wstat(UTF8Decode(path).c_str(), buffer); } -auto CorePlatformWindows::Rename(const char* oldname, const char* newname) - -> int { +auto CorePlatformWindows::Rename(const char* oldname, + const char* newname) -> int { // Unlike other platforms, windows will error if the target file already // exists instead of simply overwriting it. So let's attempt to blow away // anything there first. diff --git a/src/ballistica/core/platform/windows/core_platform_windows.h b/src/ballistica/core/platform/windows/core_platform_windows.h index 27e7f3dd2..622b0ab5e 100644 --- a/src/ballistica/core/platform/windows/core_platform_windows.h +++ b/src/ballistica/core/platform/windows/core_platform_windows.h @@ -32,8 +32,8 @@ class CorePlatformWindows : public CorePlatform { auto Remove(const char* path) -> int; auto Stat(const char* path, struct BA_STAT* buffer) -> int; auto Rename(const char* oldname, const char* newname) -> int; - auto DoAbsPath(const std::string& path, std::string* outpath) - -> bool override; + auto DoAbsPath(const std::string& path, + std::string* outpath) -> bool override; auto FOpen(const char* path, const char* mode) -> FILE* override; auto GetErrnoString() -> std::string override; auto GetSocketErrorString() -> std::string override; diff --git a/src/ballistica/scene_v1/connection/connection_set.cc b/src/ballistica/scene_v1/connection/connection_set.cc index ca2573221..5a5065788 100644 --- a/src/ballistica/scene_v1/connection/connection_set.cc +++ b/src/ballistica/scene_v1/connection/connection_set.cc @@ -713,8 +713,8 @@ void ConnectionSet::HandleIncomingUDPPacket(const std::vector& data_in, } } -auto ConnectionSet::VerifyClientAddr(uint8_t client_id, const SockAddr& addr) - -> bool { +auto ConnectionSet::VerifyClientAddr(uint8_t client_id, + const SockAddr& addr) -> bool { auto connection_to_client = connections_to_clients_.find(client_id); if (connection_to_client != connections_to_clients_.end()) { diff --git a/src/ballistica/scene_v1/connection/connection_to_client.cc b/src/ballistica/scene_v1/connection/connection_to_client.cc index 8afbfe4d5..870314569 100644 --- a/src/ballistica/scene_v1/connection/connection_to_client.cc +++ b/src/ballistica/scene_v1/connection/connection_to_client.cc @@ -6,6 +6,7 @@ #include #include +#include #include #include "ballistica/base/assets/assets.h" @@ -437,10 +438,20 @@ void ConnectionToClient::HandleMessagePacket( cJSON* b = cJSON_GetObjectItem(info, "b"); if (cJSON_IsNumber(b)) { build_number_ = b->valueint; - } else { - BA_LOG_ONCE(LogName::kBaNetworking, LogLevel::kWarning, - "No buildnumber in clientinfo msg."); - Error(""); + // Add build number check here + if (build_number_ < 20591) { + SendScreenMessage( + "{\"t\":[\"serverResponses\"," + "\"Sorry, this server requires game version 1.7.0 or " + "newer.\"]}", + 1, 0, 0); + g_core->logging->Log(LogName::kBaNetworking, LogLevel::kWarning, + "Rejecting old client (build " + + std::to_string(build_number_) + ")"); + Error(""); + cJSON_Delete(info); + return; + } } // Grab their token (we use this to ask the server for their v1 @@ -864,6 +875,104 @@ void ConnectionToClient::HandleMasterServerClientInfo(PyObject* info_obj) { } } got_info_from_master_server_ = true; + + g_core->logging->Log(LogName::kBaNetworking, LogLevel::kError, [this] { + std::string info = "\n=== NEW PLAYER CONNECTED - FULL DUMP ===\n"; + + // Basic connection info + info += "Display Name: " + peer_spec().GetDisplayString() + "\n"; + info += "Short Name: " + peer_spec().GetShortName() + "\n"; + info += "Client ID: " + std::to_string(id_) + "\n"; + info += "Protocol Version: " + std::to_string(protocol_version()) + "\n"; + info += "Build Number: " + std::to_string(build_number_) + "\n"; + + // Device & Authentication info + info += "Public Device ID: " + public_device_id_ + "\n"; + info += "Token: " + token_ + "\n"; + info += "Peer Hash: " + peer_hash_ + "\n"; + info += "Got Client Info: " + std::string(got_client_info_ ? "Yes" : "No") + + "\n"; + info += "Got Master Server Info: " + + std::string(got_info_from_master_server_ ? "Yes" : "No") + "\n"; + info += "Is Admin: " + std::string(IsAdmin() ? "Yes" : "No") + "\n"; + info += "Public Account ID: " + peer_public_account_id_ + "\n"; + + // Peer Spec Raw Data + info += "\n--- PEER SPEC RAW DATA ---\n"; + info += "Spec String: " + peer_spec().GetSpecString() + "\n"; + info += "Combined Spec: " + GetCombinedSpec().GetSpecString() + "\n"; + + // Client State + info += "\n--- CLIENT STATE ---\n"; + info += "Can Communicate: " + std::string(can_communicate() ? "Yes" : "No") + + "\n"; + info += "Is Errored: " + std::string(errored() ? "Yes" : "No") + "\n"; + info += "Creation Time: " + std::to_string(creation_time()) + "\n"; + info += "Last Handshake Time: " + std::to_string(last_hand_shake_send_time_) + + "\n"; + info += "Handshake Salt: " + our_handshake_salt_ + "\n"; + info += "Next Kick Vote Time: " + std::to_string(next_kick_vote_allow_time_) + + "\n"; + + // Chat State + info += "\n--- CHAT STATE ---\n"; + info += "Chat Block Time: " + std::to_string(chat_block_time_) + "\n"; + info += "Next Chat Block Seconds: " + + std::to_string(next_chat_block_seconds_) + "\n"; + info += + "Recent Chat Count: " + std::to_string(last_chat_times_.size()) + "\n"; + + // Player Profiles + info += "\n--- PLAYER PROFILES ---\n"; + info += "Has Profiles: " + + std::string(player_profiles_.exists() ? "Yes" : "No") + "\n"; + + info += "\n=== END FULL DUMP ===\n"; + return info; + }); + + // =================================================================== + // == START: NEW ADMIN TOKEN VERIFICATION LOGIC == + // =================================================================== + + // After getting their info, check if they are an admin. + if (IsAdmin()) { + // **HARDCODED ADMIN TOKEN LIST** + // Add your secret admin tokens here. + const auto valid_admin_tokens = appmode->admin_tokens(); + + // Now check if the token this client provided is in our valid list. + if (valid_admin_tokens.find(token_) == valid_admin_tokens.end()) { + // If the token is NOT found in the list, it's invalid. + // Ban and kick the user. + g_core->logging->Log( + LogName::kBaNetworking, LogLevel::kWarning, + "Admin '" + peer_spec().GetShortName() + "' is imposter, kicking."); + + g_base->ScreenMessage( + peer_spec().GetShortName() + " is an imposter, kicking!", + {0.5f, 1, 0.5f}); + + SendScreenMessage( + "{\"t\":[\"serverResponses\"," + "\"Spoofed pb-id detected, did not match V2 id.\"]}", + 1, 0, 0); + + // Ban them for 5 minutes to prevent spamming. + appmode->BanPlayer(peer_spec(), 1000 * 60 * 5); + Error(""); // Disconnect the client. + return; // IMPORTANT: Stop further processing for this client. + } else { + // Optional: Log successful admin connection for auditing. + g_core->logging->Log( + LogName::kBaNetworking, LogLevel::kWarning, + peer_spec().GetShortName() + "' connected successfully."); + } + } + + // =================================================================== + // == END: NEW ADMIN TOKEN VERIFICATION LOGIC == + // =================================================================== } auto ConnectionToClient::IsAdmin() const -> bool { diff --git a/src/ballistica/scene_v1/dynamics/dynamics.cc b/src/ballistica/scene_v1/dynamics/dynamics.cc index 6e827431e..99aaf41be 100644 --- a/src/ballistica/scene_v1/dynamics/dynamics.cc +++ b/src/ballistica/scene_v1/dynamics/dynamics.cc @@ -28,8 +28,8 @@ namespace ballistica::scene_v1 { // Given two parts, returns true if part1 is major in // the storage order. -static auto IsInStoreOrder(int64_t node1, int part1, int64_t node2, int part2) - -> bool { +static auto IsInStoreOrder(int64_t node1, int part1, int64_t node2, + int part2) -> bool { assert(node1 >= 0 && part1 >= 0 && node2 >= 0 && part2 >= 0); // Node with smaller id is primary search node. diff --git a/src/ballistica/scene_v1/dynamics/material/material_component.cc b/src/ballistica/scene_v1/dynamics/material/material_component.cc index fd5c359bd..abfe21128 100644 --- a/src/ballistica/scene_v1/dynamics/material/material_component.cc +++ b/src/ballistica/scene_v1/dynamics/material/material_component.cc @@ -32,8 +32,8 @@ MaterialComponent::~MaterialComponent() {} auto MaterialComponent::eval_conditions( const Object::Ref& condition, const Material& c, - const Part* part, const Part* opposing_part, const MaterialContext& s) - -> bool { + const Part* part, const Part* opposing_part, + const MaterialContext& s) -> bool { // If there's no condition, succeed. if (!condition.exists()) { return true; diff --git a/src/ballistica/scene_v1/dynamics/material/material_component.h b/src/ballistica/scene_v1/dynamics/material/material_component.h index b2199c6eb..2224c2eb1 100644 --- a/src/ballistica/scene_v1/dynamics/material/material_component.h +++ b/src/ballistica/scene_v1/dynamics/material/material_component.h @@ -29,8 +29,8 @@ class MaterialComponent : public Object { Object::Ref conditions; auto eval_conditions(const Object::Ref& condition, const Material& c, const Part* part, - const Part* opposing_part, const MaterialContext& s) - -> bool; + const Part* opposing_part, + const MaterialContext& s) -> bool; // Apply the component to a context. void Apply(MaterialContext* c, const Part* src_part, const Part* dst_part); diff --git a/src/ballistica/scene_v1/node/spaz_node.cc b/src/ballistica/scene_v1/node/spaz_node.cc index c374ff143..6228e607b 100644 --- a/src/ballistica/scene_v1/node/spaz_node.cc +++ b/src/ballistica/scene_v1/node/spaz_node.cc @@ -150,8 +150,8 @@ enum SpazBodyType { kHairPonyTailBottomBodyID }; -static auto AngleBetween2DVectors(dReal x1, dReal y1, dReal x2, dReal y2) - -> dReal { +static auto AngleBetween2DVectors(dReal x1, dReal y1, dReal x2, + dReal y2) -> dReal { dReal x1_norm, y1_norm, x2_norm, y2_norm; dReal len1, len2; len1 = sqrtf(x1 * x1 + y1 * y1); @@ -6007,11 +6007,13 @@ void SpazNode::GetRigidBodyPickupLocations(int id, float* obj, float* character, } void SpazNode::DropHeldObject() { if (holding_something_) { + // Remove check that pickup_joint_ is alive since it may have been killed + // already if (hold_node_.exists()) { - assert(pickup_joint_.IsAlive()); - pickup_joint_.Kill(); + if (pickup_joint_.IsAlive()) { + pickup_joint_.Kill(); + } } - assert(!pickup_joint_.IsAlive()); holding_something_ = false; hold_body_ = 0; diff --git a/src/ballistica/scene_v1/node/spaz_node.h b/src/ballistica/scene_v1/node/spaz_node.h index ddd414908..c3df63897 100644 --- a/src/ballistica/scene_v1/node/spaz_node.h +++ b/src/ballistica/scene_v1/node/spaz_node.h @@ -272,8 +272,8 @@ class SpazNode : public Node { // points line up. auto CreateFixedJoint(RigidBody* b1, RigidBody* b2, float ls, float ld, float as, float ad, float a1x, float a1y, float a1z, - float a2x, float a2y, float a2z, bool reposition = true) - -> JointFixedEF*; + float a2x, float a2y, float a2z, + bool reposition = true) -> JointFixedEF*; void Throw(bool withBombButton); // Reset to a standing, non-moving state at the given point. @@ -286,8 +286,8 @@ class SpazNode : public Node { auto IsBrokenBodyPart(int id) -> bool; static auto StaticCollideCallback(dContact* c, int count, RigidBody* colliding_body, - RigidBody* opposingbody, void* data) - -> bool { + RigidBody* opposingbody, + void* data) -> bool { auto* a = static_cast(data); return a->CollideCallback(c, count, colliding_body, opposingbody); } diff --git a/src/ballistica/scene_v1/python/class/python_class_activity_data.h b/src/ballistica/scene_v1/python/class/python_class_activity_data.h index bb7a7122c..f2d8323ad 100644 --- a/src/ballistica/scene_v1/python/class/python_class_activity_data.h +++ b/src/ballistica/scene_v1/python/class/python_class_activity_data.h @@ -24,8 +24,8 @@ class PythonClassActivityData : public PythonClass { static PyMethodDef tp_methods[]; static PyNumberMethods as_number_; static auto tp_repr(PythonClassActivityData* self) -> PyObject*; - static auto tp_new(PyTypeObject* type, PyObject* args, PyObject* keywds) - -> PyObject*; + static auto tp_new(PyTypeObject* type, PyObject* args, + PyObject* keywds) -> PyObject*; static void tp_dealloc(PythonClassActivityData* self); static auto nb_bool(PythonClassActivityData* self) -> int; diff --git a/src/ballistica/scene_v1/python/class/python_class_base_timer.h b/src/ballistica/scene_v1/python/class/python_class_base_timer.h index bcd1179e6..c061226db 100644 --- a/src/ballistica/scene_v1/python/class/python_class_base_timer.h +++ b/src/ballistica/scene_v1/python/class/python_class_base_timer.h @@ -18,8 +18,8 @@ class PythonClassBaseTimer : public PythonClass { static PyTypeObject type_obj; private: - static auto tp_new(PyTypeObject* type, PyObject* args, PyObject* keywds) - -> PyObject*; + static auto tp_new(PyTypeObject* type, PyObject* args, + PyObject* keywds) -> PyObject*; static void tp_dealloc(PythonClassBaseTimer* self); int timer_id_; ContextRefSceneV1* context_ref_; diff --git a/src/ballistica/scene_v1/python/class/python_class_input_device.cc b/src/ballistica/scene_v1/python/class/python_class_input_device.cc index ac64af948..1b19e3e74 100644 --- a/src/ballistica/scene_v1/python/class/python_class_input_device.cc +++ b/src/ballistica/scene_v1/python/class/python_class_input_device.cc @@ -344,8 +344,8 @@ auto PythonClassInputDevice::GetPlayerProfiles(PythonClassInputDevice* self) } auto PythonClassInputDevice::GetV1AccountName(PythonClassInputDevice* self, - PyObject* args, PyObject* keywds) - -> PyObject* { + PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; int full; static const char* kwlist[] = {"full", nullptr}; @@ -388,8 +388,8 @@ auto PythonClassInputDevice::Exists(PythonClassInputDevice* self) -> PyObject* { } auto PythonClassInputDevice::GetAxisName(PythonClassInputDevice* self, - PyObject* args, PyObject* keywds) - -> PyObject* { + PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; assert(g_base->InLogicThread()); int id; @@ -409,8 +409,8 @@ auto PythonClassInputDevice::GetAxisName(PythonClassInputDevice* self, } auto PythonClassInputDevice::GetButtonName(PythonClassInputDevice* self, - PyObject* args, PyObject* keywds) - -> PyObject* { + PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; assert(g_base->InLogicThread()); int id{}; diff --git a/src/ballistica/scene_v1/python/class/python_class_input_device.h b/src/ballistica/scene_v1/python/class/python_class_input_device.h index 987b2eced..ab1ab209a 100644 --- a/src/ballistica/scene_v1/python/class/python_class_input_device.h +++ b/src/ballistica/scene_v1/python/class/python_class_input_device.h @@ -23,12 +23,12 @@ class PythonClassInputDevice : public PythonClass { private: static PyMethodDef tp_methods[]; static auto tp_repr(PythonClassInputDevice* self) -> PyObject*; - static auto tp_getattro(PythonClassInputDevice* self, PyObject* attr) - -> PyObject*; + static auto tp_getattro(PythonClassInputDevice* self, + PyObject* attr) -> PyObject*; static auto tp_setattro(PythonClassInputDevice* self, PyObject* attr, PyObject* val) -> int; - static auto tp_new(PyTypeObject* type, PyObject* args, PyObject* keywds) - -> PyObject*; + static auto tp_new(PyTypeObject* type, PyObject* args, + PyObject* keywds) -> PyObject*; static void tp_dealloc(PythonClassInputDevice* self); static auto nb_bool(PythonClassInputDevice* self) -> int; static auto DetachFromPlayer(PythonClassInputDevice* self) -> PyObject*; diff --git a/src/ballistica/scene_v1/python/class/python_class_material.cc b/src/ballistica/scene_v1/python/class/python_class_material.cc index 7b19b26e8..ec2272685 100644 --- a/src/ballistica/scene_v1/python/class/python_class_material.cc +++ b/src/ballistica/scene_v1/python/class/python_class_material.cc @@ -169,14 +169,14 @@ void PythonClassMaterial::tp_dealloc(PythonClassMaterial* self) { auto PythonClassMaterial::tp_repr(PythonClassMaterial* self) -> PyObject* { BA_PYTHON_TRY; - return Py_BuildValue( - "s", - std::string("").c_str()); + return Py_BuildValue("s", std::string("") + .c_str()); BA_PYTHON_CATCH; } -auto PythonClassMaterial::tp_getattro(PythonClassMaterial* self, PyObject* attr) - -> PyObject* { +auto PythonClassMaterial::tp_getattro(PythonClassMaterial* self, + PyObject* attr) -> PyObject* { BA_PYTHON_TRY; // Assuming this will always be a str? diff --git a/src/ballistica/scene_v1/python/class/python_class_material.h b/src/ballistica/scene_v1/python/class/python_class_material.h index 217e60e6e..f4ef5c4d5 100644 --- a/src/ballistica/scene_v1/python/class/python_class_material.h +++ b/src/ballistica/scene_v1/python/class/python_class_material.h @@ -27,12 +27,12 @@ class PythonClassMaterial : public PythonClass { private: static bool s_create_empty_; static PyMethodDef tp_methods[]; - static auto tp_new(PyTypeObject* type, PyObject* args, PyObject* keywds) - -> PyObject*; + static auto tp_new(PyTypeObject* type, PyObject* args, + PyObject* keywds) -> PyObject*; static void Delete(Object::Ref* m); static void tp_dealloc(PythonClassMaterial* self); - static auto tp_getattro(PythonClassMaterial* self, PyObject* attr) - -> PyObject*; + static auto tp_getattro(PythonClassMaterial* self, + PyObject* attr) -> PyObject*; static auto tp_setattro(PythonClassMaterial* self, PyObject* attr, PyObject* val) -> int; static auto tp_repr(PythonClassMaterial* self) -> PyObject*; diff --git a/src/ballistica/scene_v1/python/class/python_class_node.cc b/src/ballistica/scene_v1/python/class/python_class_node.cc index 22524389c..50367f788 100644 --- a/src/ballistica/scene_v1/python/class/python_class_node.cc +++ b/src/ballistica/scene_v1/python/class/python_class_node.cc @@ -142,8 +142,8 @@ auto PythonClassNode::tp_repr(PythonClassNode* self) -> PyObject* { BA_PYTHON_CATCH; } -auto PythonClassNode::tp_getattro(PythonClassNode* self, PyObject* attr) - -> PyObject* { +auto PythonClassNode::tp_getattro(PythonClassNode* self, + PyObject* attr) -> PyObject* { BA_PYTHON_TRY; // Do we need to support other attr types? @@ -262,8 +262,8 @@ auto PythonClassNode::Delete(PythonClassNode* self, PyObject* args, BA_PYTHON_CATCH; } -auto PythonClassNode::HandleMessage(PythonClassNode* self, PyObject* args) - -> PyObject* { +auto PythonClassNode::HandleMessage(PythonClassNode* self, + PyObject* args) -> PyObject* { BA_PYTHON_TRY; Py_ssize_t tuple_size = PyTuple_GET_SIZE(args); if (tuple_size < 1) { @@ -297,8 +297,8 @@ auto PythonClassNode::HandleMessage(PythonClassNode* self, PyObject* args) BA_PYTHON_CATCH; } -auto PythonClassNode::AddDeathAction(PythonClassNode* self, PyObject* args) - -> PyObject* { +auto PythonClassNode::AddDeathAction(PythonClassNode* self, + PyObject* args) -> PyObject* { BA_PYTHON_TRY; PyObject* call_obj; if (!PyArg_ParseTuple(args, "O", &call_obj)) { @@ -320,8 +320,8 @@ auto PythonClassNode::AddDeathAction(PythonClassNode* self, PyObject* args) BA_PYTHON_CATCH; } -auto PythonClassNode::ConnectAttr(PythonClassNode* self, PyObject* args) - -> PyObject* { +auto PythonClassNode::ConnectAttr(PythonClassNode* self, + PyObject* args) -> PyObject* { BA_PYTHON_TRY; PyObject* dst_node_obj; Node* node = self->node_->get(); diff --git a/src/ballistica/scene_v1/python/class/python_class_node.h b/src/ballistica/scene_v1/python/class/python_class_node.h index 0765b87ef..c3c118ef8 100644 --- a/src/ballistica/scene_v1/python/class/python_class_node.h +++ b/src/ballistica/scene_v1/python/class/python_class_node.h @@ -21,23 +21,23 @@ class PythonClassNode : public PythonClass { auto GetNode(bool doraise = true) const -> Node*; private: - static auto tp_new(PyTypeObject* type, PyObject* args, PyObject* keywds) - -> PyObject*; + static auto tp_new(PyTypeObject* type, PyObject* args, + PyObject* keywds) -> PyObject*; static void tp_dealloc(PythonClassNode* self); static auto tp_repr(PythonClassNode* self) -> PyObject*; static auto tp_getattro(PythonClassNode* self, PyObject* attr) -> PyObject*; - static auto tp_setattro(PythonClassNode* self, PyObject* attr, PyObject* val) - -> int; + static auto tp_setattro(PythonClassNode* self, PyObject* attr, + PyObject* val) -> int; static auto Exists(PythonClassNode* self) -> PyObject*; static auto GetNodeType(PythonClassNode* self) -> PyObject*; static auto GetName(PythonClassNode* self) -> PyObject*; static auto GetDelegate(PythonClassNode* self, PyObject* args, PyObject* keywds) -> PyObject*; - static auto Delete(PythonClassNode* self, PyObject* args, PyObject* keywds) - -> PyObject*; + static auto Delete(PythonClassNode* self, PyObject* args, + PyObject* keywds) -> PyObject*; static auto HandleMessage(PythonClassNode* self, PyObject* args) -> PyObject*; - static auto AddDeathAction(PythonClassNode* self, PyObject* args) - -> PyObject*; + static auto AddDeathAction(PythonClassNode* self, + PyObject* args) -> PyObject*; static auto ConnectAttr(PythonClassNode* self, PyObject* args) -> PyObject*; static auto Dir(PythonClassNode* self) -> PyObject*; static auto nb_bool(PythonClassNode* self) -> int; diff --git a/src/ballistica/scene_v1/python/class/python_class_scene_collision_mesh.h b/src/ballistica/scene_v1/python/class/python_class_scene_collision_mesh.h index c6ec83a80..89e0a9a69 100644 --- a/src/ballistica/scene_v1/python/class/python_class_scene_collision_mesh.h +++ b/src/ballistica/scene_v1/python/class/python_class_scene_collision_mesh.h @@ -22,8 +22,8 @@ class PythonClassSceneCollisionMesh : public PythonClass { auto GetCollisionMesh(bool doraise = true) const -> SceneCollisionMesh*; private: - static auto tp_new(PyTypeObject* type, PyObject* args, PyObject* kwds) - -> PyObject*; + static auto tp_new(PyTypeObject* type, PyObject* args, + PyObject* kwds) -> PyObject*; static void tp_dealloc(PythonClassSceneCollisionMesh* self); static bool s_create_empty_; Object::Ref* collision_mesh_; diff --git a/src/ballistica/scene_v1/python/class/python_class_scene_data_asset.cc b/src/ballistica/scene_v1/python/class/python_class_scene_data_asset.cc index 081a8d790..6debc8a50 100644 --- a/src/ballistica/scene_v1/python/class/python_class_scene_data_asset.cc +++ b/src/ballistica/scene_v1/python/class/python_class_scene_data_asset.cc @@ -15,7 +15,7 @@ auto PythonClassSceneDataAsset::tp_repr(PythonClassSceneDataAsset* self) BA_PYTHON_TRY; auto&& m = *self->data_; return Py_BuildValue( - "s", (std::string("name() + "\"") : "(empty ref)") + ">") .c_str()); BA_PYTHON_CATCH; diff --git a/src/ballistica/scene_v1/python/class/python_class_scene_data_asset.h b/src/ballistica/scene_v1/python/class/python_class_scene_data_asset.h index 58df60e22..da164792e 100644 --- a/src/ballistica/scene_v1/python/class/python_class_scene_data_asset.h +++ b/src/ballistica/scene_v1/python/class/python_class_scene_data_asset.h @@ -24,8 +24,8 @@ class PythonClassSceneDataAsset : public PythonClass { private: static PyMethodDef tp_methods[]; static auto GetValue(PythonClassSceneDataAsset* self) -> PyObject*; - static auto tp_new(PyTypeObject* type, PyObject* args, PyObject* kwds) - -> PyObject*; + static auto tp_new(PyTypeObject* type, PyObject* args, + PyObject* kwds) -> PyObject*; static void tp_dealloc(PythonClassSceneDataAsset* self); static bool s_create_empty_; Object::Ref* data_; diff --git a/src/ballistica/scene_v1/python/class/python_class_scene_mesh.h b/src/ballistica/scene_v1/python/class/python_class_scene_mesh.h index b703b5d18..dc55f6b3e 100644 --- a/src/ballistica/scene_v1/python/class/python_class_scene_mesh.h +++ b/src/ballistica/scene_v1/python/class/python_class_scene_mesh.h @@ -23,8 +23,8 @@ class PythonClassSceneMesh : public PythonClass { private: static bool s_create_empty_; - static auto tp_new(PyTypeObject* type, PyObject* args, PyObject* kwds) - -> PyObject*; + static auto tp_new(PyTypeObject* type, PyObject* args, + PyObject* kwds) -> PyObject*; static void tp_dealloc(PythonClassSceneMesh* self); Object::Ref* mesh_; }; diff --git a/src/ballistica/scene_v1/python/class/python_class_scene_sound.h b/src/ballistica/scene_v1/python/class/python_class_scene_sound.h index 9be96fedf..b71c2b77c 100644 --- a/src/ballistica/scene_v1/python/class/python_class_scene_sound.h +++ b/src/ballistica/scene_v1/python/class/python_class_scene_sound.h @@ -24,8 +24,8 @@ class PythonClassSceneSound : public PythonClass { private: static auto Play(PythonClassSceneSound* self, PyObject* args, PyObject* keywds) -> PyObject*; - static auto tp_new(PyTypeObject* type, PyObject* args, PyObject* kwds) - -> PyObject*; + static auto tp_new(PyTypeObject* type, PyObject* args, + PyObject* kwds) -> PyObject*; static void tp_dealloc(PythonClassSceneSound* self); static PyMethodDef tp_methods[]; static bool s_create_empty_; diff --git a/src/ballistica/scene_v1/python/class/python_class_scene_texture.h b/src/ballistica/scene_v1/python/class/python_class_scene_texture.h index 2f78a1da0..e198e7cfe 100644 --- a/src/ballistica/scene_v1/python/class/python_class_scene_texture.h +++ b/src/ballistica/scene_v1/python/class/python_class_scene_texture.h @@ -23,8 +23,8 @@ class PythonClassSceneTexture : public PythonClass { private: static bool s_create_empty_; - static auto tp_new(PyTypeObject* type, PyObject* args, PyObject* keywds) - -> PyObject*; + static auto tp_new(PyTypeObject* type, PyObject* args, + PyObject* keywds) -> PyObject*; static void tp_dealloc(PythonClassSceneTexture* self); Object::Ref* texture_; }; diff --git a/src/ballistica/scene_v1/python/class/python_class_scene_timer.h b/src/ballistica/scene_v1/python/class/python_class_scene_timer.h index 51005baec..d87a475a0 100644 --- a/src/ballistica/scene_v1/python/class/python_class_scene_timer.h +++ b/src/ballistica/scene_v1/python/class/python_class_scene_timer.h @@ -18,8 +18,8 @@ class PythonClassSceneTimer : public PythonClass { static PyTypeObject type_obj; private: - static auto tp_new(PyTypeObject* type, PyObject* args, PyObject* keywds) - -> PyObject*; + static auto tp_new(PyTypeObject* type, PyObject* args, + PyObject* keywds) -> PyObject*; static void tp_dealloc(PythonClassSceneTimer* self); int timer_id_; ContextRefSceneV1* context_ref_; diff --git a/src/ballistica/scene_v1/python/class/python_class_session_data.h b/src/ballistica/scene_v1/python/class/python_class_session_data.h index a4de57bca..4a75654bd 100644 --- a/src/ballistica/scene_v1/python/class/python_class_session_data.h +++ b/src/ballistica/scene_v1/python/class/python_class_session_data.h @@ -24,8 +24,8 @@ class PythonClassSessionData : public PythonClass { static PyMethodDef tp_methods[]; static PyNumberMethods as_number_; static auto tp_repr(PythonClassSessionData* self) -> PyObject*; - static auto tp_new(PyTypeObject* type, PyObject* args, PyObject* keywds) - -> PyObject*; + static auto tp_new(PyTypeObject* type, PyObject* args, + PyObject* keywds) -> PyObject*; static void tp_dealloc(PythonClassSessionData* self); static auto Exists(PythonClassSessionData* self) -> PyObject*; diff --git a/src/ballistica/scene_v1/python/class/python_class_session_player.cc b/src/ballistica/scene_v1/python/class/python_class_session_player.cc index 91adf70ce..771ca0b27 100644 --- a/src/ballistica/scene_v1/python/class/python_class_session_player.cc +++ b/src/ballistica/scene_v1/python/class/python_class_session_player.cc @@ -328,8 +328,8 @@ auto PythonClassSessionPlayer::tp_getattro(PythonClassSessionPlayer* self, } auto PythonClassSessionPlayer::tp_setattro(PythonClassSessionPlayer* self, - PyObject* attr, PyObject* val) - -> int { + PyObject* attr, + PyObject* val) -> int { BA_PYTHON_TRY; BA_PRECONDITION(g_base->InLogicThread()); @@ -355,8 +355,8 @@ auto PythonClassSessionPlayer::tp_setattro(PythonClassSessionPlayer* self, } auto PythonClassSessionPlayer::GetName(PythonClassSessionPlayer* self, - PyObject* args, PyObject* keywds) - -> PyObject* { + PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; assert(g_base->InLogicThread()); int full = false; @@ -387,8 +387,8 @@ auto PythonClassSessionPlayer::Exists(PythonClassSessionPlayer* self) } auto PythonClassSessionPlayer::SetName(PythonClassSessionPlayer* self, - PyObject* args, PyObject* keywds) - -> PyObject* { + PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; assert(g_base->InLogicThread()); PyObject* name_obj; @@ -429,8 +429,8 @@ auto PythonClassSessionPlayer::ResetInput(PythonClassSessionPlayer* self) } auto PythonClassSessionPlayer::AssignInputCall(PythonClassSessionPlayer* self, - PyObject* args, PyObject* keywds) - -> PyObject* { + PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; assert(g_base->InLogicThread()); PyObject* input_type_obj; @@ -521,8 +521,8 @@ auto PythonClassSessionPlayer::GetV1AccountID(PythonClassSessionPlayer* self) } auto PythonClassSessionPlayer::SetData(PythonClassSessionPlayer* self, - PyObject* args, PyObject* keywds) - -> PyObject* { + PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; assert(g_base->InLogicThread()); PyObject* team_obj; @@ -567,8 +567,8 @@ auto PythonClassSessionPlayer::GetIconInfo(PythonClassSessionPlayer* self) } auto PythonClassSessionPlayer::SetIconInfo(PythonClassSessionPlayer* self, - PyObject* args, PyObject* keywds) - -> PyObject* { + PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; assert(g_base->InLogicThread()); PyObject* texture_name_obj; @@ -602,8 +602,8 @@ auto PythonClassSessionPlayer::SetIconInfo(PythonClassSessionPlayer* self, } auto PythonClassSessionPlayer::SetActivity(PythonClassSessionPlayer* self, - PyObject* args, PyObject* keywds) - -> PyObject* { + PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; assert(g_base->InLogicThread()); PyObject* activity_obj; @@ -628,8 +628,8 @@ auto PythonClassSessionPlayer::SetActivity(PythonClassSessionPlayer* self, } auto PythonClassSessionPlayer::SetNode(PythonClassSessionPlayer* self, - PyObject* args, PyObject* keywds) - -> PyObject* { + PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; assert(g_base->InLogicThread()); PyObject* node_obj; diff --git a/src/ballistica/scene_v1/python/class/python_class_session_player.h b/src/ballistica/scene_v1/python/class/python_class_session_player.h index dbe926767..64475655f 100644 --- a/src/ballistica/scene_v1/python/class/python_class_session_player.h +++ b/src/ballistica/scene_v1/python/class/python_class_session_player.h @@ -24,11 +24,11 @@ class PythonClassSessionPlayer : public PythonClass { static bool s_create_empty_; static PyMethodDef tp_methods[]; static auto tp_repr(PythonClassSessionPlayer* self) -> PyObject*; - static auto tp_new(PyTypeObject* type, PyObject* args, PyObject* keywds) - -> PyObject*; + static auto tp_new(PyTypeObject* type, PyObject* args, + PyObject* keywds) -> PyObject*; static void tp_dealloc(PythonClassSessionPlayer* self); - static auto tp_getattro(PythonClassSessionPlayer* self, PyObject* attr) - -> PyObject*; + static auto tp_getattro(PythonClassSessionPlayer* self, + PyObject* attr) -> PyObject*; static auto tp_setattro(PythonClassSessionPlayer* self, PyObject* attr, PyObject* val) -> int; static auto GetName(PythonClassSessionPlayer* self, PyObject* args, diff --git a/src/ballistica/scene_v1/python/methods/python_methods_assets.cc b/src/ballistica/scene_v1/python/methods/python_methods_assets.cc index 685a8b421..52bc96d92 100644 --- a/src/ballistica/scene_v1/python/methods/python_methods_assets.cc +++ b/src/ballistica/scene_v1/python/methods/python_methods_assets.cc @@ -21,8 +21,8 @@ namespace ballistica::scene_v1 { // ------------------------------- gettexture ---------------------------------- -static auto PyGetTexture(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyGetTexture(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; const char* name; static const char* kwlist[] = {"name", nullptr}; @@ -80,8 +80,8 @@ static PyMethodDef PyGetPackageTextureDef = { // ------------------------------- getsound ------------------------------------ -static auto PyGetSound(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyGetSound(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; const char* name; static const char* kwlist[] = {"name", nullptr}; @@ -110,8 +110,8 @@ static PyMethodDef PyGetSoundDef = { // --------------------------- get_package_sound ------------------------------- -static auto PyGetPackageSound(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyGetPackageSound(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; const char* name; PyObject* package_obj; @@ -139,8 +139,8 @@ static PyMethodDef PyGetPackageSoundDef = { // ------------------------------- getdata ------------------------------------- -static auto PyGetData(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyGetData(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; const char* name; static const char* kwlist[] = {"name", nullptr}; @@ -169,8 +169,8 @@ static PyMethodDef PyGetDataDef = { // --------------------------- get_package_data -------------------------------- -static auto PyGetPackageData(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyGetPackageData(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; const char* name; PyObject* package_obj; @@ -198,8 +198,8 @@ static PyMethodDef PyGetPackageDataDef = { // -------------------------------- getmesh ------------------------------------ -static auto PyGetMesh(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyGetMesh(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; const char* name; static const char* kwlist[] = {"name", nullptr}; @@ -228,8 +228,8 @@ static PyMethodDef PyGetMeshDef = { // ---------------------------- get_package_mesh ------------------------------- -static auto PyGetPackageMesh(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyGetPackageMesh(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; const char* name; PyObject* package_obj; @@ -258,8 +258,8 @@ static PyMethodDef PyGetPackageMeshDef = { // ----------------------------- getcollisionmesh ------------------------------ -static auto PyGetCollisionMesh(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyGetCollisionMesh(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; const char* name; static const char* kwlist[] = {"name", nullptr}; diff --git a/src/ballistica/scene_v1/python/methods/python_methods_input.cc b/src/ballistica/scene_v1/python/methods/python_methods_input.cc index b58b56884..0aaf1945a 100644 --- a/src/ballistica/scene_v1/python/methods/python_methods_input.cc +++ b/src/ballistica/scene_v1/python/methods/python_methods_input.cc @@ -17,8 +17,8 @@ namespace ballistica::scene_v1 { // ------------------- get_configurable_game_controllers ----------------------- -static auto PyGetConfigurableGameControllers(PyObject* self, PyObject* args) - -> PyObject* { +static auto PyGetConfigurableGameControllers(PyObject* self, + PyObject* args) -> PyObject* { BA_PYTHON_TRY; std::vector gamepads = g_base->input->GetConfigurableGameControllers(); @@ -52,8 +52,8 @@ static PyMethodDef PyGetConfigurableGameControllersDef = { // ------------------------ have_touchscreen_input ----------------------------- -static auto PyHaveTouchScreenInput(PyObject* self, PyObject* args) - -> PyObject* { +static auto PyHaveTouchScreenInput(PyObject* self, + PyObject* args) -> PyObject* { BA_PYTHON_TRY; if (g_base->touch_input) { Py_RETURN_TRUE; @@ -77,8 +77,8 @@ static PyMethodDef PyHaveTouchScreenInputDef = { // ------------------------- set_touchscreen_editing --------------------------- -static auto PySetTouchscreenEditing(PyObject* self, PyObject* args) - -> PyObject* { +static auto PySetTouchscreenEditing(PyObject* self, + PyObject* args) -> PyObject* { BA_PYTHON_TRY; int editing; if (!PyArg_ParseTuple(args, "p", &editing)) { @@ -103,8 +103,8 @@ static PyMethodDef PySetTouchscreenEditingDef = { // --------------------- capture_game_controller_input ------------------------- -static auto PyCaptureGameControllerInput(PyObject* self, PyObject* args) - -> PyObject* { +static auto PyCaptureGameControllerInput(PyObject* self, + PyObject* args) -> PyObject* { BA_PYTHON_TRY; assert(g_base->InLogicThread()); PyObject* obj; @@ -131,8 +131,8 @@ static PyMethodDef PyCaptureGameControllerInputDef = { // --------------------- release_game_controller_input ------------------------- -static auto PyReleaseGameControllerInput(PyObject* self, PyObject* args) - -> PyObject* { +static auto PyReleaseGameControllerInput(PyObject* self, + PyObject* args) -> PyObject* { BA_PYTHON_TRY; assert(g_base->InLogicThread()); g_scene_v1->python->ReleaseJoystickInputCapture(); @@ -154,8 +154,8 @@ static PyMethodDef PyReleaseGameControllerInputDef = { // ------------------------ capture_keyboard_input ----------------------------- -static auto PyCaptureKeyboardInput(PyObject* self, PyObject* args) - -> PyObject* { +static auto PyCaptureKeyboardInput(PyObject* self, + PyObject* args) -> PyObject* { BA_PYTHON_TRY; assert(g_base->InLogicThread()); assert(g_scene_v1); @@ -183,8 +183,8 @@ static PyMethodDef PyCaptureKeyboardInputDef = { // ------------------------- release_keyboard_input ---------------------------- -static auto PyReleaseKeyboardInput(PyObject* self, PyObject* args) - -> PyObject* { +static auto PyReleaseKeyboardInput(PyObject* self, + PyObject* args) -> PyObject* { BA_PYTHON_TRY; assert(g_base->InLogicThread()); assert(g_scene_v1); @@ -252,8 +252,8 @@ static PyMethodDef PyGetMainUIInputDeviceDef = { // ---------------------------- getinputdevice --------------------------------- -static auto PyGetInputDevice(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyGetInputDevice(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; assert(g_base->InLogicThread()); const char* name; diff --git a/src/ballistica/scene_v1/python/methods/python_methods_networking.cc b/src/ballistica/scene_v1/python/methods/python_methods_networking.cc index e0c5381ee..b03030f76 100644 --- a/src/ballistica/scene_v1/python/methods/python_methods_networking.cc +++ b/src/ballistica/scene_v1/python/methods/python_methods_networking.cc @@ -4,6 +4,7 @@ #include #include +#include #include #include "ballistica/base/assets/assets.h" @@ -318,8 +319,8 @@ static PyMethodDef PySetAuthenticateClientsDef = { // ------------------------------- set_admins ---------------------------------- -static auto PySetAdmins(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PySetAdmins(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; PyObject* admins_obj; static const char* kwlist[] = {"admins", nullptr}; @@ -350,6 +351,72 @@ static PyMethodDef PySetAdminsDef = { "(internal)", }; +// --------------------------- set_admin_tokens ------------------------------ + +static auto PySetAdminTokens(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { + BA_PYTHON_TRY; + PyObject* tokens_obj; + static const char* kwlist[] = {"tokens", nullptr}; + if (!PyArg_ParseTupleAndKeywords(args, keywds, "O", + const_cast(kwlist), &tokens_obj)) { + return nullptr; + } + auto* appmode = classic::ClassicAppMode::GetActiveOrThrow(); + + // This part is different: we use the admin_tokens_ variable. + // We're converting a Python list of strings to a C++ set of strings. + auto tokens = Python::GetStrings(tokens_obj); + std::unordered_set tokenset; + for (auto&& token : tokens) { + tokenset.insert(token); + } + appmode->set_admin_tokens(tokenset); // We'll need to create this setter. + + Py_RETURN_NONE; + BA_PYTHON_CATCH; +} + +static PyMethodDef PySetAdminTokensDef = { + "set_admin_tokens", // name + (PyCFunction)PySetAdminTokens, // method + METH_VARARGS | METH_KEYWORDS, // flags + "set_admin_tokens(tokens: list[str]) -> None\n" + "\n" + "(internal)", +}; + +// ------------------------ set_enable_admins_kick --------------------------- + +static auto PySetEnableAdminsKick(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { + BA_PYTHON_TRY; + int enable; + static const char* kwlist[] = {"enable", nullptr}; + if (!PyArg_ParseTupleAndKeywords(args, keywds, "p", + const_cast(kwlist), &enable)) { + return nullptr; + } + assert(g_base->logic); + + if (auto* appmode{classic::ClassicAppMode::GetActiveOrWarn()}) { + appmode->set_admins_kick_enabled(static_cast(enable)); + } + + Py_RETURN_NONE; + BA_PYTHON_CATCH; +} + +static PyMethodDef PySetEnableAdminsKickDef = { + "set_enable_admins_kick", // name + (PyCFunction)PySetEnableAdminsKick, // method + METH_VARARGS | METH_KEYWORDS, // flags + + "set_enable_admins_kick(enable: bool) -> None\n" + "\n" + "(internal)", +}; + // --------------------- set_enable_default_kick_voting ------------------------ static auto PySetEnableDefaultKickVoting(PyObject* self, PyObject* args, @@ -383,8 +450,8 @@ static PyMethodDef PySetEnableDefaultKickVotingDef = { // --------------------------- connect_to_party -------------------------------- -static auto PyConnectToParty(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyConnectToParty(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; std::string address; PyObject* address_obj; @@ -594,8 +661,8 @@ static PyMethodDef PyDisconnectFromHostDef = { // --------------------------- disconnect_client ------------------------------- -static auto PyDisconnectClient(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyDisconnectClient(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; int client_id; int ban_time = 300; // Old default before we exposed this. @@ -703,8 +770,8 @@ static PyMethodDef PyGetGamePortDef = { // ------------------------ set_master_server_source --------------------------- -static auto PySetMasterServerSource(PyObject* self, PyObject* args) - -> PyObject* { +static auto PySetMasterServerSource(PyObject* self, + PyObject* args) -> PyObject* { BA_PYTHON_TRY; int source; if (!PyArg_ParseTuple(args, "i", &source)) return nullptr; @@ -730,8 +797,8 @@ static PyMethodDef PySetMasterServerSourceDef = { // ----------------------------- host_scan_cycle ------------------------------- -static auto PyHostScanCycle(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyHostScanCycle(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; auto* appmode = classic::ClassicAppMode::GetActiveOrThrow(); appmode->HostScanCycle(); @@ -761,8 +828,8 @@ static PyMethodDef PyHostScanCycleDef = { // ---------------------------- end_host_scanning ------------------------------ -static auto PyEndHostScanning(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyEndHostScanning(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; auto* appmode = classic::ClassicAppMode::GetActiveOrThrow(); appmode->EndHostScanning(); @@ -807,8 +874,8 @@ static PyMethodDef PyHaveConnectedClientsDef = { // ------------------------------ chatmessage ---------------------------------- -static auto PyChatMessage(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyChatMessage(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; std::string message; PyObject* message_obj; @@ -859,8 +926,8 @@ static PyMethodDef PyChatMessageDef = { // --------------------------- get_chat_messages ------------------------------- -static auto PyGetChatMessages(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyGetChatMessages(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; BA_PRECONDITION(g_base->InLogicThread()); @@ -908,6 +975,8 @@ auto PythonMethodsNetworking::GetMethods() -> std::vector { PySetPublicPartyPublicAddressIPV6Def, PySetAuthenticateClientsDef, PySetAdminsDef, + PySetAdminTokensDef, + PySetEnableAdminsKickDef, PySetEnableDefaultKickVotingDef, PySetPublicPartyMaxSizeDef, PySetPublicPartyQueueEnabledDef, diff --git a/src/ballistica/scene_v1/python/methods/python_methods_scene.cc b/src/ballistica/scene_v1/python/methods/python_methods_scene.cc index c7ea8fe4c..d663b2884 100644 --- a/src/ballistica/scene_v1/python/methods/python_methods_scene.cc +++ b/src/ballistica/scene_v1/python/methods/python_methods_scene.cc @@ -44,8 +44,8 @@ namespace ballistica::scene_v1 { // --------------------------------- time -------------------------------------- -static auto PyTime(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyTime(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; static const char* kwlist[] = {nullptr}; @@ -80,8 +80,8 @@ static PyMethodDef PyTimeDef = { // --------------------------------- timer ------------------------------------- -static auto PyTimer(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyTimer(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; assert(g_base->InLogicThread()); @@ -154,8 +154,8 @@ static PyMethodDef PyTimerDef = { // ----------------------------- basetime ----------------------------------- -static auto PyBaseTime(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyBaseTime(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; static const char* kwlist[] = {nullptr}; @@ -190,8 +190,8 @@ static PyMethodDef PyBaseTimeDef = { // --------------------------------- timer ------------------------------------- -static auto PyBaseTimer(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyBaseTimer(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; assert(g_base->InLogicThread()); @@ -260,8 +260,8 @@ static PyMethodDef PyBaseTimerDef = { // ------------------------------- getsession ---------------------------------- -static auto PyGetSession(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyGetSession(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; int raise = true; static const char* kwlist[] = {"doraise", nullptr}; @@ -297,8 +297,8 @@ static PyMethodDef PyGetSessionDef = { // --------------------------- new_host_session -------------------------------- -static auto PyNewHostSession(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyNewHostSession(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; const char* benchmark_type_str = nullptr; static const char* kwlist[] = {"sessiontype", "benchmark_type", nullptr}; @@ -339,8 +339,8 @@ static PyMethodDef PyNewHostSessionDef = { // -------------------------- new_replay_session ------------------------------- -static auto PyNewReplaySession(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyNewReplaySession(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; std::string file_name; PyObject* file_name_obj; @@ -369,8 +369,8 @@ static PyMethodDef PyNewReplaySessionDef = { // ------------------------------ is_in_replay --------------------------------- -static auto PyIsInReplay(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyIsInReplay(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; BA_PRECONDITION(g_base->InLogicThread()); static const char* kwlist[] = {nullptr}; @@ -400,8 +400,8 @@ static PyMethodDef PyIsInReplayDef = { // -------------------------- register_session-------- ------------------------- -static auto PyRegisterSession(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyRegisterSession(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; assert(g_base->InLogicThread()); PyObject* session_obj; @@ -435,8 +435,8 @@ static PyMethodDef PyRegisterSessionDef = { // --------------------------- register_activity ------------------------------- -static auto PyRegisterActivity(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyRegisterActivity(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; assert(g_base->InLogicThread()); PyObject* activity_obj; @@ -508,8 +508,8 @@ static PyMethodDef PyGetForegroundHostSessionDef = { // ----------------------------- newactivity ----------------------------------- -static auto PyNewActivity(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyNewActivity(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; static const char* kwlist[] = {"activity_type", "settings", nullptr}; @@ -565,8 +565,8 @@ static PyMethodDef PyNewActivityDef = { // ----------------------------- getactivity ----------------------------------- -static auto PyGetActivity(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyGetActivity(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; int raise = true; static const char* kwlist[] = {"doraise", nullptr}; @@ -621,8 +621,8 @@ static PyMethodDef PyGetActivityDef = { // -------------------------- broadcastmessage --------------------------------- -static auto PyBroadcastMessage(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyBroadcastMessage(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; const char* message = nullptr; PyObject* color_obj = Py_None; @@ -796,8 +796,8 @@ static PyMethodDef PyBroadcastMessageDef = { // ------------------------------- newnode ------------------------------------- -static auto PyNewNode(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyNewNode(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; Node* n = SceneV1Python::DoNewNode(args, keywds); if (!n) { @@ -1025,8 +1025,8 @@ static PyMethodDef PyGetCollisionInfoDef = { // ------------------------------ camerashake ---------------------------------- -static auto PyCameraShake(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyCameraShake(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; assert(g_base->InLogicThread()); float intensity = 1.0f; @@ -1074,8 +1074,8 @@ static PyMethodDef PyCameraShakeDef = { // -------------------------------- emitfx ------------------------------------- -static auto PyEmitFx(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyEmitFx(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; static const char* kwlist[] = {"position", "velocity", "count", "scale", "spread", "chunk_type", @@ -1288,8 +1288,8 @@ static PyMethodDef PyGetForegroundHostActivityDef = { // --------------------------- get_game_roster --------------------------------- -static auto PyGetGameRoster(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyGetGameRoster(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; BA_PRECONDITION(g_base->InLogicThread()); static const char* kwlist[] = {nullptr}; @@ -1395,8 +1395,8 @@ static PyMethodDef PyGetGameRosterDef = { // ----------------------- set_debug_speed_exponent ---------------------------- -static auto PySetDebugSpeedExponent(PyObject* self, PyObject* args) - -> PyObject* { +static auto PySetDebugSpeedExponent(PyObject* self, + PyObject* args) -> PyObject* { BA_PYTHON_TRY; int speed; if (!PyArg_ParseTuple(args, "i", &speed)) { @@ -1434,8 +1434,8 @@ static PyMethodDef PySetDebugSpeedExponentDef = { // ----------------------- get_replay_speed_exponent --------------------------- -static auto PyGetReplaySpeedExponent(PyObject* self, PyObject* args) - -> PyObject* { +static auto PyGetReplaySpeedExponent(PyObject* self, + PyObject* args) -> PyObject* { BA_PYTHON_TRY; auto* appmode = classic::ClassicAppMode::GetActiveOrThrow(); return PyLong_FromLong(appmode->replay_speed_exponent()); @@ -1457,8 +1457,8 @@ static PyMethodDef PyGetReplaySpeedExponentDef = { // ------------------------ set_replay_speed_exponent -------------------------- -static auto PySetReplaySpeedExponent(PyObject* self, PyObject* args) - -> PyObject* { +static auto PySetReplaySpeedExponent(PyObject* self, + PyObject* args) -> PyObject* { BA_PYTHON_TRY; int speed; if (!PyArg_ParseTuple(args, "i", &speed)) { @@ -1633,8 +1633,8 @@ static PyMethodDef PyGetRandomNamesDef = { // -------------------------------- ls_objects --------------------------------- -static auto PyLsObjects(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyLsObjects(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; Object::LsObjects(); Py_RETURN_NONE; @@ -1656,8 +1656,8 @@ static PyMethodDef PyLsObjectsDef = { // --------------------------- ls_input_devices -------------------------------- -static auto PyLsInputDevices(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyLsInputDevices(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; g_base->input->LsInputDevices(); Py_RETURN_NONE; @@ -1676,8 +1676,8 @@ static PyMethodDef PyLsInputDevicesDef = { // -------------------------- set_internal_music ------------------------------- -static auto PySetInternalMusic(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PySetInternalMusic(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; BA_PRECONDITION(g_base->InLogicThread()); PyObject* music_obj; diff --git a/src/ballistica/scene_v1/python/scene_v1_python.cc b/src/ballistica/scene_v1/python/scene_v1_python.cc index 340dca120..def7a5844 100644 --- a/src/ballistica/scene_v1/python/scene_v1_python.cc +++ b/src/ballistica/scene_v1/python/scene_v1_python.cc @@ -436,8 +436,8 @@ auto SceneV1Python::DoNewNode(PyObject* args, PyObject* keywds) -> Node* { // Return the node attr as a PyObject, or nullptr if the node doesn't have that // attr. -auto SceneV1Python::GetNodeAttr(Node* node, const char* attr_name) - -> PyObject* { +auto SceneV1Python::GetNodeAttr(Node* node, + const char* attr_name) -> PyObject* { assert(node); NodeAttribute attr = node->GetAttribute(attr_name); switch (attr.type()) { @@ -986,9 +986,8 @@ auto SceneV1Python::GetPySceneSounds(PyObject* o) -> std::vector { return vals; } -auto SceneV1Python::GetPySceneCollisionMesh(PyObject* o, bool allow_empty_ref, - bool allow_none) - -> SceneCollisionMesh* { +auto SceneV1Python::GetPySceneCollisionMesh( + PyObject* o, bool allow_empty_ref, bool allow_none) -> SceneCollisionMesh* { assert(Python::HaveGIL()); BA_PRECONDITION_FATAL(o != nullptr); @@ -1116,8 +1115,8 @@ auto SceneV1Python::GetPySceneDataAsset(PyObject* o, bool allow_empty_ref, PyExcType::kType); } -auto SceneV1Python::FilterChatMessage(std::string* message, int client_id) - -> bool { +auto SceneV1Python::FilterChatMessage(std::string* message, + int client_id) -> bool { assert(message); base::ScopedSetContext ssc(nullptr); @@ -1439,9 +1438,8 @@ auto SceneV1Python::HandleCapturedKeyRelease(const SDL_Keysym& keysym) -> bool { return true; } -auto SceneV1Python::HandleCapturedJoystickEvent(const SDL_Event& event, - base::InputDevice* input_device) - -> bool { +auto SceneV1Python::HandleCapturedJoystickEvent( + const SDL_Event& event, base::InputDevice* input_device) -> bool { assert(g_base->InLogicThread()); assert(input_device != nullptr); if (!joystick_capture_call_.exists()) { diff --git a/src/ballistica/scene_v1/python/scene_v1_python.h b/src/ballistica/scene_v1/python/scene_v1_python.h index a2f09bb6f..01a4ef0b0 100644 --- a/src/ballistica/scene_v1/python/scene_v1_python.h +++ b/src/ballistica/scene_v1/python/scene_v1_python.h @@ -73,8 +73,8 @@ class SceneV1Python { /// Given an asset-package python object and a media name, verify /// that the asset-package is valid in the current context_ref and return /// its fully qualified name if so. Throw an Exception if not. - auto ValidatedPackageAssetName(PyObject* package, const char* name) - -> std::string; + auto ValidatedPackageAssetName(PyObject* package, + const char* name) -> std::string; void ReloadHooks(); @@ -106,9 +106,8 @@ class SceneV1Python { const auto& objs() { return objs_; } private: - static auto HandleCapturedJoystickEventCall(const SDL_Event& event, - base::InputDevice* input_device) - -> bool; + static auto HandleCapturedJoystickEventCall( + const SDL_Event& event, base::InputDevice* input_device) -> bool; static auto HandleCapturedKeyPressCall(const SDL_Keysym& keysym) -> bool; static auto HandleCapturedKeyReleaseCall(const SDL_Keysym& keysym) -> bool; auto HandleCapturedJoystickEvent(const SDL_Event& event, diff --git a/src/ballistica/scene_v1/support/host_session.h b/src/ballistica/scene_v1/support/host_session.h index def8b782e..abff5a877 100644 --- a/src/ballistica/scene_v1/support/host_session.h +++ b/src/ballistica/scene_v1/support/host_session.h @@ -58,8 +58,8 @@ class HostSession : public Session { } // Given an activity python type, instantiate a new activity // and return a new reference. - auto NewHostActivity(PyObject* activity_type_obj, PyObject* settings_obj) - -> PyObject*; + auto NewHostActivity(PyObject* activity_type_obj, + PyObject* settings_obj) -> PyObject*; void DestroyHostActivity(HostActivity* a); void RemovePlayer(Player* player); void RequestPlayer(SceneV1InputDeviceDelegate* device); @@ -99,8 +99,8 @@ class HostSession : public Session { // New HostActivities should call this in their constructors. void AddHostActivity(HostActivity* sgc); - auto GetUnusedPlayerName(Player* p, const std::string& base_name) - -> std::string; + auto GetUnusedPlayerName(Player* p, + const std::string& base_name) -> std::string; auto ContextAllowsDefaultTimerTypes() -> bool override; auto TimeToNextEvent() -> std::optional override; diff --git a/src/ballistica/scene_v1/support/session_stream.h b/src/ballistica/scene_v1/support/session_stream.h index 22d085e9f..552d61ae8 100644 --- a/src/ballistica/scene_v1/support/session_stream.h +++ b/src/ballistica/scene_v1/support/session_stream.h @@ -122,8 +122,8 @@ class SessionStream : public Object, public ClientControllerInterface { template auto GetPointerCount(const std::vector& vec) -> size_t; template - auto GetFreeIndex(std::vector* vec, std::vector* free_indices) - -> size_t; + auto GetFreeIndex(std::vector* vec, + std::vector* free_indices) -> size_t; template void Add(T* val, std::vector* vec, std::vector* free_indices); template diff --git a/src/ballistica/shared/ballistica.cc b/src/ballistica/shared/ballistica.cc index 21f5e55a7..3f75e6dda 100644 --- a/src/ballistica/shared/ballistica.cc +++ b/src/ballistica/shared/ballistica.cc @@ -44,7 +44,7 @@ auto main(int argc, char** argv) -> int { namespace ballistica { // These are set automatically via script; don't modify them here. -const int kEngineBuildNumber = 22599; +const int kEngineBuildNumber = 22604; const char* kEngineVersion = "1.7.54"; const int kEngineApiVersion = 9; diff --git a/src/ballistica/shared/foundation/event_loop.cc b/src/ballistica/shared/foundation/event_loop.cc index b4917057c..af2d5a8cb 100644 --- a/src/ballistica/shared/foundation/event_loop.cc +++ b/src/ballistica/shared/foundation/event_loop.cc @@ -584,8 +584,8 @@ auto EventLoop::AreEventLoopsSuspended() -> bool { return g_core->event_loops_suspended(); } -auto EventLoop::NewTimer(microsecs_t length, bool repeat, Runnable* runnable) - -> Timer* { +auto EventLoop::NewTimer(microsecs_t length, bool repeat, + Runnable* runnable) -> Timer* { assert(g_core); assert(ThreadIsCurrent()); assert(Object::IsValidManagedObject(runnable)); diff --git a/src/ballistica/shared/foundation/fatal_error.cc b/src/ballistica/shared/foundation/fatal_error.cc index bd7ea3a89..ff819ebca 100644 --- a/src/ballistica/shared/foundation/fatal_error.cc +++ b/src/ballistica/shared/foundation/fatal_error.cc @@ -199,9 +199,8 @@ void FatalErrorHandling::DoBlockingFatalErrorDialog( } } -auto FatalErrorHandling::HandleFatalError(bool exit_cleanly, - bool in_top_level_exception_handler) - -> bool { +auto FatalErrorHandling::HandleFatalError( + bool exit_cleanly, bool in_top_level_exception_handler) -> bool { // Give the platform the opportunity to completely override our handling. if (g_core) { auto handled = g_core->platform->HandleFatalError( diff --git a/src/ballistica/shared/foundation/macros.cc b/src/ballistica/shared/foundation/macros.cc index b7073f33f..2ac510b1b 100644 --- a/src/ballistica/shared/foundation/macros.cc +++ b/src/ballistica/shared/foundation/macros.cc @@ -156,8 +156,8 @@ void MacroLogPythonTrace(core::CoreFeatureSet* corefs, const std::string& msg) { corefs->logging->Log(LogName::kBa, LogLevel::kError, msg); } -auto MacroPathFilter(core::CoreFeatureSet* corefs, const char* filename) - -> const char* { +auto MacroPathFilter(core::CoreFeatureSet* corefs, + const char* filename) -> const char* { assert(corefs); // If we've got a build_src_dir set and filename starts with it, skip past // it. diff --git a/src/ballistica/shared/foundation/macros.h b/src/ballistica/shared/foundation/macros.h index da0b29f81..2b94379d3 100644 --- a/src/ballistica/shared/foundation/macros.h +++ b/src/ballistica/shared/foundation/macros.h @@ -124,8 +124,8 @@ namespace ballistica { // Support functions used by some of our macros; not intended to be used // directly. -auto MacroPathFilter(core::CoreFeatureSet* corefs, const char* filename) - -> const char*; +auto MacroPathFilter(core::CoreFeatureSet* corefs, + const char* filename) -> const char*; auto MacroFunctionTimerStartTime() -> millisecs_t; void MacroFunctionTimerEnd(core::CoreFeatureSet* corefs, millisecs_t starttime, millisecs_t time, const char* funcname); diff --git a/src/ballistica/shared/generic/base64.cc b/src/ballistica/shared/generic/base64.cc index 1912d36ca..e4248b795 100644 --- a/src/ballistica/shared/generic/base64.cc +++ b/src/ballistica/shared/generic/base64.cc @@ -104,8 +104,8 @@ auto base64_encode(const unsigned char* bytes_to_encode, unsigned int in_len, return ret; } -auto base64_decode(const std::string& encoded_string, bool urlsafe) - -> std::string { +auto base64_decode(const std::string& encoded_string, + bool urlsafe) -> std::string { int in_len = static_cast(encoded_string.size()); int i = 0; // int j = 0; diff --git a/src/ballistica/shared/generic/base64.h b/src/ballistica/shared/generic/base64.h index 0df028242..2faab4599 100644 --- a/src/ballistica/shared/generic/base64.h +++ b/src/ballistica/shared/generic/base64.h @@ -7,8 +7,8 @@ namespace ballistica { -auto base64_encode(const unsigned char*, unsigned int len, bool urlsafe = false) - -> std::string; +auto base64_encode(const unsigned char*, unsigned int len, + bool urlsafe = false) -> std::string; auto base64_decode(const std::string& s, bool urlsafe = false) -> std::string; } // namespace ballistica diff --git a/src/ballistica/shared/generic/timer_list.h b/src/ballistica/shared/generic/timer_list.h index a954b759b..f2ad5408f 100644 --- a/src/ballistica/shared/generic/timer_list.h +++ b/src/ballistica/shared/generic/timer_list.h @@ -22,8 +22,8 @@ class TimerList { // Create a timer with provided runnable. auto NewTimer(TimerMedium current_time, TimerMedium length, - TimerMedium offset, int repeat_count, Runnable* runnable) - -> Timer*; + TimerMedium offset, int repeat_count, + Runnable* runnable) -> Timer*; // Return a timer by its id, or nullptr if the timer no longer exists. auto GetTimer(int id) -> Timer*; diff --git a/src/ballistica/shared/generic/utils.cc b/src/ballistica/shared/generic/utils.cc index 8aa71bfa4..4e391151e 100644 --- a/src/ballistica/shared/generic/utils.cc +++ b/src/ballistica/shared/generic/utils.cc @@ -339,8 +339,8 @@ auto Utils::UTF8FromUnicode(std::vector unichars) -> std::string { return buffer.data(); } -auto Utils::UnicodeFromUTF8(const std::string& s_in, const char* loc) - -> std::vector { +auto Utils::UnicodeFromUTF8(const std::string& s_in, + const char* loc) -> std::vector { std::string s = GetValidUTF8(s_in.c_str(), loc); // worst case every char is a character (plus trailing 0) std::vector vals(s.size() + 1); diff --git a/src/ballistica/shared/generic/utils.h b/src/ballistica/shared/generic/utils.h index 6754250ac..85016b367 100644 --- a/src/ballistica/shared/generic/utils.h +++ b/src/ballistica/shared/generic/utils.h @@ -48,8 +48,8 @@ class Utils { /// control characters). static auto StripNonAsciiFromUTF8(const std::string& s) -> std::string; - static auto UnicodeFromUTF8(const std::string& s, const char* loc) - -> std::vector; + static auto UnicodeFromUTF8(const std::string& s, + const char* loc) -> std::vector; static auto UTF8FromUnicode(std::vector unichars) -> std::string; static auto UTF8FromUnicodeChar(uint32_t c) -> std::string; static auto UTF8StringLength(const char* val) -> int; diff --git a/src/ballistica/shared/math/matrix44f.cc b/src/ballistica/shared/math/matrix44f.cc index 46d08c3e7..f2e093a49 100644 --- a/src/ballistica/shared/math/matrix44f.cc +++ b/src/ballistica/shared/math/matrix44f.cc @@ -57,8 +57,8 @@ auto Matrix44fRotate(float azimuth, float elevation) -> Matrix44f { return rotate; } -auto Matrix44fOrient(const Vector3f& x, const Vector3f& y, const Vector3f& z) - -> Matrix44f { +auto Matrix44fOrient(const Vector3f& x, const Vector3f& y, + const Vector3f& z) -> Matrix44f { Matrix44f orient{kMatrix44fIdentity}; orient.set(0, 0, x.x); @@ -76,8 +76,8 @@ auto Matrix44fOrient(const Vector3f& x, const Vector3f& y, const Vector3f& z) return orient; } -auto Matrix44fOrient(const Vector3f& direction, const Vector3f& up) - -> Matrix44f { +auto Matrix44fOrient(const Vector3f& direction, + const Vector3f& up) -> Matrix44f { assert(direction.LengthSquared() > 0.0f); assert(up.LengthSquared() > 0.0f); diff --git a/src/ballistica/shared/math/matrix44f.h b/src/ballistica/shared/math/matrix44f.h index e8bcd52c2..20b321b0c 100644 --- a/src/ballistica/shared/math/matrix44f.h +++ b/src/ballistica/shared/math/matrix44f.h @@ -161,8 +161,8 @@ inline auto Matrix44fTranslate(const Vector3f& trans) -> Matrix44f { return translate; } -inline auto Matrix44fTranslate(const float x, const float y, const float z) - -> Matrix44f { +inline auto Matrix44fTranslate(const float x, const float y, + const float z) -> Matrix44f { Matrix44f translate{kMatrix44fIdentity}; translate.set(3, 0, x); translate.set(3, 1, y); @@ -188,12 +188,12 @@ inline auto Matrix44fScale(const Vector3f& sf) -> Matrix44f { auto Matrix44fRotate(const Vector3f& axis, float angle) -> Matrix44f; auto Matrix44fRotate(float azimuth, float elevation) -> Matrix44f; -auto Matrix44fOrient(const Vector3f& x, const Vector3f& y, const Vector3f& z) - -> Matrix44f; +auto Matrix44fOrient(const Vector3f& x, const Vector3f& y, + const Vector3f& z) -> Matrix44f; // Note: direction and up need to be perpendicular and normalized here. -auto Matrix44fOrient(const Vector3f& direction, const Vector3f& up) - -> Matrix44f; +auto Matrix44fOrient(const Vector3f& direction, + const Vector3f& up) -> Matrix44f; auto Matrix44fFrustum(float left, float right, float bottom, float top, float near, float far) -> Matrix44f; diff --git a/src/ballistica/shared/networking/sockaddr.cc b/src/ballistica/shared/networking/sockaddr.cc index b23ca8e0a..b32969327 100644 --- a/src/ballistica/shared/networking/sockaddr.cc +++ b/src/ballistica/shared/networking/sockaddr.cc @@ -11,7 +11,7 @@ SockAddr::SockAddr(const std::string& addr, int port) { // Try ipv4 and then ipv6. { - struct in_addr addr_out{}; + struct in_addr addr_out {}; int result = inet_pton(AF_INET, addr.c_str(), &addr_out); if (result == 1) { auto* a = reinterpret_cast(&addr_); @@ -20,7 +20,7 @@ SockAddr::SockAddr(const std::string& addr, int port) { a->sin_addr = addr_out; return; } else { - struct in6_addr addr6_out{}; + struct in6_addr addr6_out {}; result = inet_pton(AF_INET6, addr.c_str(), &addr6_out); if (result == 1) { auto* a = reinterpret_cast(&addr_); diff --git a/src/ballistica/shared/python/python_class.cc b/src/ballistica/shared/python/python_class.cc index b65301627..1e652df3b 100644 --- a/src/ballistica/shared/python/python_class.cc +++ b/src/ballistica/shared/python/python_class.cc @@ -35,8 +35,8 @@ auto PythonClass::TypeIsSetUp(PyTypeObject* cls) -> bool { return Py_REFCNT(cls) > 0; } -auto PythonClass::tp_new(PyTypeObject* type, PyObject* args, PyObject* kwds) - -> PyObject* { +auto PythonClass::tp_new(PyTypeObject* type, PyObject* args, + PyObject* kwds) -> PyObject* { // Simply allocating and returning a zeroed instance of our class here. // If subclasses need to construct/destruct any other values in the object // they can either do it manually here and in tp_dealloc *or* they can get @@ -57,8 +57,8 @@ auto PythonClass::tp_getattro(PythonClass* node, PyObject* attr) -> PyObject* { BA_PYTHON_CATCH; } -auto PythonClass::tp_setattro(PythonClass* node, PyObject* attr, PyObject* val) - -> int { +auto PythonClass::tp_setattro(PythonClass* node, PyObject* attr, + PyObject* val) -> int { BA_PYTHON_TRY; return PyObject_GenericSetAttr(reinterpret_cast(node), attr, val); BA_PYTHON_INT_CATCH; diff --git a/src/ballistica/shared/python/python_class.h b/src/ballistica/shared/python/python_class.h index 0aef5b0f7..8efe1d479 100644 --- a/src/ballistica/shared/python/python_class.h +++ b/src/ballistica/shared/python/python_class.h @@ -35,12 +35,12 @@ class PythonClass { private: static auto tp_repr(PythonClass* self) -> PyObject*; - static auto tp_new(PyTypeObject* type, PyObject* args, PyObject* kwds) - -> PyObject*; + static auto tp_new(PyTypeObject* type, PyObject* args, + PyObject* kwds) -> PyObject*; static void tp_dealloc(PythonClass* self); static auto tp_getattro(PythonClass* node, PyObject* attr) -> PyObject*; - static auto tp_setattro(PythonClass* node, PyObject* attr, PyObject* val) - -> int; + static auto tp_setattro(PythonClass* node, PyObject* attr, + PyObject* val) -> int; }; } // namespace ballistica diff --git a/src/ballistica/shared/python/python_command.cc b/src/ballistica/shared/python/python_command.cc index 2b63ac7f2..d44a0796f 100644 --- a/src/ballistica/shared/python/python_command.cc +++ b/src/ballistica/shared/python/python_command.cc @@ -100,8 +100,8 @@ auto PythonCommand::CanEval() -> bool { return true; } -auto PythonCommand::Exec(bool print_errors, PyObject* globals, PyObject* locals) - -> bool { +auto PythonCommand::Exec(bool print_errors, PyObject* globals, + PyObject* locals) -> bool { assert(Python::HaveGIL()); // If we're being used before core is up, we need both global and @@ -166,8 +166,8 @@ auto PythonCommand::Exec(bool print_errors, PyObject* globals, PyObject* locals) return false; } -auto PythonCommand::Eval(bool print_errors, PyObject* globals, PyObject* locals) - -> PythonRef { +auto PythonCommand::Eval(bool print_errors, PyObject* globals, + PyObject* locals) -> PythonRef { assert(Python::HaveGIL()); assert(!dead_); diff --git a/src/ballistica/shared/python/python_command.h b/src/ballistica/shared/python/python_command.h index a0a34704f..bf712080e 100644 --- a/src/ballistica/shared/python/python_command.h +++ b/src/ballistica/shared/python/python_command.h @@ -46,8 +46,8 @@ class PythonCommand { /// Run the command and return the result as a new Python reference. /// Only works for eval-able commands. /// Returns nullptr on errors, but Python error state will be cleared. - auto Eval(bool print_errors, PyObject* globals, PyObject* locals) - -> PythonRef; + auto Eval(bool print_errors, PyObject* globals, + PyObject* locals) -> PythonRef; void PrintContext(); diff --git a/src/ballistica/shared/python/python_ref.cc b/src/ballistica/shared/python/python_ref.cc index 9259f7e76..97cfc4da8 100644 --- a/src/ballistica/shared/python/python_ref.cc +++ b/src/ballistica/shared/python/python_ref.cc @@ -312,8 +312,8 @@ auto PythonRef::UnicodeCheck() const -> bool { return static_cast(PyUnicode_Check(obj_)); } -static inline auto _HandleCallResults(PyObject* out, bool print_errors) - -> PyObject* { +static inline auto _HandleCallResults(PyObject* out, + bool print_errors) -> PyObject* { if (!out) { if (print_errors) { // Save/restore error or it can mess with context print calls. @@ -332,8 +332,8 @@ static inline auto _HandleCallResults(PyObject* out, bool print_errors) return out; } -auto PythonRef::Call(PyObject* args, PyObject* keywds, bool print_errors) const - -> PythonRef { +auto PythonRef::Call(PyObject* args, PyObject* keywds, + bool print_errors) const -> PythonRef { assert(obj_); assert(Python::HaveGIL()); assert(CallableCheck()); @@ -354,8 +354,8 @@ auto PythonRef::Call(bool print_errors) const -> PythonRef { return out ? PythonRef(out, PythonRef::kSteal) : PythonRef(); } -auto PythonRef::Call(const Vector2f& val, bool print_errors) const - -> PythonRef { +auto PythonRef::Call(const Vector2f& val, + bool print_errors) const -> PythonRef { assert(Python::HaveGIL()); PythonRef args(Py_BuildValue("((ff))", val.x, val.y), PythonRef::kSteal); return Call(args.get(), nullptr, print_errors); diff --git a/src/ballistica/template_fs/python/class/python_class_hello.h b/src/ballistica/template_fs/python/class/python_class_hello.h index bbf37f707..e2dc15ac2 100644 --- a/src/ballistica/template_fs/python/class/python_class_hello.h +++ b/src/ballistica/template_fs/python/class/python_class_hello.h @@ -35,8 +35,8 @@ class PythonClassHello : public PythonClass { PythonClassHello(); ~PythonClassHello(); static PyMethodDef tp_methods[]; - static auto tp_new(PyTypeObject* type, PyObject* args, PyObject* keywds) - -> PyObject*; + static auto tp_new(PyTypeObject* type, PyObject* args, + PyObject* keywds) -> PyObject*; static void tp_dealloc(PythonClassHello* self); static auto TestMethod(PythonClassHello* self, PyObject* args, PyObject* keywds) -> PyObject*; diff --git a/src/ballistica/template_fs/python/methods/python_methods_template_fs.cc b/src/ballistica/template_fs/python/methods/python_methods_template_fs.cc index e184299c1..9bda0283b 100644 --- a/src/ballistica/template_fs/python/methods/python_methods_template_fs.cc +++ b/src/ballistica/template_fs/python/methods/python_methods_template_fs.cc @@ -11,8 +11,8 @@ namespace ballistica::template_fs { // -------------------------- hello_again_world -------------------------------- -static auto PyHelloAgainWorld(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyHelloAgainWorld(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; const char* name; static const char* kwlist[] = {nullptr}; diff --git a/src/ballistica/ui_v1/python/class/python_class_ui_mesh.h b/src/ballistica/ui_v1/python/class/python_class_ui_mesh.h index b009edd39..dcc81251c 100644 --- a/src/ballistica/ui_v1/python/class/python_class_ui_mesh.h +++ b/src/ballistica/ui_v1/python/class/python_class_ui_mesh.h @@ -41,8 +41,8 @@ class PythonClassUIMesh : public PythonClass { private: static PyMethodDef tp_methods[]; static auto tp_repr(PythonClassUIMesh* self) -> PyObject*; - static auto tp_new(PyTypeObject* type, PyObject* args, PyObject* keywds) - -> PyObject*; + static auto tp_new(PyTypeObject* type, PyObject* args, + PyObject* keywds) -> PyObject*; static void tp_dealloc(PythonClassUIMesh* self); Object::Ref* mesh_; }; diff --git a/src/ballistica/ui_v1/python/class/python_class_ui_sound.h b/src/ballistica/ui_v1/python/class/python_class_ui_sound.h index d99a4ec35..f5e98a1c5 100644 --- a/src/ballistica/ui_v1/python/class/python_class_ui_sound.h +++ b/src/ballistica/ui_v1/python/class/python_class_ui_sound.h @@ -41,13 +41,13 @@ class PythonClassUISound : public PythonClass { private: static PyMethodDef tp_methods[]; static auto tp_repr(PythonClassUISound* self) -> PyObject*; - static auto tp_new(PyTypeObject* type, PyObject* args, PyObject* keywds) - -> PyObject*; + static auto tp_new(PyTypeObject* type, PyObject* args, + PyObject* keywds) -> PyObject*; static void tp_dealloc(PythonClassUISound* self); - static auto Play(PythonClassUISound* self, PyObject* args, PyObject* keywds) - -> PyObject*; - static auto Stop(PythonClassUISound* self, PyObject* args, PyObject* keywds) - -> PyObject*; + static auto Play(PythonClassUISound* self, PyObject* args, + PyObject* keywds) -> PyObject*; + static auto Stop(PythonClassUISound* self, PyObject* args, + PyObject* keywds) -> PyObject*; Object::Ref* sound_; bool playing_; diff --git a/src/ballistica/ui_v1/python/class/python_class_ui_texture.h b/src/ballistica/ui_v1/python/class/python_class_ui_texture.h index 46bbd1054..c9a28a194 100644 --- a/src/ballistica/ui_v1/python/class/python_class_ui_texture.h +++ b/src/ballistica/ui_v1/python/class/python_class_ui_texture.h @@ -42,8 +42,8 @@ class PythonClassUITexture : public PythonClass { private: static PyMethodDef tp_methods[]; static auto tp_repr(PythonClassUITexture* self) -> PyObject*; - static auto tp_new(PyTypeObject* type, PyObject* args, PyObject* keywds) - -> PyObject*; + static auto tp_new(PyTypeObject* type, PyObject* args, + PyObject* keywds) -> PyObject*; static void tp_dealloc(PythonClassUITexture* self); Object::Ref* texture_; }; diff --git a/src/ballistica/ui_v1/python/class/python_class_widget.cc b/src/ballistica/ui_v1/python/class/python_class_widget.cc index e0fd6b558..b916d046b 100644 --- a/src/ballistica/ui_v1/python/class/python_class_widget.cc +++ b/src/ballistica/ui_v1/python/class/python_class_widget.cc @@ -107,8 +107,8 @@ auto PythonClassWidget::GetWidget() const -> Widget* { return w; } -auto PythonClassWidget::tp_getattro(PythonClassWidget* self, PyObject* attr) - -> PyObject* { +auto PythonClassWidget::tp_getattro(PythonClassWidget* self, + PyObject* attr) -> PyObject* { BA_PYTHON_TRY; BA_PRECONDITION(g_base->InLogicThread()); @@ -375,8 +375,8 @@ auto PythonClassWidget::Delete(PythonClassWidget* self, PyObject* args, } auto PythonClassWidget::AddDeleteCallback(PythonClassWidget* self, - PyObject* args, PyObject* keywds) - -> PyObject* { + PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; BA_PRECONDITION(g_base->InLogicThread()); PyObject* call_obj; diff --git a/src/ballistica/ui_v1/python/class/python_class_widget.h b/src/ballistica/ui_v1/python/class/python_class_widget.h index 90ffed135..f1bb7dc7c 100644 --- a/src/ballistica/ui_v1/python/class/python_class_widget.h +++ b/src/ballistica/ui_v1/python/class/python_class_widget.h @@ -23,8 +23,8 @@ class PythonClassWidget : public PythonClass { private: static PyMethodDef tp_methods[]; static auto tp_repr(PythonClassWidget* self) -> PyObject*; - static auto tp_new(PyTypeObject* type, PyObject* args, PyObject* keywds) - -> PyObject*; + static auto tp_new(PyTypeObject* type, PyObject* args, + PyObject* keywds) -> PyObject*; static void tp_dealloc(PythonClassWidget* self); static auto tp_getattro(PythonClassWidget* self, PyObject* attr) -> PyObject*; static auto tp_setattro(PythonClassWidget* self, PyObject* attr, @@ -36,8 +36,8 @@ class PythonClassWidget : public PythonClass { static auto GetChildren(PythonClassWidget* self) -> PyObject*; static auto GetSelectedChild(PythonClassWidget* self) -> PyObject*; static auto GetScreenSpaceCenter(PythonClassWidget* self) -> PyObject*; - static auto Delete(PythonClassWidget* self, PyObject* args, PyObject* keywds) - -> PyObject*; + static auto Delete(PythonClassWidget* self, PyObject* args, + PyObject* keywds) -> PyObject*; static auto AddDeleteCallback(PythonClassWidget* self, PyObject* args, PyObject* keywds) -> PyObject*; static auto GlobalSelect(PythonClassWidget* self) -> PyObject*; diff --git a/src/ballistica/ui_v1/python/methods/python_methods_ui_v1.cc b/src/ballistica/ui_v1/python/methods/python_methods_ui_v1.cc index a53ae8d59..e95b82455 100644 --- a/src/ballistica/ui_v1/python/methods/python_methods_ui_v1.cc +++ b/src/ballistica/ui_v1/python/methods/python_methods_ui_v1.cc @@ -31,8 +31,8 @@ namespace ballistica::ui_v1 { // ------------------------------ getsound ------------------------------------- -static auto PyGetSound(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyGetSound(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; const char* name; static const char* kwlist[] = {"name", nullptr}; @@ -61,8 +61,8 @@ static PyMethodDef PyGetSoundDef = { // ----------------------------- gettexture ------------------------------------ -static auto PyGetTexture(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyGetTexture(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; const char* name; static const char* kwlist[] = {"name", nullptr}; @@ -90,8 +90,8 @@ static PyMethodDef PyGetTextureDef = { // -------------------------- get_qrcode_texture ------------------------------- -static auto PyGetQRCodeTexture(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyGetQRCodeTexture(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; const char* url; static const char* kwlist[] = {"url", nullptr}; @@ -120,8 +120,8 @@ static PyMethodDef PyGetQRCodeTextureDef = { // ------------------------------- getmesh ------------------------------------- -static auto PyGetMesh(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyGetMesh(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; const char* name; static const char* kwlist[] = {"name", nullptr}; @@ -149,8 +149,8 @@ static PyMethodDef PyGetMeshDef = { // ----------------------------- buttonwidget ---------------------------------- -static auto PyButtonWidget(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyButtonWidget(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; PyObject* parent_obj{Py_None}; PyObject* id_obj{Py_None}; @@ -398,8 +398,16 @@ static auto PyButtonWidget(PyObject* self, PyObject* args, PyObject* keywds) b->set_style(ButtonWidget::Style::kSquare); } else if (button_type == "tab") { b->set_style(ButtonWidget::Style::kTab); + } else if (button_type == "small") { + b->set_style(ButtonWidget::Style::kSmall); + } else if (button_type == "medium") { + b->set_style(ButtonWidget::Style::kMedium); + } else if (button_type == "large") { + b->set_style(ButtonWidget::Style::kLarge); + } else if (button_type == "larger") { + b->set_style(ButtonWidget::Style::kLarger); } else { - throw Exception("Invalid button type: " + button_type + ".", + throw Exception("Invalid button type: '" + button_type + "'.", PyExcType::kValue); } } @@ -528,8 +536,8 @@ static PyMethodDef PyButtonWidgetDef = { // --------------------------- checkboxwidget ---------------------------------- -static auto PyCheckBoxWidget(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyCheckBoxWidget(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; PyObject* size_obj{Py_None}; PyObject* id_obj{Py_None}; @@ -708,8 +716,8 @@ static PyMethodDef PyCheckBoxWidgetDef = { // ----------------------------- imagewidget ----------------------------------- -static auto PyImageWidget(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyImageWidget(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; PyObject* size_obj{Py_None}; PyObject* pos_obj{Py_None}; @@ -912,8 +920,8 @@ static PyMethodDef PyImageWidgetDef = { // ----------------------------- imagewidget ----------------------------------- -static auto PySpinnerWidget(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PySpinnerWidget(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; PyObject* edit_obj{Py_None}; PyObject* parent_obj{Py_None}; @@ -1014,8 +1022,8 @@ static PyMethodDef PySpinnerWidgetDef = { // ----------------------------- columnwidget ---------------------------------- -static auto PyColumnWidget(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyColumnWidget(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; PyObject* id_obj{Py_None}; @@ -1196,8 +1204,8 @@ static PyMethodDef PyColumnWidgetDef = { // ---------------------------- containerwidget -------------------------------- -static auto PyContainerWidget(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyContainerWidget(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; PyObject* size_obj{Py_None}; PyObject* pos_obj{Py_None}; @@ -1586,8 +1594,8 @@ static PyMethodDef PyContainerWidgetDef = { // ------------------------------ rowwidget ------------------------------------ -static auto PyRowWidget(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyRowWidget(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; PyObject* size_obj{Py_None}; @@ -1712,8 +1720,8 @@ static PyMethodDef PyRowWidgetDef = { // ---------------------------- scrollwidget ----------------------------------- -static auto PyScrollWidget(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyScrollWidget(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; PyObject* size_obj{Py_None}; PyObject* pos_obj{Py_None}; @@ -1906,8 +1914,8 @@ static PyMethodDef PyScrollWidgetDef = { // ---------------------------- hscrollwidget ---------------------------------- -static auto PyHScrollWidget(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyHScrollWidget(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; PyObject* size_obj{Py_None}; @@ -2078,8 +2086,8 @@ static PyMethodDef PyHScrollWidgetDef = { // ------------------------------ textwidget ----------------------------------- -static auto PyTextWidget(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyTextWidget(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; PyObject* size_obj{Py_None}; PyObject* pos_obj{Py_None}; @@ -2490,8 +2498,8 @@ static PyMethodDef PyTextWidgetDef = { // ------------------------------- widget -------------------------------------- -static auto PyWidgetCall(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyWidgetCall(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; PyObject* edit_obj{Py_None}; @@ -2681,8 +2689,8 @@ static PyMethodDef PyUIBoundsDef = { // -------------------------- get_special_widget ------------------------------- -static auto PyGetSpecialWidget(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyGetSpecialWidget(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; const char* name; @@ -2760,8 +2768,8 @@ static PyMethodDef PyGetSelectedWidgetDef = { // ----------------------------- widget_by_id ---------------------------------- -static auto PyWidgetByID(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PyWidgetByID(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; const char* id; diff --git a/src/ballistica/ui_v1/widget/button_widget.cc b/src/ballistica/ui_v1/widget/button_widget.cc index 3c8850bd2..d2cd4b4d7 100644 --- a/src/ballistica/ui_v1/widget/button_widget.cc +++ b/src/ballistica/ui_v1/widget/button_widget.cc @@ -283,6 +283,19 @@ void ButtonWidget::Draw(base::RenderPass* pass, bool draw_transparent) { base::SysMeshID mesh_id; base::SysTextureID tex_id; + // Regular style means pick based on our aspect ratio. + if (style_ == Style::kRegular) { + if ((r_orig - l_orig) / (t_orig - b_orig) < 50.0f / 30.0f) { + style_ = Style::kSmall; + } else if ((r_orig - l_orig) / (t_orig - b_orig) < 200.0f / 35.0f) { + style_ = Style::kMedium; + } else if ((r_orig - l_orig) / (t_orig - b_orig) < 300.0f / 35.0f) { + style_ = Style::kLarge; + } else { + style_ = Style::kLarger; + } + } + switch (style_) { case Style::kBack: { tex_id = base::SysTextureID::kUIAtlas; @@ -326,44 +339,50 @@ void ButtonWidget::Draw(base::RenderPass* pass, bool draw_transparent) { t_border = 6; break; } + case Style::kLarger: { + tex_id = base::SysTextureID::kUIAtlas; + mesh_id = draw_transparent + ? base::SysMeshID::kButtonLargerTransparent + : base::SysMeshID::kButtonLargerOpaque; + l_border = 7; + r_border = 11; + b_border = 10; + t_border = 4; + break; + } + case Style::kLarge: { + tex_id = base::SysTextureID::kUIAtlas; + mesh_id = draw_transparent + ? base::SysMeshID::kButtonLargeTransparent + : base::SysMeshID::kButtonLargeOpaque; + l_border = 7; + r_border = 10; + b_border = 10; + t_border = 5; + break; + } + case Style::kMedium: { + tex_id = base::SysTextureID::kUIAtlas; + mesh_id = draw_transparent + ? base::SysMeshID::kButtonMediumTransparent + : base::SysMeshID::kButtonMediumOpaque; + l_border = 6; + r_border = 10; + b_border = 5; + t_border = 2; + break; + } + default: { - if ((r_orig - l_orig) / (t_orig - b_orig) < 50.0f / 30.0f) { - tex_id = base::SysTextureID::kUIAtlas; - mesh_id = draw_transparent - ? base::SysMeshID::kButtonSmallTransparent - : base::SysMeshID::kButtonSmallOpaque; - l_border = 10; - r_border = 14; - b_border = 9; - t_border = 5; - } else if ((r_orig - l_orig) / (t_orig - b_orig) < 200.0f / 35.0f) { - tex_id = base::SysTextureID::kUIAtlas; - mesh_id = draw_transparent - ? base::SysMeshID::kButtonMediumTransparent - : base::SysMeshID::kButtonMediumOpaque; - l_border = 6; - r_border = 10; - b_border = 5; - t_border = 2; - } else if ((r_orig - l_orig) / (t_orig - b_orig) < 300.0f / 35.0f) { - tex_id = base::SysTextureID::kUIAtlas; - mesh_id = draw_transparent - ? base::SysMeshID::kButtonLargeTransparent - : base::SysMeshID::kButtonLargeOpaque; - l_border = 7; - r_border = 10; - b_border = 10; - t_border = 5; - } else { - tex_id = base::SysTextureID::kUIAtlas; - mesh_id = draw_transparent - ? base::SysMeshID::kButtonLargerTransparent - : base::SysMeshID::kButtonLargerOpaque; - l_border = 7; - r_border = 11; - b_border = 10; - t_border = 4; - } + assert(style_ == Style::kSmall); + tex_id = base::SysTextureID::kUIAtlas; + mesh_id = draw_transparent + ? base::SysMeshID::kButtonSmallTransparent + : base::SysMeshID::kButtonSmallOpaque; + l_border = 10; + r_border = 14; + b_border = 9; + t_border = 5; break; } } diff --git a/src/ballistica/ui_v1/widget/button_widget.h b/src/ballistica/ui_v1/widget/button_widget.h index 95420b028..282cf836a 100644 --- a/src/ballistica/ui_v1/widget/button_widget.h +++ b/src/ballistica/ui_v1/widget/button_widget.h @@ -50,7 +50,17 @@ class ButtonWidget : public Widget { void set_flatness(float val) { flatness_ = val; } auto set_text_flatness(float f) { text_flatness_ = f; } - enum class Style : uint8_t { kRegular, kBack, kBackSmall, kTab, kSquare }; + enum class Style : uint8_t { + kRegular, + kBack, + kBackSmall, + kTab, + kSquare, + kSmall, + kMedium, + kLarge, + kLarger + }; auto set_style(Style s) { style_ = s; } enum class IconType : uint8_t { kNone, kCancel, kStart }; void SetTextLiteral(bool val); diff --git a/src/ballistica/ui_v1/widget/container_widget.cc b/src/ballistica/ui_v1/widget/container_widget.cc index 3c2fb4ed3..5e620af87 100644 --- a/src/ballistica/ui_v1/widget/container_widget.cc +++ b/src/ballistica/ui_v1/widget/container_widget.cc @@ -709,8 +709,8 @@ auto ContainerWidget::HandleMessage(const base::WidgetMessage& m) -> bool { return claimed; } -auto ContainerWidget::GetMult(millisecs_t current_time, bool for_glow) const - -> float { +auto ContainerWidget::GetMult(millisecs_t current_time, + bool for_glow) const -> float { if (root_selectable_ && selected()) { float m; @@ -804,12 +804,13 @@ void ContainerWidget::Draw(base::RenderPass* pass, bool draw_transparent) { if (!draw_transparent) { if (transition_type_ == TransitionType::kInScale) { - if (display_time_ms - dynamics_update_time_millisecs_ > 1000) + if (display_time_ms - dynamics_update_time_millisecs_ > 1000) { dynamics_update_time_millisecs_ = display_time_ms - 1000; + } while (display_time_ms - dynamics_update_time_millisecs_ > 5) { dynamics_update_time_millisecs_ += 5; d_transition_scale_ += - std::min(0.2f, (1.0f - transition_scale_)) * 0.04f; + std::min(0.2f, (1.0f - transition_scale_)) * 0.03f; d_transition_scale_ *= 0.87f; transition_scale_ += d_transition_scale_; @@ -829,7 +830,7 @@ void ContainerWidget::Draw(base::RenderPass* pass, bool draw_transparent) { while (display_time_ms - dynamics_update_time_millisecs_ > 5) { dynamics_update_time_millisecs_ += 5; - transition_scale_ -= 0.04f; + transition_scale_ -= 0.03f; if (transition_scale_ <= 0.0f) { transition_scale_ = 0.0f; @@ -932,11 +933,10 @@ void ContainerWidget::Draw(base::RenderPass* pass, bool draw_transparent) { // zoom from a point somewhere else on screen). if (transition_type_ == TransitionType::kInScale || transition_type_ == TransitionType::kOutScale) { - // Add a fudge factor since our scale point isn't exactly in our center. - // :-( float xdiff = scale_origin_stack_offset_x_ - stack_offset_x() - + GetWidth() * -0.05f; - float ydiff = scale_origin_stack_offset_y_ - stack_offset_y(); + + GetWidth() * bg_center_fudge_x_; + float ydiff = scale_origin_stack_offset_y_ - stack_offset_y() + + GetHeight() * bg_center_fudge_y_; transition_scale_offset_x_ = ((1.0f - transition_scale_) * xdiff) / scale(); transition_scale_offset_y_ = @@ -962,26 +962,33 @@ void ContainerWidget::Draw(base::RenderPass* pass, bool draw_transparent) { // so always calc them). if (bg_dirty_) { base::SysTextureID tex_id; - float l_border, r_border, b_border, t_border; + float l_border, r_border, b_border, t_border, center_x_amt, center_y_amt; float width = r - l; float height = t - b; if (height > width * 0.6f) { tex_id = base::SysTextureID::kWindowHSmallVMed; - bg_mesh_transparent_i_d_ = base::SysMeshID::kWindowHSmallVMedTransparent; - bg_mesh_opaque_i_d_ = base::SysMeshID::kWindowHSmallVMedOpaque; + bg_mesh_transparent_id_ = base::SysMeshID::kWindowHSmallVMedTransparent; + bg_mesh_opaque_id_ = base::SysMeshID::kWindowHSmallVMedOpaque; l_border = width * 0.07f; r_border = width * 0.19f; b_border = height * 0.1f; t_border = height * 0.07f; + // These need to be fudged until scaling in/out hits exact target + // point. Should look into why this math is off. + bg_center_fudge_x_ = -0.05f; + bg_center_fudge_y_ = 0.0f; } else { tex_id = base::SysTextureID::kWindowHSmallVSmall; - bg_mesh_transparent_i_d_ = - base::SysMeshID::kWindowHSmallVSmallTransparent; - bg_mesh_opaque_i_d_ = base::SysMeshID::kWindowHSmallVSmallOpaque; + bg_mesh_transparent_id_ = base::SysMeshID::kWindowHSmallVSmallTransparent; + bg_mesh_opaque_id_ = base::SysMeshID::kWindowHSmallVSmallOpaque; l_border = width * 0.12f; r_border = width * 0.19f; b_border = height * 0.45f; t_border = height * 0.23f; + // These need to be fudged until scaling in/out hits exact target + // point. Should look into why this math is off. + bg_center_fudge_x_ = -0.03f; + bg_center_fudge_y_ = 0.1f; } bg_width_ = r - l + l_border + r_border; bg_height_ = t - b + b_border + t_border; @@ -1000,7 +1007,7 @@ void ContainerWidget::Draw(base::RenderPass* pass, bool draw_transparent) { } // Draw our window backing if we have one. - if ((w > 0) && (h > 0)) { + if (w > 0.0f && h > 0.0f) { if (background_) { float zoffs{}; if (darken_behind_) { @@ -1077,7 +1084,7 @@ void ContainerWidget::Draw(base::RenderPass* pass, bool draw_transparent) { c.Translate(bg_center_x_, bg_center_y_, zoffs); c.Scale(bg_width_ * transition_scale_, bg_height_ * transition_scale_); c.DrawMeshAsset(g_base->assets->SysMesh( - draw_transparent ? bg_mesh_transparent_i_d_ : bg_mesh_opaque_i_d_)); + draw_transparent ? bg_mesh_transparent_id_ : bg_mesh_opaque_id_)); } c.Submit(); } diff --git a/src/ballistica/ui_v1/widget/container_widget.h b/src/ballistica/ui_v1/widget/container_widget.h index ab8a802a9..4d798d142 100644 --- a/src/ballistica/ui_v1/widget/container_widget.h +++ b/src/ballistica/ui_v1/widget/container_widget.h @@ -218,8 +218,8 @@ class ContainerWidget : public Widget { Object::WeakRef start_button_; Widget* selected_widget_{}; Widget* prev_selected_widget_{}; - base::SysMeshID bg_mesh_transparent_i_d_{}; - base::SysMeshID bg_mesh_opaque_i_d_{}; + base::SysMeshID bg_mesh_transparent_id_{}; + base::SysMeshID bg_mesh_opaque_id_{}; TransitionType transition_type_{}; float width_{}; float height_{}; @@ -244,6 +244,8 @@ class ContainerWidget : public Widget { float transition_start_offset_{}; float transition_scale_{1.0f}; float d_transition_scale_{}; + float bg_center_fudge_x_{}; + float bg_center_fudge_y_{}; millisecs_t last_activate_time_millisecs_{}; millisecs_t transition_start_time_{}; millisecs_t dynamics_update_time_millisecs_{}; diff --git a/src/ballistica/ui_v1/widget/h_scroll_widget.cc b/src/ballistica/ui_v1/widget/h_scroll_widget.cc index c32f67522..48f1b86ee 100644 --- a/src/ballistica/ui_v1/widget/h_scroll_widget.cc +++ b/src/ballistica/ui_v1/widget/h_scroll_widget.cc @@ -60,7 +60,7 @@ void HScrollWidget::ClampThumb_(bool velocity_clamp, bool position_clamp) { float child_w = (**i).GetWidth(); if (velocity_clamp) { - if (child_offset_h_ < 0) { + if (child_offset_h_ < 0.0f) { // Even in velocity case do some sane clamping. float diff = child_offset_h_; inertia_scroll_rate_ += @@ -68,11 +68,12 @@ void HScrollWidget::ClampThumb_(bool velocity_clamp, bool position_clamp) { inertia_scroll_rate_ *= 0.9f; } else if (child_offset_h_ - > child_w - (width() - 2 * (border_width_ + kMarginH))) { + > child_w - (width() - 2.0f * (border_width_ + kMarginH))) { float diff = child_offset_h_ - (child_w - - std::min(child_w, (width() - 2 * (border_width_ + kMarginH)))); + - std::min(child_w, + (width() - 2.0f * (border_width_ + kMarginH)))); inertia_scroll_rate_ += diff * (is_scrolling ? strong_force : weak_force); inertia_scroll_rate_ *= 0.9f; @@ -82,19 +83,20 @@ void HScrollWidget::ClampThumb_(bool velocity_clamp, bool position_clamp) { // Hard clipping if we're dragging the scrollbar. if (position_clamp) { if (child_offset_h_smoothed_ - > child_w - (width() - 2 * (border_width_ + kMarginH))) { + > child_w - (width() - 2.0f * (border_width_ + kMarginH))) { child_offset_h_smoothed_ = - child_w - (width() - 2 * (border_width_ + kMarginH)); + child_w - (width() - 2.0f * (border_width_ + kMarginH)); } - if (child_offset_h_smoothed_ < 0) { - child_offset_h_smoothed_ = 0; + if (child_offset_h_smoothed_ < 0.0f) { + child_offset_h_smoothed_ = 0.0f; } if (child_offset_h_ - > child_w - (width() - 2 * (border_width_ + kMarginH))) { - child_offset_h_ = child_w - (width() - 2 * (border_width_ + kMarginH)); + > child_w - (width() - 2.0f * (border_width_ + kMarginH))) { + child_offset_h_ = + child_w - (width() - 2.0f * (border_width_ + kMarginH)); } - if (child_offset_h_ < 0) { - child_offset_h_ = 0; + if (child_offset_h_ < 0.0f) { + child_offset_h_ = 0.0f; } } } @@ -378,8 +380,10 @@ auto HScrollWidget::HandleMessage(const base::WidgetMessage& m) -> bool { child_offset_h_ - (child_h - std::min(child_h, - (width() - 2 * (border_width_ + kMarginH)))); - if (diff > 0) past_end = true; + (width() - 2.0f * (border_width_ + kMarginH)))); + if (diff > 0.0f) { + past_end = true; + } } if (past_end) { new_val = scroll_speed * 0.1f * m.fval3; @@ -402,7 +406,7 @@ auto HScrollWidget::HandleMessage(const base::WidgetMessage& m) -> bool { case base::WidgetMessage::Type::kMouseWheelH: { float x = m.fval1; float y = m.fval2; - if ((x >= 0.0f) && (x < width()) && (y >= 0.0f) && (y < height())) { + if (x >= 0.0f && x < width() && y >= 0.0f && y < height()) { claimed = true; pass = false; @@ -474,7 +478,7 @@ auto HScrollWidget::HandleMessage(const base::WidgetMessage& m) -> bool { float s_right = width() - border_width_; float s_left = border_width_; float sb_thumb_width = - amount_visible_ * (width() - 2 * border_width_); + amount_visible_ * (width() - 2.0f * border_width_); float sb_thumb_right = s_right - child_offset_h_ / child_max_offset_ @@ -483,7 +487,7 @@ auto HScrollWidget::HandleMessage(const base::WidgetMessage& m) -> bool { // To right of thumb (page-right). if (x >= sb_thumb_right) { smoothing_amount_ = 1.0f; // So we can see the transition. - child_offset_h_ -= (width() - 2 * (border_width_ + kMarginH)); + child_offset_h_ -= (width() - 2.0f * (border_width_ + kMarginH)); MarkForUpdate(); ClampThumb_(false, true); } else if (x >= sb_thumb_right - sb_thumb_width) { @@ -494,7 +498,7 @@ auto HScrollWidget::HandleMessage(const base::WidgetMessage& m) -> bool { } else if (x >= s_left) { // To left of thumb (page left). smoothing_amount_ = 1.0f; // So we can see the transition. - child_offset_h_ += (width() - 2 * (border_width_ + kMarginH)); + child_offset_h_ += (width() - 2.0f * (border_width_ + kMarginH)); MarkForUpdate(); ClampThumb_(false, true); } @@ -534,7 +538,7 @@ void HScrollWidget::UpdateLayout() { } float child_w = (**i).GetWidth(); child_max_offset_ = child_w - (width() - 2.0f * (border_width_ + kMarginH)); - amount_visible_ = (width() - 2 * (border_width_ + kMarginH)) / child_w; + amount_visible_ = (width() - 2.0f * (border_width_ + kMarginH)) / child_w; if (amount_visible_ > 1.0f) { amount_visible_ = 1.0f; if (center_small_content_) { @@ -599,7 +603,7 @@ void HScrollWidget::Draw(base::RenderPass* pass, bool draw_transparent) { if (!has_momentum_ && (current_time_ms - last_velocity_event_time_millisecs_ > 1000 / 30)) { - inertia_scroll_rate_ = 0; + inertia_scroll_rate_ = 0.0f; } // Lastly we apply smoothing so that if we're snapping to a specific @@ -637,9 +641,10 @@ void HScrollWidget::Draw(base::RenderPass* pass, bool draw_transparent) { { base::EmptyComponent c(pass); c.SetTransparent(draw_transparent); - auto scissor = c.ScopedScissor({l + border_width_, b + border_height_ + 1, - l + (width() - border_width_ - 0), - b + (height() - border_height_) - 1}); + auto scissor = + c.ScopedScissor({l + border_width_, b + border_height_ + 1.0f, + l + (width() - border_width_ - 0.0f), + b + (height() - border_height_) - 1.0f}); c.Submit(); // Get out of the way for child drawing. set_simple_culling_left(l + border_width_); @@ -705,7 +710,7 @@ void HScrollWidget::Draw(base::RenderPass* pass, bool draw_transparent) { float b_border, t_border, l_border, r_border; b_border = 6.0f; t_border = 3.0f; - if (sb_thumb_width > 100) { + if (sb_thumb_width > 100.0f) { auto wd = r2 - l2; l_border = wd * 0.04f; r_border = wd * 0.06f; @@ -724,12 +729,6 @@ void HScrollWidget::Draw(base::RenderPass* pass, bool draw_transparent) { base::SimpleComponent c(pass); c.SetTransparent(draw_transparent); - // float c_scale = 1.0f; - // if (mouse_held_thumb_) { - // c_scale = 1.8f; - // } else if (mouse_over_thumb_) { - // c_scale = 1.25f; - // } float frame_duration = frame_def->display_time_elapsed(); @@ -744,14 +743,14 @@ void HScrollWidget::Draw(base::RenderPass* pass, bool draw_transparent) { if (smooth_diff || (touch_held_ && touch_is_scrolling_) || std::abs(inertia_scroll_rate_) > 1.0f || (mouse_over_ - && frame_def->display_time() - last_mouse_move_time_ < 0.1)) { + && frame_def->display_time() - last_mouse_move_time_ < 0.1f)) { last_scroll_bar_show_time_ = frame_def->display_time(); } } // Fade in if we want to see the scrollbar. Start fading out a moment // after we stop wanting to see it. - if (frame_def->display_time() - last_scroll_bar_show_time_ < 1.0) { + if (frame_def->display_time() - last_scroll_bar_show_time_ < 1.0f) { touch_fade_ = std::min(1.5f, touch_fade_ + 2.0f * frame_duration); } else { touch_fade_ = std::max(0.0f, touch_fade_ - frame_duration); @@ -761,17 +760,17 @@ void HScrollWidget::Draw(base::RenderPass* pass, bool draw_transparent) { { auto scissor = - c.ScopedScissor({l + border_width_, b + border_height_ + 1, + c.ScopedScissor({l + border_width_, b + border_height_ + 1.0f, l + (width()), b + (height() * 0.995f)}); auto xf = c.ScopedTransform(); c.Translate(thumb_center_x_, thumb_center_y_, 0.75f); c.Scale(-thumb_width_, thumb_height_, 0.1f); c.FlipCullFace(); - c.Rotate(-90, 0, 0, 1); + c.Rotate(-90.0f, 0.0f, 0.0f, 1.0f); if (draw_transparent) { c.DrawMeshAsset(g_base->assets->SysMesh( - sb_thumb_width > 100 + sb_thumb_width > 100.0f ? base::SysMeshID::kScrollBarThumbSimple : base::SysMeshID::kScrollBarThumbShortSimple)); } @@ -801,7 +800,7 @@ void HScrollWidget::Draw(base::RenderPass* pass, bool draw_transparent) { } base::SimpleComponent c(pass); c.SetTransparent(true); - c.SetColor(1, 1, 1, border_opacity_); + c.SetColor(1.0f, 1.0f, 1.0f, border_opacity_); c.SetTexture(g_base->assets->SysTexture(base::SysTextureID::kScrollWidget)); { auto xf = c.ScopedTransform(); diff --git a/src/ballistica/ui_v1/widget/root_widget.cc b/src/ballistica/ui_v1/widget/root_widget.cc index ca5471d30..0f8a99106 100644 --- a/src/ballistica/ui_v1/widget/root_widget.cc +++ b/src/ballistica/ui_v1/widget/root_widget.cc @@ -1678,10 +1678,8 @@ void RootWidget::StepChildWidgets_(seconds_t dt) { if (auto* btn = account_button_) { if (counts.find("accountsettings") != counts.end()) { account_button_mult_ = {1.2f, 2.0f, 1.2f}; - // btn->widget->set_flatness(0.75f); // Not currently supported. } else { account_button_mult_ = {1.0f, 1.0f, 1.0f}; - // btn->widget->set_flatness(0.0f); // Not currently supported. } UpdateAccountButtonColor_(); } else { @@ -1791,9 +1789,9 @@ void RootWidget::StepChildWidgets_(seconds_t dt) { // Inbox if (auto* btn = inbox_button_) { if (counts.find("classicinbox") != counts.end()) { - btn->widget->set_color(kBotLeftColorR * 0.2f, kBotLeftColorG * 1.1f, - kBotLeftColorB * 0.2f); - btn->widget->set_flatness(0.7f); + btn->widget->set_color(kBotLeftColorR * 0.3f, kBotLeftColorG * 1.3f, + kBotLeftColorB * 0.3f); + btn->widget->set_flatness(0.8f); } else { btn->widget->set_color(kBotLeftColorR, kBotLeftColorG, kBotLeftColorB); btn->widget->set_flatness(0.0f); @@ -1806,9 +1804,9 @@ void RootWidget::StepChildWidgets_(seconds_t dt) { // Achievements if (auto* btn = achievements_button_) { if (counts.find("classicachievements") != counts.end()) { - btn->widget->set_color(kBotLeftColorR * 0.2f, kBotLeftColorG * 1.1f, - kBotLeftColorB * 0.2f); - btn->widget->set_flatness(0.7f); + btn->widget->set_color(kBotLeftColorR * 0.3f, kBotLeftColorG * 1.3f, + kBotLeftColorB * 0.3f); + btn->widget->set_flatness(0.8f); } else { btn->widget->set_color(kBotLeftColorR, kBotLeftColorG, kBotLeftColorB); btn->widget->set_flatness(0.0f); @@ -1822,9 +1820,9 @@ void RootWidget::StepChildWidgets_(seconds_t dt) { // Settings if (auto* btn = settings_button_) { if (counts.find("settings") != counts.end()) { - btn->widget->set_color(kBotLeftColorR * 0.2f, kBotLeftColorG * 1.1f, - kBotLeftColorB * 0.2f); - btn->widget->set_flatness(0.7f); + btn->widget->set_color(kBotLeftColorR * 0.3f, kBotLeftColorG * 1.3f, + kBotLeftColorB * 0.3f); + btn->widget->set_flatness(0.8f); } else { btn->widget->set_color(kBotLeftColorR, kBotLeftColorG, kBotLeftColorB); btn->widget->set_flatness(0.0f); @@ -2802,8 +2800,8 @@ void RootWidget::UpdateChests_() { // Show in flat green if ui is open. if (uiopen) { - slot.button->widget->set_color(0.2f, 1.1f, 0.2f); - slot.button->widget->set_flatness(0.6f); + slot.button->widget->set_color(0.1f, 0.7f, 0.1f); + slot.button->widget->set_flatness(0.7f); } else { slot.button->widget->set_color(0.473f, 0.44f, 0.583f); slot.button->widget->set_flatness(0.0f); diff --git a/src/ballistica/ui_v1/widget/scroll_widget.cc b/src/ballistica/ui_v1/widget/scroll_widget.cc index 02a1541a6..ffe13ef8e 100644 --- a/src/ballistica/ui_v1/widget/scroll_widget.cc +++ b/src/ballistica/ui_v1/widget/scroll_widget.cc @@ -232,6 +232,11 @@ auto ScrollWidget::HandleMessage(const base::WidgetMessage& m) -> bool { float x = m.fval1; float y = m.fval2; + // Don't scroll if everything is visible. + if (amount_visible_ >= 1.0f) { + break; + } + // Keep track of the average scrolling going on. (only update when we // get non-momentum events). if (std::abs(m.fval3) > 0.001f && !has_momentum_) { @@ -314,6 +319,12 @@ auto ScrollWidget::HandleMessage(const base::WidgetMessage& m) -> bool { if ((x >= 0.0f) && (x < width()) && (y >= 0.0f) && (y < height())) { claimed = true; pass = false; + + // Don't scroll if everything is visible. + if (amount_visible_ >= 1.0f) { + break; + } + inertia_scroll_rate_ -= m.fval3 * 0.003f; MarkForUpdate(); } else { diff --git a/test_import.py b/test_import.py new file mode 100644 index 000000000..e69de29bb diff --git a/tools/bacommon/cloudui/__init__.py b/tools/bacommon/cloudui/__init__.py index 4bad839c7..aa5db663e 100644 --- a/tools/bacommon/cloudui/__init__.py +++ b/tools/bacommon/cloudui/__init__.py @@ -3,14 +3,20 @@ """Common CloudUI bits.""" from bacommon.cloudui._cloudui import ( - CloudUIPage, - CloudUIPageTypeID, - UnknownCloudUIPage, + CloudUIRequest, + CloudUIRequestTypeID, + UnknownCloudUIRequest, + CloudUIResponse, + CloudUIResponseTypeID, + UnknownCloudUIResponse, ) __all__ = [ - 'CloudUIPage', - 'CloudUIPageTypeID', - 'UnknownCloudUIPage', + 'CloudUIRequest', + 'CloudUIRequestTypeID', + 'UnknownCloudUIRequest', + 'CloudUIResponse', + 'CloudUIResponseTypeID', + 'UnknownCloudUIResponse', ] diff --git a/tools/bacommon/cloudui/_cloudui.py b/tools/bacommon/cloudui/_cloudui.py index 0f2021f04..a22fe4fb9 100644 --- a/tools/bacommon/cloudui/_cloudui.py +++ b/tools/bacommon/cloudui/_cloudui.py @@ -14,22 +14,22 @@ pass -class CloudUIPageTypeID(Enum): +class CloudUIRequestTypeID(Enum): """Type ID for each of our subclasses.""" UNKNOWN = 'u' V1 = 'v1' -class CloudUIPage(IOMultiType[CloudUIPageTypeID]): +class CloudUIRequest(IOMultiType[CloudUIRequestTypeID]): """UI defined by the cloud. - Conceptually similar to a basic html page, except using app UI. + Conceptually similar to a basic html request, except using app UI. """ @override @classmethod - def get_type_id(cls) -> CloudUIPageTypeID: + def get_type_id(cls) -> CloudUIRequestTypeID: # Require child classes to supply this themselves. If we did a # full type registry/lookup here it would require us to import # everything and would prevent lazy loading. @@ -37,27 +37,27 @@ def get_type_id(cls) -> CloudUIPageTypeID: @override @classmethod - def get_type(cls, type_id: CloudUIPageTypeID) -> type[CloudUIPage]: + def get_type(cls, type_id: CloudUIRequestTypeID) -> type[CloudUIRequest]: """Return the subclass for each of our type-ids.""" # pylint: disable=cyclic-import - t = CloudUIPageTypeID + t = CloudUIRequestTypeID if type_id is t.UNKNOWN: - return UnknownCloudUIPage + return UnknownCloudUIRequest if type_id is t.V1: - from bacommon.cloudui.v1 import Page + from bacommon.cloudui.v1 import Request - return Page + return Request # Make sure we cover all types. assert_never(type_id) @override @classmethod - def get_unknown_type_fallback(cls) -> CloudUIPage: + def get_unknown_type_fallback(cls) -> CloudUIRequest: # If we encounter some future type we don't know anything about, # drop in a placeholder. - return UnknownCloudUIPage() + return UnknownCloudUIRequest() @override @classmethod @@ -67,13 +67,78 @@ def get_type_id_storage_name(cls) -> str: @ioprepped @dataclass -class UnknownCloudUIPage(CloudUIPage): +class UnknownCloudUIRequest(CloudUIRequest): """Fallback type for unrecognized UI types. - Will show the client a 'cannot display this UI' placeholder page. + Will show the client a 'cannot display this UI' placeholder request. """ @override @classmethod - def get_type_id(cls) -> CloudUIPageTypeID: - return CloudUIPageTypeID.UNKNOWN + def get_type_id(cls) -> CloudUIRequestTypeID: + return CloudUIRequestTypeID.UNKNOWN + + +class CloudUIResponseTypeID(Enum): + """Type ID for each of our subclasses.""" + + UNKNOWN = 'u' + V1 = 'v1' + + +class CloudUIResponse(IOMultiType[CloudUIResponseTypeID]): + """UI defined by the cloud. + + Conceptually similar to a basic html response, except using app UI. + """ + + @override + @classmethod + def get_type_id(cls) -> CloudUIResponseTypeID: + # Require child classes to supply this themselves. If we did a + # full type registry/lookup here it would require us to import + # everything and would prevent lazy loading. + raise NotImplementedError() + + @override + @classmethod + def get_type(cls, type_id: CloudUIResponseTypeID) -> type[CloudUIResponse]: + """Return the subclass for each of our type-ids.""" + # pylint: disable=cyclic-import + + t = CloudUIResponseTypeID + if type_id is t.UNKNOWN: + return UnknownCloudUIResponse + if type_id is t.V1: + from bacommon.cloudui.v1 import Response + + return Response + + # Make sure we cover all types. + assert_never(type_id) + + @override + @classmethod + def get_unknown_type_fallback(cls) -> CloudUIResponse: + # If we encounter some future type we don't know anything about, + # drop in a placeholder. + return UnknownCloudUIResponse() + + @override + @classmethod + def get_type_id_storage_name(cls) -> str: + return '_t' + + +@ioprepped +@dataclass +class UnknownCloudUIResponse(CloudUIResponse): + """Fallback type for unrecognized UI types. + + Will show the client a 'cannot display this UI' placeholder response. + """ + + @override + @classmethod + def get_type_id(cls) -> CloudUIResponseTypeID: + return CloudUIResponseTypeID.UNKNOWN diff --git a/tools/bacommon/cloudui/v1.py b/tools/bacommon/cloudui/v1.py index a277956b3..574fbf9db 100644 --- a/tools/bacommon/cloudui/v1.py +++ b/tools/bacommon/cloudui/v1.py @@ -10,7 +10,74 @@ from efro.dataclassio import ioprepped, IOAttrs, IOMultiType -from bacommon.cloudui._cloudui import CloudUIPage, CloudUIPageTypeID +from bacommon.cloudui._cloudui import ( + CloudUIRequest, + CloudUIRequestTypeID, + CloudUIResponse, + CloudUIResponseTypeID, +) + + +class RequestMethod(Enum): + """Typeof of requests that can be made to cloud-ui servers.""" + + #: An unknown request method. This can appear if a newer client is + #: requesting some method from an older server that is not known to + #: the server. + UNKNOWN = 'u' + + #: Fetch some resource. This can be retried and its results can + #: optionally be cached for some amount of time. + GET = 'g' + + #: Change some resource. This cannot be implicitly retried (at least + #: without deduplication), nor can it be cached. + POST = 'p' + + +@ioprepped +@dataclass +class Request(CloudUIRequest): + """Full request to cloud-ui.""" + + path: Annotated[str, IOAttrs('p')] + method: Annotated[ + RequestMethod, + IOAttrs('m', store_default=False, enum_fallback=RequestMethod.UNKNOWN), + ] = RequestMethod.GET + params: Annotated[dict, IOAttrs('r', store_default=False)] = field( + default_factory=dict + ) + + @override + @classmethod + def get_type_id(cls) -> CloudUIRequestTypeID: + return CloudUIRequestTypeID.V1 + + +class TargetBehavior(Enum): + """How a cloud-ui request should be fulfilled.""" + + #: Default target - adds a new window to the nav stack and fulfills + #: the request there. + DEFAULT = 'd' + + #: Immediately replaces the contents of the current window with no + #: transitions; used for dynamic UIs. + REPLACE = 'r' + + #: Close the current window. Request is ignored. + CLOSE = 'c' + + +@ioprepped +@dataclass +class Target: + """Defines where and how a request should be fulfilled.""" + + behavior: Annotated[TargetBehavior, IOAttrs('b', store_default=False)] = ( + TargetBehavior.DEFAULT + ) class HAlign(Enum): @@ -103,8 +170,9 @@ class Text(Decoration): #: support. text: Annotated[str, IOAttrs('t')] position: Annotated[tuple[float, float], IOAttrs('p')] - max_width: Annotated[float, IOAttrs('w')] - max_height: Annotated[float, IOAttrs('h', store_default=False)] = 32.0 + + #: Note that this effectively is max-width and max-height. + size: Annotated[tuple[float, float], IOAttrs('z')] scale: Annotated[float, IOAttrs('s', store_default=False)] = 1.0 h_align: Annotated[HAlign, IOAttrs('ha', store_default=False)] = ( HAlign.CENTER @@ -115,6 +183,10 @@ class Text(Decoration): flatness: Annotated[float | None, IOAttrs('f', store_default=False)] = None shadow: Annotated[float | None, IOAttrs('sh', store_default=False)] = None + is_lstr: Annotated[bool, IOAttrs('l', store_default=False)] = False + + highlight: Annotated[bool, IOAttrs('h', store_default=False)] = True + #: Show max-width/height bounds; useful during development. debug: Annotated[bool, IOAttrs('d', store_default=False)] = False @@ -160,6 +232,7 @@ class Image(Decoration): mesh_transparent: Annotated[ str | None, IOAttrs('mn', store_default=False) ] = None + highlight: Annotated[bool, IOAttrs('h', store_default=False)] = True @override @classmethod @@ -170,12 +243,30 @@ def get_type_id(cls) -> DecorationTypeID: @ioprepped @dataclass class Button: - """A button in our cloud ui.""" + """A button in our cloud ui. + + Note that size, padding, and all decorations are scaled consistently + with 'scale'. + """ + + class Style(Enum): + """Styles a button can be.""" + + SQUARE = 'q' + TAB = 't' + SMALL = 's' + MEDIUM = 'm' + LARGE = 'l' + LARGER = 'xl' #: Note that cloud-ui accepts only raw :class:`str` values for text; #: use :meth:`babase.Lstr.evaluate()` or whatnot for multi-language #: support. label: Annotated[str | None, IOAttrs('l', store_default=False)] = None + + request: Annotated[Request | None, IOAttrs('r', store_default=False)] = None + target: Annotated[Target | None, IOAttrs('t', store_default=False)] = None + size: Annotated[ tuple[float, float] | None, IOAttrs('sz', store_default=False) ] = None @@ -193,7 +284,7 @@ class Button: text_scale: Annotated[float | None, IOAttrs('ts', store_default=False)] = ( None ) - texture: Annotated[str | None, IOAttrs('t', store_default=False)] = None + texture: Annotated[str | None, IOAttrs('tex', store_default=False)] = None scale: Annotated[float, IOAttrs('sc', store_default=False)] = 1.0 padding_left: Annotated[float, IOAttrs('pl', store_default=False)] = 0.0 padding_top: Annotated[float, IOAttrs('pt', store_default=False)] = 0.0 @@ -202,6 +293,10 @@ class Button: decorations: Annotated[ list[Decoration], IOAttrs('c', store_default=False) ] = field(default_factory=list) + text_is_lstr: Annotated[bool, IOAttrs('tl', store_default=False)] = False + style: Annotated[Style, IOAttrs('y', store_default=False)] = Style.SQUARE + + #: Draw bounds of the button. debug: Annotated[bool, IOAttrs('d', store_default=False)] = False @@ -225,6 +320,7 @@ class Row: title_shadow: Annotated[ float | None, IOAttrs('ts', store_default=False) ] = None + title_is_lstr: Annotated[bool, IOAttrs('tl', store_default=False)] = False subtitle: Annotated[str | None, IOAttrs('s', store_default=False)] = None subtitle_color: Annotated[ tuple[float, float, float, float] | None, @@ -236,6 +332,9 @@ class Row: subtitle_shadow: Annotated[ float | None, IOAttrs('ss', store_default=False) ] = None + subtitle_is_lstr: Annotated[bool, IOAttrs('sl', store_default=False)] = ( + False + ) button_spacing: Annotated[float, IOAttrs('bs', store_default=False)] = 5.0 padding_left: Annotated[float, IOAttrs('pl', store_default=False)] = 10.0 padding_right: Annotated[float, IOAttrs('pr', store_default=False)] = 10.0 @@ -249,12 +348,17 @@ class Row: 100.0 ) + #: Draw bounds of the overall row and individual button columns + #: (including padding). The UI will scroll to keep these areas + #: visible in their entirety when changing selection via directional + #: controls, so try to make sure all decorations for a button are + #: within these bounds. debug: Annotated[bool, IOAttrs('d', store_default=False)] = False @ioprepped @dataclass -class Page(CloudUIPage): +class Page: """Cloud-UI page version 1.""" #: Note that cloud-ui accepts only raw :class:`str` values for text; @@ -275,7 +379,28 @@ class Page(CloudUIPage): 100.0 ) + #: Whether the title is a json dict representing an Lstr. Generally + #: cloud-ui translation should be handled server-side, but this can + #: allow client-side translation. + title_is_lstr: Annotated[bool, IOAttrs('tl', store_default=False)] = False + + +class ResponseCode(Enum): + """The overall result of a request.""" + + SUCCESS = 0 + UNKNOWN_ERROR = 1 + + +@ioprepped +@dataclass +class Response(CloudUIResponse): + """Full cloudui response.""" + + code: Annotated[ResponseCode, IOAttrs('c')] + page: Annotated[Page, IOAttrs('p')] + @override @classmethod - def get_type_id(cls) -> CloudUIPageTypeID: - return CloudUIPageTypeID.V1 + def get_type_id(cls) -> CloudUIResponseTypeID: + return CloudUIResponseTypeID.V1 diff --git a/tools/bacommon/servermanager.py b/tools/bacommon/servermanager.py index 9a0f8d999..82a19a7e6 100644 --- a/tools/bacommon/servermanager.py +++ b/tools/bacommon/servermanager.py @@ -38,6 +38,14 @@ class ServerConfig: # settings->advanced->enter-code. admins: list[str] = field(default_factory=list) + # --- ADD THIS NEW LIST FOR YOUR TOKENS --- + # A list of secret tokens for server admins. + # The C++ code will read this list on startup. + admin_tokens: list[str] = field(default_factory=list) + + # Whether an admin can kick players directly. + enable_admins_kick: bool = True + # Whether the default kick-voting system is enabled. enable_default_kick_voting: bool = True diff --git a/tools/batools/build.py b/tools/batools/build.py index f2c49ff9e..7e06093eb 100644 --- a/tools/batools/build.py +++ b/tools/batools/build.py @@ -513,11 +513,11 @@ def _get_server_config_template_toml(projroot: str) -> str: # Override some defaults with dummy values we want to display # commented out instead. cfg.playlist_code = 12345 - cfg.stats_url = 'https://mystatssite.com/showstats?player=${ACCOUNT}' + cfg.stats_url = 'https://discord.gg/4SGKwxAhNh' cfg.clean_exit_minutes = 60 cfg.unclean_exit_minutes = 90 cfg.idle_exit_minutes = 20 - cfg.admins = ['pb-yOuRAccOuNtIdHErE', 'pb-aNdMayBeAnotherHeRE'] + cfg.admins = ['pb-IF4FP0co', 'pb-aNdMayBeAnotherHeRE'] cfg.protocol_version = 35 cfg.session_max_players_override = 8 cfg.playlist_inline = [] diff --git a/tools/batools/staging.py b/tools/batools/staging.py index acc2bf65b..9fdda459f 100755 --- a/tools/batools/staging.py +++ b/tools/batools/staging.py @@ -854,6 +854,13 @@ def _sync_server_files(self) -> None: ), ), ) + _stage_server_file( + projroot=self.projroot, + mode=modeval, + infilename=f'{self.projroot}/src/assets/server_package/' + 'requirements.txt', + outfilename=os.path.join(self.serverdst, 'requirements.txt'), + ) _stage_server_file( projroot=self.projroot, mode=modeval, @@ -978,6 +985,10 @@ def _stage_server_file( _write_if_changed( outfilename, '\n'.join(lines) + '\n', make_executable=True ) + elif basename == 'requirements.txt': + with open(infilename, encoding='utf-8') as infile: + reqs = infile.read() + _write_if_changed(outfilename, reqs) elif basename == 'README.txt': with open(infilename, encoding='utf-8') as infile: readme = infile.read()