diff --git a/.efrocachemap b/.efrocachemap index b215deb1..8a824167 100644 --- a/.efrocachemap +++ b/.efrocachemap @@ -420,20 +420,20 @@ "assets/build/ba_data/audio/zoeOw.ogg": "https://files.ballistica.net/cache/ba1/04/0a/c4f7d2794b018593ab0b2bcb07f0", "assets/build/ba_data/audio/zoePickup01.ogg": "https://files.ballistica.net/cache/ba1/06/4d/18777c9a2eb2207a2891a2837a70", "assets/build/ba_data/audio/zoeScream01.ogg": "https://files.ballistica.net/cache/ba1/81/90/23ab1ecc8c55267bd904a9c05344", - "assets/build/ba_data/data/langdata.json": "https://files.ballistica.net/cache/ba1/48/27/1b912861ed8d8795add395df41b6", + "assets/build/ba_data/data/langdata.json": "https://files.ballistica.net/cache/ba1/05/20/767f42e4f0f289389cbdfb61eb10", "assets/build/ba_data/data/languages/arabic.json": "https://files.ballistica.net/cache/ba1/de/25/74be4875c2a0e22b813a4e1a103b", - "assets/build/ba_data/data/languages/belarussian.json": "https://files.ballistica.net/cache/ba1/49/5f/b29bb65369040892fe6601801637", - "assets/build/ba_data/data/languages/chinese.json": "https://files.ballistica.net/cache/ba1/c3/3f/c37ac3c65ac65f171af9313a502a", - "assets/build/ba_data/data/languages/chinesetraditional.json": "https://files.ballistica.net/cache/ba1/5a/9e/e8cad6f08b2b19803ab20fdc80d0", + "assets/build/ba_data/data/languages/belarussian.json": "https://files.ballistica.net/cache/ba1/c1/2b/54aeb92c709c4af443f4a9013b3d", + "assets/build/ba_data/data/languages/chinese.json": "https://files.ballistica.net/cache/ba1/69/cc/f8bdd1e83162481c6bf2a78cb5e0", + "assets/build/ba_data/data/languages/chinesetraditional.json": "https://files.ballistica.net/cache/ba1/7c/b5/ddf2aedf7a7821b134d3663ae320", "assets/build/ba_data/data/languages/croatian.json": "https://files.ballistica.net/cache/ba1/56/02/c22deb7174aabdcbffe1da23e484", "assets/build/ba_data/data/languages/czech.json": "https://files.ballistica.net/cache/ba1/97/6c/61de67c477c98b7c4fddd22e6ae0", "assets/build/ba_data/data/languages/danish.json": "https://files.ballistica.net/cache/ba1/3f/46/e4da3c1d2b0ebf916df55c608b28", "assets/build/ba_data/data/languages/dutch.json": "https://files.ballistica.net/cache/ba1/d1/07/37b7adc3dbec7328d26c5325f212", - "assets/build/ba_data/data/languages/english.json": "https://files.ballistica.net/cache/ba1/d4/5b/fbd64cf1846340db0606824568c2", + "assets/build/ba_data/data/languages/english.json": "https://files.ballistica.net/cache/ba1/3b/0d/eb66b073295cacde00e5d054dbae", "assets/build/ba_data/data/languages/esperanto.json": "https://files.ballistica.net/cache/ba1/6e/fd/685a4e1da031474d47a1d9eb2731", "assets/build/ba_data/data/languages/french.json": "https://files.ballistica.net/cache/ba1/e3/6d/63e78b12ae9d99e0929671f52c12", "assets/build/ba_data/data/languages/german.json": "https://files.ballistica.net/cache/ba1/3f/62/4eedf8cfad2e5f7ff2136ec19277", - "assets/build/ba_data/data/languages/gibberish.json": "https://files.ballistica.net/cache/ba1/97/3a/b6746b7f89af424584c794c4d11f", + "assets/build/ba_data/data/languages/gibberish.json": "https://files.ballistica.net/cache/ba1/93/31/66df9784a606600b7933bcb8ffa1", "assets/build/ba_data/data/languages/greek.json": "https://files.ballistica.net/cache/ba1/a4/2c/8c43b338be2367441b87378e19c1", "assets/build/ba_data/data/languages/hindi.json": "https://files.ballistica.net/cache/ba1/d5/19/5e450e35b83fe68722330d03b896", "assets/build/ba_data/data/languages/hungarian.json": "https://files.ballistica.net/cache/ba1/87/2d/027aa239eb66ea8f496562f4fd83", @@ -442,12 +442,12 @@ "assets/build/ba_data/data/languages/korean.json": "https://files.ballistica.net/cache/ba1/0a/84/bbb6ed2abf66509406f534cbbb52", "assets/build/ba_data/data/languages/persian.json": "https://files.ballistica.net/cache/ba1/1e/67/f8d1d6579698c10af9da2ecb62d9", "assets/build/ba_data/data/languages/polish.json": "https://files.ballistica.net/cache/ba1/88/4b/6745a1a58220772e259f0de51196", - "assets/build/ba_data/data/languages/portuguese.json": "https://files.ballistica.net/cache/ba1/42/b5/7612cce15fe4555889585108b3ef", + "assets/build/ba_data/data/languages/portuguese.json": "https://files.ballistica.net/cache/ba1/59/49/d75f2b9916541bd2be8c49a5171f", "assets/build/ba_data/data/languages/romanian.json": "https://files.ballistica.net/cache/ba1/44/3c/7cc06ca8d5475e1687d0ed05bdbf", "assets/build/ba_data/data/languages/russian.json": "https://files.ballistica.net/cache/ba1/d4/bb/69c09648f60e36f35bd38be20cf8", "assets/build/ba_data/data/languages/serbian.json": "https://files.ballistica.net/cache/ba1/e7/d8/ace32888249fc8b8cca0e2edb48b", "assets/build/ba_data/data/languages/slovak.json": "https://files.ballistica.net/cache/ba1/b7/0a/fab820b96e7aa587ee56427ecdc2", - "assets/build/ba_data/data/languages/spanish.json": "https://files.ballistica.net/cache/ba1/32/0e/ac0b8dcef065d7934a6bc30d7560", + "assets/build/ba_data/data/languages/spanish.json": "https://files.ballistica.net/cache/ba1/2a/35/eab086a95c41287fa0a856e59fb6", "assets/build/ba_data/data/languages/swedish.json": "https://files.ballistica.net/cache/ba1/50/9f/be006ba19be6a69a57837eb6dca0", "assets/build/ba_data/data/languages/turkish.json": "https://files.ballistica.net/cache/ba1/9a/8a/f7b2521c1904ffc83262dff1e11b", "assets/build/ba_data/data/languages/ukrainian.json": "https://files.ballistica.net/cache/ba1/26/62/a072404c02c576a5c3f09059b582", @@ -3932,24 +3932,24 @@ "assets/build/windows/Win32/ucrtbased.dll": "https://files.ballistica.net/cache/ba1/b5/85/f8b6d0558ddb87267f34254b1450", "assets/build/windows/Win32/vc_redist.x86.exe": "https://files.ballistica.net/cache/ba1/1c/e1/4a1a2eddda2f4aebd5f8b64ab08e", "assets/build/windows/Win32/vcruntime140d.dll": "https://files.ballistica.net/cache/ba1/50/8d/bc2600ac9491f1b14d659709451f", - "build/prefab/full/linux_x86_64/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/57/c4/65b46cdf99edf156347ed3381223", - "build/prefab/full/linux_x86_64/release/ballisticacore": "https://files.ballistica.net/cache/ba1/24/06/49b1e54e764a57c0c925cd48d71e", - "build/prefab/full/linux_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/b1/e9/12e2965cd54d3cbc02f795222441", - "build/prefab/full/linux_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/94/e8/4b12bc80273d1527e2a8210cf090", - "build/prefab/full/mac_x86_64/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/b3/f2/25d7adfb561752522269fe0677e7", - "build/prefab/full/mac_x86_64/release/ballisticacore": "https://files.ballistica.net/cache/ba1/e4/37/aa7613de3f87a18a5f981f73786a", - "build/prefab/full/mac_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/de/7a/54548afb27b6027ff7c10c41838f", - "build/prefab/full/mac_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/6b/f3/8e66e8bfdc5ca8fbe5b61838c7b8", - "build/prefab/full/windows_x86/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/a0/af/d65a065f6da183970c71c2f479d7", - "build/prefab/full/windows_x86/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/38/bc/dc1d1354f9a81f9e0120a3de63a7", - "build/prefab/full/windows_x86_server/debug/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/da/9a/0c9617ad9d98f912db70b91dfd20", - "build/prefab/full/windows_x86_server/release/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/53/28/8e4c340f2accc1cf3dfb600a2981", + "build/prefab/full/linux_x86_64/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/29/71/f22b8e8297b5fcda6a055501dd9a", + "build/prefab/full/linux_x86_64/release/ballisticacore": "https://files.ballistica.net/cache/ba1/e3/4b/cd188b8da52207463fdeb6176704", + "build/prefab/full/linux_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/10/52/435cd568d1690c023b8c1aebafff", + "build/prefab/full/linux_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/6d/b7/57677f9d991f046f6b784bdd6321", + "build/prefab/full/mac_x86_64/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/0c/77/4c9ff1fa0630936563f0f7c9fee4", + "build/prefab/full/mac_x86_64/release/ballisticacore": "https://files.ballistica.net/cache/ba1/80/25/43012563e7b0db4a76c431fcbb40", + "build/prefab/full/mac_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/b3/e5/e2da42b1e5a6031487333c72da70", + "build/prefab/full/mac_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/e1/89/0491751bb4d33a3d3ffc758a6b20", + "build/prefab/full/windows_x86/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/3a/c5/623f2be3c9f061941c0ed1543588", + "build/prefab/full/windows_x86/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/47/54/9f7775a0cdf008be499e80d62a8f", + "build/prefab/full/windows_x86_server/debug/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/2b/29/060806ab4ed3b47f3890b14a36e9", + "build/prefab/full/windows_x86_server/release/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/e4/7e/943f5f6a581e4fccfe763b20fd30", "build/prefab/lib/linux_x86_64/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/80/d2/d1c6766cf07f4c4828bbd5899f79", "build/prefab/lib/linux_x86_64/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/de/49/2cfc34ac856737d903954db5571b", "build/prefab/lib/linux_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/17/9a/fa66aafdf31fc9bdd92ce382c619", "build/prefab/lib/linux_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/86/de/7d9c9a2b7bba34c630130ed759c9", - "build/prefab/lib/mac_x86_64/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/7c/71/59aa912540741d36fca95baf0cdf", - "build/prefab/lib/mac_x86_64/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/f7/0e/da229849278095b6dcf26432909a", - "build/prefab/lib/mac_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/58/e2/e78c6993c58dae2a94aa1c36139b", - "build/prefab/lib/mac_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/8b/28/742e9c33d64ceeb5e1ff87da5577" + "build/prefab/lib/mac_x86_64/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/11/ca/ef8dc06a074606894db186954d49", + "build/prefab/lib/mac_x86_64/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/24/4d/f64b747dbea2588e6ffe8d3e24e8", + "build/prefab/lib/mac_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/35/5f/e588c08ae05abeb233bb803c695b", + "build/prefab/lib/mac_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/c6/a4/77a6648afb78e6b452bcccad2c8e" } \ No newline at end of file diff --git a/.idea/dictionaries/ericf.xml b/.idea/dictionaries/ericf.xml index 533c9d1d..c695c388 100644 --- a/.idea/dictionaries/ericf.xml +++ b/.idea/dictionaries/ericf.xml @@ -30,8 +30,8 @@ achname achs acinstance - ack ack'ed + ack acked acks acnt @@ -153,8 +153,8 @@ bacommon badguy bafoundation - ballistica ballistica's + ballistica ballisticacore ballisticacorecb bamaster @@ -703,6 +703,7 @@ filterlines filterpath filterpaths + filterval finalhash finalmaterials finfo @@ -801,8 +802,8 @@ gamedata gameinstance gamemap - gamepad gamepad's + gamepad gamepadadvanced gamepads gamepadselect @@ -1191,8 +1192,8 @@ lsqlite lssl lstart - lstr lstr's + lstr lstrs lsval ltex @@ -1828,8 +1829,8 @@ sessionname sessionplayer sessionplayers - sessionteam sessionteam's + sessionteam sessionteams sessiontype setactivity @@ -2164,8 +2165,8 @@ txtw typeargs typecheck - typechecker typechecker's + typechecker typedval typeshed typestr diff --git a/CHANGELOG.md b/CHANGELOG.md index 689009c4..cdb2b58c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -### 1.5.27 (20224) +### 1.5.27 (20231) - Language functionality has been consolidated into a LanguageSubsystem object at ba.app.lang - ba.get_valid_languages() is now an attr: ba.app.lang.available_languages - Achievement functionality has been consolidated into an AchievementSubsystem object at ba.app.ach @@ -9,6 +9,8 @@ - Revamped tab-button functionality into a cleaner type-safe class (bastd.ui.tabs.TabRow) - Split Gather-Window tabs out into individual classes for future improvements (bastd.ui.gather.*) - Added the ability to disable ticket-purchasing UIs for builds (ba.app.allow_ticket_purchases) +- Reworked the public party gather section to perform better; it should no longer have to rebuild the list from scratch each time the UI is visited. +- Added a filter option to the public party list (sorry it has taken so long). ### 1.5.26 (20217) - Simplified licensing header on python scripts. diff --git a/README.md b/README.md index 162a1e9d..e84378f9 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ The Ballistica project is the foundation for the next generation of [BombSquad]( * A: No, BombSquad is still BombSquad. 'Ballistica' is simply the new name for the engine/app-framework. This way it can also be used for other game/app projects without causing confusion (though that is mostly theoretical at this point). As a modder, the biggest changes you will notice is 'ba' prefixes in the API instead of 'bs' and naming that follows Python PEP8 standards (underscores and lowercase instead of camel-case). So `bs.playSound(mySound)` in the old system might look like `ba.playsound(my_sound)` in the new. You may also see the word 'BallisticaCore' show up various places, which in actual releases gets replaced by 'BombSquad'. * **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, etc. Over time I hope to expand this to include at least some 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 game binaries. So in a nutshell: create and share mods to your heart's content, but please don't distribute your own complete copies of the game without permission. Please email support@froemling.net if you have any questions about this. +* 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 game libraries/binaries. So in a nutshell: create and share mods to your heart's content, but please don't distribute your own complete copies of the game without permission. Please email support@froemling.net if you have any questions about this. * **Q: Will my existing BombSquad 1.4.x mods still work?** * A: Not 'out of the box'. All mods will need to be explicitly updated to work with the new ballistica apis in 1.5+. This may or may not be a significant amount of work depending on the mod. I would highly suggest tinkering around with some of the new features in 1.5 such as type-safe Python and dynamic assets before attempting to port any old mods, as some things are done significantly differently now. You may also want to consider simply sticking with 1.4 builds for a while longer, especially for server duties, since they will remain fully compatible with clients running 1.5. The new ballistica APIs may be changing significantly for at least a while as the dust settles, but they will be worth switching to in the end, I promise! diff --git a/assets/src/ba_data/python/_ba.py b/assets/src/ba_data/python/_ba.py index 0640f6fe..0db90ad6 100644 --- a/assets/src/ba_data/python/_ba.py +++ b/assets/src/ba_data/python/_ba.py @@ -2622,6 +2622,7 @@ def hscrollwidget(edit: ba.Widget = None, border_opacity: float = None, simple_culling_h: float = None, claims_left_right: bool = None, + claims_up_down: bool = None, claims_tab: bool = None) -> ba.Widget: """hscrollwidget(edit: ba.Widget = None, parent: ba.Widget = None, size: Sequence[float] = None, position: Sequence[float] = None, @@ -2632,6 +2633,7 @@ def hscrollwidget(edit: ba.Widget = None, highlight: bool = None, border_opacity: float = None, simple_culling_h: float = None, claims_left_right: bool = None, + claims_up_down: bool = None, claims_tab: bool = None) -> ba.Widget Create or edit a horizontal scroll widget. @@ -3388,6 +3390,7 @@ def scrollwidget(edit: ba.Widget = None, simple_culling_v: float = None, selection_loops_to_parent: bool = None, claims_left_right: bool = None, + claims_up_down: bool = None, claims_tab: bool = None, autoselect: bool = None) -> ba.Widget: """scrollwidget(edit: ba.Widget = None, parent: ba.Widget = None, @@ -3399,6 +3402,7 @@ def scrollwidget(edit: ba.Widget = None, simple_culling_v: float = None, selection_loops_to_parent: bool = None, claims_left_right: bool = None, + claims_up_down: bool = None, claims_tab: bool = None, autoselect: bool = None) -> ba.Widget diff --git a/assets/src/ba_data/python/bastd/mainmenu.py b/assets/src/ba_data/python/bastd/mainmenu.py index 9697e521..c7670156 100644 --- a/assets/src/ba_data/python/bastd/mainmenu.py +++ b/assets/src/ba_data/python/bastd/mainmenu.py @@ -45,6 +45,7 @@ class MainMenuActivity(ba.Activity[ba.Player, ba.Team]): if not ba.app.toolbar_test: color = ((1.0, 1.0, 1.0, 1.0) if vr_mode else (0.5, 0.6, 0.5, 0.6)) + # FIXME: Need a node attr for vr-specific-scale. scale = (0.9 if (app.ui.uiscale is ba.UIScale.SMALL or vr_mode) else 0.7) diff --git a/assets/src/ba_data/python/bastd/ui/gather/publictab.py b/assets/src/ba_data/python/bastd/ui/gather/publictab.py index 383cb02a..eb669893 100644 --- a/assets/src/ba_data/python/bastd/ui/gather/publictab.py +++ b/assets/src/ba_data/python/bastd/ui/gather/publictab.py @@ -20,6 +20,7 @@ if TYPE_CHECKING: from typing import Callable, Any, Optional, Dict, Union, Tuple, List from bastd.ui.gather import GatherWindow +# Print a bit of info about pings, queries, etc. DEBUG_SERVER_COMMUNICATION = False @@ -58,6 +59,7 @@ class State: sub_tab: SubTabType = SubTabType.JOIN parties: Optional[List[PartyEntry]] = None next_entry_index: int = 0 + filter_value: str = '' class SelectionComponent(Enum): @@ -186,6 +188,7 @@ class PublicGatherTab(GatherTab): self._container: Optional[ba.Widget] = None self._join_text: Optional[ba.Widget] = None self._host_text: Optional[ba.Widget] = None + self._filter_text: Optional[ba.Widget] = None self._local_address: Optional[str] = None self._last_connect_attempt_time: Optional[float] = None self._sub_tab: SubTabType = SubTabType.JOIN @@ -207,7 +210,8 @@ class PublicGatherTab(GatherTab): self._first_server_list_rebuild_time: Optional[float] = None self._next_entry_index = 0 self._have_valid_server_list = False - self._built_join_list = False + self._server_list_dirty = True + self._filter_value = '' def on_activate( self, @@ -278,7 +282,6 @@ class PublicGatherTab(GatherTab): if self._local_address is None: AddrFetchThread(ba.WeakCall(self._fetch_local_addr_cb)).start() - assert self._sub_tab is not None self._set_sub_tab(self._sub_tab, region_width, region_height) self._update_timer = ba.Timer(0.2, ba.WeakCall(self._update), @@ -292,13 +295,15 @@ class PublicGatherTab(GatherTab): def save_state(self) -> None: - # Save off a small number of parties with the lowest ping; this - # should be most of the ones that matter and will keep things - # a reasonable size. + # Save off a small number of parties with the lowest ping; we'll + # display these immediately when our UI comes back up which should + # be enough to make things feel nice and crisp while we do a full + # server re-query or whatnot. ba.app.ui.window_states[self.__class__.__name__] = State( sub_tab=self._sub_tab, - parties=[copy.copy(p) for p in self._get_ordered_parties()[:20]], - next_entry_index=self._next_entry_index) + parties=[copy.copy(p) for p in self._get_ordered_parties()[:40]], + next_entry_index=self._next_entry_index, + filter_value=self._filter_value) def restore_state(self) -> None: state = ba.app.ui.window_states.get(self.__class__.__name__) @@ -307,7 +312,7 @@ class PublicGatherTab(GatherTab): assert isinstance(state, State) self._sub_tab = state.sub_tab - # Restore the parties we stored... + # Restore the parties we stored. if state.parties: self._parties = { f'{p.address}_{p.port}': copy.copy(p) @@ -315,6 +320,7 @@ class PublicGatherTab(GatherTab): } self._next_entry_index = state.next_entry_index self._have_valid_server_list = True + self._filter_value = state.filter_value def _set_sub_tab(self, value: SubTabType, @@ -346,16 +352,9 @@ class PublicGatherTab(GatherTab): if widget and widget not in {self._host_text, self._join_text}: widget.delete() - c_width = region_width - c_height = region_height - 20 - sub_scroll_height = c_height - 90 - sub_scroll_width = 830 - v = c_height - 35 - v -= 25 if value is SubTabType.JOIN: - self._build_join_tab(v, sub_scroll_width, sub_scroll_height, - c_width, c_height) - self._built_join_list = False + self._build_join_tab(region_width, region_height) + self._server_list_dirty = True # If we've not yet successfully fetched a server list, # force an attempt now and show the user a 'loading...' status. @@ -372,12 +371,36 @@ class PublicGatherTab(GatherTab): ba.textwidget(edit=self._join_status_text, text=join_status_str) if value is SubTabType.HOST: - self._build_host_tab(v, sub_scroll_width, sub_scroll_height, - c_width, c_height) + self._build_host_tab(region_width, region_height) + + def _build_join_tab(self, region_width: float, + region_height: float) -> None: + c_width = region_width + c_height = region_height - 20 + sub_scroll_height = c_height - 125 + sub_scroll_width = 830 + v = c_height - 35 + v -= 60 + self._filter_text = ba.textwidget(parent=self._container, + text=self._filter_value, + size=(350, 45), + position=(290, v - 10), + h_align='left', + v_align='center', + editable=True) + ba.widget(edit=self._filter_text, up_widget=self._join_text) + ba.textwidget(text=ba.Lstr(resource='filterText'), + parent=self._container, + size=(0, 0), + position=(270, v + 15), + maxwidth=100, + scale=0.8, + color=(0.5, 0.5, 0.5), + flatness=1.0, + shadow=0.0, + h_align='right', + v_align='center') - def _build_join_tab(self, v: float, sub_scroll_width: float, - sub_scroll_height: float, c_width: float, - c_height: float) -> None: ba.textwidget(text=ba.Lstr(resource='nameText'), parent=self._container, size=(0, 0), @@ -412,19 +435,18 @@ class PublicGatherTab(GatherTab): h_align='center', v_align='center') v -= sub_scroll_height + 23 - self._host_scrollwidget = scrollw = ba.scrollwidget( parent=self._container, simple_culling_v=10, position=((c_width - sub_scroll_width) * 0.5, v), size=(sub_scroll_width, sub_scroll_height), + claims_up_down=False, claims_left_right=True, autoselect=True) self._join_list_column = ba.containerwidget(parent=scrollw, background=False, size=(400, 400), claims_left_right=True) - self._join_status_text = ba.textwidget(parent=self._container, text='', size=(0, 0), @@ -438,10 +460,12 @@ class PublicGatherTab(GatherTab): position=(c_width * 0.5, c_height * 0.5)) - def _build_host_tab(self, v: float, sub_scroll_width: float, - sub_scroll_height: float, c_width: float, - c_height: float) -> None: - del sub_scroll_width, sub_scroll_height, c_height # Unused. + def _build_host_tab(self, region_width: float, + region_height: float) -> None: + c_width = region_width + c_height = region_height - 20 + v = c_height - 35 + v -= 25 is_public_enabled = _ba.get_public_party_enabled() v -= 30 party_name_text = ba.Lstr( @@ -584,7 +608,7 @@ class PublicGatherTab(GatherTab): since_first = cur_time - self._first_server_list_rebuild_time wait_time = (1.0 if since_first < 2.0 else 2.5 if since_first < 10.0 else 5.0) - if (self._built_join_list + if (not self._server_list_dirty and self._last_server_list_update_time is not None and cur_time - self._last_server_list_update_time < wait_time): return @@ -595,7 +619,7 @@ class PublicGatherTab(GatherTab): return self._last_server_list_update_time = cur_time - self._built_join_list = True + self._server_list_dirty = False with ba.Context('ui'): @@ -605,6 +629,14 @@ class PublicGatherTab(GatherTab): ordered_parties = self._get_ordered_parties() + # If we've got a filter, filter them. + if self._filter_value: + # Let's do case-insensitive searching. + filterval = self._filter_value.lower() + ordered_parties = [ + p for p in ordered_parties if filterval in p.name.lower() + ] + sub_scroll_width = 830 lineheight = 42 sub_scroll_height = lineheight * len(ordered_parties) + 50 @@ -699,10 +731,10 @@ class PublicGatherTab(GatherTab): if first: if party.stats_button: ba.widget(edit=party.stats_button, - up_widget=self._join_text) + up_widget=self._filter_text) if party.name_widget: ba.widget(edit=party.name_widget, - up_widget=self._join_text) + up_widget=self._filter_text) first = False party.size_widget = ba.textwidget( @@ -820,54 +852,70 @@ class PublicGatherTab(GatherTab): _ba.set_public_party_name(name) if self._sub_tab is SubTabType.JOIN: - now = ba.time(ba.TimeType.REAL) - # Fire off a new public-party query periodically. - if (self._last_server_list_query_time is None - or now - self._last_server_list_query_time > 0.001 * - _ba.get_account_misc_read_val('pubPartyRefreshMS', 10000)): - self._last_server_list_query_time = now + # If our filter value has changed, refresh the list + # using the new one. + text = self._filter_text + if text: + filter_value = cast(str, ba.textwidget(query=text)) + if filter_value != self._filter_value: + self._filter_value = filter_value + self._server_list_dirty = True + self._update_server_list() + + self._query_party_list_periodically() + self._ping_parties_periodically() + + def _query_party_list_periodically(self) -> None: + now = ba.time(ba.TimeType.REAL) + + # Fire off a new public-party query periodically. + if (self._last_server_list_query_time is None + or now - self._last_server_list_query_time > 0.001 * + _ba.get_account_misc_read_val('pubPartyRefreshMS', 10000)): + self._last_server_list_query_time = now + if DEBUG_SERVER_COMMUNICATION: + print('REQUESTING SERVER LIST') + _ba.add_transaction( + { + 'type': 'PUBLIC_PARTY_QUERY', + 'proto': ba.app.protocol_version, + 'lang': ba.app.lang.language + }, + callback=ba.WeakCall(self._on_public_party_query_result)) + _ba.run_transactions() + + def _ping_parties_periodically(self) -> None: + now = ba.time(ba.TimeType.REAL) + + # Go through our existing public party entries firing off pings + # for any that have timed out. + for party in list(self._parties.values()): + if party.next_ping_time <= now and ba.app.ping_thread_count < 15: + + # Crank the interval up for high-latency or non-responding + # parties to save us some useless work. + mult = 1 + if party.ping_responses == 0: + if party.ping_attempts > 4: + mult = 10 + elif party.ping_attempts > 2: + mult = 5 + if party.ping is not None: + mult = (10 if party.ping > 300 else + 5 if party.ping > 150 else 2) + + interval = party.ping_interval * mult if DEBUG_SERVER_COMMUNICATION: - print('REQUESTING SERVER LIST') - _ba.add_transaction( - { - 'type': 'PUBLIC_PARTY_QUERY', - 'proto': ba.app.protocol_version, - 'lang': ba.app.lang.language - }, - callback=ba.WeakCall(self._on_public_party_query_result)) - _ba.run_transactions() + print(f'pinging #{party.index} cur={party.ping} ' + f'interval={interval} ' + f'({party.ping_responses}/{party.ping_attempts})') - # Go through our existing public party entries firing off pings - # for any that have timed out. - for party in list(self._parties.values()): - if (party.next_ping_time <= now - and ba.app.ping_thread_count < 15): + party.next_ping_time = now + party.ping_interval * mult + party.ping_attempts += 1 - # Crank the interval up for high-latency or non-responding - # parties to save us some useless work. - mult = 1 - if party.ping_responses == 0: - if party.ping_attempts > 4: - mult = 10 - elif party.ping_attempts > 2: - mult = 5 - if party.ping is not None: - mult = (10 if party.ping > 300 else - 5 if party.ping > 150 else 2) - - interval = party.ping_interval * mult - if DEBUG_SERVER_COMMUNICATION: - print( - f'pinging #{party.index} cur={party.ping} ' - f'interval={interval} ' - f'({party.ping_responses}/{party.ping_attempts})') - - party.next_ping_time = now + party.ping_interval * mult - party.ping_attempts += 1 - - PingThread(party.address, party.port, - ba.WeakCall(self._ping_callback)).start() + PingThread(party.address, party.port, + ba.WeakCall(self._ping_callback)).start() def _ping_callback(self, address: str, port: Optional[int], result: Optional[int]) -> None: diff --git a/assets/src/ba_data/python/bastd/ui/mainmenu.py b/assets/src/ba_data/python/bastd/ui/mainmenu.py index 3741acca..80ef7f95 100644 --- a/assets/src/ba_data/python/bastd/ui/mainmenu.py +++ b/assets/src/ba_data/python/bastd/ui/mainmenu.py @@ -38,8 +38,11 @@ class MainMenuWindow(ba.Window): toolbar_visibility='menu_minimal_no_back' if self. _in_game else 'menu_minimal_no_back')) + # Grab this stuff in case it changes. self._is_demo = ba.app.demo_mode self._is_arcade = ba.app.arcade_mode + self._is_iircade = ba.app.iircade_mode + self._tdelay = 0.0 self._t_delay_inc = 0.02 self._t_delay_play = 1.7 @@ -179,7 +182,7 @@ class MainMenuWindow(ba.Window): self._have_settings_button = ( (not self._in_game or not app.toolbar_test) - and not (self._is_demo or self._is_arcade)) + and not (self._is_demo or self._is_arcade or self._is_iircade)) self._input_device = input_device = _ba.get_ui_input_device() self._input_player = input_device.player if input_device else None @@ -436,7 +439,9 @@ class MainMenuWindow(ba.Window): account_type_icon_color = (1.0, 1.0, 1.0) account_type_call = self._show_account_window account_type_enable_button_sound = True - b_count = 4 # play, help, credits, settings + b_count = 3 # play, help, credits + if self._have_settings_button: + b_count += 1 if enable_account_button: b_count += 1 if self._have_quit_button: diff --git a/assets/src/ba_data/python/bastd/ui/settings/allsettings.py b/assets/src/ba_data/python/bastd/ui/settings/allsettings.py index cd987740..32c3e3e8 100644 --- a/assets/src/ba_data/python/bastd/ui/settings/allsettings.py +++ b/assets/src/ba_data/python/bastd/ui/settings/allsettings.py @@ -209,15 +209,6 @@ class AllSettingsWindow(ba.Window): # pylint: disable=cyclic-import from bastd.ui.settings.controls import ControlsSettingsWindow self._save_state() - - # Disallow this on iircade. - if ba.app.iircade_mode: - ba.playsound(ba.getsound('error')) - ba.screenmessage( - ba.Lstr(resource='getTicketsWindow.unavailableText'), - color=(1, 0, 0)) - return - ba.containerwidget(edit=self._root_widget, transition='out_left') ba.app.ui.set_main_menu_window( ControlsSettingsWindow( @@ -227,15 +218,6 @@ class AllSettingsWindow(ba.Window): # pylint: disable=cyclic-import from bastd.ui.settings.graphics import GraphicsSettingsWindow self._save_state() - - # Disallow this on iircade. - if ba.app.iircade_mode: - ba.playsound(ba.getsound('error')) - ba.screenmessage( - ba.Lstr(resource='getTicketsWindow.unavailableText'), - color=(1, 0, 0)) - return - ba.containerwidget(edit=self._root_widget, transition='out_left') ba.app.ui.set_main_menu_window( GraphicsSettingsWindow( diff --git a/ballisticacore-cmake/.idea/dictionaries/ericf.xml b/ballisticacore-cmake/.idea/dictionaries/ericf.xml index 62684921..de0b1de7 100644 --- a/ballisticacore-cmake/.idea/dictionaries/ericf.xml +++ b/ballisticacore-cmake/.idea/dictionaries/ericf.xml @@ -13,8 +13,8 @@ ack'ed acked acks - aclass aclass's + aclass activityplayer addrs adjoint @@ -146,8 +146,8 @@ cmath cmds cmdvals - codewarrior codewarrior's + codewarrior cofnodes collapseable collidable @@ -287,6 +287,7 @@ fffffffffifff fgets fifteenbits + filterval finishedptr fjco fjcoiwef diff --git a/docs/ba_module.md b/docs/ba_module.md index 7f09f540..a8dc9b91 100644 --- a/docs/ba_module.md +++ b/docs/ba_module.md @@ -1,5 +1,5 @@ -

last updated on 2020-10-27 for Ballistica version 1.5.27 build 20228

+

last updated on 2020-10-29 for Ballistica version 1.5.27 build 20230

This page documents the Python classes and functions in the 'ba' module, which are the ones most relevant to modding in Ballistica. If you come across something you feel should be included here or could be better explained, please let me know. Happy modding!


@@ -6760,6 +6760,7 @@ in the background if necessary.

highlight: bool = None, border_opacity: float = None, simple_culling_h: float = None, claims_left_right: bool = None, + claims_up_down: bool = None, claims_tab: bool = None) -> ba.Widget

Create or edit a horizontal scroll widget.

@@ -7036,6 +7037,7 @@ Currently the 'clients' option only works for transient messages.

simple_culling_v: float = None, selection_loops_to_parent: bool = None, claims_left_right: bool = None, + claims_up_down: bool = None, claims_tab: bool = None, autoselect: bool = None) -> ba.Widget

diff --git a/src/ballistica/ballistica.cc b/src/ballistica/ballistica.cc index 0f20126f..fef28f63 100644 --- a/src/ballistica/ballistica.cc +++ b/src/ballistica/ballistica.cc @@ -21,7 +21,7 @@ namespace ballistica { // These are set automatically via script; don't change here. -const int kAppBuildNumber = 20229; +const int kAppBuildNumber = 20232; const char* kAppVersion = "1.5.27"; // Our standalone globals. diff --git a/src/ballistica/python/methods/python_methods_ui.cc b/src/ballistica/python/methods/python_methods_ui.cc index b1f032de..388b8c42 100644 --- a/src/ballistica/python/methods/python_methods_ui.cc +++ b/src/ballistica/python/methods/python_methods_ui.cc @@ -1179,6 +1179,7 @@ auto PyScrollWidget(PyObject* self, PyObject* args, PyObject* keywds) PyObject* simple_culling_v_obj{Py_None}; PyObject* selection_loops_to_parent_obj{Py_None}; PyObject* claims_left_right_obj{Py_None}; + PyObject* claims_up_down_obj{Py_None}; PyObject* claims_tab_obj{Py_None}; PyObject* autoselect_obj{Py_None}; @@ -1197,18 +1198,19 @@ auto PyScrollWidget(PyObject* self, PyObject* args, PyObject* keywds) "simple_culling_v", "selection_loops_to_parent", "claims_left_right", + "claims_up_down", "claims_tab", "autoselect", nullptr}; if (!PyArg_ParseTupleAndKeywords( - args, keywds, "|OOOOOOOOOOOOOOOOO", const_cast(kwlist), + args, keywds, "|OOOOOOOOOOOOOOOOOO", const_cast(kwlist), &edit_obj, &parent_obj, &size_obj, &pos_obj, &background_obj, &selected_child_obj, &capture_arrows_obj, &on_select_call_obj, ¢er_small_content_obj, &color_obj, &highlight_obj, &border_opacity_obj, &simple_culling_v_obj, &selection_loops_to_parent_obj, &claims_left_right_obj, - &claims_tab_obj, &autoselect_obj)) + &claims_up_down_obj, &claims_tab_obj, &autoselect_obj)) return nullptr; if (!g_game->IsInUIContext()) { @@ -1287,6 +1289,9 @@ auto PyScrollWidget(PyObject* self, PyObject* args, PyObject* keywds) if (claims_left_right_obj != Py_None) { widget->set_claims_left_right(Python::GetPyBool(claims_left_right_obj)); } + if (claims_up_down_obj != Py_None) { + widget->set_claims_up_down(Python::GetPyBool(claims_up_down_obj)); + } if (claims_tab_obj != Py_None) { widget->set_claims_tab(Python::GetPyBool(claims_tab_obj)); } @@ -1324,6 +1329,7 @@ auto PyHScrollWidget(PyObject* self, PyObject* args, PyObject* keywds) PyObject* border_opacity_obj = Py_None; PyObject* simple_culling_h_obj = Py_None; PyObject* claims_left_right_obj = Py_None; + PyObject* claims_up_down_obj = Py_None; PyObject* claims_tab_obj = Py_None; PyObject* autoselect_obj = Py_None; @@ -1341,6 +1347,7 @@ auto PyHScrollWidget(PyObject* self, PyObject* args, PyObject* keywds) "border_opacity", "simple_culling_h", "claims_left_right", + "claims_up_down", "claims_tab", "autoselect", nullptr}; @@ -1351,7 +1358,7 @@ auto PyHScrollWidget(PyObject* self, PyObject* args, PyObject* keywds) &selected_child_obj, &capture_arrows_obj, &on_select_call_obj, ¢er_small_content_obj, &color_obj, &highlight_obj, &border_opacity_obj, &simple_culling_h_obj, &claims_left_right_obj, - &claims_tab_obj, &autoselect_obj)) + &claims_up_down_obj, &claims_tab_obj, &autoselect_obj)) return nullptr; if (!g_game->IsInUIContext()) { @@ -1425,6 +1432,9 @@ auto PyHScrollWidget(PyObject* self, PyObject* args, PyObject* keywds) if (claims_left_right_obj != Py_None) { widget->set_claims_left_right(Python::GetPyBool(claims_left_right_obj)); } + if (claims_up_down_obj != Py_None) { + widget->set_claims_up_down(Python::GetPyBool(claims_up_down_obj)); + } if (claims_tab_obj != Py_None) { widget->set_claims_tab(Python::GetPyBool(claims_tab_obj)); } @@ -2643,6 +2653,7 @@ PyMethodDef PythonMethodsUI::methods_def[] = { " simple_culling_v: float = None,\n" " selection_loops_to_parent: bool = None,\n" " claims_left_right: bool = None,\n" + " claims_up_down: bool = None,\n" " claims_tab: bool = None,\n" " autoselect: bool = None) -> ba.Widget\n" "\n" @@ -2665,6 +2676,7 @@ PyMethodDef PythonMethodsUI::methods_def[] = { " highlight: bool = None, border_opacity: float = None,\n" " simple_culling_h: float = None,\n" " claims_left_right: bool = None,\n" + " claims_up_down: bool = None,\n" " claims_tab: bool = None) -> ba.Widget\n" "\n" "Create or edit a horizontal scroll widget.\n" diff --git a/src/ballistica/ui/widget/text_widget.cc b/src/ballistica/ui/widget/text_widget.cc index 77a982f7..b193915b 100644 --- a/src/ballistica/ui/widget/text_widget.cc +++ b/src/ballistica/ui/widget/text_widget.cc @@ -301,6 +301,7 @@ void TextWidget::Draw(RenderPass* pass, bool draw_transparent) { if (text_group_dirty_) { text_group_->SetText(text_translated_, align_h, align_v, big_, res_scale_); text_width_ = g_text_graphics->GetStringWidth(text_translated_, big_); + // FIXME: doesnt support big. text_height_ = g_text_graphics->GetStringHeight(text_translated_); text_group_dirty_ = false; @@ -334,7 +335,9 @@ void TextWidget::Draw(RenderPass* pass, bool draw_transparent) { for (int e = 0; e < elem_count; e++) { // Gracefully skip unloaded textures.. TextureData* t2 = text_group_->GetElementTexture(e); - if (!t2->preloaded()) continue; + if (!t2->preloaded()) { + continue; + } c.SetTexture(t2); c.SetMaskUV2Texture(text_group_->GetElementMaskUV2Texture(e)); c.SetShadow(-0.004f * text_group_->GetElementUScale(e), @@ -607,23 +610,22 @@ auto TextWidget::HandleMessage(const WidgetMessage& m) -> bool { return false; case SDLK_RETURN: case SDLK_KP_ENTER: - -#if BA_OSTYPE_IOS_TVOS || BA_OSTYPE_ANDROID - // On iOS, return currently just deselects us. - g_audio->PlaySound(g_media->GetSound(SystemSoundID::kSwish)); - parent_widget()->SelectWidget(nullptr); - return true; -#else - if (on_return_press_call_.exists()) { - claimed = true; + if (g_buildconfig.ostype_ios_tvos() || g_buildconfig.ostype_android()) { + // On mobile, return currently just deselects us. + g_audio->PlaySound(g_media->GetSound(SystemSoundID::kSwish)); + parent_widget()->SelectWidget(nullptr); + return true; + } else { if (on_return_press_call_.exists()) { - // Call this in the next cycle (don't wanna risk mucking with UI - // from within a UI loop) - g_game->PushPythonWeakCall( - Object::WeakRef(on_return_press_call_)); + claimed = true; + if (on_return_press_call_.exists()) { + // Call this in the next cycle (don't wanna risk mucking with UI + // from within a UI loop) + g_game->PushPythonWeakCall( + Object::WeakRef(on_return_press_call_)); + } } } -#endif // BA_OSTYPE_IOS_TVOS break; case SDLK_LEFT: if (editable()) { diff --git a/tools/batools/build.py b/tools/batools/build.py index e9a2a79e..b82e7eb7 100644 --- a/tools/batools/build.py +++ b/tools/batools/build.py @@ -33,7 +33,7 @@ PIP_REQUIREMENTS = [ PipRequirement(modulename='mypy', minversion=[0, 790]), PipRequirement(modulename='yapf', minversion=[0, 30, 0]), PipRequirement(modulename='cpplint', minversion=[1, 5, 4]), - PipRequirement(modulename='pytest', minversion=[6, 1, 1]), + PipRequirement(modulename='pytest', minversion=[6, 1, 2]), PipRequirement(modulename='typing_extensions'), PipRequirement(modulename='pytz'), PipRequirement(modulename='yaml', pipname='PyYAML'), @@ -739,41 +739,54 @@ def cmake_prep_dir(dirname: str) -> None: else: versions = {} - # Get version of installed cmake. + # Start fresh if cmake version changes. cmake_ver_output = subprocess.run(['cmake', '--version'], check=True, capture_output=True).stdout.decode() cmake_ver = cmake_ver_output.splitlines()[0].split('cmake version ')[1] - - cmake_ver_existing = versions.get('cmake') + cmake_ver_existing = versions.get('cmake_version') assert isinstance(cmake_ver_existing, (str, type(None))) - # Get specific version of our target python. + # ...or if python's version changes. python_ver_output = subprocess.run([f'python{PYVER}', '--version'], check=True, capture_output=True).stdout.decode() python_ver = python_ver_output.splitlines()[0].split('Python ')[1] - - python_ver_existing = versions.get('python') + python_ver_existing = versions.get('python_version') assert isinstance(python_ver_existing, (str, type(None))) - # If they don't match, blow away the dir and write the current version. - if cmake_ver_existing != cmake_ver or python_ver_existing != python_ver: - if (cmake_ver_existing != cmake_ver - and cmake_ver_existing is not None): + # ...or if the actual location of python on disk changes. + python_path = os.path.realpath( + subprocess.run(['which', f'python{PYVER}'], + check=True, + capture_output=True).stdout.decode()) + python_path_existing = versions.get('python_path') + assert isinstance(python_path_existing, (str, type(None))) + + # Blow away and start from scratch if any vals differ from existing. + if (cmake_ver_existing != cmake_ver or python_ver_existing != python_ver + or python_path != python_path_existing): + if (cmake_ver_existing is not None + and cmake_ver_existing != cmake_ver): print(f'{Clr.BLU}CMake version changed from {cmake_ver_existing}' f' to {cmake_ver}; clearing existing build at' f' "{dirname}".{Clr.RST}') - if (python_ver_existing != python_ver - and python_ver_existing is not None): + if (python_ver_existing is not None + and python_ver_existing != python_ver): print(f'{Clr.BLU}Python version changed from {python_ver_existing}' f' to {python_ver}; clearing existing build at' f' "{dirname}".{Clr.RST}') + if (python_path_existing is not None + and python_path_existing != python_path): + print(f'{Clr.BLU}Python path changed from {python_path_existing}' + f' to {python_path}; clearing existing build at' + f' "{dirname}".{Clr.RST}') subprocess.run(['rm', '-rf', dirname], check=True) os.makedirs(dirname, exist_ok=True) with open(verfilename, 'w') as outfile: outfile.write( json.dumps({ - 'cmake': cmake_ver, - 'python': python_ver + 'cmake_version': cmake_ver, + 'python_version': python_ver, + 'python_path': python_path })) diff --git a/tools/batools/pcommand.py b/tools/batools/pcommand.py index 2b4c5ddc..825b6819 100644 --- a/tools/batools/pcommand.py +++ b/tools/batools/pcommand.py @@ -733,7 +733,6 @@ def cmake_prep_dir() -> None: """ from efro.error import CleanError import batools.build - if len(sys.argv) != 3: raise CleanError('Expected 1 arg (dir name)') dirname = sys.argv[2]