diff --git a/.efrocachemap b/.efrocachemap
index c5f35717..9a964e39 100644
--- a/.efrocachemap
+++ b/.efrocachemap
@@ -420,16 +420,16 @@
"assets/build/ba_data/audio/zoeOw.ogg": "https://files.ballistica.net/cache/ba1/51/eb/0a567253cc08c94c5d315a64d9af",
"assets/build/ba_data/audio/zoePickup01.ogg": "https://files.ballistica.net/cache/ba1/bc/8f/a9c51a09c418136e386b7fdf21c7",
"assets/build/ba_data/audio/zoeScream01.ogg": "https://files.ballistica.net/cache/ba1/02/e5/84916e123f47ccf11ddda380d699",
- "assets/build/ba_data/data/langdata.json": "https://files.ballistica.net/cache/ba1/aa/52/0eb8b7da64db10e02b695a1806f6",
+ "assets/build/ba_data/data/langdata.json": "https://files.ballistica.net/cache/ba1/d9/3d/da829892fc4c495a6cdd51bbc4d0",
"assets/build/ba_data/data/languages/arabic.json": "https://files.ballistica.net/cache/ba1/0f/e1/94378b32c786d5365a7810a15d73",
"assets/build/ba_data/data/languages/belarussian.json": "https://files.ballistica.net/cache/ba1/55/8c/8d0a0585e434b94865ae4befc090",
"assets/build/ba_data/data/languages/chinese.json": "https://files.ballistica.net/cache/ba1/db/e1/03b6c717e7e0eacb7bc29d3d4ae3",
- "assets/build/ba_data/data/languages/chinesetraditional.json": "https://files.ballistica.net/cache/ba1/ff/7c/3bcab4ae1f39977434acb0d6f795",
+ "assets/build/ba_data/data/languages/chinesetraditional.json": "https://files.ballistica.net/cache/ba1/ef/c2/a607f318b815f025a20ab92f0a7b",
"assets/build/ba_data/data/languages/croatian.json": "https://files.ballistica.net/cache/ba1/66/bf/6e98398016da261296b8c306560e",
"assets/build/ba_data/data/languages/czech.json": "https://files.ballistica.net/cache/ba1/82/61/8319e81bc3fed77e8319a2fd6988",
"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/97/90/39ba65c2ad714429aec82ea1ae3e",
- "assets/build/ba_data/data/languages/english.json": "https://files.ballistica.net/cache/ba1/5a/63/382c2cd707cd34d232f28f45f5d3",
+ "assets/build/ba_data/data/languages/english.json": "https://files.ballistica.net/cache/ba1/99/2a/bdcfa0932cf73e5cf63fd8113b1b",
"assets/build/ba_data/data/languages/esperanto.json": "https://files.ballistica.net/cache/ba1/4c/c7/0184b8178869d1a3827a1bfcd5bb",
"assets/build/ba_data/data/languages/filipino.json": "https://files.ballistica.net/cache/ba1/52/09/449a5ff97460b32cf13edfd1fdbc",
"assets/build/ba_data/data/languages/french.json": "https://files.ballistica.net/cache/ba1/b6/e0/37dd30b686f475733ccc4b3cab49",
@@ -439,9 +439,9 @@
"assets/build/ba_data/data/languages/hindi.json": "https://files.ballistica.net/cache/ba1/c2/f5/e7549f5179c22c6da97fafffc058",
"assets/build/ba_data/data/languages/hungarian.json": "https://files.ballistica.net/cache/ba1/2d/e5/3737c6c3979cf381321c5472bea5",
"assets/build/ba_data/data/languages/indonesian.json": "https://files.ballistica.net/cache/ba1/b1/81/d99fb5b8c368430944b357aa15fe",
- "assets/build/ba_data/data/languages/italian.json": "https://files.ballistica.net/cache/ba1/d7/f0/436072225f713259eaa5fa39ba5d",
+ "assets/build/ba_data/data/languages/italian.json": "https://files.ballistica.net/cache/ba1/c7/16/e31ce16d1b4150c271401669f24f",
"assets/build/ba_data/data/languages/korean.json": "https://files.ballistica.net/cache/ba1/d7/8b/acdfb39196be7856f8bad77eb6a0",
- "assets/build/ba_data/data/languages/persian.json": "https://files.ballistica.net/cache/ba1/5b/77/f09dfde1b14ff978737c8935474a",
+ "assets/build/ba_data/data/languages/persian.json": "https://files.ballistica.net/cache/ba1/02/ab/e310f81582b6dc2ae93348d45166",
"assets/build/ba_data/data/languages/polish.json": "https://files.ballistica.net/cache/ba1/04/fa/b24dd48bfbf3c2d67ee5ad1269c3",
"assets/build/ba_data/data/languages/portuguese.json": "https://files.ballistica.net/cache/ba1/26/41/f1246ab56c6b7853f605c3a95889",
"assets/build/ba_data/data/languages/romanian.json": "https://files.ballistica.net/cache/ba1/82/12/57bf144e12be229a9b70da9c45cb",
@@ -3971,50 +3971,50 @@
"assets/src/ba_data/python/ba/_generated/__init__.py": "https://files.ballistica.net/cache/ba1/ee/e8/cad05aa531c7faf7ff7b96db7f6e",
"assets/src/ba_data/python/ba/_generated/enums.py": "https://files.ballistica.net/cache/ba1/b2/e5/0ee0561e16257a32830645239f34",
"ballisticacore-windows/Generic/BallisticaCore.ico": "https://files.ballistica.net/cache/ba1/89/c0/e32c7d2a35dc9aef57cc73b0911a",
- "build/prefab/full/linux_arm64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/22/91/9788a455078b2c6ae6b0530b376f",
+ "build/prefab/full/linux_arm64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/1a/f8/ad0c078b60b48e42c6338adf6593",
"build/prefab/full/linux_arm64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/74/1d/fc9e33e565475daaac80da5252f0",
- "build/prefab/full/linux_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/c1/4b/87fc75b8f04952794a897fb19fc7",
- "build/prefab/full/linux_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/e3/66/afbe79ac0d6fbd593ec1e549d5c0",
- "build/prefab/full/linux_x86_64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/e4/73/d07113aab410b1c51090952bfafe",
+ "build/prefab/full/linux_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/4f/56/2f420e3ff1dfbcbc27e9b6f8cab1",
+ "build/prefab/full/linux_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/75/08/a3aa8a9b72b395427d80a799ce8a",
+ "build/prefab/full/linux_x86_64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/c1/fd/cf7c98243cf9c09a60e53dcf5fea",
"build/prefab/full/linux_x86_64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/9c/7b/ac1a200be0f37078af0991faca3b",
- "build/prefab/full/linux_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/c2/7c/0c439110ac2fdbeb5548ad35632b",
- "build/prefab/full/linux_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/c1/41/fe950818630506df8fdfd04d674d",
- "build/prefab/full/mac_arm64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/e4/a9/59db1426754a1c7716850c17be91",
- "build/prefab/full/mac_arm64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/7e/c9/90e3afef7cc1aea9e11a252e3379",
- "build/prefab/full/mac_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/6d/f6/41fcb16519bb5257c337749a78ea",
- "build/prefab/full/mac_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/e3/f4/27e8aa597a15296c07cb77263451",
- "build/prefab/full/mac_x86_64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/35/0c/be285aa08f62ac2420126455357c",
- "build/prefab/full/mac_x86_64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/36/69/075a41d69723e230e9ae9ca0f6c7",
- "build/prefab/full/mac_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/09/f3/e81bb333811f848a056f2a2d4c92",
- "build/prefab/full/mac_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/8d/7b/e5d18bf5d8f78a9c2ac050f17979",
- "build/prefab/full/windows_x86_gui/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/5b/6d/1fcbd8637eb1c2d09a0ec042d2f9",
- "build/prefab/full/windows_x86_gui/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/04/85/c400c05b6b6ea1d41cff444f2608",
- "build/prefab/full/windows_x86_server/debug/dist/BallisticaCoreHeadless.exe": "https://files.ballistica.net/cache/ba1/07/1c/7c4e59c63f8a359c179a2a74a802",
- "build/prefab/full/windows_x86_server/release/dist/BallisticaCoreHeadless.exe": "https://files.ballistica.net/cache/ba1/fe/37/e82e03fdf5b5396f975ab15cbc71",
- "build/prefab/lib/linux_arm64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/f9/29/302b9242929cb49edf898ed1ba7a",
- "build/prefab/lib/linux_arm64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/ed/9d/dd639f3199ec5bc1caf901987ff4",
- "build/prefab/lib/linux_arm64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/d2/f5/848874824f57b67c5b6b3c6a2baa",
- "build/prefab/lib/linux_arm64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/d8/c0/a522335c7ba3150af576bb7136a3",
- "build/prefab/lib/linux_x86_64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/21/3d/75e919b2acd230c2f107f0a4776a",
- "build/prefab/lib/linux_x86_64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/ad/88/01e67d29156c38f2774cea30c188",
- "build/prefab/lib/linux_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/e0/a3/924f6b88737442fc0190ee6bfed7",
- "build/prefab/lib/linux_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/7e/96/52a2c78644879deb38677259fdaa",
- "build/prefab/lib/mac_arm64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/6a/45/47fa87fc676af9b738afd5a89fc7",
- "build/prefab/lib/mac_arm64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/47/17/eaffbc7ed2863ab398a23d5d247c",
- "build/prefab/lib/mac_arm64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/8b/42/b133f6e102f69ae685ad5b3f87eb",
- "build/prefab/lib/mac_arm64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/06/08/60a2858cb8760b8e5d5b24d525ad",
- "build/prefab/lib/mac_x86_64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/43/76/21a77c1d04568338ee36e476dfb4",
- "build/prefab/lib/mac_x86_64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/9f/01/cfdeb633d83f043df25405c24e7b",
- "build/prefab/lib/mac_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/27/09/8433d1e7e83d80223a47bb6e9e57",
- "build/prefab/lib/mac_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/79/12/b247bac22dc3af4ade11f805901a",
- "build/prefab/lib/windows/Debug_Win32/BallisticaCoreGenericInternal.lib": "https://files.ballistica.net/cache/ba1/da/a3/75db770dd02eb2049fa84de64a3b",
- "build/prefab/lib/windows/Debug_Win32/BallisticaCoreGenericInternal.pdb": "https://files.ballistica.net/cache/ba1/b4/e7/0e6c1bdc7b66093c5613dfbb5c5d",
- "build/prefab/lib/windows/Debug_Win32/BallisticaCoreHeadlessInternal.lib": "https://files.ballistica.net/cache/ba1/04/2d/0c5eb518b40c9c31cafad1bab5d5",
- "build/prefab/lib/windows/Debug_Win32/BallisticaCoreHeadlessInternal.pdb": "https://files.ballistica.net/cache/ba1/42/f3/ac8ddc452b6380f902f3de61f180",
- "build/prefab/lib/windows/Release_Win32/BallisticaCoreGenericInternal.lib": "https://files.ballistica.net/cache/ba1/58/c8/ab5043d41c6bd0d556e037cfca97",
- "build/prefab/lib/windows/Release_Win32/BallisticaCoreGenericInternal.pdb": "https://files.ballistica.net/cache/ba1/c6/16/a8a82b70227523b5ee154763468d",
- "build/prefab/lib/windows/Release_Win32/BallisticaCoreHeadlessInternal.lib": "https://files.ballistica.net/cache/ba1/36/76/be5b138bcdefd00a033c657b004c",
- "build/prefab/lib/windows/Release_Win32/BallisticaCoreHeadlessInternal.pdb": "https://files.ballistica.net/cache/ba1/53/91/c294c4bd174ce28f2546dd9a57b4",
+ "build/prefab/full/linux_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/f6/4b/555e3b9a9e2d86daed786e962cdc",
+ "build/prefab/full/linux_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/ab/b9/5251e7cf19ce49426ed06abab235",
+ "build/prefab/full/mac_arm64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/c4/62/a30289b64948799104f09f5b523a",
+ "build/prefab/full/mac_arm64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/35/34/91df78166ea43e9d293bc9272575",
+ "build/prefab/full/mac_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/6d/1d/7c362a4bc1e9e59636a5327a611f",
+ "build/prefab/full/mac_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/5c/75/cde9c1909117d7442cb2302c274f",
+ "build/prefab/full/mac_x86_64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/54/df/d28fbd98a92ce45cdb8141563205",
+ "build/prefab/full/mac_x86_64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/42/bb/e022386630ffcb17f567c8bb65b1",
+ "build/prefab/full/mac_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/c9/c2/1e8ad4f633f662a0d142883cd7a0",
+ "build/prefab/full/mac_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/8a/a4/479729125294f2aed832491ed2ca",
+ "build/prefab/full/windows_x86_gui/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/7d/9c/009ee3c03e90ffdca73aaed30ffa",
+ "build/prefab/full/windows_x86_gui/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/9d/62/1bbbf5499f99f2749b54ac7f85ca",
+ "build/prefab/full/windows_x86_server/debug/dist/BallisticaCoreHeadless.exe": "https://files.ballistica.net/cache/ba1/92/5c/315e8b6475c502a96d7cc393712d",
+ "build/prefab/full/windows_x86_server/release/dist/BallisticaCoreHeadless.exe": "https://files.ballistica.net/cache/ba1/d3/45/d6f13256b2238c1bda90faf3e02d",
+ "build/prefab/lib/linux_arm64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/c0/e6/606201d69229c046ed9fb5c3338b",
+ "build/prefab/lib/linux_arm64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/63/a9/9da6ca254d462675cdce8abdbf0b",
+ "build/prefab/lib/linux_arm64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/61/45/eee74c4c33a9a76376b452a75689",
+ "build/prefab/lib/linux_arm64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/7a/74/60613067338c57246225eb95828d",
+ "build/prefab/lib/linux_x86_64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/f9/75/28d109017fc6233bf6c5e18c3ad3",
+ "build/prefab/lib/linux_x86_64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/bc/38/15e5bde3bd60cd8d760cacb7c825",
+ "build/prefab/lib/linux_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/ff/4d/95cf853485249ae7110b10093e65",
+ "build/prefab/lib/linux_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/43/6a/85802eec068c4aad35253ade806c",
+ "build/prefab/lib/mac_arm64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/b1/07/3409386feb1e5cd448a4e89007db",
+ "build/prefab/lib/mac_arm64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/87/6c/560bd65f3fbdb007e562593079df",
+ "build/prefab/lib/mac_arm64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/3e/03/8d57f7814793dab7907834cf29ff",
+ "build/prefab/lib/mac_arm64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/d9/c8/6e1236bd6f7e6067fd43a2a3b28d",
+ "build/prefab/lib/mac_x86_64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/c5/d7/7b3d6226d950c09d90ca8dbf3732",
+ "build/prefab/lib/mac_x86_64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/78/3a/d36f49ada60ed6d8cb4fabceca2d",
+ "build/prefab/lib/mac_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/d0/49/5818e127d362afa6ab928323ff43",
+ "build/prefab/lib/mac_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/6c/d9/dce5d751277c55ad994446dcf4e2",
+ "build/prefab/lib/windows/Debug_Win32/BallisticaCoreGenericInternal.lib": "https://files.ballistica.net/cache/ba1/ca/74/2be44182ddaf1ff46c02ce461bb8",
+ "build/prefab/lib/windows/Debug_Win32/BallisticaCoreGenericInternal.pdb": "https://files.ballistica.net/cache/ba1/89/40/d140336bb8aba43c63a192ed5626",
+ "build/prefab/lib/windows/Debug_Win32/BallisticaCoreHeadlessInternal.lib": "https://files.ballistica.net/cache/ba1/f1/db/6de5b4ba914384b8e77a3c207be2",
+ "build/prefab/lib/windows/Debug_Win32/BallisticaCoreHeadlessInternal.pdb": "https://files.ballistica.net/cache/ba1/c6/96/e6d4806efe59a47cd6a0e7119dfe",
+ "build/prefab/lib/windows/Release_Win32/BallisticaCoreGenericInternal.lib": "https://files.ballistica.net/cache/ba1/18/77/57cae7a43acfce18ad25d311ae30",
+ "build/prefab/lib/windows/Release_Win32/BallisticaCoreGenericInternal.pdb": "https://files.ballistica.net/cache/ba1/6d/a8/3d30810f5efbea3e501216269e7a",
+ "build/prefab/lib/windows/Release_Win32/BallisticaCoreHeadlessInternal.lib": "https://files.ballistica.net/cache/ba1/3d/a4/3a98bbe8716ed349005076422ca9",
+ "build/prefab/lib/windows/Release_Win32/BallisticaCoreHeadlessInternal.pdb": "https://files.ballistica.net/cache/ba1/ad/b2/335bf62301d7b6bc7b9444192693",
"src/ballistica/generated/python_embedded/binding.inc": "https://files.ballistica.net/cache/ba1/c5/18/29d9fe8e483ce222d3263336f7e6",
"src/ballistica/generated/python_embedded/bootstrap.inc": "https://files.ballistica.net/cache/ba1/9d/7e/0a5125aa421e722c59d22b8beb19"
}
\ No newline at end of file
diff --git a/.idea/dictionaries/ericf.xml b/.idea/dictionaries/ericf.xml
index f90bcf08..28760bb1 100644
--- a/.idea/dictionaries/ericf.xml
+++ b/.idea/dictionaries/ericf.xml
@@ -167,6 +167,7 @@
bacfg
backgrounded
backgrounding
+ backwin
bacloud
bacloudcmd
bacommon
@@ -183,6 +184,7 @@
bametainternal
barcolor
barebones
+ baseaddr
baseh
basemult
basepos
@@ -317,6 +319,7 @@
cbgn
cbits
cbot
+ cbresults
cbtn
cbtnoffs
ccfgs
@@ -1523,6 +1526,7 @@
ndbm
ndkpath
nearbytab
+ nearstr
neededsettings
ness
netcode
@@ -2456,6 +2460,7 @@
tval
tvalue
tvos
+ twidth
twrths
txtactor
txtl
@@ -2591,6 +2596,7 @@
weakref
weakrefs
weakrefset
+ weakwin
webbrowser
webpage
webpages
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 773b65db..250182b8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,7 @@
-### 1.6.9 (20461, 2022-02-17)
+### 1.6.9 (20466, 2022-02-18)
- Upgraded Android Python to 3.9.10
- Fixed an issue with SSL in Android builds that was preventing communication with the master-server in 1.6.8
+- Added a new network-diagnostics tool at 'Settings->Advanced->Network Testing'. Can be used to diagnose issues talking to master-servers/etc. (especially useful now that SSL can factor in)
### 1.6.8 (20458, 2022-02-16)
- Added Filipino language (Thanks David!)
diff --git a/README.md b/README.md
index e84378f9..65c96357 100644
--- a/README.md
+++ b/README.md
@@ -6,13 +6,13 @@

-The Ballistica project is the foundation for the next generation of [BombSquad](http://bombsquadgame.com). It will be debuting with the upcoming 1.5 release of the game and lays the foundation for some of the big changes coming in 2.0.
+The Ballistica project is the foundation for the next generation of [BombSquad](http://bombsquadgame.com). It debuted with version 1.5 of the game and lays the foundation for some of the big changes coming in 2.0.
[Head to the project wiki to get started](https://github.com/efroemling/ballistica/wiki), or learn more about the project below.
### Project Goals
* Cleanup
- * BombSquad's codebase, and especially its scripting layer, have grown a lot over its lifetime, but not always in a 'designed' way. It was overdue for a major refactoring, which should keep it more maintainable for years to come. Examples of this include breaking up the monstrous twenty-six-thousand-line bsUI.py file into a much cleaner individual subpackages, updating all code from Python 2.7 to 3.7, and adding type annotations to the entire codebase.
+ * BombSquad's codebase, and especially its scripting layer, have grown a lot over its lifetime, but not always in a 'designed' way. It was overdue for a major refactoring, which should keep it more maintainable for years to come. Examples of this include breaking up the monstrous twenty-six-thousand-line bsUI.py file into a much cleaner individual subpackages, updating all code from Python 2.7 to the latest 3.X versions, and adding type annotations to the entire codebase.
* Provide modders and tinkerers with the best possible development environment
* I've spent a lot of time incorporating auto-formatters, type-checkers, linters, and smart IDEs into my development workflow and have found them to be an enormous help. By sharing my setup here I hope to make them easily accessible to everyone.
* Improve transparency
diff --git a/assets/src/ba_data/python/bastd/ui/settings/nettesting.py b/assets/src/ba_data/python/bastd/ui/settings/nettesting.py
index e8b645c5..9fe1910e 100644
--- a/assets/src/ba_data/python/bastd/ui/settings/nettesting.py
+++ b/assets/src/ba_data/python/bastd/ui/settings/nettesting.py
@@ -4,11 +4,290 @@
from __future__ import annotations
+import time
+import copy
+import weakref
+from threading import Thread
+from typing import TYPE_CHECKING
+
+import _ba
import ba
-from bastd.ui.settings import testing
+from bastd.ui.settings.testing import TestingWindow
+
+if TYPE_CHECKING:
+ from typing import Callable, Any, Optional
-class NetTestingWindow(testing.TestingWindow):
+class NetTestingWindow(ba.Window):
+ """Window that runs a networking test suite to help diagnose issues."""
+
+ def __init__(self, transition: str = 'in_right'):
+ self._width = 820
+ self._height = 500
+ self._printed_lines: list[str] = []
+ uiscale = ba.app.ui.uiscale
+ super().__init__(root_widget=ba.containerwidget(
+ size=(self._width, self._height),
+ scale=(1.56 if uiscale is ba.UIScale.SMALL else
+ 1.2 if uiscale is ba.UIScale.MEDIUM else 0.8),
+ stack_offset=(0.0, -7 if uiscale is ba.UIScale.SMALL else 0.0),
+ transition=transition))
+ self._done_button = ba.buttonwidget(parent=self._root_widget,
+ position=(40, self._height - 77),
+ size=(120, 60),
+ scale=0.8,
+ autoselect=True,
+ label=ba.Lstr(resource='doneText'),
+ on_activate_call=self._done)
+
+ self._copy_button = ba.buttonwidget(parent=self._root_widget,
+ position=(self._width - 200,
+ self._height - 77),
+ size=(100, 60),
+ scale=0.8,
+ autoselect=True,
+ label=ba.Lstr(resource='copyText'),
+ on_activate_call=self._copy)
+
+ self._settings_button = ba.buttonwidget(
+ parent=self._root_widget,
+ position=(self._width - 100, self._height - 77),
+ size=(60, 60),
+ scale=0.8,
+ autoselect=True,
+ label=ba.Lstr(value='...'),
+ on_activate_call=self._show_val_testing)
+
+ twidth = self._width - 450
+ ba.textwidget(
+ parent=self._root_widget,
+ position=(self._width * 0.5, self._height - 55),
+ size=(0, 0),
+ text=ba.Lstr(resource='settingsWindowAdvanced.netTestingText'),
+ color=(0.8, 0.8, 0.8, 1.0),
+ h_align='center',
+ v_align='center',
+ maxwidth=twidth)
+
+ self._scroll = ba.scrollwidget(parent=self._root_widget,
+ position=(50, 50),
+ size=(self._width - 100,
+ self._height - 140),
+ capture_arrows=True,
+ autoselect=True)
+ self._rows = ba.columnwidget(parent=self._scroll)
+
+ ba.containerwidget(edit=self._root_widget,
+ cancel_button=self._done_button)
+
+ # Now kick off the tests.
+ # Pass a weak-ref to this window so we don't keep it alive
+ # if we back out before it completes. Also set is as daemon
+ # so it doesn't keep the app running if the user is trying to quit.
+ Thread(
+ daemon=True,
+ target=ba.Call(_run_diagnostics, weakref.ref(self)),
+ ).start()
+
+ def print(self, text: str, color: tuple[float, float, float]) -> None:
+ """Print text to our console thingie."""
+ for line in text.splitlines():
+ txt = ba.textwidget(parent=self._rows,
+ color=color,
+ text=line,
+ scale=0.75,
+ flatness=1.0,
+ shadow=0.0,
+ size=(0, 20))
+ ba.containerwidget(edit=self._rows, visible_child=txt)
+ self._printed_lines.append(line)
+
+ def _copy(self) -> None:
+ if not ba.clipboard_is_supported():
+ ba.screenmessage('Clipboard not supported on this platform.',
+ color=(1, 0, 0))
+ return
+ ba.clipboard_set_text('\n'.join(self._printed_lines))
+ ba.screenmessage(f'{len(self._printed_lines)} lines copied.')
+
+ def _show_val_testing(self) -> None:
+ ba.app.ui.set_main_menu_window(NetValTestingWindow().get_root_widget())
+ ba.containerwidget(edit=self._root_widget, transition='out_left')
+
+ def _done(self) -> None:
+ # pylint: disable=cyclic-import
+ from bastd.ui.settings.advanced import AdvancedSettingsWindow
+ ba.app.ui.set_main_menu_window(
+ AdvancedSettingsWindow(transition='in_left').get_root_widget())
+ ba.containerwidget(edit=self._root_widget, transition='out_right')
+
+
+def _run_diagnostics(weakwin: weakref.ref[NetTestingWindow]) -> None:
+
+ from efro.util import utc_now
+
+ have_error = [False]
+
+ # We're running in a background thread but UI stuff needs to run
+ # in the logic thread; give ourself a way to pass stuff to it.
+ def _print(text: str, color: tuple[float, float, float] = None) -> None:
+
+ def _print_in_logic_thread() -> None:
+ win = weakwin()
+ if win is not None:
+ win.print(text, (1.0, 1.0, 1.0) if color is None else color)
+
+ ba.pushcall(_print_in_logic_thread, from_other_thread=True)
+
+ def _print_test_results(call: Callable[[], Any]) -> None:
+ """Run the provided call; return success/fail text & color."""
+ starttime = time.monotonic()
+ try:
+ call()
+ duration = time.monotonic() - starttime
+ _print(f'Succeeded in {duration:.2f}s.', color=(0, 1, 0))
+ except Exception as exc:
+ duration = time.monotonic() - starttime
+ _print(f'Failed in {duration:.2f}s. Error={exc!r}',
+ color=(1, 0, 0))
+ have_error[0] = True
+
+ try:
+ _print(f'Running network diagnostics...\n'
+ f'ua: {_ba.app.user_agent_string}\n'
+ f'time: {utc_now()}.')
+
+ if bool(False):
+ _print('\nRunning dummy success test...')
+ _print_test_results(_dummy_success)
+
+ _print('\nRunning dummy fail test...')
+ _print_test_results(_dummy_fail)
+
+ # V1 ping
+ baseaddr = _ba.get_master_server_address(internal=True,
+ source=0,
+ version=1)
+ _print(f'\nContacting V1 master-server ({baseaddr})...')
+ _print_test_results(lambda: _test_fetch(baseaddr))
+
+ # V1 alternate ping
+ baseaddr = _ba.get_master_server_address(internal=True,
+ source=1,
+ version=1)
+ _print(f'\nContacting V1 alt master-server ({baseaddr})...')
+ _print_test_results(lambda: _test_fetch(baseaddr))
+
+ _print('\nRunning V1 transaction...')
+ _print_test_results(_test_v1_transaction)
+
+ # V2 ping
+ baseaddr = _ba.get_master_server_address(internal=True, version=2)
+ _print(f'\nContacting V2 master-server ({baseaddr})...')
+ _print_test_results(lambda: _test_fetch(baseaddr))
+
+ # Get V2 nearby region
+ with ba.app.net.region_pings_lock:
+ region_pings = copy.deepcopy(ba.app.net.region_pings)
+ nearest_region = (None if not region_pings else sorted(
+ region_pings.items(), key=lambda i: i[1])[0])
+
+ if nearest_region is not None:
+ nearstr = f'{nearest_region[0]}: {nearest_region[1]:.0f}ms'
+ else:
+ nearstr = '-'
+ _print(f'\nChecking nearest V2 region ping ({nearstr})...')
+ _print_test_results(lambda: _test_nearby_region_ping(nearest_region))
+
+ if have_error[0]:
+ _print('\nDiagnostics complete. Some diagnostics failed.',
+ color=(10, 0, 0))
+ else:
+ _print('\nDiagnostics complete. Everything looks good!',
+ color=(0, 1, 0))
+ except Exception:
+ import traceback
+ _print(
+ f'An unexpected error occurred during testing;'
+ f' please report this.\n'
+ f'{traceback.format_exc()}',
+ color=(1, 0, 0))
+
+
+def _dummy_success() -> None:
+ """Dummy success test."""
+ time.sleep(1.2)
+
+
+def _dummy_fail() -> None:
+ """Dummy fail test case."""
+ raise RuntimeError('fail-test')
+
+
+def _test_v1_transaction() -> None:
+ """Dummy fail test case."""
+ if _ba.get_account_state() != 'signed_in':
+ raise RuntimeError('Not signed in.')
+
+ starttime = time.monotonic()
+
+ # Gets set to True on success or string on error.
+ results: list[Any] = [False]
+
+ def _cb(cbresults: Any) -> None:
+ # Simply set results here; our other thread acts on them.
+ if not isinstance(cbresults, dict) or 'party_code' not in cbresults:
+ results[0] = 'Unexpected transaction response'
+ return
+ results[0] = True # Success!
+
+ def _do_it() -> None:
+ # Fire off a transaction with a callback.
+ _ba.add_transaction(
+ {
+ 'type': 'PRIVATE_PARTY_QUERY',
+ 'expire_time': time.time() + 20,
+ },
+ callback=_cb,
+ )
+ _ba.run_transactions()
+
+ ba.pushcall(_do_it, from_other_thread=True)
+
+ while results[0] is False:
+ time.sleep(0.01)
+ if time.monotonic() - starttime > 10.0:
+ raise RuntimeError('timed out')
+
+ # If we got left a string, its an error.
+ if isinstance(results[0], str):
+ raise RuntimeError(results[0])
+
+
+def _test_fetch(baseaddr: str) -> None:
+ # pylint: disable=consider-using-with
+ import urllib.request
+ response = urllib.request.urlopen(urllib.request.Request(
+ f'{baseaddr}/ping', None, {'User-Agent': _ba.app.user_agent_string}),
+ timeout=10.0)
+ if response.getcode() != 200:
+ raise RuntimeError(
+ f'Got unexpected response code {response.getcode()}.')
+ data = response.read()
+ if data != b'pong':
+ raise RuntimeError('Got unexpected response data.')
+
+
+def _test_nearby_region_ping(
+ nearest_region: Optional[tuple[str, float]]) -> None:
+ """Try to ping nearest v2 region."""
+ if nearest_region is None:
+ raise RuntimeError('No nearest region.')
+ if nearest_region[1] > 500:
+ raise RuntimeError('Ping too high.')
+
+
+class NetValTestingWindow(TestingWindow):
"""Window to test network related settings."""
def __init__(self, transition: str = 'in_right'):
@@ -35,6 +314,8 @@ class NetTestingWindow(testing.TestingWindow):
'increment': 1
},
]
- testing.TestingWindow.__init__(
- self, ba.Lstr(resource='settingsWindowAdvanced.netTestingText'),
- entries, transition)
+ super().__init__(
+ title=ba.Lstr(resource='settingsWindowAdvanced.netTestingText'),
+ entries=entries,
+ transition=transition,
+ back_call=lambda: NetTestingWindow(transition='in_left'))
diff --git a/assets/src/ba_data/python/bastd/ui/settings/testing.py b/assets/src/ba_data/python/bastd/ui/settings/testing.py
index 51f237e8..54e8349e 100644
--- a/assets/src/ba_data/python/bastd/ui/settings/testing.py
+++ b/assets/src/ba_data/python/bastd/ui/settings/testing.py
@@ -11,7 +11,7 @@ import _ba
import ba
if TYPE_CHECKING:
- from typing import Any
+ from typing import Any, Callable, Optional
class TestingWindow(ba.Window):
@@ -20,11 +20,13 @@ class TestingWindow(ba.Window):
def __init__(self,
title: ba.Lstr,
entries: list[dict[str, Any]],
- transition: str = 'in_right'):
+ transition: str = 'in_right',
+ back_call: Optional[Callable[[], ba.Window]] = None):
uiscale = ba.app.ui.uiscale
self._width = 600
self._height = 324 if uiscale is ba.UIScale.SMALL else 400
self._entries = copy.deepcopy(entries)
+ self._back_call = back_call
super().__init__(root_widget=ba.containerwidget(
size=(self._width, self._height),
transition=transition,
@@ -176,8 +178,8 @@ class TestingWindow(ba.Window):
def _do_back(self) -> None:
# pylint: disable=cyclic-import
- import bastd.ui.settings.advanced
+ from bastd.ui.settings.advanced import AdvancedSettingsWindow
ba.containerwidget(edit=self._root_widget, transition='out_right')
- ba.app.ui.set_main_menu_window(
- bastd.ui.settings.advanced.AdvancedSettingsWindow(
- transition='in_left').get_root_widget())
+ backwin = (self._back_call() if self._back_call is not None else
+ AdvancedSettingsWindow(transition='in_left'))
+ ba.app.ui.set_main_menu_window(backwin.get_root_widget())
diff --git a/assets/src/ba_data/python/bastd/ui/settings/vrtesting.py b/assets/src/ba_data/python/bastd/ui/settings/vrtesting.py
index a899caac..a6723f78 100644
--- a/assets/src/ba_data/python/bastd/ui/settings/vrtesting.py
+++ b/assets/src/ba_data/python/bastd/ui/settings/vrtesting.py
@@ -7,13 +7,13 @@ from __future__ import annotations
from typing import TYPE_CHECKING
import ba
-from bastd.ui.settings import testing
+from bastd.ui.settings.testing import TestingWindow
if TYPE_CHECKING:
from typing import Any
-class VRTestingWindow(testing.TestingWindow):
+class VRTestingWindow(TestingWindow):
"""Window for testing vr settings."""
def __init__(self, transition: str = 'in_right'):
diff --git a/ballisticacore-cmake/.idea/dictionaries/ericf.xml b/ballisticacore-cmake/.idea/dictionaries/ericf.xml
index 1ffa37a4..4e9a01f4 100644
--- a/ballisticacore-cmake/.idea/dictionaries/ericf.xml
+++ b/ballisticacore-cmake/.idea/dictionaries/ericf.xml
@@ -77,6 +77,7 @@
backgrounded
backgrounding
backtraces
+ backwin
ballistica
ballisticacore
bamasteraddr
@@ -84,6 +85,7 @@
bameta
bametainternal
barebones
+ baseaddr
basetype
basicsize
basn
@@ -167,6 +169,7 @@
capitan
cargs
cbgn
+ cbresults
cbtnoffs
ccdd
ccind
@@ -720,6 +723,7 @@
nameval
ndebug
nearbytab
+ nearstr
nearval
needwindow
negativex
@@ -1169,6 +1173,7 @@
tval
tvos
tweakage
+ twidth
twotimer
twst
typeobj
@@ -1241,6 +1246,7 @@
wdeprecated
weakref
weakthis
+ weakwin
weeeird
welp
whaaaaaaa
diff --git a/src/ballistica/ballistica.cc b/src/ballistica/ballistica.cc
index 96a921db..7d5fbdd9 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 modify them here.
-const int kAppBuildNumber = 20461;
+const int kAppBuildNumber = 20466;
const char* kAppVersion = "1.6.9";
// Our standalone globals.
diff --git a/src/ballistica/core/fatal_error.cc b/src/ballistica/core/fatal_error.cc
index 1e231b59..80736b28 100644
--- a/src/ballistica/core/fatal_error.cc
+++ b/src/ballistica/core/fatal_error.cc
@@ -42,15 +42,12 @@ auto FatalError::ReportFatalError(const std::string& message,
if (!dialog_msg.empty()) {
dialog_msg += "\n";
}
- // (No longer adding this note; individual errors to which the log is
- // relevant can do to themselves).
- // dialog_msg += "See BallisticaCore log for details.";
auto starttime = time(nullptr);
// Launch a thread and give it a chance to directly send our logs to the
// master-server. The standard mechanism probably won't get the job done
- // since it relies on the game thread loop and we're likely blocking that.
+ // since it relies on the logic thread loop and we're likely blocking that.
// But generally we want to stay in this function and call abort() or whatnot
// from here so that our stack trace makes it into platform logs.
int result{};