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 @@
-
-
-***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) [](https://github.com/efroemling/ballistica/actions/workflows/cd.yml) [](https://github.com/efroemling/ballistica/actions/workflows/nightly.yml) [](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) [](https://github.com/HeyFang/bombsquad_server_utils/actions/workflows/cd.yml) [](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()