From 7ba24ecbcfa103680d2ee86a2da4828f5a52aade Mon Sep 17 00:00:00 2001 From: Ayush Saini <36878972+imayushsaini@users.noreply.github.com> Date: Sat, 27 Jan 2024 21:25:16 +0530 Subject: [PATCH] ba_data update --- dist/ba_data/data/langdata.json | 26 +- dist/ba_data/data/languages/arabic.json | 6 + dist/ba_data/data/languages/belarussian.json | 4 + dist/ba_data/data/languages/chinese.json | 5 + .../data/languages/chinesetraditional.json | 13 + dist/ba_data/data/languages/czech.json | 1 + dist/ba_data/data/languages/dutch.json | 51 ++++ dist/ba_data/data/languages/english.json | 1 + dist/ba_data/data/languages/filipino.json | 161 +++++------ dist/ba_data/data/languages/french.json | 5 + dist/ba_data/data/languages/gibberish.json | 1 + dist/ba_data/data/languages/hindi.json | 13 + dist/ba_data/data/languages/indonesian.json | 7 + dist/ba_data/data/languages/italian.json | 1 + dist/ba_data/data/languages/korean.json | 5 + dist/ba_data/data/languages/persian.json | 1 + dist/ba_data/data/languages/portuguese.json | 5 + dist/ba_data/data/languages/russian.json | 5 + dist/ba_data/data/languages/spanish.json | 9 +- dist/ba_data/data/languages/thai.json | 2 + dist/ba_data/data/languages/turkish.json | 5 + dist/ba_data/data/languages/ukrainian.json | 9 + dist/ba_data/data/languages/venetian.json | 4 + .../python-site-packages/typing_extensions.py | 255 ++++++++++++++---- dist/ba_data/python/babase/__init__.py | 6 +- dist/ba_data/python/babase/_app.py | 86 +++++- dist/ba_data/python/babase/_appmode.py | 7 + dist/ba_data/python/babase/_appsubsystem.py | 8 +- dist/ba_data/python/babase/_apputils.py | 5 + dist/ba_data/python/babase/_devconsole.py | 4 + dist/ba_data/python/babase/_emptyappmode.py | 6 + dist/ba_data/python/babase/_env.py | 2 + dist/ba_data/python/babase/_general.py | 4 + dist/ba_data/python/babase/_language.py | 6 + dist/ba_data/python/babase/_login.py | 3 + dist/ba_data/python/babase/_meta.py | 2 +- dist/ba_data/python/babase/_mgen/enums.py | 156 +++++------ dist/ba_data/python/babase/_plugin.py | 7 + dist/ba_data/python/babase/_ui.py | 4 + dist/ba_data/python/baclassic/_ads.py | 4 +- dist/ba_data/python/baclassic/_benchmark.py | 104 ++++--- dist/ba_data/python/baclassic/_net.py | 2 + dist/ba_data/python/baclassic/_subsystem.py | 17 +- dist/ba_data/python/baclassic/macmusicapp.py | 7 + dist/ba_data/python/baclassic/osmusic.py | 7 + dist/ba_data/python/bacommon/build.py | 2 +- dist/ba_data/python/bacommon/cloud.py | 31 +++ dist/ba_data/python/bacommon/transfer.py | 18 +- dist/ba_data/python/baenv.py | 16 +- dist/ba_data/python/baplus/__init__.py | 2 + dist/ba_data/python/baplus/_cloud.py | 214 +++++++++++++++ dist/ba_data/python/baplus/_subsystem.py | 9 +- dist/ba_data/python/bascenev1/__init__.py | 6 + .../python/bascenev1/_activitytypes.py | 9 + dist/ba_data/python/bascenev1/_appmode.py | 21 +- dist/ba_data/python/bascenev1/_coopgame.py | 4 + dist/ba_data/python/bascenev1/_coopsession.py | 5 + dist/ba_data/python/bascenev1/_dependency.py | 2 + .../python/bascenev1/_dualteamsession.py | 2 + .../python/bascenev1/_freeforallsession.py | 2 + .../ba_data/python/bascenev1/_gameactivity.py | 6 + dist/ba_data/python/bascenev1/_level.py | 2 + dist/ba_data/python/bascenev1/_lobby.py | 2 +- dist/ba_data/python/bascenev1/_map.py | 3 + .../python/bascenev1/_multiteamsession.py | 8 + dist/ba_data/python/bascenev1/_nodeactor.py | 4 + dist/ba_data/python/bascenev1/_session.py | 2 +- dist/ba_data/python/bascenev1/_teamgame.py | 9 +- .../python/bascenev1lib/activity/coopjoin.py | 2 + .../python/bascenev1lib/activity/coopscore.py | 5 + .../python/bascenev1lib/activity/drawscore.py | 3 + .../bascenev1lib/activity/dualteamscore.py | 3 + .../activity/freeforallvictory.py | 5 +- .../bascenev1lib/activity/multiteamjoin.py | 3 + .../bascenev1lib/activity/multiteamscore.py | 3 + .../bascenev1lib/activity/multiteamvictory.py | 3 + .../python/bascenev1lib/actor/background.py | 3 + .../ba_data/python/bascenev1lib/actor/bomb.py | 19 +- .../bascenev1lib/actor/controlsguide.py | 3 + .../ba_data/python/bascenev1lib/actor/flag.py | 5 +- .../python/bascenev1lib/actor/image.py | 2 + .../bascenev1lib/actor/onscreencountdown.py | 2 + .../bascenev1lib/actor/onscreentimer.py | 2 + .../python/bascenev1lib/actor/playerspaz.py | 3 + .../python/bascenev1lib/actor/popuptext.py | 2 + .../python/bascenev1lib/actor/powerupbox.py | 3 + .../ba_data/python/bascenev1lib/actor/spaz.py | 8 +- .../python/bascenev1lib/actor/spazbot.py | 4 + .../ba_data/python/bascenev1lib/actor/text.py | 2 + .../python/bascenev1lib/actor/tipstext.py | 2 + .../python/bascenev1lib/actor/zoomtext.py | 2 + .../python/bascenev1lib/game/assault.py | 13 +- .../bascenev1lib/game/capturetheflag.py | 14 +- .../python/bascenev1lib/game/chosenone.py | 12 +- .../python/bascenev1lib/game/conquest.py | 14 +- .../python/bascenev1lib/game/deathmatch.py | 13 +- .../python/bascenev1lib/game/easteregghunt.py | 13 +- .../python/bascenev1lib/game/elimination.py | 16 +- .../python/bascenev1lib/game/football.py | 22 +- .../python/bascenev1lib/game/hockey.py | 13 +- .../python/bascenev1lib/game/keepaway.py | 13 +- .../python/bascenev1lib/game/kingofthehill.py | 12 +- .../python/bascenev1lib/game/meteorshower.py | 11 +- .../python/bascenev1lib/game/ninjafight.py | 10 +- .../python/bascenev1lib/game/onslaught.py | 10 +- dist/ba_data/python/bascenev1lib/game/race.py | 16 +- .../python/bascenev1lib/game/runaround.py | 10 +- .../bascenev1lib/game/targetpractice.py | 13 +- .../python/bascenev1lib/game/thelaststand.py | 9 +- dist/ba_data/python/bascenev1lib/mainmenu.py | 34 ++- dist/ba_data/python/bascenev1lib/maps.py | 59 ++++ dist/ba_data/python/bascenev1lib/tutorial.py | 5 + dist/ba_data/python/bauiv1/__init__.py | 2 + dist/ba_data/python/bauiv1/_subsystem.py | 3 + dist/ba_data/python/bauiv1/_uitypes.py | 3 + .../python/bauiv1lib/account/viewer.py | 5 +- dist/ba_data/python/bauiv1lib/achievements.py | 3 + .../python/bauiv1lib/characterpicker.py | 3 + dist/ba_data/python/bauiv1lib/colorpicker.py | 4 + dist/ba_data/python/bauiv1lib/fileselector.py | 3 + .../python/bauiv1lib/gather/abouttab.py | 3 + .../python/bauiv1lib/gather/manualtab.py | 11 +- .../python/bauiv1lib/gather/nearbytab.py | 6 +- .../python/bauiv1lib/gather/privatetab.py | 5 + .../python/bauiv1lib/gather/publictab.py | 7 + dist/ba_data/python/bauiv1lib/getremote.py | 3 + dist/ba_data/python/bauiv1lib/iconpicker.py | 3 + dist/ba_data/python/bauiv1lib/mainmenu.py | 49 +++- .../python/bauiv1lib/playlist/share.py | 3 + dist/ba_data/python/bauiv1lib/playoptions.py | 2 + dist/ba_data/python/bauiv1lib/popup.py | 3 + dist/ba_data/python/bauiv1lib/promocode.py | 42 ++- dist/ba_data/python/bauiv1lib/qrcode.py | 5 +- .../python/bauiv1lib/resourcetypeinfo.py | 3 + .../python/bauiv1lib/settings/advanced.py | 31 ++- .../python/bauiv1lib/teamnamescolors.py | 3 + .../python/bauiv1lib/tournamententry.py | 3 + .../python/bauiv1lib/tournamentscores.py | 3 + dist/ba_data/python/bauiv1lib/trophies.py | 3 + dist/ba_data/python/efro/dataclassio/_base.py | 7 + .../python/efro/dataclassio/_inputter.py | 12 + .../ba_data/python/efro/dataclassio/extras.py | 3 + dist/ba_data/python/efro/error.py | 3 + dist/ba_data/python/efro/log.py | 2 + dist/ba_data/python/efro/message/_protocol.py | 1 + dist/ba_data/python/efro/util.py | 19 +- 146 files changed, 1756 insertions(+), 347 deletions(-) create mode 100644 dist/ba_data/python/baplus/_cloud.py diff --git a/dist/ba_data/data/langdata.json b/dist/ba_data/data/langdata.json index 5a268a0..9621466 100644 --- a/dist/ba_data/data/langdata.json +++ b/dist/ba_data/data/langdata.json @@ -9,7 +9,7 @@ "Danish": "Dansk", "Dutch": "Nederlands", "Esperanto": "Esperanto", - "Filipino": "Tagalog ", + "Filipino": "Wikang Tagalog ", "French": "Français", "German": "Deutsch", "Gibberish": "Abuktarika", @@ -44,10 +44,12 @@ "\"9۝ÅℳЇℜρℜѺ۝ƬǀGΞЯ", "\"Unknown\"", "/in/dev/", + "0Globalsters", "1.4.139", "11", "123", "123123123", + "1234abcdS", "1Platinumpatty", "1SpaZ", "228варенье", @@ -148,12 +150,14 @@ "Shadiq Ali", "alireza", "alirezaalidokht", + "AliSh8787", "ALISSON", "Virgile Allard", "Allinol", "ahmed alomari", "Alonso", "Alp", + "Alpcik910", "Alper", "Alpha", "AlphaT", @@ -250,6 +254,7 @@ "awase2020@gmail.com", "sev alaslam Awd", "Axel", + "Aynursargi", "ayub", "masoud azad(fireboy)", "Md azar", @@ -260,6 +265,7 @@ "Myth B.", "B4likeBefore", "Praveen Babu", + "Badmoss", "Baechu", "Balage8", "BalaguerM", @@ -326,6 +332,7 @@ "Daniel Block", "BlueBlur", "bob bobber", + "Bombay De Bom", "BomBillo", "The Bomboler 💣", "bombsquad", @@ -346,6 +353,7 @@ "bouabdellah", "Antoine Boulanger", "Thomas Bouwmeester", + "Boxan", "Ali x boy", "boyhero7779", "Bořivoj", @@ -473,6 +481,7 @@ "Die or Dead", "Привет от детей DeadLine", "deepjith", + "DEFENDER", "Defiant", "dekarl", "deliciouspudding43", @@ -554,6 +563,7 @@ "Emanuel_12", "Emil", "Kürti Emil", + "55Hamza Emin", "Muhammed emir", "Muhammet Emir", "EmirSametEr", @@ -825,6 +835,7 @@ "Jacek", "Jack556", "Jhon Jairo", + "jakecato1602@gmail.com", "wahid jamaludin", "Tarun Jangra", "Aleksandar Janic", @@ -864,6 +875,7 @@ "joke", "Jonatas", "Jop", + "Jor", "JoseANG3L", "Joseetion", "Joseph", @@ -1025,6 +1037,7 @@ "Loex", "Loko", "Longkencok", + "longmouses", "杰瑞 longmouses", "looooooooou", "LordHiohi", @@ -1083,6 +1096,8 @@ "Wagdy mamdouh", "Mani", "Manimutharu", + "Manmath", + "ManmathTheGreat", "Ahmed Mansy", "Manu", "Mapk58", @@ -1188,6 +1203,7 @@ "mr", "mr.Dark", "Mr.Smoothy", + "MR0000000001", "MrDaniel715", "MrGlu10free", "Mrmaxmeier", @@ -1503,6 +1519,7 @@ "Guilherme Santana", "Santiago", "Ivan Santos :)", + "santosamerica880@gmail.com", "Diamond Sanwich", "SAO_OMH", "Dimas Saptandi", @@ -1654,6 +1671,7 @@ "Tiberiu", "Cristian Ticu", "Robert Tieber", + "TieDan", "TIGEE", "Tim", "Tingis2", @@ -1679,6 +1697,7 @@ "Konstantin Tsvetkov", "Kontantin Tsvetkov", "Tudikk", + "Romioza TV", "Jan Tymll", "Zacker Tz", "Zoltán Tóth", @@ -1730,6 +1749,7 @@ "Vaibhav Wakchaure", "Simon Wang", "Will Wang", + "WeanCZ", "Tilman Weber", "webparham", "Wesley", @@ -1795,6 +1815,7 @@ "Z@p€g@m€r", "Zac", "Dawn Zac", + "zahra", "Zaidan64GT", "Zain", "Zajle", @@ -1804,6 +1825,7 @@ "ZaraMax", "zecharaiah", "Daniele Zennaro", + "Zenotaiko", "zFliws", "zfuw668", "Alex Zhao", @@ -1819,6 +1841,7 @@ "Lukáš Zounek", "ZOUZ", "ZpeedTube", + "Zwizard", "|_Jenqa_|", "¥¥S.A.N.A¥", "Danijel Ćelić", @@ -1913,6 +1936,7 @@ "علی", "سيد عمر", "عيسى", + "مجتبی", "اللهم صل على محمد وآل محمد", "امیر محمد", "هادی مرادی", diff --git a/dist/ba_data/data/languages/arabic.json b/dist/ba_data/data/languages/arabic.json index 6778cef..47c5e8d 100644 --- a/dist/ba_data/data/languages/arabic.json +++ b/dist/ba_data/data/languages/arabic.json @@ -336,6 +336,8 @@ "getMoreGamesText": "الحصول على المزيد من الألعاب", "titleText": "إضافة لعبة" }, + "addToFavoritesText": "الإضافة إلى المفضلات", + "addedToFavoritesText": ".إلى المفضلات '${NAME}' تمت إضافة", "allText": "جميع", "allowText": "السماح", "alreadySignedInText": "تم تسجيل الدخول من حسابك من جهاز آخر.\n يرجى تبديل الحسابات أو إغلاق اللعبة على الأجهزة الأخرى\n وحاول مرة أخرى.", @@ -565,6 +567,8 @@ "disableXInputDescriptionText": "يسمح أكثر من 4 وحدات تحكم ولكن قد لا تعمل كذلك.", "disableXInputText": "xinput تعطيل", "disabledText": "معطل", + "discordFriendsText": "تريد المزيد من الناس للعب معهم؟\n!إنضم إلى سيرفر الديسكورد و أوجد اصدقاء جدد", + "discordJoinText": "إنضم إلى الديسكورد", "doneText": "تم", "drawText": "تعادل", "duplicateText": "مكرر", @@ -750,6 +754,7 @@ "manualYourLocalAddressText": "عنوانك المباشر", "nearbyText": "الأقرب", "noConnectionText": "<لا يوجد اتصال>", + "noPartiesAddedText": "لا مجموعات مضافة", "otherVersionsText": "(اصدارات اخرى)", "partyCodeText": "رمز السيرفر", "partyInviteAcceptText": "قبول", @@ -1064,6 +1069,7 @@ "noContinuesText": "(لا يستمر)", "noExternalStorageErrorText": "لم يتم العثور على وحدة تخزين خارجية على هذا الجهاز", "noGameCircleText": "خطأ: لم يتم تسجيل الدخول الئ gamecircle", + "noPluginsInstalledText": "لا إضافات مثبتة", "noScoresYetText": "لا نقاط حتى الآن.", "noServersFoundText": "لا توجد اي سيرفرات.", "noThanksText": "لا شكرا", diff --git a/dist/ba_data/data/languages/belarussian.json b/dist/ba_data/data/languages/belarussian.json index 884ad03..3fb9bf5 100644 --- a/dist/ba_data/data/languages/belarussian.json +++ b/dist/ba_data/data/languages/belarussian.json @@ -337,6 +337,8 @@ "getMoreGamesText": "Атрымаць больш гульняў...", "titleText": "Дадаць гульню" }, + "addToFavoritesText": "Дадаць у абранае", + "addedToFavoritesText": "'${NAME}' дададзены ў абранае.", "allText": "Усё", "allowText": "Дазволіць", "alreadySignedInText": "Ваш уліковы запіс увайшоў з іншай прылады;\nкалі ласка, пераключыце ўліковыя запісы альбо зачыніце гульню на вашым\nіншыя прылады і паспрабуйце яшчэ раз", @@ -755,6 +757,7 @@ "manualYourLocalAddressText": "Ваш лакальны адрас:", "nearbyText": "Побач", "noConnectionText": "<няма злучэння>", + "noPartiesAddedText": "Партыі не дададзены", "otherVersionsText": "(іншыя версіі)", "partyCodeText": "Код вечарыны", "partyInviteAcceptText": "Згадзіцца", @@ -1073,6 +1076,7 @@ "noContinuesText": "(без працягу)", "noExternalStorageErrorText": "Знешняя памяць не знойдзена", "noGameCircleText": "Памылка: вы не ўвайшлі ў GameCircle", + "noPluginsInstalledText": "Убудовы не ўстаноўлены", "noProfilesErrorText": "У вас няма ніводнага профіля, таму вас будуць называць '${NAME}'.\nЗайдзіце ў \"Налады -> Профілі\", каб стварыць уласны профіль.", "noScoresYetText": "Вынікаў пакуль няма.", "noServersFoundText": "Серверы не знойдзены.", diff --git a/dist/ba_data/data/languages/chinese.json b/dist/ba_data/data/languages/chinese.json index 9b1383b..1fa2c47 100644 --- a/dist/ba_data/data/languages/chinese.json +++ b/dist/ba_data/data/languages/chinese.json @@ -339,6 +339,8 @@ "getMoreGamesText": "获取更多游戏模式…", "titleText": "添加比赛" }, + "addToFavoritesText": "添加到收藏", + "addedToFavoritesText": "将'${NAME}'添加到收藏", "allText": "全部", "allowText": "允许", "alreadySignedInText": "您的账号已在其他设备登录;\n请切换账号或者退出已登录的设备,\n然后再试一次", @@ -758,6 +760,7 @@ "manualYourLocalAddressText": "本地地址:", "nearbyText": "附近", "noConnectionText": "<无连接>", + "noPartiesAddedText": "没有加入派对", "otherVersionsText": "(其他版本)", "partyCodeText": "派对代码", "partyInviteAcceptText": "接受", @@ -1075,6 +1078,7 @@ "noContinuesText": "(无可继续)", "noExternalStorageErrorText": "该设备上未发现外部存储器", "noGameCircleText": "错误:未登入GameCircle", + "noPluginsInstalledText": "没有安装插件", "noProfilesErrorText": "您没有玩家档案,所以还得忍受“${NAME}”这个名字。\n进入设置->玩家档案,为自己创建档案。", "noScoresYetText": "还未有得分记录。", "noServersFoundText": "未找到服务器", @@ -1292,6 +1296,7 @@ "netTestingText": "网络测试", "resetText": "恢复默认值", "showBombTrajectoriesText": "显示炸弹轨迹", + "showDemosWhenIdleText": "当游戏空闲时播放演示画面", "showDevConsoleButtonText": "显示开发者控制台按钮", "showInGamePingText": "显示游戏延迟", "showPlayerNamesText": "显示玩家名字", diff --git a/dist/ba_data/data/languages/chinesetraditional.json b/dist/ba_data/data/languages/chinesetraditional.json index 97f44a4..3e9c279 100644 --- a/dist/ba_data/data/languages/chinesetraditional.json +++ b/dist/ba_data/data/languages/chinesetraditional.json @@ -29,6 +29,7 @@ "signInWithGooglePlayText": "用play商店登入", "signInWithTestAccountInfoText": "(舊有的帳號登入方式;使用後來的創設的帳號)", "signInWithTestAccountText": "用測試帳號登入", + "signInWithText": "通過${SERVICE}登錄", "signInWithV2InfoText": "(可用於所有平臺的賬戶)", "signInWithV2Text": "使用Bombsquad賬戶登入", "signOutText": "登出", @@ -332,6 +333,8 @@ "getMoreGamesText": "獲取更多比賽模式", "titleText": "新增比賽" }, + "addToFavoritesText": "添加到收藏", + "addedToFavoritesText": "已將'${NAME}'添加到收藏", "allText": "全部", "allowText": "允許", "alreadySignedInText": "你的賬號已在其他設備上登錄\n請退出其他設備的登錄\n然後重試", @@ -558,6 +561,8 @@ "disableXInputDescriptionText": "允許使用四個以上的控制器,但可能不會正常工作", "disableXInputText": "禁用XInput", "disabledText": "禁用", + "discordFriendsText": "想要尋找新的朋友一起遊玩嗎?\n快來加入我們的Discord社區發現新夥伴!", + "discordJoinText": "加入Discord社區", "doneText": "完成", "drawText": "平局", "duplicateText": "複製", @@ -743,6 +748,7 @@ "manualYourLocalAddressText": "本地地址:", "nearbyText": "附近", "noConnectionText": "<無連接>", + "noPartiesAddedText": "未添加派對", "otherVersionsText": "(其他版本)", "partyCodeText": "派對代碼", "partyInviteAcceptText": "接受", @@ -814,6 +820,7 @@ "alwaysText": "總是", "fullScreenCmdText": "全屏顯示Cmd-F", "fullScreenCtrlText": "全屏顯示(Ctrl-F)", + "fullScreenText": "全屏", "gammaText": "Gamma", "highText": "高", "higherText": "最高", @@ -1056,7 +1063,9 @@ "noContinuesText": "(無可繼續)", "noExternalStorageErrorText": "該設備未發現外部存儲器", "noGameCircleText": "錯誤:未登錄GameCircle", + "noPluginsInstalledText": "未安裝插件", "noScoresYetText": "沒有得分記錄", + "noServersFoundText": "未找到服務器", "noThanksText": "不,謝謝", "noTournamentsInTestBuildText": "注意:此測試版本的比賽分數將會被作廢", "noValidMapsErrorText": "沒有發現該比賽類型的有效地圖", @@ -1358,6 +1367,8 @@ "storeText": "商店", "submitText": "提交", "submittingPromoCodeText": "正在提交代碼...", + "successText": "大功告成!", + "supportEmailText": "如果您在使用應用中遇到任何問題,\n請發送郵件至${EMAIL}.", "teamNamesColorText": "團隊名稱/顏色...", "telnetAccessGrantedText": "Telnet訪問以啟用", "telnetAccessText": "檢測到Telnet訪問,是否允許?", @@ -1811,6 +1822,7 @@ "unlockThisInTheStoreText": "這必須從商店解鎖", "unlockThisProfilesText": "如需創建超過${NUM}個檔案,你需要", "unlockThisText": "你需要這些來解鎖", + "unsupportedControllerText": "抱歉,不支持控制器\"${NAME}\"", "unsupportedHardwareText": "抱歉,此版本的遊戲不支持該硬件", "upFirstText": "進入第一局", "upNextText": "進入比賽${COUNT}第二局", @@ -1822,6 +1834,7 @@ "usingItunesText": "使用音樂軟件設置背景音樂...", "v2AccountLinkingInfoText": "要鏈接 V2 帳戶,請使用“管理帳戶”按鈕。", "validatingTestBuildText": "測試版驗證中", + "viaText": "其他賬戶", "victoryText": "勝利!", "voteDelayText": "你不能在${NUMBER}內發起一個新的投票", "voteInProgressText": "已經有一個投票正在進行中了", diff --git a/dist/ba_data/data/languages/czech.json b/dist/ba_data/data/languages/czech.json index 6a6d873..1cb0f87 100644 --- a/dist/ba_data/data/languages/czech.json +++ b/dist/ba_data/data/languages/czech.json @@ -1297,6 +1297,7 @@ "netTestingText": "Testování sítě", "resetText": "Obnovit", "showBombTrajectoriesText": "Ukazovat trajektorii bomb", + "showDemosWhenIdleText": "Zobrazit ukázky při nečinnosti", "showDevConsoleButtonText": "Ukaž tlačítko vývojářské konzoly", "showInGamePingText": "Ukazovat ping při hře", "showPlayerNamesText": "Ukazovat jména hráčů", diff --git a/dist/ba_data/data/languages/dutch.json b/dist/ba_data/data/languages/dutch.json index a706cfc..d7481a5 100644 --- a/dist/ba_data/data/languages/dutch.json +++ b/dist/ba_data/data/languages/dutch.json @@ -9,6 +9,7 @@ "changeOncePerSeasonError": "U moet wachten tot het volgende seizoen om dit te veranderen (${NUM} dagen)", "customName": "Aangepaste naam", "deviceSpecificAccountText": "U gebruikt nu het apparaat-specifieke account: ${NAME}", + "googlePlayGamesAccountSwitchText": "Als je een ander Google-account \nwilt gebruiken, gebruik dan de Google Play Games-app om over te schakelen.", "linkAccountsEnterCodeText": "Voer Code In", "linkAccountsGenerateCodeText": "Genereer Code", "linkAccountsInfoText": "(deel voortgang over verschillende platformen)", @@ -16,6 +17,7 @@ "linkAccountsInstructionsText": "Om twee accounts te koppelen, genereer je bij een\ndaar van een code die je invult bij de ander.\nVoortgang en inventaris worden dan samengevoegd.\nJe kan maximaal ${COUNT} accounts koppelen.\n\nBELANGRIJK: Koppel alleen accounts die jij bezit!\nAls je accounts van vrienden koppelt kan je niet \nmeer tegelijkertijd spelen!\n\nOok: Dit kan momenteel niet ongedaan gemaakt worden, dus pas op!", "linkAccountsText": "Koppel Accounts", "linkedAccountsText": "Verbonden Accounts:", + "manageAccountText": "Beheer account", "nameChangeConfirm": "Verander je account naam naar ${NAME}?", "notLoggedInText": "", "resetProgressConfirmNoAchievementsText": "Dit reset uw co-op campagne voortgang en\nlokale high-scores (maar niet uw tickets).\nDit kan niet ongedaan worden gemaakt. Weet u het zeker?", @@ -31,6 +33,7 @@ "signInWithGooglePlayText": "Log in met Google Play", "signInWithTestAccountInfoText": "(oudere account type; gebruik device account gaat door)", "signInWithTestAccountText": "Log in met test account", + "signInWithText": "Log in met ${SERVICE}", "signInWithV2InfoText": "(een account dat op alle platforms werkt)", "signInWithV2Text": "Inloggen met een BombSquad rekening", "signOutText": "Log Uit", @@ -43,6 +46,7 @@ "titleText": "Profiel", "unlinkAccountsInstructionsText": "Selecteer een account om los te koppelen.", "unlinkAccountsText": "Koppel accounts los.", + "unlinkLegacyV1AccountsText": "Ontkoppel oude (V1) accounts", "v2LinkInstructionsText": "Gebruik deze link om een ​​account aan te maken of in te loggen.", "viaAccount": "(via account ${NAME})", "youAreLoggedInAsText": "Je bent ingelogd als:", @@ -332,10 +336,14 @@ "achievementsRemainingText": "Resterende Prestaties:", "achievementsText": "Prestaties", "achievementsUnavailableForOldSeasonsText": "Sorry, prestatie gegevens voor oude seizoenen zijn niet beschikbaar.", + "activatedText": "${THING} geactiveerd.", "addGameWindow": { "getMoreGamesText": "Verkrijg Meer Spellen...", "titleText": "Voeg Game toe" }, + "addToFavoritesText": "Toevoegen aan favorieten", + "addedToFavoritesText": "'${NAME}' toegevoegd aan Favorieten.", + "allText": "Alle", "allowText": "Toestaan", "alreadySignedInText": "Uw account is al ingelogd op een ander apparaat;\nVerander van account of sluit het spel op uw andere\napparaten en probeer het opnieuw.", "apiVersionErrorText": "Kan module ${NAME} niet laden; deze gebruikt api-versie ${VERSION_USED}; benodigd is ${VERSION_REQUIRED}.", @@ -371,6 +379,7 @@ "chatMutedText": "berichten gedempt", "chatUnMuteText": "maak de chat ongedaan", "choosingPlayerText": "", + "codesExplainText": "Codes worden door de \nontwikkelaar verstrekt om accountproblemen te diagnosticeren en te corrigeren.", "completeThisLevelToProceedText": "U moet dit level voltooien\nom door te gaan!", "completionBonusText": "Voltooiing Bonus", "configControllersWindow": { @@ -454,6 +463,7 @@ "titleText": "Configureer Touchscreen", "touchControlsScaleText": "Touch Besturing Schalen" }, + "configureDeviceInSystemSettingsText": "${DEVICE} kan worden geconfigureerd in de Systeeminstellingen-app.", "configureItNowText": "Nu configureren?", "configureText": "Configureren", "connectMobileDevicesWindow": { @@ -509,6 +519,7 @@ "welcome2Text": "U kunt ook tickets verdienen van veel van dezelfde activiteiten.\nTickets kunnen gebruikt worden om mee te doen aan toernooien,\nvoor het vrijspelen van nieuwe karakters, speelvelden, mini-spellen en meer.", "yourPowerRankingText": "Uw Macht Klassement:" }, + "copyConfirmText": "Gekopieerd naar het klembord.", "copyOfText": "${NAME} Kopie", "copyText": "Kopiëren", "copyrightText": "© 2013 Eric Froemling", @@ -562,7 +573,9 @@ "deleteText": "Verwijder", "demoText": "demonstratie", "denyText": "Weigeren", + "deprecatedText": "Verouderd", "desktopResText": "Bureaublad Resolutie", + "deviceAccountUpgradeText": "Waarschuwing: u bent ingelogd met een apparaataccount (${NAME}). \nApparaataccounts worden in een toekomstige update verwijderd. \nUpgrade naar een V2-account als u uw voortgang wilt \nbehouden.", "difficultyEasyText": "Makkelijk", "difficultyHardOnlyText": "Alleen moeilijk", "difficultyHardText": "Moeilijk", @@ -571,6 +584,9 @@ "disableRemoteAppConnectionsText": "Schakel Afstandsbediening-App connecties uit", "disableXInputDescriptionText": "Staat meer dan 4 controllers toe, maar werkt misschien niet helemaal goed.", "disableXInputText": "Schakel XInput uit", + "disabledText": "Gehandicapt", + "discordFriendsText": "Wil je op zoek naar nieuwe mensen om mee te spelen? \nSluit je aan bij onze Discord en vind nieuwe vrienden!", + "discordJoinText": "Sluit je aan bij de onenigheid", "doneText": "Klaar", "drawText": "Gelijk", "duplicateText": "Kopieer", @@ -644,12 +660,15 @@ "useMusicFolderText": "Map met Muziek Bestanden" }, "editText": "Bewerk", + "enabledText": "Ingeschakeld", "endText": "Einde", "enjoyText": "Geniet!", "epicDescriptionFilterText": "${DESCRIPTION} In epische slow motion.", "epicNameFilterText": "Epische ${NAME}", "errorAccessDeniedText": "toegang geweigerd", + "errorDeviceTimeIncorrectText": "De tijd van je apparaat is ${HOURS} uur \nonjuist. Dit zal waarschijnlijk problemen \nveroorzaken. Controleer uw tijd- en tijdzone-instellingen.", "errorOutOfDiskSpaceText": "schuifruimte vol", + "errorSecureConnectionFailText": "Kan geen veilige cloudverbinding tot stand brengen; netwerkfunctionaliteit kan mislukken.", "errorText": "Fout", "errorUnknownText": "onbekende fout", "exitGameText": "${APP_NAME} Verlaten?", @@ -764,6 +783,7 @@ "manualYourLocalAddressText": "Uw lokale adres:", "nearbyText": "Dichtbij", "noConnectionText": "", + "noPartiesAddedText": "Geen partijen toegevoegd", "otherVersionsText": "(andere versies)", "partyCodeText": "Partijcode", "partyInviteAcceptText": "Accepteren", @@ -838,15 +858,19 @@ "youHaveText": "U heeft ${COUNT} tickets" }, "googleMultiplayerDiscontinuedText": "Sorry, de Google Play multi-player functie is nu even niet beschikbaar. \nIk werk hard aan een vervanger. \nProbeer nu alsjeblieft een andere connectie mogelijkheid.\n-Eric", + "googlePlayPurchasesNotAvailableText": "Google Play-aankopen zijn niet beschikbaar. \nMogelijk moet u uw winkel-app updaten.", + "googlePlayServicesNotAvailableText": "Google Play-services zijn niet beschikbaar. \nSommige app-functionaliteit is mogelijk uitgeschakeld.", "googlePlayText": "Google Play", "graphicsSettingsWindow": { "alwaysText": "Altijd", "fullScreenCmdText": "Volledig scherm (Cmd-F)", "fullScreenCtrlText": "Volledig scherm (Cmd-F)", + "fullScreenText": "Volledig scherm", "gammaText": "Gamma", "highText": "Hoog", "higherText": "Hoger", "lowText": "Laag", + "maxFPSText": "Maximale FPS", "mediumText": "Gemiddeld", "neverText": "Nooit", "resolutionText": "Resolutie", @@ -1041,6 +1065,7 @@ "creditsText": "Credits", "demoMenuText": "Voorbeeld Menu", "endGameText": "Beëindig Spel", + "endTestText": "Einde proef", "exitGameText": "Verlaat Spel", "exitToMenuText": "Verlaat naar menu?", "howToPlayText": "Hoe te Spelen", @@ -1061,6 +1086,7 @@ "maxConnectionsText": "Max Connecties", "maxPartySizeText": "Max Partij Grootte", "maxPlayersText": "Max Spelers", + "merchText": "Koopwaar!", "modeArcadeText": "Speelhal Modus", "modeClassicText": "Klassieke Modus", "modeDemoText": "Demo Modus", @@ -1091,8 +1117,10 @@ "noExternalStorageErrorText": "Geen externe opslag gevonden op dit apparaat", "noGameCircleText": "Fout: niet aangemeld bij GameCircle", "noJoinCoopMidwayText": "Je kan niet halverwegen inspringen bij coöperatieve spellen.", + "noPluginsInstalledText": "Geen plug-ins geïnstalleerd", "noProfilesErrorText": "U heeeft geen speler profielen, dus zit u vast aan '${NAME}'.\nGa naar Instellingen->Speler Profielen om een profiel te maken voor uzelf.", "noScoresYetText": "Nog geen scores.", + "noServersFoundText": "Geen servers gevonden.", "noThanksText": "Nee Bedankt", "noTournamentsInTestBuildText": "PAS OP: De punten van deze test tournament worden niet meegerekend.", "noValidMapsErrorText": "Geen geldige gebieden gevonden voor dit speltype.", @@ -1102,6 +1130,7 @@ "notSignedInErrorText": "Je moet inloggen om dit te doen.", "notSignedInGooglePlayErrorText": "Je moet ingelogd zijn met Google Play om dit te doen.", "notSignedInText": "niet ingelogd", + "notUsingAccountText": "Opmerking: ${SERVICE}-account negeren. \nGa naar 'Account -> Inloggen met ${SERVICE}' als je hier gebruik van wilt maken.", "nothingIsSelectedErrorText": "Er is niks geselecteerd!", "numberText": "#${NUMBER}", "offText": "Uit", @@ -1163,7 +1192,14 @@ "playlistsText": "Speellijsten", "pleaseRateText": "Als u geniet van ${APP_NAME}, zou u dan een moment willen\nnemen om een waardering te geven of recensie te schrijven.\nDit geeft ons nuttige feedback voor toekomstige ontwikkelingen.\n\nBedankt!\n-eric", "pleaseWaitText": "Even geduld...", + "pluginClassLoadErrorText": "Fout bij het laden van plug-inklasse '${PLUGIN}': ${ERROR}", + "pluginInitErrorText": "Fout bij het starten van plug-in '${PLUGIN}': ${ERROR}", + "pluginSettingsText": "Plugin-instellingen", + "pluginsAutoEnableNewText": "Schakel nieuwe plug-ins automatisch in", "pluginsDetectedText": "Nieuw contact gedecteerd. Schakel in/configureer in de instellingen.", + "pluginsDisableAllText": "Schakel alle plug-ins uit", + "pluginsEnableAllText": "Schakel alle plug-ins in", + "pluginsRemovedText": "${NUM} plug-in(s) niet meer gevonden.", "pluginsText": "Contacten", "practiceText": "Oefenen", "pressAnyButtonPlayAgainText": "Druk een knop om opnieuw te spelen...", @@ -1259,6 +1295,8 @@ "runText": "Ren", "saveText": "Opslaan", "scanScriptsErrorText": "Fout(en) bij inlezen scripts; zie log voor details.", + "scanScriptsMultipleModulesNeedUpdatesText": "${PATH} en ${NUM} andere module(s) moeten worden bijgewerkt voor api ${API}.", + "scanScriptsSingleModuleNeedsUpdatesText": "${PATH} moet worden bijgewerkt voor API ${API}.", "scoreChallengesText": "Score Uitdagingen", "scoreListUnavailableText": "Score lijst niet beschikbaar.", "scoreText": "Score", @@ -1305,6 +1343,9 @@ "netTestingText": "Netwerk Testen", "resetText": "Reset", "showBombTrajectoriesText": "Baan van de bom weergeven", + "showDemosWhenIdleText": "Toon demo's wanneer deze niet actief zijn", + "showDevConsoleButtonText": "Toon dev-consoleknop", + "showInGamePingText": "Toon in-game ping", "showPlayerNamesText": "Toon Namen Spelers", "showUserModsText": "Toon Aanpassingen Map", "titleText": "Geavanceerd", @@ -1399,6 +1440,8 @@ "storeText": "Winkel", "submitText": "Voorleggen", "submittingPromoCodeText": "Code verzenden ...", + "successText": "Succes!", + "supportEmailText": "Als u problemen ondervindt met de app, \nkunt u een e-mail sturen naar ${EMAIL}.", "teamNamesColorText": "Teamnamen / kleuren ...", "teamsText": "Teams", "telnetAccessGrantedText": "Telnet toegang ingeschakeld.", @@ -1427,6 +1470,7 @@ "tournamentStandingsText": "Toernooi Stand", "tournamentText": "Toernooi", "tournamentTimeExpiredText": "Toernooi Tijd Verlopen", + "tournamentsDisabledWorkspaceText": "Toernooien zijn uitgeschakeld als werkruimten actief zijn. \nOm toernooien opnieuw in te schakelen, schakelt u uw werkruimte uit en start u opnieuw op.", "tournamentsText": "Toernooien", "translations": { "characterNames": { @@ -1598,6 +1642,7 @@ "Italian": "Italiaans", "Japanese": "Japans", "Korean": "Koreaans", + "Malay": "Maleis", "Persian": "Perzisch", "Polish": "Pools", "Portuguese": "Portugees", @@ -1878,6 +1923,7 @@ "unlockThisInTheStoreText": "Hiervoor moet een aankoop gedaan worden in de winkel.", "unlockThisProfilesText": "Om meer dan ${NUM} profielen te maken heb je nodig:", "unlockThisText": "Om dit te ontgrendelen, moet u:", + "unsupportedControllerText": "Sorry, controller \"${NAME}\" wordt niet ondersteund.", "unsupportedHardwareText": "Sorry, deze hardware wordt niet ondersteunt door deze versie van het spel.", "upFirstText": "Als eerste:", "upNextText": "Daarna volgt in potje ${COUNT}:", @@ -1888,8 +1934,10 @@ "usesExternalControllerText": "Dit spel maakt gebruik van een externe controller als input.", "usingItunesText": "De muziek app wordt gebruikt voor de muziek...", "usingItunesTurnRepeatAndShuffleOnText": "Zorg er er voor dat shuffle AAN staat en herhalen op ALLES staat in iTunes.", + "v2AccountLinkingInfoText": "Om V2-accounts te koppelen, gebruikt u de knop 'Account beheren'.", "validatingBetaText": "Beta wordt Gevalideerd...", "validatingTestBuildText": "Valideren Test Versie...", + "viaText": "via", "victoryText": "Overwinning!", "voteDelayText": "Je kan geen andere stemming starten voor ${NUMBER} seconden", "voteInProgressText": "Er is al een stemming bezig.", @@ -1922,6 +1970,7 @@ }, "waveText": "Ronde", "wellSureText": "Maar Natuurlijk!", + "whatIsThisText": "Wat is dit?", "wiimoteLicenseWindow": { "licenseTextScale": 0.62, "titleText": "DarwiinRemote Copyright" @@ -1946,6 +1995,8 @@ "winsPlayerText": "${NAME} Wint!", "winsTeamText": "${NAME} Wint!", "winsText": "${NAME} Wint!", + "workspaceSyncErrorText": "Fout bij synchroniseren van ${WORKSPACE}. Zie logboek voor details.", + "workspaceSyncReuseText": "Kan ${WORKSPACE} niet synchroniseren. Hergebruik van de vorige gesynchroniseerde versie.", "worldScoresUnavailableText": "Wereldwijde scores niet beschikbaar.", "worldsBestScoresText": "'s Werelds Beste Scores", "worldsBestTimesText": "'s Werelds Beste Tijden", diff --git a/dist/ba_data/data/languages/english.json b/dist/ba_data/data/languages/english.json index 7c2e618..70cec44 100644 --- a/dist/ba_data/data/languages/english.json +++ b/dist/ba_data/data/languages/english.json @@ -1277,6 +1277,7 @@ "netTestingText": "Network Testing", "resetText": "Reset", "showBombTrajectoriesText": "Show Bomb Trajectories", + "showDemosWhenIdleText": "Show Demos When Idle", "showDevConsoleButtonText": "Show Dev Console Button", "showInGamePingText": "Show In-Game Ping", "showPlayerNamesText": "Show Player Names", diff --git a/dist/ba_data/data/languages/filipino.json b/dist/ba_data/data/languages/filipino.json index 5d52a80..481eac6 100644 --- a/dist/ba_data/data/languages/filipino.json +++ b/dist/ba_data/data/languages/filipino.json @@ -331,9 +331,11 @@ "achievementsUnavailableForOldSeasonsText": "Pasensya na, hindi available ang mga detalye ng achievements para sa mga lumang seasons.", "activatedText": "Na-aktibo ang ${THING}.", "addGameWindow": { - "getMoreGamesText": "Kukuha ng higit pang mga laro…", + "getMoreGamesText": "Kumuha ng Higit pang mga Laro…", "titleText": "Magdagdag Ng Laro" }, + "addToFavoritesText": "Ilagay sa Mga Paborito", + "addedToFavoritesText": "Nakalagay na ang '${NAME}' sa Mga Paborito.", "allText": "Lahat", "allowText": "Payagan", "alreadySignedInText": "Ang iyong account ay naka-sign in mula sa isa pang device;\nMangyaring lumipat ng mga accounts o isara ang laro sa iyong\niba pang mga device at subukan muli.", @@ -341,16 +343,16 @@ "audioSettingsWindow": { "headRelativeVRAudioInfoText": "(Lalabas ang “Auto” ng ito kapag nakasaksak ang headphones)", "headRelativeVRAudioText": "Head-Relative VR Audio", - "musicVolumeText": "Volume ng Musika", + "musicVolumeText": "Lakas ng Musika", "soundVolumeText": "Lakas ng Tunog", "soundtrackButtonText": "Mga Soundtrack", "soundtrackDescriptionText": "(I-assign ang iyong sariling musika para magtugtug kapag naglalaro)", - "titleText": "Audio" + "titleText": "Tugtugan" }, "autoText": "Auto", "backText": "Bumalik", - "banThisPlayerText": "I-ban ang Manlalarong Ito", - "bestOfFinalText": "Pinakamahusay-sa-${COUNT} Final", + "banThisPlayerText": "Bawalin ang Manlalarong Ito", + "bestOfFinalText": "Wakas ng Pinakamahusay-sa-${COUNT}", "bestOfSeriesText": "Pinakamahusay sa ${COUNT} series:", "bestOfUseFirstToInstead": 0, "bestRankText": "Ang iyong pinakamahusay ay #${RANK}", @@ -366,10 +368,10 @@ "challengeEndedText": "Natapos na ang challenge na ito.", "chatMuteText": "Tahimikin Ang Chat Mo", "chatMutedText": "Na-mute ang Chat", - "chatUnMuteText": "I-unmute ang Chat", + "chatUnMuteText": "Ipaganahin Mo Na ang Chat", "choosingPlayerText": "", - "codesExplainText": "Binigay ng developer ang mga codes \npara i-diagnose at i-tama ang mga issues sa account.", - "completeThisLevelToProceedText": "I complete mo muna\nang level na ito bago ka mag-proceed!", + "codesExplainText": "Binigay ng developer ang mga kowd \npara kilalanin at maitama ang mga problema sa account.", + "completeThisLevelToProceedText": "Tapusin mo muna\nang antas na ito bago tumuloy!", "completionBonusText": "Bonus sa Pagkumpleto nito:", "configControllersWindow": { "configureControllersText": "I-configure ang mga Controller", @@ -380,7 +382,7 @@ "ps3Text": "PS3 Controllers", "titleText": "Mga Controller", "wiimotesText": "Wiimotes", - "xbox360Text": "Xbox 360 Controllers" + "xbox360Text": "Controllers ng Xbox 360" }, "configGamepadSelectWindow": { "androidNoteText": "Tandaan: nag-iiba-iba ang suporta sa controller sa mga devices at bersyon ng Andriod.", @@ -396,17 +398,17 @@ "autoRecalibrateDescriptionText": "(paganahin ito kung ang iyong karakter ay hindi gumagalaw ng buong bilis)", "autoRecalibrateText": "Auto-Recalibrate Analog Stick", "axisText": "aksis", - "clearText": "i-bura", + "clearText": "ibura", "dpadText": "DPAD", "extraStartButtonText": "Extra na Start Button", "ifNothingHappensTryAnalogText": "Kapag walang gumagana, i-try na i-assign sa analog stick.", "ifNothingHappensTryDpadText": "Kapag hindi gumana, i-try na i-assign sa d-pad.", "ignoreCompletelyDescriptionText": "(pigilan ang controller na ito na maapektuhan ang alinman sa laro o mga menu)", "ignoreCompletelyText": "Huwag Pansinin", - "ignoredButton1Text": "Pindutan Na ‘Di Pansinin 1", - "ignoredButton2Text": "Pindutan Na ‘Di Pansinin 2", - "ignoredButton3Text": "Pindutan Na ‘Di Pansinin 3", - "ignoredButton4Text": "Pindutan Na ‘Di Pansinin 4", + "ignoredButton1Text": "Pindutan Na ‘Di Pinansin 1", + "ignoredButton2Text": "Pindutan Na ‘Di Pinansin 2", + "ignoredButton3Text": "Pindutan Na ‘Di Pinansin 3", + "ignoredButton4Text": "Pindutan Na ‘Di Pinansin 4", "ignoredButtonDescriptionText": "(gamitin ito para ma-prevent ang ‘home’ o ‘sync’ buttons na nakakaapekto sa UI)", "pressAnyAnalogTriggerText": "Pindutin ang anumang analog trigger…", "pressAnyButtonOrDpadText": "Pindutin ang anumang pindutan o dpad…", @@ -751,6 +753,7 @@ "manualYourLocalAddressText": "Iyong lokal na address:", "nearbyText": "Malapit", "noConnectionText": "", + "noPartiesAddedText": "Walang Nadagdag na mga Partido", "otherVersionsText": "", "partyCodeText": "Kowd ng Partido", "partyInviteAcceptText": "Tanggapin", @@ -776,7 +779,7 @@ "publicText": "Publiko", "requestingAPromoCodeText": "Humihiling ng kowd...", "sendDirectInvitesText": "I-send ng direktang imbitasyon", - "shareThisCodeWithFriendsText": "Ibahagi ang code na ito sa iyong mga kaibigan:", + "shareThisCodeWithFriendsText": "Ibahagi ang kowd nito sa iyong mga kaibigan:", "showMyAddressText": "Ipakita Ang Address Ko", "startHostingPaidText": "Mag-host ngayon ng ${COST}", "startHostingText": "Host", @@ -789,7 +792,7 @@ "wifiDirectText": "Wi-Fi Direct", "worksBetweenAllPlatformsText": "(ito’y gumagana sa pagitan ng lahat ng mga platform)", "worksWithGooglePlayDevicesText": "(ito’y gumagana sa mga devices na tumatakbo ang bersyon ng Google Play (android) ng larong ito)", - "youHaveBeenSentAPromoCodeText": "Pinadalhan ka ng promo code na ${APP_NAME}:" + "youHaveBeenSentAPromoCodeText": "Pinadalhan ka ng promo kowd ng ${APP_NAME}:" }, "getTicketsWindow": { "freeText": "LIBRE!", @@ -887,7 +890,7 @@ "holdAnyButtonText": "", "holdAnyKeyText": "", "hostIsNavigatingMenusText": "- Ang ${HOST} ay nagna-navigate sa mga menu tulad ng isang boss -", - "importPlaylistCodeInstructionsText": "Gamitin ang sumusunod na code upang i-import ang playlist na ito sa ibang lugar:", + "importPlaylistCodeInstructionsText": "Gamitin ang sumusunod na kowd upang i-import ang playlist na ito sa ibang lugar:", "importPlaylistSuccessText": "Na-import na ${TYPE} na playlist '${NAME}'", "importText": "I-Import", "importingText": "Nag-Iimport…", @@ -1054,21 +1057,22 @@ "nameDiedText": "${NAME} ay namatay.", "nameKilledText": "Pinatay ni ${NAME} si ${VICTIM}", "nameNotEmptyText": "Hindi pwede ang walang pangalan!", - "nameScoresText": "${NAME} Naka-score!", + "nameScoresText": "Nakaiskor si ${NAME}!", "nameSuicideKidFriendlyText": "Hindi sinasadyang namatay si ${NAME}.", "nameSuicideText": "Nagpakamatay si ${NAME}.", "nameText": "Pangalan", "nativeText": "Natural", "newPersonalBestText": "Bagong personal na pinakamahusay!", "newTestBuildAvailableText": "Available ang isang mas bagong pagsubok na build! (${VERSION} build ${BUILD}).\nKunin ito sa ${ADDRESS}", - "newText": "Bago", + "newText": "Gumawa ng Bago", "newVersionAvailableText": "Available ang isang mas bagong bersyon ng ${APP_NAME}! (${VERSION})", "nextAchievementsText": "Mga Susunod na Nakamit:", - "nextLevelText": "Mga Susunod na Level", + "nextLevelText": "Susunod na Antas", "noAchievementsRemainingText": "- wala", "noContinuesText": "(hindi i-continue)", "noExternalStorageErrorText": "Walang nakitang external na storage sa device na ito", "noGameCircleText": "Error: hindi naka-log in sa GameCircle", + "noPluginsInstalledText": "Hindi Nakabit ang mga Plugin", "noScoresYetText": "Wala pang score.", "noServersFoundText": "Walang nahanap na servers.", "noThanksText": "Salamat Nalang", @@ -1159,7 +1163,7 @@ "pressToSelectProfileText": "pindutin ang ${BUTTONS} upang pumili ng manlalaro", "pressToSelectTeamText": "pindutin ang ${BUTTONS} para pumili ng team", "promoCodeWindow": { - "codeText": "Code", + "codeText": "Kowd", "enterText": "I-enter" }, "promoSubmitErrorText": "Error sa pagsusumite ng kowd; suriin ang iyong koneksyon sa internet", @@ -1245,12 +1249,12 @@ "pointsText": "Puntos", "secondsText": "Segundo" }, - "scoreWasText": "(ay nasa ${COUNT})", + "scoreWasText": "(mula sa ${COUNT})", "selectText": "Pilihin", "seriesWinLine1PlayerText": "ANG NANALO SA", "seriesWinLine1TeamText": "ANG NANALO SA", "seriesWinLine1Text": "ANG NANALO SA", - "seriesWinLine2Text": "SERYENG NITO!", + "seriesWinLine2Text": "SERYE NITO!", "settingsWindow": { "accountText": "Account", "advancedText": "Mga Iba Pa", @@ -1269,7 +1273,7 @@ "disableThisNotice": "(maaari mong i-disable ang notice na ito sa mga advanced na setting)", "enablePackageModsDescriptionText": "(nagpapagana ng mga karagdagang kakayahan sa pag-modding ngunit hindi pinapagana ang net-play)", "enablePackageModsText": "Paganahin ang Lokal na Package Mods", - "enterPromoCodeText": "Ilagay ang Code", + "enterPromoCodeText": "Ilagay ang Kowd", "forTestingText": "Tandaan: ang mga value na ito ay para lamang sa pagsubok at mawawala kapag lumabas ang app.", "helpTranslateText": "Ang mga pagsasalin na hindi Ingles ng ${APP_NAME} ay isang komunidad\nsuportadong pagsisikap. Kung gusto mong mag-ambag o magtama\nisang pagsasalin, sundan ang link sa ibaba. Salamat!", "kickIdlePlayersText": "I-kick Ang Mga Idle na Manlalaro", @@ -1280,6 +1284,7 @@ "netTestingText": "Pagsusuri ng Network", "resetText": "I-reset", "showBombTrajectoriesText": "Ipakita ang Mga Trajectory ng Bomba", + "showDemosWhenIdleText": "Ipakita Ang Demo Habang Nakatigil", "showDevConsoleButtonText": "Ipakita ang Dev Console Button", "showInGamePingText": "Ipakita ang In-Game Ping", "showPlayerNamesText": "Ipakita ang Mga Pangalan ng Manlalaro", @@ -1289,14 +1294,14 @@ "translationFetchErrorText": "hindi available ang katayuan ng wika", "translationFetchingStatusText": "sinusuri ang status ng lengguwahe…", "translationInformMe": "Ipaalam sa akin kapag ang aking wika ay nangangailangan ng mga update", - "translationNoUpdateNeededText": "ang kasalukuyang wika ay makabago; woohoo!", + "translationNoUpdateNeededText": "ang kasalukuyang wika ay makabago na muli; yehey!", "translationUpdateNeededText": "** ang kasalukuyang wika ay nangangailangan ng mga update!! **", "vrTestingText": "Testing ng VR" }, "shareText": "I-share", "sharingText": "Nagbabahagi….", "showText": "Ipakita", - "signInForPromoCodeText": "Dapat kang mag-sign in sa isang account para magkabisa ang mga code.", + "signInForPromoCodeText": "Dapat kang mag-sign in sa isang account para magkabisa ang mga kowd.", "signInWithGameCenterText": "Para gumamit ng Game Center account,\nmag-sign in gamit ang Game Center app.", "singleGamePlaylistNameText": "${GAME} lang", "singlePlayerCountText": "1 manlalaro", @@ -1371,7 +1376,7 @@ }, "storeText": "Tindahan", "submitText": "Ipasa", - "submittingPromoCodeText": "Nagsusumite ng Code...", + "submittingPromoCodeText": "Pinapasa ang Kowd...", "successText": "Wakas!", "supportEmailText": "Pag may problema sa app, \npaki-email ang ${EMAIL}.", "teamNamesColorText": "Mga Pangalan/Kulay ng Team…", @@ -1514,7 +1519,7 @@ "Conquest": "Pagsakop", "Death Match": "Laban ng Kamatayan", "Easter Egg Hunt": "Paghahanap ng mga Easter Egg", - "Elimination": "Pagbabawas sa away", + "Elimination": "Pagbabawasan", "Football": "Putbol", "Hockey": "Hockey", "Keep Away": "Layuan Mo", @@ -1522,7 +1527,7 @@ "Meteor Shower": "Ulan ng mga Bulalakaw", "Ninja Fight": "Labanan ng mga Ninja", "Onslaught": "Pagsalakay", - "Race": "Takbuan", + "Race": "Takbuhan", "Runaround": "Bantayan", "Target Practice": "Pagsasanay ng Patamaan", "The Last Stand": "Ang Huling Labanan" @@ -1532,44 +1537,44 @@ "Keyboard P2": "Keyboard F2" }, "languages": { - "Arabic": "Arabe", - "Belarussian": "Belaruso", - "Chinese": "Tsino", - "ChineseTraditional": "Tsinong Tradisyonal", - "Croatian": "Kroatyano", - "Czech": "Tsek", - "Danish": "Makadenmark", - "Dutch": "Olandes", - "English": "Ingles", - "Esperanto": "Esperanto", - "Filipino": "Tagalog", - "Finnish": "Finnish", - "French": "Pranses", - "German": "Aleman", - "Gibberish": "Hindi Naiitindihan na Linguahe", - "Greek": "Griyego", - "Hindi": "Indiyano", - "Hungarian": "Hanggaryan", - "Indonesian": "Indonesiyo", - "Italian": "Italiyano", - "Japanese": "Nippongo", - "Korean": "Koreano", - "Malay": "Malay", - "Persian": "Persyano", - "Polish": "Polish", - "Portuguese": "Portuges", - "Romanian": "Rumano", - "Russian": "Ruso", - "Serbian": "Serbyan", - "Slovak": "Eslobako", - "Spanish": "Espanyol", - "Swedish": "Suweko", - "Tamil": "Tamil", - "Thai": "Siyam", - "Turkish": "Turko", - "Ukrainian": "Ukranyo", - "Venetian": "Benesiya", - "Vietnamese": "Byetnam" + "Arabic": "Wikang Arabik", + "Belarussian": "Wikang Belaruso", + "Chinese": "Wikang Tsino", + "ChineseTraditional": "Wikang Tsinong Tradisyonal", + "Croatian": "Wikang Kroatyano", + "Czech": "Wikang Tsek", + "Danish": "Wikang Denmark", + "Dutch": "Wikang Olandes", + "English": "Wikang Ingles", + "Esperanto": "Wikang Esperanto", + "Filipino": "Wikang Tagalog", + "Finnish": "Wikang Finnish", + "French": "Wikang Pranses", + "German": "Wikang Alemanya", + "Gibberish": "Hindi Maintindihan nito", + "Greek": "Wikang Griyego", + "Hindi": "Wikang Indiyano", + "Hungarian": "Wikang Hanggaryan", + "Indonesian": "Wikang Indonesiyo", + "Italian": "Wikang Italiyano", + "Japanese": "Wikang Hapon", + "Korean": "Wikang Koreano", + "Malay": "Wikang Malay", + "Persian": "Wikang Persyano", + "Polish": "Wikang Polish", + "Portuguese": "Wikang Portuges", + "Romanian": "Wikang Rumano", + "Russian": "Wikang Ruso", + "Serbian": "Wikang Serbyan", + "Slovak": "Wikang Eslobako", + "Spanish": "Wikang Espanyol", + "Swedish": "Wikang Suweko", + "Tamil": "Wikang Tamil", + "Thai": "Wikang Thai", + "Turkish": "Wikang Turko", + "Ukrainian": "Wikang Ukranyo", + "Venetian": "Wikang Benesiya", + "Vietnamese": "Wikang Byetnam" }, "leagueNames": { "Bronze": "Tanso", @@ -1580,7 +1585,7 @@ "mapsNames": { "Big G": "Malaking G", "Bridgit": "Tawiring Tulay", - "Courtyard": "Looban Patyo", + "Courtyard": "Loobang Patyo", "Crag Castle": "Kastilyong Bangin", "Doom Shroom": "Itim na Kabute", "Football Stadium": "Istadyum", @@ -1598,7 +1603,7 @@ }, "playlistNames": { "Just Epic": "Epic Lang", - "Just Sports": "Shorts Lang" + "Just Sports": "Sports Lang" }, "scoreNames": { "Flags": "Watawat", @@ -1606,10 +1611,10 @@ "Score": "Iskor", "Survived": "Nakaligtas", "Time": "Oras", - "Time Held": "Oras na Nahawak" + "Time Held": "Oras na Gaganapin" }, "serverResponses": { - "A code has already been used on this account.": "Nagamit na ang isang code sa account na ito.", + "A code has already been used on this account.": "Nagamit na ang isang kowd sa account na ito.", "A reward has already been given for that address.": "Naibigay na ang reward para sa address na iyon.", "Account linking successful!": "Matagumpay ang pag-link ng account!", "Account unlinking successful!": "Matagumpay ang pag-unlink ng account!", @@ -1628,9 +1633,9 @@ "Could not establish a secure connection.": "Hindi makapagtatag ng secure na koneksyon.", "Daily maximum reached.": "Naabot na ang pang-araw-araw ng request.", "Entering tournament...": "Papasok sa paligsahan…", - "Invalid code.": "Di-wastong code.", + "Invalid code.": "Di-wasto ang kowd.", "Invalid payment; purchase canceled.": "Di-wastong pagbabayad; kinansela ang pagbili.", - "Invalid promo code.": "Di-wastong promo code.", + "Invalid promo code.": "Di-wasto ang promo kowd.", "Invalid purchase.": "Di-wastong bilihin", "Invalid tournament entry; score will be ignored.": "Di-wastong entry sa paligsahan; hindi papansinin ang mga iskor.", "Item unlocked!": "Na-unlock ang aytem!", @@ -1638,7 +1643,7 @@ "Link account ${ACCOUNT} to this account?\nAll existing data on ${ACCOUNT} will be lost.\nThis can not be undone. Are you sure?": "I-link ang account na ${ACCOUNT} sa account na ito?\nMawawala ang lahat ng umiiral na data sa ${ACCOUNT}.\nHindi na ito maaaring bawiin. Sigurado ka ba?", "Max number of playlists reached.": "Naabot na ang maximum na bilang ng mga playlist.", "Max number of profiles reached.": "Naabot na ang maximum na bilang ng mga profile.", - "Maximum friend code rewards reached.": "Naabot ang maximum na mga reward sa code ng kaibigan.", + "Maximum friend code rewards reached.": "Naabot ang maximum na mga reward sa kowd ng kaibigan.", "Message is too long.": "Ang mensahe ay napakahaba.", "No servers are available. Please try again soon.": "Walang makakuha na mga server. Pakisubukang muli sa lalong madaling oras.", "Profile \"${NAME}\" upgraded successfully.": "Matagumpay na na-upgrade ang profile na \"${NAME}\".", @@ -1646,10 +1651,10 @@ "Purchase successful!": "Matagumpay ang pagbili!", "Received ${COUNT} tickets for signing in.\nCome back tomorrow to receive ${TOMORROW_COUNT}.": "Nakatanggap ng ${COUNT} na tiket para sa pag-sign in.\nBumalik bukas para makatanggap ng ${TOMORROW_COUNT}.", "Server functionality is no longer supported in this version of the game;\nPlease update to a newer version.": "Hindi na sinusuportahan ang functionality ng server sa bersyong ito ng laro;\nMangyaring mag-update sa isang mas bagong bersyon.", - "Sorry, there are no uses remaining on this code.": "Pasensya na, wala nang natitirang gamit sa code na ito.", - "Sorry, this code has already been used.": "Pasensya na, nagamit na ang code na ito.", - "Sorry, this code has expired.": "Pasensya na, nag-expire na ang code na ito.", - "Sorry, this code only works for new accounts.": "Pasensya na, gumagana lang ang code na ito para sa mga bagong account.", + "Sorry, there are no uses remaining on this code.": "Pasensya na, hindi na magagamit ang kowd na ito.", + "Sorry, this code has already been used.": "Pasensya na, nagamit na ang kowd na ito.", + "Sorry, this code has expired.": "Pasensya na, nag-expire na ang kowd na ito.", + "Sorry, this code only works for new accounts.": "Pasensya na, gumagana lang ang kowd na ito para sa mga bagong account.", "Still searching for nearby servers; please try again soon.": "Naghahanap pa rin ng mga kalapit na server; mangyaring subukan muli sa lalong madaling oras.", "Temporarily unavailable; please try again later.": "Pansamantalang hindi magagamit; Subukang muli mamaya.", "The tournament ended before you finished.": "Natapos ang tournament bago ka natapos.", diff --git a/dist/ba_data/data/languages/french.json b/dist/ba_data/data/languages/french.json index 5ce0f60..18c4ce2 100644 --- a/dist/ba_data/data/languages/french.json +++ b/dist/ba_data/data/languages/french.json @@ -340,6 +340,8 @@ "getMoreGamesText": "Obtenir plus de jeux...", "titleText": "Ajouter un Jeu" }, + "addToFavoritesText": "Ajouter aux favoris", + "addedToFavoritesText": "'${NAME}' a été ajouté aux favoris.", "allText": "Tous", "allowText": "Autoriser", "alreadySignedInText": "Votre compte est connecté sur un autre appareil;\nveuillez changer de compte ou fermez le jeu sur \nles autres appareils et réessayez.", @@ -781,6 +783,7 @@ "manualYourLocalAddressText": "Votre adresse locale:", "nearbyText": "Proche", "noConnectionText": "", + "noPartiesAddedText": "Aucune partie ajoutée", "otherVersionsText": "(autres versions)", "partyCodeText": "Code de la partie", "partyInviteAcceptText": "Accepter", @@ -1124,6 +1127,7 @@ "noExternalStorageErrorText": "Aucun stockage externe a été trouvé pour cet appareil", "noGameCircleText": "Erreur: vous n'êtes pas connecté au GameCircle", "noJoinCoopMidwayText": "Vous ne pouvez pas rejoindre une partie co-cop en plein milieu.", + "noPluginsInstalledText": "Aucun Plug-in installé", "noProfilesErrorText": "Vous avez aucun profil de joueur, vous êtes donc coincés avec '${NAME}'.\nAllez à Paramètres->Profils des Joueurs pour vous créer un profil.", "noScoresYetText": "Aucun score pour le moment.", "noServersFoundText": "Aucun serveur trouvé.", @@ -1348,6 +1352,7 @@ "netTestingText": "Tester Votre Réseau", "resetText": "Réinitialiser", "showBombTrajectoriesText": "Montrer les trajectoires de bombe", + "showDemosWhenIdleText": "Afficher les démos en cas d'inactivité", "showDevConsoleButtonText": "Afficher le Bouton de la Console de Développeur", "showInGamePingText": "Afficher La Latence En Jeu", "showPlayerNamesText": "Montrer les Noms des Joueurs", diff --git a/dist/ba_data/data/languages/gibberish.json b/dist/ba_data/data/languages/gibberish.json index 88a89fe..afd5b6b 100644 --- a/dist/ba_data/data/languages/gibberish.json +++ b/dist/ba_data/data/languages/gibberish.json @@ -1385,6 +1385,7 @@ "netTestingText": "Ntwkrz Tsstcg", "resetText": "Rsttz", "showBombTrajectoriesText": "Shzlz Bomf Tfwoejcwoefz", + "showDemosWhenIdleText": "Sho cwoefj c wdofdfjdfwf", "showDevConsoleButtonText": "Sho c weroiw c wo cwoije fwois", "showInGamePingText": "Shoe o co fowl Png", "showPlayerNamesText": "SHzlfjl Plzlrr Nmzlzlls", diff --git a/dist/ba_data/data/languages/hindi.json b/dist/ba_data/data/languages/hindi.json index d909676..be1cdc0 100644 --- a/dist/ba_data/data/languages/hindi.json +++ b/dist/ba_data/data/languages/hindi.json @@ -31,6 +31,7 @@ "signInWithGooglePlayText": "गूगल प्ले से साईन ईन करे", "signInWithTestAccountInfoText": "(पुराना खाते का प्ररूप; आगे के लिए यंत्र खाते का प्रयोग करें)", "signInWithTestAccountText": "परीक्षण के खाते से साइन इन करें", + "signInWithText": "${service} के साथ साइन इन करें", "signInWithV2InfoText": "(एक खाता जो सभी प्लेटफार्मों पर काम करता है)", "signInWithV2Text": "BombSquad खाते से साइन इन करें", "signOutText": "साइन आउट", @@ -337,6 +338,8 @@ "getMoreGamesText": "और गेम्स कि जानकारी पायें", "titleText": "गेम जोड़ें" }, + "addToFavoritesText": "पसंदीदा में जोड़े", + "addedToFavoritesText": "\"${NAME}\"को पसंदीदा में जोड़ा गया।", "allText": "सभी", "allowText": "अनुमति दें", "alreadySignedInText": "आपका खाता किसी अन्य डिवाइस से साइन किया गया है; \nकृपया खातों को स्विच करें या अपने गेम को अन्य डिवाइस \nपर बंद करें और फिर से प्रयास करें", @@ -370,6 +373,7 @@ "chatMutedText": "बातचीत मौन हो गई है", "chatUnMuteText": "बातचीत दोबारा शुरू करें", "choosingPlayerText": "<खिलाड़ी चुना जा रहा है>", + "codesExplainText": "डेवलपर द्वारा कोड प्रदान किए जाते हैं\nखाता समस्याओं का निदान और सुधार करें।", "completeThisLevelToProceedText": "आपको यह पड़ाव पार करना पड़ेगा आगे बढ़ने के लिए !", "completionBonusText": "पूर्णता पुरस्कार", "configControllersWindow": { @@ -450,6 +454,7 @@ "swipeText": "स्वाइप", "titleText": "टच स्क्रीन को कॉन्फ़िगर करें" }, + "configureDeviceInSystemSettingsText": "${DEVICE}को सिस्टम सेटिंग्स ऐप में कॉन्फ़िगर किया जा सकता है।", "configureItNowText": "अभी कॉन्फ़िगर करें ?", "configureText": "कॉन्फ़िगर", "connectMobileDevicesWindow": { @@ -563,6 +568,8 @@ "disableXInputDescriptionText": "4 नियंत्रकों से अधिक की अनुमति देता है लेकिन साथ ही साथ काम नहीं कर सकते", "disableXInputText": "Xinput अक्षम करें", "disabledText": "डिसेबल्ड", + "discordFriendsText": "क्या आप खेलने के लिए नए लोगों की तलाश करना चाहते हैं?\nहमारे डिस्कोर्ड में शामिल हों और नए दोस्त खोजें!", + "discordJoinText": "डिस्कोर्ड में शामिल हों।", "doneText": "हो गया", "drawText": "बराबर", "duplicateText": "प्रतिलिपि", @@ -750,6 +757,7 @@ "manualYourLocalAddressText": "आपका स्थानीय पता:", "nearbyText": "आस/पास", "noConnectionText": "<कोई कनेक्शन नहीं है>", + "noPartiesAddedText": "कोई दल नहीं जोड़ा गया", "otherVersionsText": "(कोई और संस्करण)", "partyCodeText": "दल कोड", "partyInviteAcceptText": "स्वीकार करें", @@ -1067,6 +1075,7 @@ "noContinuesText": "(कोई जारी रखना नहीं)", "noExternalStorageErrorText": "कोई बाहरी संचयन करने कि जगह नहीं मिली", "noGameCircleText": "त्रुटी: गेम-सर्किल में लॉग-इन नहीं हैं |", + "noPluginsInstalledText": "कोई प्लगइन्स इंस्टॉल नहीं है", "noProfilesErrorText": "आपकी कोई खिलाड़ी पार्श्वचित्र नहीं है, इसलिए आप '${NAME}' नाम के साथ फंसे हैं |\nसेटिंग -> खिलाड़ी पार्श्वचित्र में जाके अपने लिए पार्श्वचित्र बनायें |", "noScoresYetText": "अभी तक कोई स्कोर नहीं है |", "noServersFoundText": "कोई सरवर्स नहीं मिलें।", @@ -1280,6 +1289,7 @@ "netTestingText": "नेटवर्क पर परीक्षण", "resetText": "रीसेट", "showBombTrajectoriesText": "बोम्ब का पथ दिखाएँ", + "showDemosWhenIdleText": "निष्क्रिय होने पर डेमो दिखाएं", "showDevConsoleButtonText": "डेव कंसोल बटन दिखाएँ", "showInGamePingText": "गेम पिंग में दिखाएं", "showPlayerNamesText": "खिलाड़ी का नाम दिखाएँ", @@ -1373,6 +1383,7 @@ "submitText": "जमा करें", "submittingPromoCodeText": "संहिता जमा कर रहा है ...", "successText": "सफल!", + "supportEmailText": "यदि आप किसी भी समस्या का सामना कर रहे हैं\nऐप, कृपया ${EMAIL} को ईमेल करें", "teamNamesColorText": "टीम के नाम / रंग ...", "telnetAccessGrantedText": "टेलनेट एक्सेस सक्षम है", "telnetAccessText": "टेलनेट पहुंच का पता चला; अनुमति देते हैं?", @@ -1826,6 +1837,7 @@ "unlockThisInTheStoreText": "यह स्टोर में अनलॉक होना चाहिए।", "unlockThisProfilesText": "${NUM} प्रोफ़ाइल बनाने के लिए, आपको इसकी आवश्यकता है:", "unlockThisText": "इसे अनलॉक करने के लिए, आपको इसकी आवश्यकता है:", + "unsupportedControllerText": "क्षमा करें, नियंत्रक \"${NAME}\" समर्थित नहीं है।", "unsupportedHardwareText": "क्षमा करें, यह हार्डवेयर गेम के इस निर्माण द्वारा समर्थित नहीं है।", "upFirstText": "सर्व प्रथम", "upNextText": "गेम ${COUNT} में अगला:", @@ -1837,6 +1849,7 @@ "usingItunesText": "गाने के लिए संगीत ऐप का उपयोग कर रहे है ...", "v2AccountLinkingInfoText": "V2 खातों को लिंक करने के लिए, 'खाता प्रबंधित करें' बटन का उपयोग करें।", "validatingTestBuildText": "परीक्षण निर्माण मान्य ...", + "viaText": "के जरिए", "victoryText": "विजय!", "voteDelayText": "आप ${NUMBER} सेकंड के लिए एक और वोट शुरू नहीं कर सकते हैं", "voteInProgressText": "एक वोट पहले ही प्रगति पर है।", diff --git a/dist/ba_data/data/languages/indonesian.json b/dist/ba_data/data/languages/indonesian.json index 0cc730c..08891d4 100644 --- a/dist/ba_data/data/languages/indonesian.json +++ b/dist/ba_data/data/languages/indonesian.json @@ -335,6 +335,8 @@ "getMoreGamesText": "Game Lain...", "titleText": "Tambah Game" }, + "addToFavoritesText": "Tambahkan ke Favorit", + "addedToFavoritesText": "Tambah '${NAME}' ke dalam Favorit", "allText": "Semua", "allowText": "Izinkan", "alreadySignedInText": "Akunmu telah masuk di perangkat lain;\nSilakan beralih akun atau menutup permainanmu \ndi perangkat lain dan coba lagi.", @@ -563,6 +565,8 @@ "disableXInputDescriptionText": "Izinkan lebih dari 4 pengontrol tapi mungkin agak lemot.", "disableXInputText": "Blokir XInput", "disabledText": "Dimatikan", + "discordFriendsText": "Ingin mencari teman baru untuk bermain?\nGabung ke Discord kami dan temukan teman baru kamu!", + "discordJoinText": "Gabung ke Discord", "doneText": "Selesai", "drawText": "Seri", "duplicateText": "Duplikat", @@ -748,6 +752,7 @@ "manualYourLocalAddressText": "Alamat lokal Kamu:", "nearbyText": "Dekat", "noConnectionText": "", + "noPartiesAddedText": "Tidak ada Acara yang Ditambahkan", "otherVersionsText": "(Versi lain)", "partyCodeText": "Kode Acara", "partyInviteAcceptText": "Terima", @@ -1064,6 +1069,7 @@ "noContinuesText": "(tidak dapat melanjutkan)", "noExternalStorageErrorText": "Tidak ada penyimpanan eksternal", "noGameCircleText": "Kesalahan: tidak masuk ke LingkaranGame", + "noPluginsInstalledText": "Tidak ada plugin yang terinstal", "noProfilesErrorText": "Kamu tidak punya profil pemain, jadi '${NAME}' dipakai. \nMasuk Pengaturan->Profil Pemain untuk membuat profil. ", "noScoresYetText": "Belum ada skor.", "noServersFoundText": "Server tidak ditemukan.", @@ -1277,6 +1283,7 @@ "netTestingText": "Tes Jaringan", "resetText": "Atur ulang", "showBombTrajectoriesText": "Lihat Lintasan Bom", + "showDemosWhenIdleText": "Tampilkan Demo saat tidak bergerak", "showDevConsoleButtonText": "Tampilkan tombol Dev Console", "showInGamePingText": "Tampilkan Ping dalam permainan", "showPlayerNamesText": "Tunjukkan Nama Pemain", diff --git a/dist/ba_data/data/languages/italian.json b/dist/ba_data/data/languages/italian.json index 9a603d8..4fc6d17 100644 --- a/dist/ba_data/data/languages/italian.json +++ b/dist/ba_data/data/languages/italian.json @@ -1339,6 +1339,7 @@ "netTestingText": "Collaudo Rete", "resetText": "Reset", "showBombTrajectoriesText": "Mostra le traiettorie delle bombe", + "showDemosWhenIdleText": "Riproduci Demo All'Inattività", "showDevConsoleButtonText": "mostra tasto console sviluppatore", "showInGamePingText": "Mostra il Ping in gioco", "showPlayerNamesText": "Mostra i nomi dei giocatori", diff --git a/dist/ba_data/data/languages/korean.json b/dist/ba_data/data/languages/korean.json index 403a979..94e04cb 100644 --- a/dist/ba_data/data/languages/korean.json +++ b/dist/ba_data/data/languages/korean.json @@ -336,6 +336,8 @@ "getMoreGamesText": "다른 게임 보기...", "titleText": "게임 추가" }, + "addToFavoritesText": "즐겨찾기에 추가하기", + "addedToFavoritesText": "즐겨찾기에 '${NAME}'을 추가했습니다.", "allText": "모두", "allowText": "허용", "alreadySignedInText": "귀하의 계정은 다른 기기에서 로그인되었습니다. \n계정을 전환하거나 다른 기기에서 게임을 종료하고 \n다시 시도하십시오.", @@ -751,6 +753,7 @@ "manualYourLocalAddressText": "귀하의 로컬 주소:", "nearbyText": "근처", "noConnectionText": "<연결 없음>", + "noPartiesAddedText": "추가한 파티 없음", "otherVersionsText": "(다른 버전)", "partyCodeText": "파티 코드", "partyInviteAcceptText": "수락", @@ -1065,6 +1068,7 @@ "noContinuesText": "(계속 없음)", "noExternalStorageErrorText": "이 기기에서 외부 저장소를 찾지 못했습니다.", "noGameCircleText": "오류: GameCircle에 로그인되지 않았습니다", + "noPluginsInstalledText": "설치된 플러그인 없음", "noScoresYetText": "아직 점수 없음.", "noServersFoundText": "서버를 찾을수 없음.", "noThanksText": "아니요", @@ -1276,6 +1280,7 @@ "netTestingText": "네트워크 테스트", "resetText": "재설정", "showBombTrajectoriesText": "폭탄 궤적 표시", + "showDemosWhenIdleText": "유휴 상태일 때 데모 표시", "showDevConsoleButtonText": "개발자 콘솔 버튼 보이기", "showInGamePingText": "인게임 핑 보이기", "showPlayerNamesText": "플레이어 이름 표시", diff --git a/dist/ba_data/data/languages/persian.json b/dist/ba_data/data/languages/persian.json index 1082be2..545f4b6 100644 --- a/dist/ba_data/data/languages/persian.json +++ b/dist/ba_data/data/languages/persian.json @@ -1281,6 +1281,7 @@ "netTestingText": "تست شبکه", "resetText": "باز گرداندن", "showBombTrajectoriesText": "نمایش خط سیر بمب", + "showDemosWhenIdleText": "نشان دادن دمو ها در وقت بیکاری", "showDevConsoleButtonText": "نشان دادن دکمه توسعه دهنده کنسول", "showInGamePingText": "نمایش پینگ در بازی", "showPlayerNamesText": "نمایش نام بازیکنان", diff --git a/dist/ba_data/data/languages/portuguese.json b/dist/ba_data/data/languages/portuguese.json index 651ef7c..9168085 100644 --- a/dist/ba_data/data/languages/portuguese.json +++ b/dist/ba_data/data/languages/portuguese.json @@ -340,6 +340,8 @@ "getMoreGamesText": "Mais jogos...", "titleText": "Adicionar jogo" }, + "addToFavoritesText": "Adicionar aos Favoritos", + "addedToFavoritesText": "Adicionou '${NAME}' aos Favoritos.", "allText": "Tudo", "allowText": "Permitir", "alreadySignedInText": "A conta tem sessão iniciada em outro dispositivo;\nMude de conta ou feche o jogo no seu\noutro dispositivo e tente novamente.", @@ -788,6 +790,7 @@ "manualYourLocalAddressText": "Seu endereço local:", "nearbyText": "Próximo", "noConnectionText": "", + "noPartiesAddedText": "Nenhum grupo adicionado", "otherVersionsText": "(outras versões)", "partyCodeText": "Código da Sala", "partyInviteAcceptText": "Aceitar", @@ -1134,6 +1137,7 @@ "noExternalStorageErrorText": "Armazenamento externo não encontrado", "noGameCircleText": "Erro: não conectado no GameCircle", "noJoinCoopMidwayText": "Jogos cooperativos não podem ser afiliados no meio do caminho.", + "noPluginsInstalledText": "Nenhum Plugin instalado", "noProfilesErrorText": "Você não tem perfis de jogadores, então você está preso com '${NAME}'.\nVá para Configurações->Perfis de Jogador para criar um perfil.", "noScoresYetText": "Ainda sem pontuação.", "noServersFoundText": "Servidores não encontrados.", @@ -1363,6 +1367,7 @@ "netTestingText": "Teste de conexão", "resetText": "Redefinir", "showBombTrajectoriesText": "Mostrar trajetórias da bomba", + "showDemosWhenIdleText": "Mostrar demonstrações quando ocioso", "showDevConsoleButtonText": "Mostrar console de desenvolvedor", "showInGamePingText": "Mostrar latência no jogo", "showPlayerNamesText": "Mostrar nomes dos jogadores", diff --git a/dist/ba_data/data/languages/russian.json b/dist/ba_data/data/languages/russian.json index 41f0a1d..ab91cc6 100644 --- a/dist/ba_data/data/languages/russian.json +++ b/dist/ba_data/data/languages/russian.json @@ -341,6 +341,8 @@ "getMoreGamesText": "Еще игры", "titleText": "Добавить игру" }, + "addToFavoritesText": "Добавить в избранное", + "addedToFavoritesText": "Добавлен '${NAME}' в избранное.", "allText": "Все", "allowText": "Разрешить", "alreadySignedInText": "С вашего аккаунта играют на другом устройстве;\nпожалуйста зайдите с другого аккаунта или закройте\nигру на другом устройстве и попытайтесь снова.", @@ -783,6 +785,7 @@ "manualYourLocalAddressText": "Ваш локальный адрес:", "nearbyText": "Поблизости", "noConnectionText": "<нет соединения>", + "noPartiesAddedText": "Вечеринки не добавлены", "otherVersionsText": "(другие версии)", "partyCodeText": "Код лобби", "partyInviteAcceptText": "Принять", @@ -1117,6 +1120,7 @@ "noExternalStorageErrorText": "На данном устройстве не найдено внешней памяти", "noGameCircleText": "Ошибка: не вошли в GameCircle", "noJoinCoopMidwayText": "К кооперативным играм нельзя присоединиться посреди игры.", + "noPluginsInstalledText": "Плагины не установлены", "noProfilesErrorText": "У вас нет профиля игрока, так что вас будут звать '${NAME}'.\nСоздать профиль можно перейдя в 'Настройки' > 'Профили игроков'.", "noScoresYetText": "Счета пока нет.", "noServersFoundText": "Серверы не найдены.", @@ -1339,6 +1343,7 @@ "netTestingText": "Тестирование сети", "resetText": "Сбросить", "showBombTrajectoriesText": "Показывать траекторию бомбы", + "showDemosWhenIdleText": "Показывать демоверсии в режиме ожидания", "showDevConsoleButtonText": "Показать кнопку консоли", "showInGamePingText": "Показать Ping", "showPlayerNamesText": "Показывать имена игроков", diff --git a/dist/ba_data/data/languages/spanish.json b/dist/ba_data/data/languages/spanish.json index 0dd7d4b..08f850a 100644 --- a/dist/ba_data/data/languages/spanish.json +++ b/dist/ba_data/data/languages/spanish.json @@ -128,7 +128,7 @@ }, "Mine Games": { "description": "Mata a 3 chicos malos con minas terrestres", - "descriptionComplete": "Mató a 3 enemigos con minas terrestres", + "descriptionComplete": "Mató a 3 chicos malos con minas terrestres", "descriptionFull": "Mata a 3 chicos malos con minas terrestres en ${LEVEL}", "descriptionFullComplete": "Mató a 3 chicos con minas terrestres en ${LEVEL}", "name": "Juegos De Minas" @@ -1107,7 +1107,7 @@ "modeClassicText": "Modo Clásico", "modeDemoText": "Modo Demo", "mostValuablePlayerText": "Jugador Más Valioso", - "mostViolatedPlayerText": "Jugador Más Violado", + "mostViolatedPlayerText": "Jugador Más Agredido", "mostViolentPlayerText": "Jugador Más Violento", "moveText": "Mover", "multiKillText": "¡¡¡COMBO DE ${COUNT}!!!", @@ -1208,11 +1208,11 @@ "playlistsText": "Listas de juegos", "pleaseRateText": "Si te gusta ${APP_NAME}, por favor tomate un momento\npara calificar o escribir una reseña. Esto proporcionará\ninformación útil y ayuda al soporte del futuro desarrollo del juego.\n\n¡gracias!\n-eric", "pleaseWaitText": "Por favor, espera...", - "pluginClassLoadErrorText": "Error cargando la clase del complemento '${PLUGIN}': ${ERROR}", + "pluginClassLoadErrorText": "Error cargando el complemento '${PLUGIN}': ${ERROR}", "pluginInitErrorText": "Error iniciando complemento '${PLUGIN}': ${ERROR}", "pluginSettingsText": "Ajustes De Complementos", "pluginsAutoEnableNewText": "Auto Habilitar Nuevos Complementos", - "pluginsDetectedText": "Nuevo(s) complemento(s) detectado(s). Reinicie para activarlo(s), o configuralo(s) en ajustes.", + "pluginsDetectedText": "Complemento detectado(s). Reinicia el juego, o ve a ajustes para configurarlo.", "pluginsDisableAllText": "Deshabilitar Todos Los Complementos", "pluginsEnableAllText": "Habilitar Todos Los Complementos", "pluginsRemovedText": "${NUM} complemento(s) ya no se encuentra(n).", @@ -1360,6 +1360,7 @@ "netTestingText": "Prueba De Red", "resetText": "Reiniciar", "showBombTrajectoriesText": "Mostrar Trayectorias De Las Bombas", + "showDemosWhenIdleText": "Mostrar demostraciones cuando esté inactivo", "showDevConsoleButtonText": "Mostrar Botón de Consola de Desarrollador", "showInGamePingText": "Mostrar Ping En El Juego", "showPlayerNamesText": "Mostrar Nombres De Los Jugadores", diff --git a/dist/ba_data/data/languages/thai.json b/dist/ba_data/data/languages/thai.json index 422a9e9..0566599 100644 --- a/dist/ba_data/data/languages/thai.json +++ b/dist/ba_data/data/languages/thai.json @@ -334,6 +334,8 @@ "getMoreGamesText": "รับเกมเพิ่มเติม", "titleText": "เพิ่มเกม" }, + "addToFavoritesText": "เพื่มในรายการโปรด", + "addedToFavoritesText": "เพื่ม '${NAME}' ในรายการโปรดแล้ว", "allText": "ทั้งหมด", "allowText": "ยอมรับ", "alreadySignedInText": "บัญชีของคุณลงชื่อเข้าใช้จากอุปกรณ์อื่น\nโปรดเปลี่ยนบัญชีหรือปิดเกมของคุณ\nอุปกรณ์อื่นและลองอีกครั้ง", diff --git a/dist/ba_data/data/languages/turkish.json b/dist/ba_data/data/languages/turkish.json index 2b3fb6a..c1f255f 100644 --- a/dist/ba_data/data/languages/turkish.json +++ b/dist/ba_data/data/languages/turkish.json @@ -335,6 +335,8 @@ "getMoreGamesText": "Daha Çok Oyun...", "titleText": "Oyun Ekle" }, + "addToFavoritesText": "Favorilere ekle", + "addedToFavoritesText": "'${NAME}' Favorilere eklendi.", "allText": "Hepsi", "allowText": "Kabul Et", "alreadySignedInText": "Başka bir cihazda hesabına giriş yapılmış;\nlütfen hesapları değiştir ya da diğer cihazlardaki\noyunu kapat ve tekrar dene.", @@ -751,6 +753,7 @@ "manualYourLocalAddressText": "Yerel adresiniz:", "nearbyText": "Yakında", "noConnectionText": "", + "noPartiesAddedText": "Parti Eklenmedi", "otherVersionsText": "(diğer sürümler)", "partyCodeText": "Parti kodu", "partyInviteAcceptText": "Kabul Et", @@ -1065,6 +1068,7 @@ "noContinuesText": "(sürdürülemiyor)", "noExternalStorageErrorText": "Bu cihazda harici depolama bulunamadı", "noGameCircleText": "Hata: GameCircle girişi yapılamadı", + "noPluginsInstalledText": "Yüklü Eklenti Yok", "noScoresYetText": "Henüz skor yok.", "noServersFoundText": "Sunucu Bulunamadı.", "noThanksText": "Hayır Teşekkürler", @@ -1276,6 +1280,7 @@ "netTestingText": "Ağ Testi", "resetText": "Sıfırla", "showBombTrajectoriesText": "Bomba Gidişatını Göster", + "showDemosWhenIdleText": "Boşta İken Demolar Göster", "showDevConsoleButtonText": "Geliştirici panelini aç", "showInGamePingText": "Oyun İçinde Gecikmeyi Göster", "showPlayerNamesText": "Oyuncu Adlarını Göster", diff --git a/dist/ba_data/data/languages/ukrainian.json b/dist/ba_data/data/languages/ukrainian.json index cc20fc9..8bdc698 100644 --- a/dist/ba_data/data/languages/ukrainian.json +++ b/dist/ba_data/data/languages/ukrainian.json @@ -337,6 +337,8 @@ "getMoreGamesText": "Ще ігри...", "titleText": "Додати гру" }, + "addToFavoritesText": "Додати в обране", + "addedToFavoritesText": "Додано '${NAME}' до вибраного.", "allText": "Все", "allowText": "Дозволити", "alreadySignedInText": "На вашому акаунті грають на іншому пристрої;\nбудь ласка зайдіть з іншого акаунта або закрийте гру на\nіншому пристрої та спробуйте знову.", @@ -370,6 +372,7 @@ "chatMutedText": "Чат приглушений", "chatUnMuteText": "Включити чат", "choosingPlayerText": "<вибір гравця>", + "codesExplainText": "Розробник надає коди для діагностики та\nусунення проблем з обліковим записом.", "completeThisLevelToProceedText": "Щоб продовжити, потрібно\nпройти цей рівень!", "completionBonusText": "Бонус за проходження", "configControllersWindow": { @@ -564,6 +567,8 @@ "disableXInputDescriptionText": "Підключення більше 4 контролерів, але може не працювати.", "disableXInputText": "Відключити XInput", "disabledText": "Виключено", + "discordFriendsText": "Хочете шукати нових людей для гри?\nПриєднуйтесь до нашого Discord і знайдіть нових друзів!", + "discordJoinText": "Приєднуйтесь до Discord", "doneText": "Готово", "drawText": "Нічия", "duplicateText": "Дублювати", @@ -749,6 +754,7 @@ "manualYourLocalAddressText": "Ваш локальний адрес:", "nearbyText": "Поблизу", "noConnectionText": "<немає з'єднання>", + "noPartiesAddedText": "Партії не додано", "otherVersionsText": "(інші версії)", "partyCodeText": "Код групи", "partyInviteAcceptText": "Прийняти", @@ -1063,6 +1069,7 @@ "noContinuesText": "(без продовжень)", "noExternalStorageErrorText": "На цьому пристрої не знайдено зовнішньої пам'яті", "noGameCircleText": "Помилка: не ввійшли в GameCircle", + "noPluginsInstalledText": "Плагіни не встановлено", "noScoresYetText": "Рахунка поки немає.", "noServersFoundText": "Серверів не знайдено", "noThanksText": "Ні дякую", @@ -1367,6 +1374,7 @@ "submitText": "Відправити", "submittingPromoCodeText": "Активація коду...", "successText": "Успішно!", + "supportEmailText": "Якщо у вас виникли проблеми з програмою,\nнадішліть електронний лист на ${EMAIL}.", "teamNamesColorText": "імена/кольори команд", "telnetAccessGrantedText": "Доступ Telnet включений.", "telnetAccessText": "Виявлено доступ Telnet. Дозволити?", @@ -1833,6 +1841,7 @@ "usingItunesTurnRepeatAndShuffleOnText": "Будь ласка, переконайтеся, що випадковий порядок і повтор усіх пісень включений в Itunes.", "v2AccountLinkingInfoText": "Щоб підключити V2-акаунти, використовуйте кнопку 'Керування акаунтом'.", "validatingTestBuildText": "Перевірка тестової збірки...", + "viaText": "через", "victoryText": "Перемога!", "voteDelayText": "Ви зможете почати голосування через ${NUMBER} секунд", "voteInProgressText": "Голосування ще триває.", diff --git a/dist/ba_data/data/languages/venetian.json b/dist/ba_data/data/languages/venetian.json index 2b8c44f..a135d13 100644 --- a/dist/ba_data/data/languages/venetian.json +++ b/dist/ba_data/data/languages/venetian.json @@ -333,6 +333,8 @@ "getMoreGamesText": "Otien pì łevełi...", "titleText": "Zonta zugo" }, + "addToFavoritesText": "Zonta so i prefarìi", + "addedToFavoritesText": "'${NAME}' zontà so i prefarìi.", "allText": "Tuto", "allowText": "Parmeti", "alreadySignedInText": "El to account el ze in dòparo inte n’antro\ndispozidivo: canbia account o sara sù el zugo\ninte cheł’altro to dispozidivo e proa danovo.", @@ -748,6 +750,7 @@ "manualYourLocalAddressText": "El to ndariso łogałe:", "nearbyText": "Łogałe", "noConnectionText": "", + "noPartiesAddedText": "Gnaun grupo zontà", "otherVersionsText": "(par altre varsion)", "partyCodeText": "Còdaze de'l grupo", "partyInviteAcceptText": "Và ben", @@ -1062,6 +1065,7 @@ "noContinuesText": "(sensa continui)", "noExternalStorageErrorText": "So ’sto dispozidivo no ze stà catada gnauna memoria esterna", "noGameCircleText": "Eror: no te si miga conetesto co GameCircle", + "noPluginsInstalledText": "Gnauna estension instałada", "noScoresYetText": "Gnancora gnaun puntejo.", "noServersFoundText": "Gnaun server catà.", "noThanksText": "Nò, grasie", diff --git a/dist/ba_data/python-site-packages/typing_extensions.py b/dist/ba_data/python-site-packages/typing_extensions.py index c96bf90..1666e96 100644 --- a/dist/ba_data/python-site-packages/typing_extensions.py +++ b/dist/ba_data/python-site-packages/typing_extensions.py @@ -86,6 +86,7 @@ __all__ = [ 'TYPE_CHECKING', 'Never', 'NoReturn', + 'ReadOnly', 'Required', 'NotRequired', @@ -473,6 +474,7 @@ _EXCLUDED_ATTRS = { "__orig_bases__", "__module__", "_MutableMapping__marker", "__doc__", "__subclasshook__", "__orig_class__", "__init__", "__new__", "__protocol_attrs__", "__callable_proto_members_only__", + "__match_args__", } if sys.version_info >= (3, 9): @@ -503,9 +505,9 @@ def _caller(depth=2): return None -# The performance of runtime-checkable protocols is significantly improved on Python 3.12, -# so we backport the 3.12 version of Protocol to Python <=3.11 -if sys.version_info >= (3, 12): +# `__match_args__` attribute was removed from protocol members in 3.13, +# we want to backport this change to older Python versions. +if sys.version_info >= (3, 13): Protocol = typing.Protocol else: def _allow_reckless_class_checks(depth=3): @@ -569,8 +571,13 @@ else: not cls.__callable_proto_members_only__ and cls.__dict__.get("__subclasshook__") is _proto_hook ): + non_method_attrs = sorted( + attr for attr in cls.__protocol_attrs__ + if not callable(getattr(cls, attr, None)) + ) raise TypeError( - "Protocols with non-method members don't support issubclass()" + "Protocols with non-method members don't support issubclass()." + f" Non-method members: {str(non_method_attrs)[1:-1]}." ) if not getattr(cls, '_is_runtime_protocol', False): raise TypeError( @@ -767,7 +774,7 @@ def _ensure_subclassable(mro_entries): return inner -if sys.version_info >= (3, 13): +if hasattr(typing, "ReadOnly"): # The standard library TypedDict in Python 3.8 does not store runtime information # about which (if any) keys are optional. See https://bugs.python.org/issue38834 # The standard library TypedDict in Python 3.9.0/1 does not honour the "total" @@ -778,6 +785,7 @@ if sys.version_info >= (3, 13): # Aaaand on 3.12 we add __orig_bases__ to TypedDict # to enable better runtime introspection. # On 3.13 we deprecate some odd ways of creating TypedDicts. + # PEP 705 proposes adding the ReadOnly[] qualifier. TypedDict = typing.TypedDict _TypedDictMeta = typing._TypedDictMeta is_typeddict = typing.is_typeddict @@ -785,8 +793,29 @@ else: # 3.10.0 and later _TAKES_MODULE = "module" in inspect.signature(typing._type_check).parameters + def _get_typeddict_qualifiers(annotation_type): + while True: + annotation_origin = get_origin(annotation_type) + if annotation_origin is Annotated: + annotation_args = get_args(annotation_type) + if annotation_args: + annotation_type = annotation_args[0] + else: + break + elif annotation_origin is Required: + yield Required + annotation_type, = get_args(annotation_type) + elif annotation_origin is NotRequired: + yield NotRequired + annotation_type, = get_args(annotation_type) + elif annotation_origin is ReadOnly: + yield ReadOnly + annotation_type, = get_args(annotation_type) + else: + break + class _TypedDictMeta(type): - def __new__(cls, name, bases, ns, total=True): + def __new__(cls, name, bases, ns, *, total=True): """Create new typed dict class object. This method is called when TypedDict is subclassed, @@ -829,33 +858,46 @@ else: } required_keys = set() optional_keys = set() + readonly_keys = set() + mutable_keys = set() for base in bases: - annotations.update(base.__dict__.get('__annotations__', {})) - required_keys.update(base.__dict__.get('__required_keys__', ())) - optional_keys.update(base.__dict__.get('__optional_keys__', ())) + base_dict = base.__dict__ + + annotations.update(base_dict.get('__annotations__', {})) + required_keys.update(base_dict.get('__required_keys__', ())) + optional_keys.update(base_dict.get('__optional_keys__', ())) + readonly_keys.update(base_dict.get('__readonly_keys__', ())) + mutable_keys.update(base_dict.get('__mutable_keys__', ())) annotations.update(own_annotations) for annotation_key, annotation_type in own_annotations.items(): - annotation_origin = get_origin(annotation_type) - if annotation_origin is Annotated: - annotation_args = get_args(annotation_type) - if annotation_args: - annotation_type = annotation_args[0] - annotation_origin = get_origin(annotation_type) + qualifiers = set(_get_typeddict_qualifiers(annotation_type)) - if annotation_origin is Required: + if Required in qualifiers: required_keys.add(annotation_key) - elif annotation_origin is NotRequired: + elif NotRequired in qualifiers: optional_keys.add(annotation_key) elif total: required_keys.add(annotation_key) else: optional_keys.add(annotation_key) + if ReadOnly in qualifiers: + if annotation_key in mutable_keys: + raise TypeError( + f"Cannot override mutable key {annotation_key!r}" + " with read-only key" + ) + readonly_keys.add(annotation_key) + else: + mutable_keys.add(annotation_key) + readonly_keys.discard(annotation_key) tp_dict.__annotations__ = annotations tp_dict.__required_keys__ = frozenset(required_keys) tp_dict.__optional_keys__ = frozenset(optional_keys) + tp_dict.__readonly_keys__ = frozenset(readonly_keys) + tp_dict.__mutable_keys__ = frozenset(mutable_keys) if not hasattr(tp_dict, '__total__'): tp_dict.__total__ = total return tp_dict @@ -936,6 +978,8 @@ else: raise TypeError("TypedDict takes either a dict or keyword arguments," " but not both") if kwargs: + if sys.version_info >= (3, 13): + raise TypeError("TypedDict takes no keyword arguments") warnings.warn( "The kwargs-based syntax for TypedDict definitions is deprecated " "in Python 3.11, will be removed in Python 3.13, and may not be " @@ -1924,6 +1968,53 @@ else: # 3.8 """) +if hasattr(typing, 'ReadOnly'): + ReadOnly = typing.ReadOnly +elif sys.version_info[:2] >= (3, 9): # 3.9-3.12 + @_ExtensionsSpecialForm + def ReadOnly(self, parameters): + """A special typing construct to mark an item of a TypedDict as read-only. + + For example: + + class Movie(TypedDict): + title: ReadOnly[str] + year: int + + def mutate_movie(m: Movie) -> None: + m["year"] = 1992 # allowed + m["title"] = "The Matrix" # typechecker error + + There is no runtime checking for this property. + """ + item = typing._type_check(parameters, f'{self._name} accepts only a single type.') + return typing._GenericAlias(self, (item,)) + +else: # 3.8 + class _ReadOnlyForm(_ExtensionsSpecialForm, _root=True): + def __getitem__(self, parameters): + item = typing._type_check(parameters, + f'{self._name} accepts only a single type.') + return typing._GenericAlias(self, (item,)) + + ReadOnly = _ReadOnlyForm( + 'ReadOnly', + doc="""A special typing construct to mark a key of a TypedDict as read-only. + + For example: + + class Movie(TypedDict): + title: ReadOnly[str] + year: int + + def mutate_movie(m: Movie) -> None: + m["year"] = 1992 # allowed + m["title"] = "The Matrix" # typechecker error + + There is no runtime checking for this propery. + """) + + _UNPACK_DOC = """\ Type unpack operator. @@ -2251,7 +2342,7 @@ else: # <=3.11 Usage: class Base: - def method(self) -> None: ... + def method(self) -> None: pass class Child(Base): @@ -2281,20 +2372,17 @@ else: # <=3.11 return arg -if hasattr(typing, "deprecated"): - deprecated = typing.deprecated +if hasattr(warnings, "deprecated"): + deprecated = warnings.deprecated else: _T = typing.TypeVar("_T") - def deprecated( - msg: str, - /, - *, - category: typing.Optional[typing.Type[Warning]] = DeprecationWarning, - stacklevel: int = 1, - ) -> typing.Callable[[_T], _T]: + class deprecated: """Indicate that a class, function or overload is deprecated. + When this decorator is applied to an object, the type checker + will generate a diagnostic on usage of the deprecated object. + Usage: @deprecated("Use B instead") @@ -2311,49 +2399,100 @@ else: @overload def g(x: str) -> int: ... - When this decorator is applied to an object, the type checker - will generate a diagnostic on usage of the deprecated object. - - The warning specified by ``category`` will be emitted on use - of deprecated objects. For functions, that happens on calls; - for classes, on instantiation. If the ``category`` is ``None``, - no warning is emitted. The ``stacklevel`` determines where the + The warning specified by *category* will be emitted at runtime + on use of deprecated objects. For functions, that happens on calls; + for classes, on instantiation and on creation of subclasses. + If the *category* is ``None``, no warning is emitted at runtime. + The *stacklevel* determines where the warning is emitted. If it is ``1`` (the default), the warning is emitted at the direct caller of the deprecated object; if it is higher, it is emitted further up the stack. + Static type checker behavior is not affected by the *category* + and *stacklevel* arguments. - The decorator sets the ``__deprecated__`` - attribute on the decorated object to the deprecation message - passed to the decorator. If applied to an overload, the decorator + The deprecation message passed to the decorator is saved in the + ``__deprecated__`` attribute on the decorated object. + If applied to an overload, the decorator must be after the ``@overload`` decorator for the attribute to exist on the overload as returned by ``get_overloads()``. See PEP 702 for details. """ - def decorator(arg: _T, /) -> _T: + def __init__( + self, + message: str, + /, + *, + category: typing.Optional[typing.Type[Warning]] = DeprecationWarning, + stacklevel: int = 1, + ) -> None: + if not isinstance(message, str): + raise TypeError( + "Expected an object of type str for 'message', not " + f"{type(message).__name__!r}" + ) + self.message = message + self.category = category + self.stacklevel = stacklevel + + def __call__(self, arg: _T, /) -> _T: + # Make sure the inner functions created below don't + # retain a reference to self. + msg = self.message + category = self.category + stacklevel = self.stacklevel if category is None: arg.__deprecated__ = msg return arg elif isinstance(arg, type): + import functools + from types import MethodType + original_new = arg.__new__ - has_init = arg.__init__ is not object.__init__ @functools.wraps(original_new) def __new__(cls, *args, **kwargs): - warnings.warn(msg, category=category, stacklevel=stacklevel + 1) + if cls is arg: + warnings.warn(msg, category=category, stacklevel=stacklevel + 1) if original_new is not object.__new__: return original_new(cls, *args, **kwargs) # Mirrors a similar check in object.__new__. - elif not has_init and (args or kwargs): + elif cls.__init__ is object.__init__ and (args or kwargs): raise TypeError(f"{cls.__name__}() takes no arguments") else: return original_new(cls) arg.__new__ = staticmethod(__new__) + + original_init_subclass = arg.__init_subclass__ + # We need slightly different behavior if __init_subclass__ + # is a bound method (likely if it was implemented in Python) + if isinstance(original_init_subclass, MethodType): + original_init_subclass = original_init_subclass.__func__ + + @functools.wraps(original_init_subclass) + def __init_subclass__(*args, **kwargs): + warnings.warn(msg, category=category, stacklevel=stacklevel + 1) + return original_init_subclass(*args, **kwargs) + + arg.__init_subclass__ = classmethod(__init_subclass__) + # Or otherwise, which likely means it's a builtin such as + # object's implementation of __init_subclass__. + else: + @functools.wraps(original_init_subclass) + def __init_subclass__(*args, **kwargs): + warnings.warn(msg, category=category, stacklevel=stacklevel + 1) + return original_init_subclass(*args, **kwargs) + + arg.__init_subclass__ = __init_subclass__ + arg.__deprecated__ = __new__.__deprecated__ = msg + __init_subclass__.__deprecated__ = msg return arg elif callable(arg): + import functools + @functools.wraps(arg) def wrapper(*args, **kwargs): warnings.warn(msg, category=category, stacklevel=stacklevel + 1) @@ -2367,8 +2506,6 @@ else: f"a class or callable, not {arg!r}" ) - return decorator - # We have to do some monkey patching to deal with the dual nature of # Unpack/TypeVarTuple: @@ -2437,11 +2574,35 @@ else: class_getitem = typing.Generic.__class_getitem__.__func__ nm_tpl.__class_getitem__ = classmethod(class_getitem) # update from user namespace without overriding special namedtuple attributes - for key in ns: + for key, val in ns.items(): if key in _prohibited_namedtuple_fields: raise AttributeError("Cannot overwrite NamedTuple attribute " + key) - elif key not in _special_namedtuple_fields and key not in nm_tpl._fields: - setattr(nm_tpl, key, ns[key]) + elif key not in _special_namedtuple_fields: + if key not in nm_tpl._fields: + setattr(nm_tpl, key, ns[key]) + try: + set_name = type(val).__set_name__ + except AttributeError: + pass + else: + try: + set_name(val, nm_tpl, key) + except BaseException as e: + msg = ( + f"Error calling __set_name__ on {type(val).__name__!r} " + f"instance {key!r} in {typename!r}" + ) + # BaseException.add_note() existed on py311, + # but the __set_name__ machinery didn't start + # using add_note() until py312. + # Making sure exceptions are raised in the same way + # as in "normal" classes seems most important here. + if sys.version_info >= (3, 12): + e.add_note(msg) + raise + else: + raise RuntimeError(msg) from e + if typing.Generic in bases: nm_tpl.__init_subclass__() return nm_tpl @@ -2600,7 +2761,7 @@ else: num = UserId(5) + 1 # type: int """ - def __call__(self, obj): + def __call__(self, obj, /): return obj def __init__(self, name, tp): diff --git a/dist/ba_data/python/babase/__init__.py b/dist/ba_data/python/babase/__init__.py index e1b7a32..e3f115a 100644 --- a/dist/ba_data/python/babase/__init__.py +++ b/dist/ba_data/python/babase/__init__.py @@ -48,6 +48,7 @@ from _babase import ( fatal_error, get_display_resolution, get_immediate_return_code, + get_input_idle_time, get_low_level_config_value, get_max_graphics_quality, get_replays_dir, @@ -60,6 +61,7 @@ from _babase import ( have_permission, in_logic_thread, increment_analytics_count, + invoke_main_menu, is_os_playing_music, is_xcode_build, lock_all_input, @@ -116,7 +118,6 @@ from babase._apputils import ( get_remote_app_name, AppHealthMonitor, ) -from babase._cloud import CloudSubsystem from babase._devconsole import ( DevConsoleTab, DevConsoleTabEntry, @@ -211,7 +212,6 @@ __all__ = [ 'clipboard_has_text', 'clipboard_is_supported', 'clipboard_set_text', - 'CloudSubsystem', 'commit_app_config', 'ContextCall', 'ContextError', @@ -235,6 +235,7 @@ __all__ = [ 'garbage_collect', 'get_display_resolution', 'get_immediate_return_code', + 'get_input_idle_time', 'get_ip_address_type', 'get_low_level_config_value', 'get_max_graphics_quality', @@ -254,6 +255,7 @@ __all__ = [ 'increment_analytics_count', 'InputDeviceNotFoundError', 'InputType', + 'invoke_main_menu', 'is_browser_likely_available', 'is_browser_likely_available', 'is_os_playing_music', diff --git a/dist/ba_data/python/babase/_app.py b/dist/ba_data/python/babase/_app.py index 07bacfa..f7fb907 100644 --- a/dist/ba_data/python/babase/_app.py +++ b/dist/ba_data/python/babase/_app.py @@ -1,15 +1,17 @@ # Released under the MIT License. See LICENSE for details. # +# pylint: disable=too-many-lines """Functionality related to the high level state of the app.""" from __future__ import annotations import os import logging from enum import Enum -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, TypeVar from concurrent.futures import ThreadPoolExecutor from functools import cached_property +from typing_extensions import override from efro.call import tpartial import _babase @@ -26,7 +28,7 @@ from babase._devconsole import DevConsoleSubsystem if TYPE_CHECKING: import asyncio - from typing import Any, Callable, Coroutine + from typing import Any, Callable, Coroutine, Generator, Awaitable from concurrent.futures import Future import babase @@ -42,6 +44,8 @@ if TYPE_CHECKING: # __FEATURESET_APP_SUBSYSTEM_IMPORTS_END__ +T = TypeVar('T') + class App: """A class for high level app functionality and state. @@ -124,6 +128,7 @@ class App: statically in a spinoff project. """ + @override def app_mode_for_intent( self, intent: AppIntent ) -> type[AppMode] | None: @@ -199,7 +204,8 @@ class App: self._called_on_running = False self._subsystem_registration_ended = False self._pending_apply_app_config = False - self._aioloop: asyncio.AbstractEventLoop | None = None + self._asyncio_loop: asyncio.AbstractEventLoop | None = None + self._asyncio_tasks: set[asyncio.Task] = set() self._asyncio_timer: babase.AppTimer | None = None self._config: babase.AppConfig | None = None self._pending_intent: AppIntent | None = None @@ -239,18 +245,68 @@ class App: return _babase.app_is_active() @property - def aioloop(self) -> asyncio.AbstractEventLoop: + def asyncio_loop(self) -> asyncio.AbstractEventLoop: """The logic thread's asyncio event loop. This allow async tasks to be run in the logic thread. + + Generally you should call App.create_async_task() to schedule + async code to run instead of using this directly. That will + handle retaining the task and logging errors automatically. + Only schedule tasks onto asyncio_loop yourself when you intend + to hold on to the returned task and await its results. Releasing + the task reference can lead to subtle bugs such as unreported + errors and garbage-collected tasks disappearing before their + work is done. + Note that, at this time, the asyncio loop is encapsulated and explicitly stepped by the engine's logic thread loop and - thus things like asyncio.get_running_loop() will not return this - loop from most places in the logic thread; only from within a - task explicitly created in this loop. + thus things like asyncio.get_running_loop() will unintuitively + *not* return this loop from most places in the logic thread; + only from within a task explicitly created in this loop. + Hopefully this situation will be improved in the future with a + unified event loop. """ - assert self._aioloop is not None - return self._aioloop + assert _babase.in_logic_thread() + assert self._asyncio_loop is not None + return self._asyncio_loop + + def create_async_task( + self, + coro: Generator[Any, Any, T] | Coroutine[Any, Any, T], + *, + name: str | None = None, + ) -> None: + """Create a fully managed async task. + + This will automatically retain and release a reference to the task + and log any exceptions that occur in it. If you need to await a task + or otherwise need more control, schedule a task directly using + App.asyncio_loop. + """ + assert _babase.in_logic_thread() + # Hold a strong reference to the task until it is done. + # Otherwise it is possible for it to be garbage collected and + # disappear midway if the caller does not hold on to the + # returned task, which seems like a great way to introduce + # hard-to-track bugs. + task = self.asyncio_loop.create_task(coro, name=name) + self._asyncio_tasks.add(task) + task.add_done_callback(self._on_task_done) + # return task + + def _on_task_done(self, task: asyncio.Task) -> None: + # Report any errors that occurred. + try: + exc = task.exception() + if exc is not None: + logging.error( + "Error in async task '%s'.", task.get_name(), exc_info=exc + ) + except Exception: + logging.exception('Error reporting async task error.') + + self._asyncio_tasks.remove(task) @property def config(self) -> babase.AppConfig: @@ -437,6 +493,12 @@ class App: self._native_shutdown_complete_called = True self._update_state() + def on_native_active_changed(self) -> None: + """Called by the native layer when the app active state changes.""" + assert _babase.in_logic_thread() + if self._mode is not None: + self._mode.on_app_active_changed() + def read_config(self) -> None: """(internal)""" from babase._appconfig import read_app_config @@ -588,7 +650,7 @@ class App: _env.on_app_state_initing() - self._aioloop = _asyncio.setup_asyncio() + self._asyncio_loop = _asyncio.setup_asyncio() self.health_monitor = AppHealthMonitor() # __FEATURESET_APP_SUBSYSTEM_CREATE_BEGIN__ @@ -868,8 +930,8 @@ class App: ) # Now kick off any async shutdown task(s). - assert self._aioloop is not None - self._shutdown_task = self._aioloop.create_task(self._shutdown()) + assert self._asyncio_loop is not None + self._shutdown_task = self._asyncio_loop.create_task(self._shutdown()) def _on_shutdown_complete(self) -> None: """(internal)""" diff --git a/dist/ba_data/python/babase/_appmode.py b/dist/ba_data/python/babase/_appmode.py index 1da4314..cfe01f7 100644 --- a/dist/ba_data/python/babase/_appmode.py +++ b/dist/ba_data/python/babase/_appmode.py @@ -52,3 +52,10 @@ class AppMode: def on_deactivate(self) -> None: """Called when the mode is being deactivated.""" + + def on_app_active_changed(self) -> None: + """Called when babase.app.active changes. + + The app-mode may want to take action such as pausing a running + game in such cases. + """ diff --git a/dist/ba_data/python/babase/_appsubsystem.py b/dist/ba_data/python/babase/_appsubsystem.py index 812dc60..78ba01d 100644 --- a/dist/ba_data/python/babase/_appsubsystem.py +++ b/dist/ba_data/python/babase/_appsubsystem.py @@ -40,16 +40,16 @@ class AppSubsystem: """Called when the app reaches the running state.""" def on_app_suspend(self) -> None: - """Called when the app enters the paused state.""" + """Called when the app enters the suspended state.""" def on_app_unsuspend(self) -> None: - """Called when the app exits the paused state.""" + """Called when the app exits the suspended state.""" def on_app_shutdown(self) -> None: - """Called when the app is shutting down.""" + """Called when the app begins shutting down.""" def on_app_shutdown_complete(self) -> None: - """Called when the app is done shutting down.""" + """Called when the app completes shutting down.""" def do_apply_app_config(self) -> None: """Called when the app config should be applied.""" diff --git a/dist/ba_data/python/babase/_apputils.py b/dist/ba_data/python/babase/_apputils.py index 59d7648..ce911c9 100644 --- a/dist/ba_data/python/babase/_apputils.py +++ b/dist/ba_data/python/babase/_apputils.py @@ -10,9 +10,11 @@ from threading import Thread from dataclasses import dataclass from typing import TYPE_CHECKING +from typing_extensions import override from efro.call import tpartial from efro.log import LogLevel from efro.dataclassio import ioprepped, dataclass_to_json, dataclass_from_json + import _babase from babase._appsubsystem import AppSubsystem @@ -386,6 +388,7 @@ class AppHealthMonitor(AppSubsystem): self._response = False self._first_check = True + @override def on_app_loading(self) -> None: # If any traceback dumps happened last run, log and clear them. log_dumped_app_state(from_previous_run=True) @@ -449,10 +452,12 @@ class AppHealthMonitor(AppSubsystem): self._first_check = False + @override def on_app_suspend(self) -> None: assert _babase.in_logic_thread() self._running = False + @override def on_app_unsuspend(self) -> None: assert _babase.in_logic_thread() self._running = True diff --git a/dist/ba_data/python/babase/_devconsole.py b/dist/ba_data/python/babase/_devconsole.py index fb63c9e..47580b8 100644 --- a/dist/ba_data/python/babase/_devconsole.py +++ b/dist/ba_data/python/babase/_devconsole.py @@ -8,6 +8,8 @@ from typing import TYPE_CHECKING from dataclasses import dataclass import logging +from typing_extensions import override + import _babase if TYPE_CHECKING: @@ -96,6 +98,7 @@ class DevConsoleTab: class DevConsoleTabPython(DevConsoleTab): """The Python dev-console tab.""" + @override def refresh(self) -> None: self.python_terminal() @@ -103,6 +106,7 @@ class DevConsoleTabPython(DevConsoleTab): class DevConsoleTabTest(DevConsoleTab): """Test dev-console tab.""" + @override def refresh(self) -> None: import random diff --git a/dist/ba_data/python/babase/_emptyappmode.py b/dist/ba_data/python/babase/_emptyappmode.py index f3905b7..3573769 100644 --- a/dist/ba_data/python/babase/_emptyappmode.py +++ b/dist/ba_data/python/babase/_emptyappmode.py @@ -5,6 +5,7 @@ from __future__ import annotations from typing import TYPE_CHECKING +from typing_extensions import override from bacommon.app import AppExperience import _babase @@ -18,15 +19,18 @@ if TYPE_CHECKING: class EmptyAppMode(AppMode): """An empty app mode that can be used as a fallback/etc.""" + @override @classmethod def get_app_experience(cls) -> AppExperience: return AppExperience.EMPTY + @override @classmethod def _supports_intent(cls, intent: AppIntent) -> bool: # We support default and exec intents currently. return isinstance(intent, AppIntentExec | AppIntentDefault) + @override def handle_intent(self, intent: AppIntent) -> None: if isinstance(intent, AppIntentExec): _babase.empty_app_mode_handle_intent_exec(intent.code) @@ -34,10 +38,12 @@ class EmptyAppMode(AppMode): assert isinstance(intent, AppIntentDefault) _babase.empty_app_mode_handle_intent_default() + @override def on_activate(self) -> None: # Let the native layer do its thing. _babase.on_empty_app_mode_activate() + @override def on_deactivate(self) -> None: # Let the native layer do its thing. _babase.on_empty_app_mode_deactivate() diff --git a/dist/ba_data/python/babase/_env.py b/dist/ba_data/python/babase/_env.py index 2af28c2..124caa2 100644 --- a/dist/ba_data/python/babase/_env.py +++ b/dist/ba_data/python/babase/_env.py @@ -9,6 +9,7 @@ import logging import warnings from typing import TYPE_CHECKING +from typing_extensions import override from efro.log import LogLevel if TYPE_CHECKING: @@ -216,6 +217,7 @@ def _feed_logs_to_babase(log_handler: LogHandler) -> None: class _CustomHelper: """Replacement 'help' that behaves better for our setup.""" + @override def __repr__(self) -> str: return 'Type help(object) for help about object.' diff --git a/dist/ba_data/python/babase/_general.py b/dist/ba_data/python/babase/_general.py index 206ac03..2d2738d 100644 --- a/dist/ba_data/python/babase/_general.py +++ b/dist/ba_data/python/babase/_general.py @@ -10,6 +10,7 @@ import logging import inspect from typing import TYPE_CHECKING, TypeVar, Protocol, NewType +from typing_extensions import override from efro.terminal import Clr import _babase @@ -178,6 +179,7 @@ class _WeakCall: def __call__(self, *args_extra: Any) -> Any: return self._call(*self._args + args_extra, **self._keywds) + @override def __str__(self) -> str: return ( '' diff --git a/dist/ba_data/python/babase/_language.py b/dist/ba_data/python/babase/_language.py index 059e60a..3983a7a 100644 --- a/dist/ba_data/python/babase/_language.py +++ b/dist/ba_data/python/babase/_language.py @@ -8,6 +8,8 @@ import json import logging from typing import TYPE_CHECKING, overload +from typing_extensions import override + import _babase from babase._appsubsystem import AppSubsystem @@ -217,6 +219,7 @@ class LanguageSubsystem(AppSubsystem): color=(0, 1, 0), ) + @override def do_apply_app_config(self) -> None: assert _babase.in_logic_thread() assert isinstance(_babase.app.config, dict) @@ -598,9 +601,11 @@ class Lstr: _error.print_exception('_get_json failed for', self.args) return 'JSON_ERR' + @override def __str__(self) -> str: return '' + @override def __repr__(self) -> str: return '' @@ -648,5 +653,6 @@ class AttrDict(dict): assert not isinstance(val, bytes) return val + @override def __setattr__(self, attr: str, value: Any) -> None: raise AttributeError() diff --git a/dist/ba_data/python/babase/_login.py b/dist/ba_data/python/babase/_login.py index 39cfa0e..6796844 100644 --- a/dist/ba_data/python/babase/_login.py +++ b/dist/ba_data/python/babase/_login.py @@ -9,6 +9,7 @@ import logging from dataclasses import dataclass from typing import TYPE_CHECKING, final +from typing_extensions import override from bacommon.login import LoginType import _babase @@ -353,6 +354,7 @@ class LoginAdapterNative(LoginAdapter): self._sign_in_attempt_num = 123 self._sign_in_attempts: dict[int, Callable[[str | None], None]] = {} + @override def get_sign_in_token( self, completion_cb: Callable[[str | None], None] ) -> None: @@ -363,6 +365,7 @@ class LoginAdapterNative(LoginAdapter): self.login_type.value, attempt_id ) + @override def on_back_end_active_change(self, active: bool) -> None: _babase.login_adapter_back_end_active_change( self.login_type.value, active diff --git a/dist/ba_data/python/babase/_meta.py b/dist/ba_data/python/babase/_meta.py index 76f88b7..904e354 100644 --- a/dist/ba_data/python/babase/_meta.py +++ b/dist/ba_data/python/babase/_meta.py @@ -26,7 +26,7 @@ EXPORT_CLASS_NAME_SHORTCUTS: dict[str, str] = { 'plugin': 'babase.Plugin', # DEPRECATED as of 12/2023. Currently am warning if finding these # but should take this out eventually. - 'keyboard': 'babase.Keyboard', + 'keyboard': 'bauiv1.Keyboard', } T = TypeVar('T') diff --git a/dist/ba_data/python/babase/_mgen/enums.py b/dist/ba_data/python/babase/_mgen/enums.py index b69ad9a..9766e64 100644 --- a/dist/ba_data/python/babase/_mgen/enums.py +++ b/dist/ba_data/python/babase/_mgen/enums.py @@ -149,78 +149,84 @@ class SpecialChar(Enum): PLAY_PAUSE_BUTTON = 13 FAST_FORWARD_BUTTON = 14 DPAD_CENTER_BUTTON = 15 - OUYA_BUTTON_O = 16 - OUYA_BUTTON_U = 17 - OUYA_BUTTON_Y = 18 - OUYA_BUTTON_A = 19 - OUYA_LOGO = 20 - LOGO = 21 - TICKET = 22 - GOOGLE_PLAY_GAMES_LOGO = 23 - GAME_CENTER_LOGO = 24 - DICE_BUTTON1 = 25 - DICE_BUTTON2 = 26 - DICE_BUTTON3 = 27 - DICE_BUTTON4 = 28 - GAME_CIRCLE_LOGO = 29 - PARTY_ICON = 30 - TEST_ACCOUNT = 31 - TICKET_BACKING = 32 - TROPHY1 = 33 - TROPHY2 = 34 - TROPHY3 = 35 - TROPHY0A = 36 - TROPHY0B = 37 - TROPHY4 = 38 - LOCAL_ACCOUNT = 39 - EXPLODINARY_LOGO = 40 - FLAG_UNITED_STATES = 41 - FLAG_MEXICO = 42 - FLAG_GERMANY = 43 - FLAG_BRAZIL = 44 - FLAG_RUSSIA = 45 - FLAG_CHINA = 46 - FLAG_UNITED_KINGDOM = 47 - FLAG_CANADA = 48 - FLAG_INDIA = 49 - FLAG_JAPAN = 50 - FLAG_FRANCE = 51 - FLAG_INDONESIA = 52 - FLAG_ITALY = 53 - FLAG_SOUTH_KOREA = 54 - FLAG_NETHERLANDS = 55 - FEDORA = 56 - HAL = 57 - CROWN = 58 - YIN_YANG = 59 - EYE_BALL = 60 - SKULL = 61 - HEART = 62 - DRAGON = 63 - HELMET = 64 - MUSHROOM = 65 - NINJA_STAR = 66 - VIKING_HELMET = 67 - MOON = 68 - SPIDER = 69 - FIREBALL = 70 - FLAG_UNITED_ARAB_EMIRATES = 71 - FLAG_QATAR = 72 - FLAG_EGYPT = 73 - FLAG_KUWAIT = 74 - FLAG_ALGERIA = 75 - FLAG_SAUDI_ARABIA = 76 - FLAG_MALAYSIA = 77 - FLAG_CZECH_REPUBLIC = 78 - FLAG_AUSTRALIA = 79 - FLAG_SINGAPORE = 80 - OCULUS_LOGO = 81 - STEAM_LOGO = 82 - NVIDIA_LOGO = 83 - FLAG_IRAN = 84 - FLAG_POLAND = 85 - FLAG_ARGENTINA = 86 - FLAG_PHILIPPINES = 87 - FLAG_CHILE = 88 - MIKIROG = 89 - V2_LOGO = 90 + PLAY_STATION_CROSS_BUTTON = 16 + PLAY_STATION_CIRCLE_BUTTON = 17 + PLAY_STATION_TRIANGLE_BUTTON = 18 + PLAY_STATION_SQUARE_BUTTON = 19 + PLAY_BUTTON = 20 + PAUSE_BUTTON = 21 + OUYA_BUTTON_O = 22 + OUYA_BUTTON_U = 23 + OUYA_BUTTON_Y = 24 + OUYA_BUTTON_A = 25 + OUYA_LOGO = 26 + LOGO = 27 + TICKET = 28 + GOOGLE_PLAY_GAMES_LOGO = 29 + GAME_CENTER_LOGO = 30 + DICE_BUTTON1 = 31 + DICE_BUTTON2 = 32 + DICE_BUTTON3 = 33 + DICE_BUTTON4 = 34 + GAME_CIRCLE_LOGO = 35 + PARTY_ICON = 36 + TEST_ACCOUNT = 37 + TICKET_BACKING = 38 + TROPHY1 = 39 + TROPHY2 = 40 + TROPHY3 = 41 + TROPHY0A = 42 + TROPHY0B = 43 + TROPHY4 = 44 + LOCAL_ACCOUNT = 45 + EXPLODINARY_LOGO = 46 + FLAG_UNITED_STATES = 47 + FLAG_MEXICO = 48 + FLAG_GERMANY = 49 + FLAG_BRAZIL = 50 + FLAG_RUSSIA = 51 + FLAG_CHINA = 52 + FLAG_UNITED_KINGDOM = 53 + FLAG_CANADA = 54 + FLAG_INDIA = 55 + FLAG_JAPAN = 56 + FLAG_FRANCE = 57 + FLAG_INDONESIA = 58 + FLAG_ITALY = 59 + FLAG_SOUTH_KOREA = 60 + FLAG_NETHERLANDS = 61 + FEDORA = 62 + HAL = 63 + CROWN = 64 + YIN_YANG = 65 + EYE_BALL = 66 + SKULL = 67 + HEART = 68 + DRAGON = 69 + HELMET = 70 + MUSHROOM = 71 + NINJA_STAR = 72 + VIKING_HELMET = 73 + MOON = 74 + SPIDER = 75 + FIREBALL = 76 + FLAG_UNITED_ARAB_EMIRATES = 77 + FLAG_QATAR = 78 + FLAG_EGYPT = 79 + FLAG_KUWAIT = 80 + FLAG_ALGERIA = 81 + FLAG_SAUDI_ARABIA = 82 + FLAG_MALAYSIA = 83 + FLAG_CZECH_REPUBLIC = 84 + FLAG_AUSTRALIA = 85 + FLAG_SINGAPORE = 86 + OCULUS_LOGO = 87 + STEAM_LOGO = 88 + NVIDIA_LOGO = 89 + FLAG_IRAN = 90 + FLAG_POLAND = 91 + FLAG_ARGENTINA = 92 + FLAG_PHILIPPINES = 93 + FLAG_CHILE = 94 + MIKIROG = 95 + V2_LOGO = 96 diff --git a/dist/ba_data/python/babase/_plugin.py b/dist/ba_data/python/babase/_plugin.py index 692840b..d83050b 100644 --- a/dist/ba_data/python/babase/_plugin.py +++ b/dist/ba_data/python/babase/_plugin.py @@ -8,6 +8,8 @@ import logging import importlib.util from typing import TYPE_CHECKING +from typing_extensions import override + import _babase from babase._appsubsystem import AppSubsystem @@ -158,6 +160,7 @@ class PluginSubsystem(AppSubsystem): if config_changed: _babase.app.config.commit() + @override def on_app_running(self) -> None: # Load up our plugins and go ahead and call their on_app_running # calls. @@ -170,6 +173,7 @@ class PluginSubsystem(AppSubsystem): _error.print_exception('Error in plugin on_app_running()') + @override def on_app_suspend(self) -> None: for plugin in self.active_plugins: try: @@ -179,6 +183,7 @@ class PluginSubsystem(AppSubsystem): _error.print_exception('Error in plugin on_app_suspend()') + @override def on_app_unsuspend(self) -> None: for plugin in self.active_plugins: try: @@ -188,6 +193,7 @@ class PluginSubsystem(AppSubsystem): _error.print_exception('Error in plugin on_app_unsuspend()') + @override def on_app_shutdown(self) -> None: for plugin in self.active_plugins: try: @@ -197,6 +203,7 @@ class PluginSubsystem(AppSubsystem): _error.print_exception('Error in plugin on_app_shutdown()') + @override def on_app_shutdown_complete(self) -> None: for plugin in self.active_plugins: try: diff --git a/dist/ba_data/python/babase/_ui.py b/dist/ba_data/python/babase/_ui.py index 6f6b059..6457ead 100644 --- a/dist/ba_data/python/babase/_ui.py +++ b/dist/ba_data/python/babase/_ui.py @@ -5,6 +5,8 @@ from __future__ import annotations from typing import TYPE_CHECKING +from typing_extensions import override + from babase._stringedit import StringEditAdapter import _babase @@ -24,9 +26,11 @@ class DevConsoleStringEditAdapter(StringEditAdapter): description, initial_text, max_length, screen_space_center ) + @override def _do_apply(self, new_text: str) -> None: _babase.set_dev_console_input_text(new_text) _babase.dev_console_input_adapter_finish() + @override def _do_cancel(self) -> None: _babase.dev_console_input_adapter_finish() diff --git a/dist/ba_data/python/baclassic/_ads.py b/dist/ba_data/python/baclassic/_ads.py index 2373df3..5cd454d 100644 --- a/dist/ba_data/python/baclassic/_ads.py +++ b/dist/ba_data/python/baclassic/_ads.py @@ -229,9 +229,7 @@ class AdsSubsystem: await asyncio.sleep(1.0) payload.run(fallback=True) - _fallback_task = babase.app.aioloop.create_task( - add_fallback_task() - ) + babase.app.create_async_task(add_fallback_task()) self.show_ad('between_game', on_completion_call=payload.run) else: babase.pushcall(call) # Just run the callback without the ad. diff --git a/dist/ba_data/python/baclassic/_benchmark.py b/dist/ba_data/python/baclassic/_benchmark.py index 569f9d6..23033ee 100644 --- a/dist/ba_data/python/baclassic/_benchmark.py +++ b/dist/ba_data/python/baclassic/_benchmark.py @@ -4,8 +4,10 @@ from __future__ import annotations import random +from dataclasses import dataclass from typing import TYPE_CHECKING +from typing_extensions import override import babase import bascenev1 import _baclassic @@ -42,70 +44,81 @@ def run_cpu_benchmark() -> None: cfg['Graphics Quality'] = self._old_quality cfg.apply() + @override def on_player_request(self, player: bascenev1.SessionPlayer) -> bool: return False bascenev1.new_host_session(BenchmarkSession, benchmark_type='cpu') +@dataclass +class _StressTestArgs: + playlist_type: str + playlist_name: str + player_count: int + round_duration: int + attract_mode: bool + + def run_stress_test( playlist_type: str = 'Random', playlist_name: str = '__default__', player_count: int = 8, round_duration: int = 30, + attract_mode: bool = False, ) -> None: """Run a stress test.""" - babase.screenmessage( - "Beginning stress test.. use 'End Test' to stop testing.", - color=(1, 1, 0), - ) with babase.ContextRef.empty(): - start_stress_test( - { - 'playlist_type': playlist_type, - 'playlist_name': playlist_name, - 'player_count': player_count, - 'round_duration': round_duration, - } + if not attract_mode: + babase.screenmessage( + "Beginning stress test.. use 'End Test' to stop testing.", + color=(1, 1, 0), + ) + _start_stress_test( + _StressTestArgs( + playlist_type=playlist_type, + playlist_name=playlist_name, + player_count=player_count, + round_duration=round_duration, + attract_mode=attract_mode, + ) ) def stop_stress_test() -> None: """End a running stress test.""" - _baclassic.set_stress_testing(False, 0) assert babase.app.classic is not None - try: - if babase.app.classic.stress_test_reset_timer is not None: - babase.screenmessage('Ending stress test...', color=(1, 1, 0)) - except Exception: - pass - babase.app.classic.stress_test_reset_timer = None + + _baclassic.set_stress_testing(False, 0, False) + babase.app.classic.stress_test_update_timer = None + babase.app.classic.stress_test_update_timer_2 = None -def start_stress_test(args: dict[str, Any]) -> None: +def _start_stress_test(args: _StressTestArgs) -> None: """(internal)""" from bascenev1 import DualTeamSession, FreeForAllSession assert babase.app.classic is not None appconfig = babase.app.config - playlist_type = args['playlist_type'] + playlist_type = args.playlist_type if playlist_type == 'Random': if random.random() < 0.5: playlist_type = 'Teams' else: playlist_type = 'Free-For-All' - babase.screenmessage( - 'Running Stress Test (listType="' - + playlist_type - + '", listName="' - + args['playlist_name'] - + '")...' - ) + if not args.attract_mode: + babase.screenmessage( + 'Running Stress Test (listType="' + + playlist_type + + '", listName="' + + args.playlist_name + + '")...' + ) if playlist_type == 'Teams': - appconfig['Team Tournament Playlist Selection'] = args['playlist_name'] + appconfig['Team Tournament Playlist Selection'] = args.playlist_name appconfig['Team Tournament Playlist Randomize'] = 1 babase.apptimer( 1.0, @@ -115,7 +128,7 @@ def start_stress_test(args: dict[str, Any]) -> None: ), ) else: - appconfig['Free-for-All Playlist Selection'] = args['playlist_name'] + appconfig['Free-for-All Playlist Selection'] = args.playlist_name appconfig['Free-for-All Playlist Randomize'] = 1 babase.apptimer( 1.0, @@ -124,19 +137,38 @@ def start_stress_test(args: dict[str, Any]) -> None: babase.Call(bascenev1.new_host_session, FreeForAllSession), ), ) - _baclassic.set_stress_testing(True, args['player_count']) - babase.app.classic.stress_test_reset_timer = babase.AppTimer( - args['round_duration'], babase.Call(_reset_stress_test, args) + _baclassic.set_stress_testing(True, args.player_count, args.attract_mode) + babase.app.classic.stress_test_update_timer = babase.AppTimer( + args.round_duration, babase.Call(_reset_stress_test, args) ) + if args.attract_mode: + babase.app.classic.stress_test_update_timer_2 = babase.AppTimer( + 0.48, babase.Call(_update_attract_mode_test, args), repeat=True + ) -def _reset_stress_test(args: dict[str, Any]) -> None: - _baclassic.set_stress_testing(False, args['player_count']) - babase.screenmessage('Resetting stress test...') +def _update_attract_mode_test(args: _StressTestArgs) -> None: + if babase.get_input_idle_time() < 5.0: + _reset_stress_test(args) + + +def _reset_stress_test(args: _StressTestArgs) -> None: + _baclassic.set_stress_testing(False, args.player_count, False) + if not args.attract_mode: + babase.screenmessage('Resetting stress test...') session = bascenev1.get_foreground_host_session() assert session is not None session.end() - babase.apptimer(1.0, babase.Call(start_stress_test, args)) + + assert babase.app.classic is not None + babase.app.classic.stress_test_update_timer = None + babase.app.classic.stress_test_update_timer_2 = None + + # For regular stress tests we keep the party going. For attract-mode + # we just end back at the main menu. If things are idle there then + # we'll get sent back to a new stress test. + if not args.attract_mode: + babase.apptimer(1.0, babase.Call(_start_stress_test, args)) def run_gpu_benchmark() -> None: diff --git a/dist/ba_data/python/baclassic/_net.py b/dist/ba_data/python/baclassic/_net.py index a76258d..04d3fb6 100644 --- a/dist/ba_data/python/baclassic/_net.py +++ b/dist/ba_data/python/baclassic/_net.py @@ -9,6 +9,7 @@ import threading from enum import Enum from typing import TYPE_CHECKING +from typing_extensions import override import babase import bascenev1 @@ -68,6 +69,7 @@ class MasterServerV1CallThread(threading.Thread): with self._context: self._callback(arg) + @override def run(self) -> None: # pylint: disable=consider-using-with # pylint: disable=too-many-branches diff --git a/dist/ba_data/python/baclassic/_subsystem.py b/dist/ba_data/python/baclassic/_subsystem.py index 16299bd..bb9bbad 100644 --- a/dist/ba_data/python/baclassic/_subsystem.py +++ b/dist/ba_data/python/baclassic/_subsystem.py @@ -8,6 +8,7 @@ import random import logging import weakref +from typing_extensions import override from efro.dataclassio import dataclass_from_dict import babase import bauiv1 @@ -69,7 +70,8 @@ class ClassicSubsystem(babase.AppSubsystem): # Misc. self.tips: list[str] = [] - self.stress_test_reset_timer: babase.AppTimer | None = None + self.stress_test_update_timer: babase.AppTimer | None = None + self.stress_test_update_timer_2: babase.AppTimer | None = None self.value_test_defaults: dict = {} self.special_offer: dict | None = None self.ping_thread_count = 0 @@ -148,6 +150,7 @@ class ClassicSubsystem(babase.AppSubsystem): assert isinstance(self._env['legacy_user_agent_string'], str) return self._env['legacy_user_agent_string'] + @override def on_app_loading(self) -> None: from bascenev1lib.actor import spazappearance from bascenev1lib import maps as stdmaps @@ -229,13 +232,16 @@ class ClassicSubsystem(babase.AppSubsystem): self.accounts.on_app_loading() + @override def on_app_suspend(self) -> None: self.accounts.on_app_suspend() + @override def on_app_unsuspend(self) -> None: self.accounts.on_app_unsuspend() self.music.on_app_unsuspend() + @override def on_app_shutdown(self) -> None: self.music.on_app_shutdown() @@ -555,11 +561,18 @@ class ClassicSubsystem(babase.AppSubsystem): playlist_name: str = '__default__', player_count: int = 8, round_duration: int = 30, + attract_mode: bool = False, ) -> None: """Run a stress test.""" from baclassic._benchmark import run_stress_test as run - run(playlist_type, playlist_name, player_count, round_duration) + run( + playlist_type=playlist_type, + playlist_name=playlist_name, + player_count=player_count, + round_duration=round_duration, + attract_mode=attract_mode, + ) def get_input_device_mapped_value( self, device: bascenev1.InputDevice, name: str diff --git a/dist/ba_data/python/baclassic/macmusicapp.py b/dist/ba_data/python/baclassic/macmusicapp.py index 27b0e12..12fe684 100644 --- a/dist/ba_data/python/baclassic/macmusicapp.py +++ b/dist/ba_data/python/baclassic/macmusicapp.py @@ -8,6 +8,7 @@ import threading from collections import deque from typing import TYPE_CHECKING +from typing_extensions import override import babase from baclassic._music import MusicPlayer @@ -27,6 +28,7 @@ class MacMusicAppMusicPlayer(MusicPlayer): self._thread = _MacMusicAppThread() self._thread.start() + @override def on_select_entry( self, callback: Callable[[Any], None], @@ -40,6 +42,7 @@ class MacMusicAppMusicPlayer(MusicPlayer): callback, current_entry, selection_target_name ) + @override def on_set_volume(self, volume: float) -> None: self._thread.set_volume(volume) @@ -47,6 +50,7 @@ class MacMusicAppMusicPlayer(MusicPlayer): """Asynchronously fetch the list of available iTunes playlists.""" self._thread.get_playlists(callback) + @override def on_play(self, entry: Any) -> None: assert babase.app.classic is not None music = babase.app.classic.music @@ -59,9 +63,11 @@ class MacMusicAppMusicPlayer(MusicPlayer): entry_type, ) + @override def on_stop(self) -> None: self._thread.play_playlist(None) + @override def on_app_shutdown(self) -> None: self._thread.shutdown() @@ -77,6 +83,7 @@ class _MacMusicAppThread(threading.Thread): self._current_playlist: str | None = None self._orig_volume: int | None = None + @override def run(self) -> None: """Run the Music.app thread.""" babase.set_thread_name('BA_MacMusicAppThread') diff --git a/dist/ba_data/python/baclassic/osmusic.py b/dist/ba_data/python/baclassic/osmusic.py index d808000..f2198ce 100644 --- a/dist/ba_data/python/baclassic/osmusic.py +++ b/dist/ba_data/python/baclassic/osmusic.py @@ -9,6 +9,7 @@ import logging import threading from typing import TYPE_CHECKING +from typing_extensions import override import babase from baclassic._music import MusicPlayer @@ -33,6 +34,7 @@ class OSMusicPlayer(MusicPlayer): # FIXME: should ask the C++ layer for these; just hard-coding for now. return ['mp3', 'ogg', 'm4a', 'wav', 'flac', 'mid'] + @override def on_select_entry( self, callback: Callable[[Any], None], @@ -48,9 +50,11 @@ class OSMusicPlayer(MusicPlayer): callback, current_entry, selection_target_name ) + @override def on_set_volume(self, volume: float) -> None: babase.music_player_set_volume(volume) + @override def on_play(self, entry: Any) -> None: assert babase.app.classic is not None music = babase.app.classic.music @@ -99,11 +103,13 @@ class OSMusicPlayer(MusicPlayer): self._actually_playing = True babase.music_player_play(result) + @override def on_stop(self) -> None: self._want_to_play = False self._actually_playing = False babase.music_player_stop() + @override def on_app_shutdown(self) -> None: babase.music_player_shutdown() @@ -120,6 +126,7 @@ class _PickFolderSongThread(threading.Thread): self._callback = callback self._path = path + @override def run(self) -> None: do_log_error = True try: diff --git a/dist/ba_data/python/bacommon/build.py b/dist/ba_data/python/bacommon/build.py index 0ffcd3f..7e26ed5 100644 --- a/dist/ba_data/python/bacommon/build.py +++ b/dist/ba_data/python/bacommon/build.py @@ -21,7 +21,7 @@ class BuildInfoSet: @dataclass class Entry: - """Info about a particular build.""" + """Info about a particular app build.""" filename: Annotated[str, IOAttrs('fname')] size: Annotated[int, IOAttrs('size')] diff --git a/dist/ba_data/python/bacommon/cloud.py b/dist/ba_data/python/bacommon/cloud.py index df621b0..dd14bb5 100644 --- a/dist/ba_data/python/bacommon/cloud.py +++ b/dist/ba_data/python/bacommon/cloud.py @@ -7,6 +7,7 @@ from dataclasses import dataclass, field from typing import TYPE_CHECKING, Annotated from enum import Enum +from typing_extensions import override from efro.message import Message, Response from efro.dataclassio import ioprepped, IOAttrs from bacommon.transfer import DirectoryManifest @@ -21,6 +22,7 @@ if TYPE_CHECKING: class LoginProxyRequestMessage(Message): """Request send to the cloud to ask for a login-proxy.""" + @override @classmethod def get_response_types(cls) -> list[type[Response] | None]: return [LoginProxyRequestResponse] @@ -49,6 +51,7 @@ class LoginProxyStateQueryMessage(Message): proxyid: Annotated[str, IOAttrs('p')] proxykey: Annotated[str, IOAttrs('k')] + @override @classmethod def get_response_types(cls) -> list[type[Response] | None]: return [LoginProxyStateQueryResponse] @@ -85,6 +88,7 @@ class LoginProxyCompleteMessage(Message): class PingMessage(Message): """Standard ping.""" + @override @classmethod def get_response_types(cls) -> list[type[Response] | None]: return [PingResponse] @@ -103,6 +107,7 @@ class TestMessage(Message): testfoo: Annotated[int, IOAttrs('f')] + @override @classmethod def get_response_types(cls) -> list[type[Response] | None]: return [TestResponse] @@ -116,6 +121,28 @@ class TestResponse(Response): testfoo: Annotated[int, IOAttrs('f')] +@ioprepped +@dataclass +class PromoCodeMessage(Message): + """User is entering a promo code""" + + code: Annotated[str, IOAttrs('c')] + + @override + @classmethod + def get_response_types(cls) -> list[type[Response] | None]: + return [PromoCodeResponse] + + +@ioprepped +@dataclass +class PromoCodeResponse(Response): + """Applied that promo code for ya, boss.""" + + valid: Annotated[bool, IOAttrs('v')] + message: Annotated[str | None, IOAttrs('m', store_default=False)] = None + + @ioprepped @dataclass class WorkspaceFetchState: @@ -136,6 +163,7 @@ class WorkspaceFetchMessage(Message): workspaceid: Annotated[str, IOAttrs('w')] state: Annotated[WorkspaceFetchState, IOAttrs('s')] + @override @classmethod def get_response_types(cls) -> list[type[Response] | None]: return [WorkspaceFetchResponse] @@ -162,6 +190,7 @@ class WorkspaceFetchResponse(Response): class MerchAvailabilityMessage(Message): """Can we show merch link?""" + @override @classmethod def get_response_types(cls) -> list[type[Response] | None]: return [MerchAvailabilityResponse] @@ -187,6 +216,7 @@ class SignInMessage(Message): description: Annotated[str, IOAttrs('d', soft_default='-')] apptime: Annotated[float, IOAttrs('at', soft_default=-1.0)] + @override @classmethod def get_response_types(cls) -> list[type[Response] | None]: return [SignInResponse] @@ -205,6 +235,7 @@ class SignInResponse(Response): class ManageAccountMessage(Message): """Message asking for a manage-account url.""" + @override @classmethod def get_response_types(cls) -> list[type[Response] | None]: return [ManageAccountResponse] diff --git a/dist/ba_data/python/bacommon/transfer.py b/dist/ba_data/python/bacommon/transfer.py index f6704b2..a53c615 100644 --- a/dist/ba_data/python/bacommon/transfer.py +++ b/dist/ba_data/python/bacommon/transfer.py @@ -31,7 +31,7 @@ class DirectoryManifest: files: Annotated[dict[str, DirectoryManifestFile], IOAttrs('f')] - _empty_hash: str | None = None + # _empty_hash: str | None = None @classmethod def create_from_disk(cls, path: Path) -> DirectoryManifest: @@ -92,12 +92,12 @@ class DirectoryManifest: ) break # 1 error is enough for now. - @classmethod - def get_empty_hash(cls) -> str: - """Return the hash for an empty file.""" - if cls._empty_hash is None: - import hashlib + # @classmethod + # def get_empty_hash(cls) -> str: + # """Return the hash for an empty file.""" + # if cls._empty_hash is None: + # import hashlib - sha = hashlib.sha256() - cls._empty_hash = sha.hexdigest() - return cls._empty_hash + # sha = hashlib.sha256() + # cls._empty_hash = sha.hexdigest() + # return cls._empty_hash diff --git a/dist/ba_data/python/baenv.py b/dist/ba_data/python/baenv.py index c8f23af..01c47c9 100644 --- a/dist/ba_data/python/baenv.py +++ b/dist/ba_data/python/baenv.py @@ -52,8 +52,8 @@ if TYPE_CHECKING: # Build number and version of the ballistica binary we expect to be # using. -TARGET_BALLISTICA_BUILD = 21739 -TARGET_BALLISTICA_VERSION = '1.7.32' +TARGET_BALLISTICA_BUILD = 21762 +TARGET_BALLISTICA_VERSION = '1.7.33' @dataclass @@ -350,9 +350,15 @@ def _setup_paths( # platforms where there is no write access to said built-in # stuff. check_dir = Path(user_python_dir, 'sys', TARGET_BALLISTICA_VERSION) - if check_dir.is_dir(): - app_python_dir = str(check_dir) - is_user_app_python_dir = True + try: + if check_dir.is_dir(): + app_python_dir = str(check_dir) + is_user_app_python_dir = True + except PermissionError: + logging.warning( + "PermissionError checking user-app-python-dir path '%s'.", + check_dir, + ) # Ok, now apply these to sys.path. diff --git a/dist/ba_data/python/baplus/__init__.py b/dist/ba_data/python/baplus/__init__.py index dd2e4cb..4040f7b 100644 --- a/dist/ba_data/python/baplus/__init__.py +++ b/dist/ba_data/python/baplus/__init__.py @@ -16,9 +16,11 @@ from __future__ import annotations import logging +from baplus._cloud import CloudSubsystem from baplus._subsystem import PlusSubsystem __all__ = [ + 'CloudSubsystem', 'PlusSubsystem', ] diff --git a/dist/ba_data/python/baplus/_cloud.py b/dist/ba_data/python/baplus/_cloud.py new file mode 100644 index 0000000..d2e51eb --- /dev/null +++ b/dist/ba_data/python/baplus/_cloud.py @@ -0,0 +1,214 @@ +# Released under the MIT License. See LICENSE for details. +# +"""Functionality related to the cloud.""" + +from __future__ import annotations + +import logging +from typing import TYPE_CHECKING, overload + +import babase + +if TYPE_CHECKING: + from typing import Callable, Any + + from efro.message import Message, Response + import bacommon.cloud + +DEBUG_LOG = False + +# TODO: Should make it possible to define a protocol in bacommon.cloud and +# autogenerate this. That would give us type safety between this and +# internal protocols. + + +class CloudSubsystem(babase.AppSubsystem): + """Manages communication with cloud components.""" + + @property + def connected(self) -> bool: + """Property equivalent of CloudSubsystem.is_connected().""" + return self.is_connected() + + def is_connected(self) -> bool: + """Return whether a connection to the cloud is present. + + This is a good indicator (though not for certain) that sending + messages will succeed. + """ + return False # Needs to be overridden + + def on_connectivity_changed(self, connected: bool) -> None: + """Called when cloud connectivity state changes.""" + if DEBUG_LOG: + logging.debug('CloudSubsystem: Connectivity is now %s.', connected) + + plus = babase.app.plus + assert plus is not None + + # Inform things that use this. + # (TODO: should generalize this into some sort of registration system) + plus.accounts.on_cloud_connectivity_changed(connected) + + @overload + def send_message_cb( + self, + msg: bacommon.cloud.LoginProxyRequestMessage, + on_response: Callable[ + [bacommon.cloud.LoginProxyRequestResponse | Exception], None + ], + ) -> None: + ... + + @overload + def send_message_cb( + self, + msg: bacommon.cloud.LoginProxyStateQueryMessage, + on_response: Callable[ + [bacommon.cloud.LoginProxyStateQueryResponse | Exception], None + ], + ) -> None: + ... + + @overload + def send_message_cb( + self, + msg: bacommon.cloud.LoginProxyCompleteMessage, + on_response: Callable[[None | Exception], None], + ) -> None: + ... + + @overload + def send_message_cb( + self, + msg: bacommon.cloud.PingMessage, + on_response: Callable[[bacommon.cloud.PingResponse | Exception], None], + ) -> None: + ... + + @overload + def send_message_cb( + self, + msg: bacommon.cloud.SignInMessage, + on_response: Callable[ + [bacommon.cloud.SignInResponse | Exception], None + ], + ) -> None: + ... + + @overload + def send_message_cb( + self, + msg: bacommon.cloud.ManageAccountMessage, + on_response: Callable[ + [bacommon.cloud.ManageAccountResponse | Exception], None + ], + ) -> None: + ... + + def send_message_cb( + self, + msg: Message, + on_response: Callable[[Any], None], + ) -> None: + """Asynchronously send a message to the cloud from the logic thread. + + The provided on_response call will be run in the logic thread + and passed either the response or the error that occurred. + """ + + del msg # Unused. + + babase.pushcall( + babase.Call( + on_response, + RuntimeError('Cloud functionality is not available.'), + ) + ) + + @overload + def send_message( + self, msg: bacommon.cloud.WorkspaceFetchMessage + ) -> bacommon.cloud.WorkspaceFetchResponse: + ... + + @overload + def send_message( + self, msg: bacommon.cloud.MerchAvailabilityMessage + ) -> bacommon.cloud.MerchAvailabilityResponse: + ... + + @overload + def send_message( + self, msg: bacommon.cloud.TestMessage + ) -> bacommon.cloud.TestResponse: + ... + + def send_message(self, msg: Message) -> Response | None: + """Synchronously send a message to the cloud. + + Must be called from a background thread. + """ + raise RuntimeError('Cloud functionality is not available.') + + @overload + async def send_message_async( + self, msg: bacommon.cloud.PromoCodeMessage + ) -> bacommon.cloud.PromoCodeResponse: + ... + + @overload + async def send_message_async( + self, msg: bacommon.cloud.TestMessage + ) -> bacommon.cloud.TestResponse: + ... + + async def send_message_async(self, msg: Message) -> Response | None: + """Synchronously send a message to the cloud. + + Must be called from the logic thread. + """ + raise RuntimeError('Cloud functionality is not available.') + + +def cloud_console_exec(code: str) -> None: + """Called by the cloud console to run code in the logic thread.""" + import sys + import __main__ + + try: + # First try it as eval. + try: + evalcode = compile(code, '', 'eval') + except SyntaxError: + evalcode = None + except Exception: + # hmm; when we can't compile it as eval will we always get + # syntax error? + logging.exception( + 'unexpected error compiling code for cloud-console eval.' + ) + evalcode = None + if evalcode is not None: + # pylint: disable=eval-used + value = eval(evalcode, vars(__main__), vars(__main__)) + # For eval-able statements, print the resulting value if + # it is not None (just like standard Python interpreter). + if value is not None: + print(repr(value), file=sys.stderr) + + # Fall back to exec if we couldn't compile it as eval. + else: + execcode = compile(code, '', 'exec') + # pylint: disable=exec-used + exec(execcode, vars(__main__), vars(__main__)) + except Exception: + import traceback + + apptime = babase.apptime() + print(f'Exec error at time {apptime:.2f}.', file=sys.stderr) + traceback.print_exc() + + # This helps the logging system ship stderr back to the + # cloud promptly. + sys.stderr.flush() diff --git a/dist/ba_data/python/baplus/_subsystem.py b/dist/ba_data/python/baplus/_subsystem.py index d38ca31..567f3cd 100644 --- a/dist/ba_data/python/baplus/_subsystem.py +++ b/dist/ba_data/python/baplus/_subsystem.py @@ -5,13 +5,17 @@ from __future__ import annotations from typing import TYPE_CHECKING -import _baplus +from typing_extensions import override from babase import AppSubsystem +import _baplus + if TYPE_CHECKING: from typing import Callable, Any - from babase import CloudSubsystem, AccountV2Subsystem + from babase import AccountV2Subsystem + + from baplus._cloud import CloudSubsystem class PlusSubsystem(AppSubsystem): @@ -32,6 +36,7 @@ class PlusSubsystem(AppSubsystem): accounts: AccountV2Subsystem cloud: CloudSubsystem + @override def on_app_loading(self) -> None: _baplus.on_app_loading() self.accounts.on_app_loading() diff --git a/dist/ba_data/python/bascenev1/__init__.py b/dist/ba_data/python/bascenev1/__init__.py index e96deb0..25c1f5e 100644 --- a/dist/ba_data/python/bascenev1/__init__.py +++ b/dist/ba_data/python/bascenev1/__init__.py @@ -103,6 +103,7 @@ from _bascenev1 import ( host_scan_cycle, InputDevice, is_in_replay, + is_replay_paused, ls_input_devices, ls_objects, Material, @@ -112,11 +113,13 @@ from _bascenev1 import ( newactivity, newnode, Node, + pause_replay, printnodes, protocol_version, release_gamepad_input, release_keyboard_input, reset_random_player_names, + resume_replay, broadcastmessage, SessionData, SessionPlayer, @@ -352,6 +355,7 @@ __all__ = [ 'IntSetting', 'is_in_replay', 'is_point_in_box', + 'is_replay_paused', 'JoinActivity', 'Level', 'Lobby', @@ -374,6 +378,7 @@ __all__ = [ 'normalized_color', 'NotFoundError', 'OutOfBoundsMessage', + 'pause_replay', 'PickedUpMessage', 'PickUpMessage', 'Player', @@ -394,6 +399,7 @@ __all__ = [ 'release_gamepad_input', 'release_keyboard_input', 'reset_random_player_names', + 'resume_replay', 'safecolor', 'screenmessage', 'SceneV1AppMode', diff --git a/dist/ba_data/python/bascenev1/_activitytypes.py b/dist/ba_data/python/bascenev1/_activitytypes.py index c0e410e..9b46b3e 100644 --- a/dist/ba_data/python/bascenev1/_activitytypes.py +++ b/dist/ba_data/python/bascenev1/_activitytypes.py @@ -5,6 +5,7 @@ from __future__ import annotations from typing import TYPE_CHECKING +from typing_extensions import override import babase import _bascenev1 @@ -34,11 +35,13 @@ class EndSessionActivity(Activity[EmptyPlayer, EmptyTeam]): self.inherits_vr_camera_offset = True self.inherits_vr_overlay_center = True + @override def on_transition_in(self) -> None: super().on_transition_in() babase.fade_screen(False) babase.lock_all_input() + @override def on_begin(self) -> None: # pylint: disable=cyclic-import @@ -77,6 +80,7 @@ class JoinActivity(Activity[EmptyPlayer, EmptyTeam]): self._tips_text: bascenev1.Actor | None = None self._join_info: JoinInfo | None = None + @override def on_transition_in(self) -> None: # pylint: disable=cyclic-import from bascenev1lib.actor.tipstext import TipsText @@ -110,6 +114,7 @@ class TransitionActivity(Activity[EmptyPlayer, EmptyTeam]): super().__init__(settings) self._background: bascenev1.Actor | None = None + @override def on_transition_in(self) -> None: # pylint: disable=cyclic-import from bascenev1lib.actor.background import Background @@ -119,6 +124,7 @@ class TransitionActivity(Activity[EmptyPlayer, EmptyTeam]): fade_time=0.5, start_faded=False, show_logo=False ) + @override def on_begin(self) -> None: super().on_begin() @@ -152,6 +158,7 @@ class ScoreScreenActivity(Activity[EmptyPlayer, EmptyTeam]): self._custom_continue_message: babase.Lstr | None = None self._server_transitioning: bool | None = None + @override def on_player_join(self, player: EmptyPlayer) -> None: super().on_player_join(player) time_till_assign = max( @@ -164,6 +171,7 @@ class ScoreScreenActivity(Activity[EmptyPlayer, EmptyTeam]): time_till_assign, babase.WeakCall(self._safe_assign, player) ) + @override def on_transition_in(self) -> None: from bascenev1lib.actor.tipstext import TipsText from bascenev1lib.actor.background import Background @@ -176,6 +184,7 @@ class ScoreScreenActivity(Activity[EmptyPlayer, EmptyTeam]): self._tips_text = TipsText() setmusic(self.default_music) + @override def on_begin(self) -> None: # pylint: disable=cyclic-import from bascenev1lib.actor.text import Text diff --git a/dist/ba_data/python/bascenev1/_appmode.py b/dist/ba_data/python/bascenev1/_appmode.py index 10d1c32..481fb4e 100644 --- a/dist/ba_data/python/bascenev1/_appmode.py +++ b/dist/ba_data/python/bascenev1/_appmode.py @@ -5,8 +5,15 @@ from __future__ import annotations from typing import TYPE_CHECKING +from typing_extensions import override from bacommon.app import AppExperience -from babase import AppMode, AppIntentExec, AppIntentDefault +from babase import ( + app, + AppMode, + AppIntentExec, + AppIntentDefault, + invoke_main_menu, +) import _bascenev1 @@ -17,15 +24,18 @@ if TYPE_CHECKING: class SceneV1AppMode(AppMode): """Our app-mode.""" + @override @classmethod def get_app_experience(cls) -> AppExperience: return AppExperience.MELEE + @override @classmethod def _supports_intent(cls, intent: AppIntent) -> bool: # We support default and exec intents currently. return isinstance(intent, AppIntentExec | AppIntentDefault) + @override def handle_intent(self, intent: AppIntent) -> None: if isinstance(intent, AppIntentExec): _bascenev1.handle_app_intent_exec(intent.code) @@ -33,10 +43,19 @@ class SceneV1AppMode(AppMode): assert isinstance(intent, AppIntentDefault) _bascenev1.handle_app_intent_default() + @override def on_activate(self) -> None: # Let the native layer do its thing. _bascenev1.on_app_mode_activate() + @override def on_deactivate(self) -> None: # Let the native layer do its thing. _bascenev1.on_app_mode_deactivate() + + @override + def on_app_active_changed(self) -> None: + # If we've gone inactive, bring up the main menu, which has the + # side effect of pausing the action (when possible). + if not app.active: + invoke_main_menu() diff --git a/dist/ba_data/python/bascenev1/_coopgame.py b/dist/ba_data/python/bascenev1/_coopgame.py index 2c684f8..e0af515 100644 --- a/dist/ba_data/python/bascenev1/_coopgame.py +++ b/dist/ba_data/python/bascenev1/_coopgame.py @@ -6,6 +6,7 @@ from __future__ import annotations import logging from typing import TYPE_CHECKING, TypeVar +from typing_extensions import override import babase import _bascenev1 @@ -31,6 +32,7 @@ class CoopGameActivity(GameActivity[PlayerT, TeamT]): # We can assume our session is a CoopSession. session: bascenev1.CoopSession + @override @classmethod def supports_session_type( cls, sessiontype: type[bascenev1.Session] @@ -49,6 +51,7 @@ class CoopGameActivity(GameActivity[PlayerT, TeamT]): self._life_warning_beep_timer: bascenev1.Timer | None = None self._warn_beeps_sound = _bascenev1.getsound('warnBeeps') + @override def on_begin(self) -> None: super().on_begin() @@ -139,6 +142,7 @@ class CoopGameActivity(GameActivity[PlayerT, TeamT]): ) vval -= 55 + @override def spawn_player_spaz( self, player: PlayerT, diff --git a/dist/ba_data/python/bascenev1/_coopsession.py b/dist/ba_data/python/bascenev1/_coopsession.py index 72a6791..f1d59ae 100644 --- a/dist/ba_data/python/bascenev1/_coopsession.py +++ b/dist/ba_data/python/bascenev1/_coopsession.py @@ -5,6 +5,7 @@ from __future__ import annotations from typing import TYPE_CHECKING +from typing_extensions import override import babase import _bascenev1 @@ -97,6 +98,7 @@ class CoopSession(Session): """Get the game instance currently being played.""" return self._current_game_instance + @override def should_allow_mid_activity_joins( self, activity: bascenev1.Activity ) -> bool: @@ -174,9 +176,11 @@ class CoopSession(Session): self._tutorial_activity = _bascenev1.newactivity(TutorialActivity) + @override def get_custom_menu_entries(self) -> list[dict[str, Any]]: return self._custom_menu_ui + @override def on_player_leave(self, sessionplayer: bascenev1.SessionPlayer) -> None: super().on_player_leave(sessionplayer) @@ -256,6 +260,7 @@ class CoopSession(Session): activity.end(results={'outcome': 'restart'}, force=True) # noinspection PyUnresolvedReferences + @override def on_activity_end( self, activity: bascenev1.Activity, results: Any ) -> None: diff --git a/dist/ba_data/python/bascenev1/_dependency.py b/dist/ba_data/python/bascenev1/_dependency.py index bbc956c..0a2f49f 100644 --- a/dist/ba_data/python/bascenev1/_dependency.py +++ b/dist/ba_data/python/bascenev1/_dependency.py @@ -7,6 +7,7 @@ from __future__ import annotations import weakref from typing import Generic, TypeVar, TYPE_CHECKING +from typing_extensions import override import babase import _bascenev1 @@ -313,6 +314,7 @@ class AssetPackage(DependencyComponent): self.package_id = entry.config print(f'LOADING ASSET PACKAGE {self.package_id}') + @override @classmethod def dep_is_present(cls, config: Any = None) -> bool: assert isinstance(config, str) diff --git a/dist/ba_data/python/bascenev1/_dualteamsession.py b/dist/ba_data/python/bascenev1/_dualteamsession.py index 4b4ab10..8280768 100644 --- a/dist/ba_data/python/bascenev1/_dualteamsession.py +++ b/dist/ba_data/python/bascenev1/_dualteamsession.py @@ -5,6 +5,7 @@ from __future__ import annotations from typing import TYPE_CHECKING +from typing_extensions import override import babase import _bascenev1 @@ -32,6 +33,7 @@ class DualTeamSession(MultiTeamSession): babase.increment_analytics_count('Teams session start') super().__init__() + @override def _switch_to_score_screen(self, results: bascenev1.GameResults) -> None: # pylint: disable=cyclic-import from bascenev1lib.activity.multiteamvictory import ( diff --git a/dist/ba_data/python/bascenev1/_freeforallsession.py b/dist/ba_data/python/bascenev1/_freeforallsession.py index 6c71d1b..2718377 100644 --- a/dist/ba_data/python/bascenev1/_freeforallsession.py +++ b/dist/ba_data/python/bascenev1/_freeforallsession.py @@ -6,6 +6,7 @@ from __future__ import annotations from typing import TYPE_CHECKING +from typing_extensions import override import babase import _bascenev1 @@ -53,6 +54,7 @@ class FreeForAllSession(MultiTeamSession): babase.increment_analytics_count('Free-for-all session start') super().__init__() + @override def _switch_to_score_screen(self, results: bascenev1.GameResults) -> None: # pylint: disable=cyclic-import from efro.util import asserttype diff --git a/dist/ba_data/python/bascenev1/_gameactivity.py b/dist/ba_data/python/bascenev1/_gameactivity.py index ad9bf0a..1557ffc 100644 --- a/dist/ba_data/python/bascenev1/_gameactivity.py +++ b/dist/ba_data/python/bascenev1/_gameactivity.py @@ -9,6 +9,7 @@ import random import logging from typing import TYPE_CHECKING, TypeVar +from typing_extensions import override import babase import _bascenev1 @@ -377,6 +378,7 @@ class GameActivity(Activity[PlayerT, TeamT]): """ return '' + @override def on_transition_in(self) -> None: super().on_transition_in() @@ -488,6 +490,7 @@ class GameActivity(Activity[PlayerT, TeamT]): self.end_game() + @override def on_begin(self) -> None: super().on_begin() @@ -536,12 +539,14 @@ class GameActivity(Activity[PlayerT, TeamT]): max(5, data_t[0]['timeRemaining']) ) + @override def on_player_join(self, player: PlayerT) -> None: super().on_player_join(player) # By default, just spawn a dude. self.spawn_player(player) + @override def handlemessage(self, msg: Any) -> Any: if isinstance(msg, PlayerDiedMessage): # pylint: disable=cyclic-import @@ -835,6 +840,7 @@ class GameActivity(Activity[PlayerT, TeamT]): animate(combine, 'input3', {0: 0, 1.0: 1, 4.0: 1, 5.0: 0}) _bascenev1.timer(5.0, tnode.delete) + @override def end( self, results: Any = None, delay: float = 0.0, force: bool = False ) -> None: diff --git a/dist/ba_data/python/bascenev1/_level.py b/dist/ba_data/python/bascenev1/_level.py index 725c962..2f1906e 100644 --- a/dist/ba_data/python/bascenev1/_level.py +++ b/dist/ba_data/python/bascenev1/_level.py @@ -7,6 +7,7 @@ import copy import weakref from typing import TYPE_CHECKING +from typing_extensions import override import babase if TYPE_CHECKING: @@ -38,6 +39,7 @@ class Level: self._index: int | None = None self._score_version_string: str | None = None + @override def __repr__(self) -> str: cls = type(self) return f"<{cls.__module__}.{cls.__name__} '{self._name}'>" diff --git a/dist/ba_data/python/bascenev1/_lobby.py b/dist/ba_data/python/bascenev1/_lobby.py index aa2ab16..001aa2d 100644 --- a/dist/ba_data/python/bascenev1/_lobby.py +++ b/dist/ba_data/python/bascenev1/_lobby.py @@ -441,7 +441,7 @@ class Chooser: # list might have changed. input_device = self._sessionplayer.inputdevice is_remote = input_device.is_remote_client - is_test_input = input_device.name.startswith('TestInput') + is_test_input = input_device.is_test_input # Pull this player's list of unlocked characters. if is_remote: diff --git a/dist/ba_data/python/bascenev1/_map.py b/dist/ba_data/python/bascenev1/_map.py index 818098f..832632f 100644 --- a/dist/ba_data/python/bascenev1/_map.py +++ b/dist/ba_data/python/bascenev1/_map.py @@ -6,6 +6,7 @@ from __future__ import annotations import random from typing import TYPE_CHECKING +from typing_extensions import override import babase import _bascenev1 @@ -353,9 +354,11 @@ class Map(Actor): return self.flag_points_default[:3] return self.flag_points[team_index % len(self.flag_points)][:3] + @override def exists(self) -> bool: return bool(self.node) + @override def handlemessage(self, msg: Any) -> Any: from bascenev1 import _messages diff --git a/dist/ba_data/python/bascenev1/_multiteamsession.py b/dist/ba_data/python/bascenev1/_multiteamsession.py index c138ffe..a4d6c2d 100644 --- a/dist/ba_data/python/bascenev1/_multiteamsession.py +++ b/dist/ba_data/python/bascenev1/_multiteamsession.py @@ -8,7 +8,9 @@ import random import logging from typing import TYPE_CHECKING +from typing_extensions import override import babase + import _bascenev1 from bascenev1._session import Session @@ -70,6 +72,10 @@ class MultiTeamSession(Session): show_tutorial = cfg.get('Show Tutorial', True) + # Special case: don't show tutorial while stress testing. + if classic.stress_test_update_timer is not None: + show_tutorial = False + self._tutorial_activity_instance: bascenev1.Activity | None if show_tutorial: from bascenev1lib.tutorial import TutorialActivity @@ -164,6 +170,7 @@ class MultiTeamSession(Session): """Returns which game in the series is currently being played.""" return self._game_number + @override def on_team_join(self, team: bascenev1.SessionTeam) -> None: team.customdata['previous_score'] = team.customdata['score'] = 0 @@ -182,6 +189,7 @@ class MultiTeamSession(Session): self._next_game_spec['settings'], ) + @override def on_activity_end( self, activity: bascenev1.Activity, results: Any ) -> None: diff --git a/dist/ba_data/python/bascenev1/_nodeactor.py b/dist/ba_data/python/bascenev1/_nodeactor.py index 2e9a148..7b3551e 100644 --- a/dist/ba_data/python/bascenev1/_nodeactor.py +++ b/dist/ba_data/python/bascenev1/_nodeactor.py @@ -6,6 +6,8 @@ from __future__ import annotations from typing import TYPE_CHECKING +from typing_extensions import override + from bascenev1._messages import DieMessage from bascenev1._actor import Actor @@ -28,6 +30,7 @@ class NodeActor(Actor): super().__init__() self.node = node + @override def handlemessage(self, msg: Any) -> Any: if isinstance(msg, DieMessage): if self.node: @@ -35,5 +38,6 @@ class NodeActor(Actor): return None return super().handlemessage(msg) + @override def exists(self) -> bool: return bool(self.node) diff --git a/dist/ba_data/python/bascenev1/_session.py b/dist/ba_data/python/bascenev1/_session.py index d561f12..9ee5531 100644 --- a/dist/ba_data/python/bascenev1/_session.py +++ b/dist/ba_data/python/bascenev1/_session.py @@ -253,7 +253,7 @@ class Session: # Limit player counts *unless* we're in a stress test. if ( babase.app.classic is not None - and babase.app.classic.stress_test_reset_timer is None + and babase.app.classic.stress_test_update_timer is None ): if len(self.sessionplayers) >= self.max_players: # Print a rejection message *only* to the client trying to diff --git a/dist/ba_data/python/bascenev1/_teamgame.py b/dist/ba_data/python/bascenev1/_teamgame.py index 47c311d..e835bd0 100644 --- a/dist/ba_data/python/bascenev1/_teamgame.py +++ b/dist/ba_data/python/bascenev1/_teamgame.py @@ -7,6 +7,7 @@ from __future__ import annotations import logging from typing import TYPE_CHECKING, TypeVar +from typing_extensions import override import babase import _bascenev1 @@ -35,6 +36,7 @@ class TeamGameActivity(GameActivity[PlayerT, TeamT]): bascenev1.Player has their own bascenev1.Team) """ + @override @classmethod def supports_session_type( cls, sessiontype: type[bascenev1.Session] @@ -57,6 +59,7 @@ class TeamGameActivity(GameActivity[PlayerT, TeamT]): if isinstance(self.session, FreeForAllSession): self.show_kill_points = False + @override def on_transition_in(self) -> None: # pylint: disable=cyclic-import from bascenev1._coopsession import CoopSession @@ -67,7 +70,9 @@ class TeamGameActivity(GameActivity[PlayerT, TeamT]): # On the first game, show the controls UI momentarily. # (unless we're being run in co-op mode, in which case we leave # it up to them) - if not isinstance(self.session, CoopSession): + if not isinstance(self.session, CoopSession) and getattr( + self, 'show_controls_guide', True + ): attrname = '_have_shown_ctrl_help_overlay' if not getattr(self.session, attrname, False): delay = 4.0 @@ -83,6 +88,7 @@ class TeamGameActivity(GameActivity[PlayerT, TeamT]): ).autoretain() setattr(self.session, attrname, True) + @override def on_begin(self) -> None: super().on_begin() try: @@ -102,6 +108,7 @@ class TeamGameActivity(GameActivity[PlayerT, TeamT]): except Exception: logging.exception('Error in on_begin.') + @override def spawn_player_spaz( self, player: PlayerT, diff --git a/dist/ba_data/python/bascenev1lib/activity/coopjoin.py b/dist/ba_data/python/bascenev1lib/activity/coopjoin.py index 0777e8f..55f3fd8 100644 --- a/dist/ba_data/python/bascenev1lib/activity/coopjoin.py +++ b/dist/ba_data/python/bascenev1lib/activity/coopjoin.py @@ -4,6 +4,7 @@ from __future__ import annotations +from typing_extensions import override import bascenev1 as bs @@ -18,6 +19,7 @@ class CoopJoinActivity(bs.JoinActivity): session = self.session assert isinstance(session, bs.CoopSession) + @override def on_transition_in(self) -> None: from bascenev1lib.actor.controlsguide import ControlsGuide from bascenev1lib.actor.text import Text diff --git a/dist/ba_data/python/bascenev1lib/activity/coopscore.py b/dist/ba_data/python/bascenev1lib/activity/coopscore.py index 8b335a1..a88c418 100644 --- a/dist/ba_data/python/bascenev1lib/activity/coopscore.py +++ b/dist/ba_data/python/bascenev1lib/activity/coopscore.py @@ -9,6 +9,7 @@ import random import logging from typing import TYPE_CHECKING +from typing_extensions import override from bacommon.login import LoginType import bascenev1 as bs import bauiv1 as bui @@ -186,6 +187,7 @@ class CoopScoreScreen(bs.Activity[bs.Player, bs.Team]): self._victory: bool = settings['outcome'] == 'victory' + @override def __del__(self) -> None: super().__del__() @@ -194,6 +196,7 @@ class CoopScoreScreen(bs.Activity[bs.Player, bs.Team]): with bui.ContextRef.empty(): bui.containerwidget(edit=self._root_ui, transition='out_left') + @override def on_transition_in(self) -> None: from bascenev1lib.actor import background # FIXME NO BSSTD @@ -574,6 +577,7 @@ class CoopScoreScreen(bs.Activity[bs.Player, bs.Team]): self._player_press, ) + @override def on_player_join(self, player: bs.Player) -> None: super().on_player_join(player) @@ -585,6 +589,7 @@ class CoopScoreScreen(bs.Activity[bs.Player, bs.Team]): bs.timer(time_till_assign, bs.WeakCall(self._safe_assign, player)) + @override def on_begin(self) -> None: # FIXME: Clean this up. # pylint: disable=too-many-statements diff --git a/dist/ba_data/python/bascenev1lib/activity/drawscore.py b/dist/ba_data/python/bascenev1lib/activity/drawscore.py index 2d260df..d6e1b28 100644 --- a/dist/ba_data/python/bascenev1lib/activity/drawscore.py +++ b/dist/ba_data/python/bascenev1lib/activity/drawscore.py @@ -4,7 +4,9 @@ from __future__ import annotations +from typing_extensions import override import bascenev1 as bs + from bascenev1lib.activity.multiteamscore import MultiTeamScoreScreenActivity from bascenev1lib.actor.zoomtext import ZoomText @@ -14,6 +16,7 @@ class DrawScoreScreenActivity(MultiTeamScoreScreenActivity): default_music = None # Awkward silence... + @override def on_begin(self) -> None: bs.set_analytics_screen('Draw Score Screen') super().on_begin() diff --git a/dist/ba_data/python/bascenev1lib/activity/dualteamscore.py b/dist/ba_data/python/bascenev1lib/activity/dualteamscore.py index c369e17..2df370b 100644 --- a/dist/ba_data/python/bascenev1lib/activity/dualteamscore.py +++ b/dist/ba_data/python/bascenev1lib/activity/dualteamscore.py @@ -4,7 +4,9 @@ from __future__ import annotations +from typing_extensions import override import bascenev1 as bs + from bascenev1lib.activity.multiteamscore import MultiTeamScoreScreenActivity from bascenev1lib.actor.zoomtext import ZoomText @@ -17,6 +19,7 @@ class TeamVictoryScoreScreenActivity(MultiTeamScoreScreenActivity): self._winner: bs.SessionTeam = settings['winner'] assert isinstance(self._winner, bs.SessionTeam) + @override def on_begin(self) -> None: bs.set_analytics_screen('Teams Score Screen') super().on_begin() diff --git a/dist/ba_data/python/bascenev1lib/activity/freeforallvictory.py b/dist/ba_data/python/bascenev1lib/activity/freeforallvictory.py index 45d116a..cf82d4a 100644 --- a/dist/ba_data/python/bascenev1lib/activity/freeforallvictory.py +++ b/dist/ba_data/python/bascenev1lib/activity/freeforallvictory.py @@ -6,9 +6,11 @@ from __future__ import annotations from typing import TYPE_CHECKING -from bascenev1lib.activity.multiteamscore import MultiTeamScoreScreenActivity +from typing_extensions import override import bascenev1 as bs +from bascenev1lib.activity.multiteamscore import MultiTeamScoreScreenActivity + if TYPE_CHECKING: from typing import Any @@ -23,6 +25,7 @@ class FreeForAllVictoryScoreScreenActivity(MultiTeamScoreScreenActivity): self.transition_time = 0.5 self._cymbal_sound = bs.getsound('cymbal') + @override def on_begin(self) -> None: # pylint: disable=too-many-locals # pylint: disable=too-many-statements diff --git a/dist/ba_data/python/bascenev1lib/activity/multiteamjoin.py b/dist/ba_data/python/bascenev1lib/activity/multiteamjoin.py index 2870c1f..ff8f74b 100644 --- a/dist/ba_data/python/bascenev1lib/activity/multiteamjoin.py +++ b/dist/ba_data/python/bascenev1lib/activity/multiteamjoin.py @@ -4,7 +4,9 @@ from __future__ import annotations +from typing_extensions import override import bascenev1 as bs + from bascenev1lib.actor.text import Text @@ -15,6 +17,7 @@ class MultiTeamJoinActivity(bs.JoinActivity): super().__init__(settings) self._next_up_text: Text | None = None + @override def on_transition_in(self) -> None: from bascenev1lib.actor.controlsguide import ControlsGuide diff --git a/dist/ba_data/python/bascenev1lib/activity/multiteamscore.py b/dist/ba_data/python/bascenev1lib/activity/multiteamscore.py index ef2ba01..7b75401 100644 --- a/dist/ba_data/python/bascenev1lib/activity/multiteamscore.py +++ b/dist/ba_data/python/bascenev1lib/activity/multiteamscore.py @@ -3,7 +3,9 @@ """Functionality related to teams mode score screen.""" from __future__ import annotations +from typing_extensions import override import bascenev1 as bs + from bascenev1lib.actor.text import Text from bascenev1lib.actor.image import Image @@ -18,6 +20,7 @@ class MultiTeamScoreScreenActivity(bs.ScoreScreenActivity): self._show_up_next: bool = True + @override def on_begin(self) -> None: super().on_begin() session = self.session diff --git a/dist/ba_data/python/bascenev1lib/activity/multiteamvictory.py b/dist/ba_data/python/bascenev1lib/activity/multiteamvictory.py index 203f051..afc2a26 100644 --- a/dist/ba_data/python/bascenev1lib/activity/multiteamvictory.py +++ b/dist/ba_data/python/bascenev1lib/activity/multiteamvictory.py @@ -4,7 +4,9 @@ from __future__ import annotations +from typing_extensions import override import bascenev1 as bs + from bascenev1lib.activity.multiteamscore import MultiTeamScoreScreenActivity @@ -22,6 +24,7 @@ class TeamSeriesVictoryScoreScreenActivity(MultiTeamScoreScreenActivity): self._tips_text = None self._default_show_tips = False + @override def on_begin(self) -> None: # pylint: disable=too-many-branches # pylint: disable=too-many-locals diff --git a/dist/ba_data/python/bascenev1lib/actor/background.py b/dist/ba_data/python/bascenev1lib/actor/background.py index 2ad629a..d5625a2 100644 --- a/dist/ba_data/python/bascenev1lib/actor/background.py +++ b/dist/ba_data/python/bascenev1lib/actor/background.py @@ -9,6 +9,7 @@ import weakref import logging from typing import TYPE_CHECKING +from typing_extensions import override import bascenev1 as bs if TYPE_CHECKING: @@ -104,6 +105,7 @@ class Background(bs.Actor): timeval += random.random() * 0.1 bs.animate(cmb, 'input1', keys, loop=True) + @override def __del__(self) -> None: # Normal actors don't get sent DieMessages when their # activity is shutting down, but we still need to do so @@ -138,6 +140,7 @@ class Background(bs.Actor): ) bs.timer(self.fade_time + 0.1, self.node.delete) + @override def handlemessage(self, msg: Any) -> Any: assert not self.expired if isinstance(msg, bs.DieMessage): diff --git a/dist/ba_data/python/bascenev1lib/actor/bomb.py b/dist/ba_data/python/bascenev1lib/actor/bomb.py index 23d588d..058057d 100644 --- a/dist/ba_data/python/bascenev1lib/actor/bomb.py +++ b/dist/ba_data/python/bascenev1lib/actor/bomb.py @@ -10,7 +10,9 @@ from __future__ import annotations import random from typing import TYPE_CHECKING, TypeVar +from typing_extensions import override import bascenev1 as bs + from bascenev1lib.gameutils import SharedObjects if TYPE_CHECKING: @@ -661,6 +663,7 @@ class Blast(bs.Actor): bs.timer(0.4, _extra_debris_sound) + @override def handlemessage(self, msg: Any) -> Any: assert not self.expired @@ -935,6 +938,7 @@ class Bomb(bs.Actor): else None ) + @override def on_expire(self) -> None: super().on_expire() @@ -1140,19 +1144,18 @@ class Bomb(bs.Actor): if msg.srcnode: pass + @override def handlemessage(self, msg: Any) -> Any: if isinstance(msg, ExplodeMessage): self.explode() elif isinstance(msg, ImpactMessage): self._handle_impact() - # Ok the logic below looks like it was backwards to me. - # Disabling for now; can bring back if need be. - # elif isinstance(msg, bs.PickedUpMessage): - # # Change our source to whoever just picked us up *only* if it - # # is None. This way we can get points for killing bots with their - # # own bombs. Hmm would there be a downside to this? - # if self._source_player is not None: - # self._source_player = msg.node.source_player + elif isinstance(msg, bs.PickedUpMessage): + # Change our source to whoever just picked us up *only* if it + # is None. This way we can get points for killing bots with their + # own bombs. Hmm would there be a downside to this? + if self._source_player is None: + self._source_player = msg.node.source_player elif isinstance(msg, SplatMessage): self._handle_splat() elif isinstance(msg, bs.DroppedMessage): diff --git a/dist/ba_data/python/bascenev1lib/actor/controlsguide.py b/dist/ba_data/python/bascenev1lib/actor/controlsguide.py index 23c4a97..f3068f0 100644 --- a/dist/ba_data/python/bascenev1lib/actor/controlsguide.py +++ b/dist/ba_data/python/bascenev1lib/actor/controlsguide.py @@ -6,6 +6,7 @@ from __future__ import annotations from typing import TYPE_CHECKING +from typing_extensions import override import bascenev1 as bs if TYPE_CHECKING: @@ -547,9 +548,11 @@ class ControlsGuide(bs.Actor): self._update_timer = None self._dead = True + @override def exists(self) -> bool: return not self._dead + @override def handlemessage(self, msg: Any) -> Any: assert not self.expired if isinstance(msg, bs.DieMessage): diff --git a/dist/ba_data/python/bascenev1lib/actor/flag.py b/dist/ba_data/python/bascenev1lib/actor/flag.py index 7c8f536..cd24c9e 100644 --- a/dist/ba_data/python/bascenev1lib/actor/flag.py +++ b/dist/ba_data/python/bascenev1lib/actor/flag.py @@ -7,9 +7,11 @@ from __future__ import annotations from dataclasses import dataclass from typing import TYPE_CHECKING -from bascenev1lib.gameutils import SharedObjects +from typing_extensions import override import bascenev1 as bs +from bascenev1lib.gameutils import SharedObjects + if TYPE_CHECKING: from typing import Any, Sequence @@ -328,6 +330,7 @@ class Flag(bs.Actor): 1.0, bs.WeakCall(self._hide_score_text) ) + @override def handlemessage(self, msg: Any) -> Any: assert not self.expired if isinstance(msg, bs.DieMessage): diff --git a/dist/ba_data/python/bascenev1lib/actor/image.py b/dist/ba_data/python/bascenev1lib/actor/image.py index 4fc61bf..00e71d0 100644 --- a/dist/ba_data/python/bascenev1lib/actor/image.py +++ b/dist/ba_data/python/bascenev1lib/actor/image.py @@ -7,6 +7,7 @@ from __future__ import annotations from enum import Enum from typing import TYPE_CHECKING +from typing_extensions import override import bascenev1 as bs if TYPE_CHECKING: @@ -165,6 +166,7 @@ class Image(bs.Actor): bs.WeakCall(self.handlemessage, bs.DieMessage()), ) + @override def handlemessage(self, msg: Any) -> Any: assert not self.expired if isinstance(msg, bs.DieMessage): diff --git a/dist/ba_data/python/bascenev1lib/actor/onscreencountdown.py b/dist/ba_data/python/bascenev1lib/actor/onscreencountdown.py index 7c1a904..68c477b 100644 --- a/dist/ba_data/python/bascenev1lib/actor/onscreencountdown.py +++ b/dist/ba_data/python/bascenev1lib/actor/onscreencountdown.py @@ -6,6 +6,7 @@ from __future__ import annotations from typing import TYPE_CHECKING +from typing_extensions import override import bascenev1 as bs if TYPE_CHECKING: @@ -72,6 +73,7 @@ class OnScreenCountdown(bs.Actor): ) self._timer = bs.Timer(1.0, self._update, repeat=True) + @override def on_expire(self) -> None: super().on_expire() diff --git a/dist/ba_data/python/bascenev1lib/actor/onscreentimer.py b/dist/ba_data/python/bascenev1lib/actor/onscreentimer.py index 6f3b8df..192fecc 100644 --- a/dist/ba_data/python/bascenev1lib/actor/onscreentimer.py +++ b/dist/ba_data/python/bascenev1lib/actor/onscreentimer.py @@ -6,6 +6,7 @@ from __future__ import annotations from typing import TYPE_CHECKING import logging +from typing_extensions import override import bascenev1 as bs if TYPE_CHECKING: @@ -93,6 +94,7 @@ class OnScreenTimer(bs.Actor): """Shortcut for start time in seconds.""" return self.getstarttime() + @override def handlemessage(self, msg: Any) -> Any: # if we're asked to die, just kill our node/timer if isinstance(msg, bs.DieMessage): diff --git a/dist/ba_data/python/bascenev1lib/actor/playerspaz.py b/dist/ba_data/python/bascenev1lib/actor/playerspaz.py index 4902e57..2ea3ead 100644 --- a/dist/ba_data/python/bascenev1lib/actor/playerspaz.py +++ b/dist/ba_data/python/bascenev1lib/actor/playerspaz.py @@ -6,7 +6,9 @@ from __future__ import annotations from typing import TYPE_CHECKING, TypeVar, overload +from typing_extensions import override import bascenev1 as bs + from bascenev1lib.actor.spaz import Spaz if TYPE_CHECKING: @@ -183,6 +185,7 @@ class PlayerSpaz(Spaz): ' non-connected player' ) + @override def handlemessage(self, msg: Any) -> Any: # FIXME: Tidy this up. # pylint: disable=too-many-branches diff --git a/dist/ba_data/python/bascenev1lib/actor/popuptext.py b/dist/ba_data/python/bascenev1lib/actor/popuptext.py index c5c1dcc..6fdd41d 100644 --- a/dist/ba_data/python/bascenev1lib/actor/popuptext.py +++ b/dist/ba_data/python/bascenev1lib/actor/popuptext.py @@ -7,6 +7,7 @@ from __future__ import annotations import random from typing import TYPE_CHECKING +from typing_extensions import override import bascenev1 as bs if TYPE_CHECKING: @@ -118,6 +119,7 @@ class PopupText(bs.Actor): lifespan, bs.WeakCall(self.handlemessage, bs.DieMessage()) ) + @override def handlemessage(self, msg: Any) -> Any: assert not self.expired if isinstance(msg, bs.DieMessage): diff --git a/dist/ba_data/python/bascenev1lib/actor/powerupbox.py b/dist/ba_data/python/bascenev1lib/actor/powerupbox.py index 893cb2d..1b95e4d 100644 --- a/dist/ba_data/python/bascenev1lib/actor/powerupbox.py +++ b/dist/ba_data/python/bascenev1lib/actor/powerupbox.py @@ -7,7 +7,9 @@ from __future__ import annotations import random from typing import TYPE_CHECKING +from typing_extensions import override import bascenev1 as bs + from bascenev1lib.gameutils import SharedObjects if TYPE_CHECKING: @@ -278,6 +280,7 @@ class PowerupBox(bs.Actor): if self.node: self.node.flashing = True + @override def handlemessage(self, msg: Any) -> Any: assert not self.expired diff --git a/dist/ba_data/python/bascenev1lib/actor/spaz.py b/dist/ba_data/python/bascenev1lib/actor/spaz.py index 71a31a4..ad914b8 100644 --- a/dist/ba_data/python/bascenev1lib/actor/spaz.py +++ b/dist/ba_data/python/bascenev1lib/actor/spaz.py @@ -9,11 +9,13 @@ import random import logging from typing import TYPE_CHECKING +from typing_extensions import override +import bascenev1 as bs + from bascenev1lib.actor.bomb import Bomb, Blast from bascenev1lib.actor.powerupbox import PowerupBoxFactory from bascenev1lib.actor.spazfactory import SpazFactory from bascenev1lib.gameutils import SharedObjects -import bascenev1 as bs if TYPE_CHECKING: from typing import Any, Sequence, Callable @@ -228,9 +230,11 @@ class Spaz(bs.Actor): self.punch_callback: Callable[[Spaz], Any] | None = None self.pick_up_powerup_callback: Callable[[Spaz], Any] | None = None + @override def exists(self) -> bool: return bool(self.node) + @override def on_expire(self) -> None: super().on_expire() @@ -249,6 +253,7 @@ class Spaz(bs.Actor): assert not self.expired self._dropped_bomb_callbacks.append(call) + @override def is_alive(self) -> bool: """ Method override; returns whether ol' spaz is still kickin'. @@ -694,6 +699,7 @@ class Spaz(bs.Actor): else: self.shield_decay_timer = None + @override def handlemessage(self, msg: Any) -> Any: # pylint: disable=too-many-return-statements # pylint: disable=too-many-statements diff --git a/dist/ba_data/python/bascenev1lib/actor/spazbot.py b/dist/ba_data/python/bascenev1lib/actor/spazbot.py index 75d0eeb..bdd8c27 100644 --- a/dist/ba_data/python/bascenev1lib/actor/spazbot.py +++ b/dist/ba_data/python/bascenev1lib/actor/spazbot.py @@ -10,6 +10,7 @@ import weakref import logging from typing import TYPE_CHECKING +from typing_extensions import override import bascenev1 as bs from bascenev1lib.actor.spaz import Spaz @@ -489,6 +490,7 @@ class SpazBot(Spaz): self.on_punch_press() self.on_punch_release() + @override def on_punched(self, damage: int) -> None: """ Method override; sends bs.SpazBotPunchedMessage @@ -496,6 +498,7 @@ class SpazBot(Spaz): """ bs.getactivity().handlemessage(SpazBotPunchedMessage(self, damage)) + @override def on_expire(self) -> None: super().on_expire() @@ -503,6 +506,7 @@ class SpazBot(Spaz): # no chance of them keeping activities or other things alive. self.update_callback = None + @override def handlemessage(self, msg: Any) -> Any: # pylint: disable=too-many-branches assert not self.expired diff --git a/dist/ba_data/python/bascenev1lib/actor/text.py b/dist/ba_data/python/bascenev1lib/actor/text.py index 1a274d6..1569851 100644 --- a/dist/ba_data/python/bascenev1lib/actor/text.py +++ b/dist/ba_data/python/bascenev1lib/actor/text.py @@ -7,6 +7,7 @@ from __future__ import annotations from enum import Enum from typing import TYPE_CHECKING +from typing_extensions import override import bascenev1 as bs if TYPE_CHECKING: @@ -221,6 +222,7 @@ class Text(bs.Actor): bs.WeakCall(self.handlemessage, bs.DieMessage()), ) + @override def handlemessage(self, msg: Any) -> Any: assert not self.expired if isinstance(msg, bs.DieMessage): diff --git a/dist/ba_data/python/bascenev1lib/actor/tipstext.py b/dist/ba_data/python/bascenev1lib/actor/tipstext.py index ede0080..7a6335f 100644 --- a/dist/ba_data/python/bascenev1lib/actor/tipstext.py +++ b/dist/ba_data/python/bascenev1lib/actor/tipstext.py @@ -6,6 +6,7 @@ from __future__ import annotations from typing import TYPE_CHECKING +from typing_extensions import override import bascenev1 as bs if TYPE_CHECKING: @@ -95,6 +96,7 @@ class TipsText(bs.Actor): ) self.node.text = next_tip + @override def handlemessage(self, msg: Any) -> Any: assert not self.expired if isinstance(msg, bs.DieMessage): diff --git a/dist/ba_data/python/bascenev1lib/actor/zoomtext.py b/dist/ba_data/python/bascenev1lib/actor/zoomtext.py index e50a16a..12cdb75 100644 --- a/dist/ba_data/python/bascenev1lib/actor/zoomtext.py +++ b/dist/ba_data/python/bascenev1lib/actor/zoomtext.py @@ -8,6 +8,7 @@ import random import logging from typing import TYPE_CHECKING +from typing_extensions import override import bascenev1 as bs if TYPE_CHECKING: @@ -158,6 +159,7 @@ class ZoomText(bs.Actor): if lifespan is not None: bs.timer(lifespan, bs.WeakCall(self.handlemessage, bs.DieMessage())) + @override def handlemessage(self, msg: Any) -> Any: assert not self.expired if isinstance(msg, bs.DieMessage): diff --git a/dist/ba_data/python/bascenev1lib/game/assault.py b/dist/ba_data/python/bascenev1lib/game/assault.py index 183a017..12eb122 100644 --- a/dist/ba_data/python/bascenev1lib/game/assault.py +++ b/dist/ba_data/python/bascenev1lib/game/assault.py @@ -10,11 +10,13 @@ from __future__ import annotations import random from typing import TYPE_CHECKING +from typing_extensions import override +import bascenev1 as bs + from bascenev1lib.actor.playerspaz import PlayerSpaz from bascenev1lib.actor.flag import Flag from bascenev1lib.actor.scoreboard import Scoreboard from bascenev1lib.gameutils import SharedObjects -import bascenev1 as bs if TYPE_CHECKING: from typing import Any, Sequence @@ -71,10 +73,12 @@ class AssaultGame(bs.TeamGameActivity[Player, Team]): bs.BoolSetting('Epic Mode', default=False), ] + @override @classmethod def supports_session_type(cls, sessiontype: type[bs.Session]) -> bool: return issubclass(sessiontype, bs.DualTeamSession) + @override @classmethod def get_supported_maps(cls, sessiontype: type[bs.Session]) -> list[str]: assert bs.app.classic is not None @@ -96,16 +100,19 @@ class AssaultGame(bs.TeamGameActivity[Player, Team]): bs.MusicType.EPIC if self._epic_mode else bs.MusicType.FORWARD_MARCH ) + @override def get_instance_description(self) -> str | Sequence: if self._score_to_win == 1: return 'Touch the enemy flag.' return 'Touch the enemy flag ${ARG1} times.', self._score_to_win + @override def get_instance_description_short(self) -> str | Sequence: if self._score_to_win == 1: return 'touch 1 flag' return 'touch ${ARG1} flags', self._score_to_win + @override def create_team(self, sessionteam: bs.SessionTeam) -> Team: shared = SharedObjects.get() base_pos = self.map.get_flag_position(sessionteam.id) @@ -151,16 +158,19 @@ class AssaultGame(bs.TeamGameActivity[Player, Team]): return team + @override def on_team_join(self, team: Team) -> None: # Can't do this in create_team because the team's color/etc. have # not been wired up yet at that point. 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() + @override def handlemessage(self, msg: Any) -> Any: if isinstance(msg, bs.PlayerDiedMessage): super().handlemessage(msg) # Augment standard. @@ -249,6 +259,7 @@ class AssaultGame(bs.TeamGameActivity[Player, Team]): if player_team.score >= self._score_to_win: self.end_game() + @override def end_game(self) -> None: results = bs.GameResults() for team in self.teams: diff --git a/dist/ba_data/python/bascenev1lib/game/capturetheflag.py b/dist/ba_data/python/bascenev1lib/game/capturetheflag.py index 4e5da2d..837c3be 100644 --- a/dist/ba_data/python/bascenev1lib/game/capturetheflag.py +++ b/dist/ba_data/python/bascenev1lib/game/capturetheflag.py @@ -10,6 +10,9 @@ from __future__ import annotations import logging from typing import TYPE_CHECKING +from typing_extensions import override +import bascenev1 as bs + from bascenev1lib.actor.playerspaz import PlayerSpaz from bascenev1lib.actor.scoreboard import Scoreboard from bascenev1lib.actor.flag import ( @@ -19,7 +22,6 @@ from bascenev1lib.actor.flag import ( FlagDroppedMessage, FlagDiedMessage, ) -import bascenev1 as bs if TYPE_CHECKING: from typing import Any, Sequence @@ -141,10 +143,12 @@ class CaptureTheFlagGame(bs.TeamGameActivity[Player, Team]): bs.BoolSetting('Epic Mode', default=False), ] + @override @classmethod def supports_session_type(cls, sessiontype: type[bs.Session]) -> bool: return issubclass(sessiontype, bs.DualTeamSession) + @override @classmethod def get_supported_maps(cls, sessiontype: type[bs.Session]) -> list[str]: assert bs.app.classic is not None @@ -173,16 +177,19 @@ class CaptureTheFlagGame(bs.TeamGameActivity[Player, Team]): bs.MusicType.EPIC if self._epic_mode else bs.MusicType.FLAG_CATCHER ) + @override def get_instance_description(self) -> str | Sequence: if self._score_to_win == 1: return 'Steal the enemy flag.' return 'Steal the enemy flag ${ARG1} times.', self._score_to_win + @override def get_instance_description_short(self) -> str | Sequence: if self._score_to_win == 1: return 'return 1 flag' return 'return ${ARG1} flags', self._score_to_win + @override def create_team(self, sessionteam: bs.SessionTeam) -> Team: # Create our team instance and its initial values. @@ -272,12 +279,14 @@ class CaptureTheFlagGame(bs.TeamGameActivity[Player, Team]): return team + @override def on_team_join(self, team: Team) -> None: # Can't do this in create_team because the team's color/etc. have # not been wired up yet at that point. self._spawn_flag_for_team(team) self._update_scoreboard() + @override def on_begin(self) -> None: super().on_begin() self.setup_standard_time_limit(self._time_limit) @@ -406,6 +415,7 @@ class CaptureTheFlagGame(bs.TeamGameActivity[Player, Team]): if team.score >= self._score_to_win: self.end_game() + @override def end_game(self) -> None: results = bs.GameResults() for team in self.teams: @@ -532,6 +542,7 @@ class CaptureTheFlagGame(bs.TeamGameActivity[Player, Team]): bs.animate(light, 'intensity', {0.0: 0, 0.25: 2.0, 0.5: 0}, loop=True) bs.timer(length, light.delete) + @override def spawn_player_spaz( self, player: Player, @@ -576,6 +587,7 @@ class CaptureTheFlagGame(bs.TeamGameActivity[Player, Team]): team, team.score, self._score_to_win ) + @override def handlemessage(self, msg: Any) -> Any: if isinstance(msg, bs.PlayerDiedMessage): super().handlemessage(msg) # Augment standard behavior. diff --git a/dist/ba_data/python/bascenev1lib/game/chosenone.py b/dist/ba_data/python/bascenev1lib/game/chosenone.py index 8157619..aba373d 100644 --- a/dist/ba_data/python/bascenev1lib/game/chosenone.py +++ b/dist/ba_data/python/bascenev1lib/game/chosenone.py @@ -10,11 +10,13 @@ from __future__ import annotations import logging from typing import TYPE_CHECKING +from typing_extensions import override +import bascenev1 as bs + from bascenev1lib.actor.flag import Flag from bascenev1lib.actor.playerspaz import PlayerSpaz from bascenev1lib.actor.scoreboard import Scoreboard from bascenev1lib.gameutils import SharedObjects -import bascenev1 as bs if TYPE_CHECKING: from typing import Any, Sequence @@ -83,6 +85,7 @@ class ChosenOneGame(bs.TeamGameActivity[Player, Team]): ] scoreconfig = bs.ScoreConfig(label='Time Held') + @override @classmethod def get_supported_maps(cls, sessiontype: type[bs.Session]) -> list[str]: assert bs.app.classic is not None @@ -121,20 +124,25 @@ class ChosenOneGame(bs.TeamGameActivity[Player, Team]): bs.MusicType.EPIC if self._epic_mode else bs.MusicType.CHOSEN_ONE ) + @override def get_instance_description(self) -> str | Sequence: return 'There can be only one.' + @override def create_team(self, sessionteam: bs.SessionTeam) -> Team: return Team(time_remaining=self._chosen_one_time) + @override def on_team_join(self, team: Team) -> None: self._update_scoreboard() + @override def on_player_leave(self, player: Player) -> None: super().on_player_leave(player) if self._get_chosen_one_player() is player: self._set_chosen_one_player(None) + @override def on_begin(self) -> None: super().on_begin() shared = SharedObjects.get() @@ -251,6 +259,7 @@ class ChosenOneGame(bs.TeamGameActivity[Player, Team]): logging.error('got nonexistent player as chosen one in _tick') self._set_chosen_one_player(None) + @override def end_game(self) -> None: results = bs.GameResults() for team in self.teams: @@ -335,6 +344,7 @@ class ChosenOneGame(bs.TeamGameActivity[Player, Team]): 'position', light.node, 'position' ) + @override def handlemessage(self, msg: Any) -> Any: if isinstance(msg, bs.PlayerDiedMessage): # Augment standard behavior. diff --git a/dist/ba_data/python/bascenev1lib/game/conquest.py b/dist/ba_data/python/bascenev1lib/game/conquest.py index 8b1589c..d50041f 100644 --- a/dist/ba_data/python/bascenev1lib/game/conquest.py +++ b/dist/ba_data/python/bascenev1lib/game/conquest.py @@ -10,12 +10,14 @@ from __future__ import annotations import random from typing import TYPE_CHECKING +from typing_extensions import override +import bascenev1 as bs + from bascenev1lib.actor.flag import Flag from bascenev1lib.actor.scoreboard import Scoreboard from bascenev1lib.actor.playerspaz import PlayerSpaz from bascenev1lib.gameutils import SharedObjects from bascenev1lib.actor.respawnicon import RespawnIcon -import bascenev1 as bs if TYPE_CHECKING: from typing import Any, Sequence @@ -108,10 +110,12 @@ class ConquestGame(bs.TeamGameActivity[Player, Team]): bs.BoolSetting('Epic Mode', default=False), ] + @override @classmethod def supports_session_type(cls, sessiontype: type[bs.Session]) -> bool: return issubclass(sessiontype, bs.DualTeamSession) + @override @classmethod def get_supported_maps(cls, sessiontype: type[bs.Session]) -> list[str]: assert bs.app.classic is not None @@ -143,16 +147,20 @@ class ConquestGame(bs.TeamGameActivity[Player, Team]): ), ) + @override def get_instance_description(self) -> str | Sequence: return 'Secure all ${ARG1} flags.', len(self.map.flag_points) + @override def get_instance_description_short(self) -> str | Sequence: return 'secure all ${ARG1} flags', len(self.map.flag_points) + @override def on_team_join(self, team: Team) -> None: if self.has_begun(): self._update_scores() + @override def on_player_join(self, player: Player) -> None: player.respawn_timer = None @@ -160,6 +168,7 @@ class ConquestGame(bs.TeamGameActivity[Player, Team]): if player.team.flags_held > 0: self.spawn_player(player) + @override def on_begin(self) -> None: super().on_begin() self.setup_standard_time_limit(self._time_limit) @@ -221,6 +230,7 @@ class ConquestGame(bs.TeamGameActivity[Player, Team]): team, team.flags_held, len(self._flags) ) + @override def end_game(self) -> None: results = bs.GameResults() for team in self.teams: @@ -272,6 +282,7 @@ class ConquestGame(bs.TeamGameActivity[Player, Team]): ): self.spawn_player(otherplayer) + @override def handlemessage(self, msg: Any) -> Any: if isinstance(msg, bs.PlayerDiedMessage): # Augment standard behavior. @@ -287,6 +298,7 @@ class ConquestGame(bs.TeamGameActivity[Player, Team]): else: super().handlemessage(msg) + @override def spawn_player(self, player: Player) -> bs.Actor: # We spawn players at different places based on what flags are held. return self.spawn_player_spaz( diff --git a/dist/ba_data/python/bascenev1lib/game/deathmatch.py b/dist/ba_data/python/bascenev1lib/game/deathmatch.py index ccbadd8..7721b63 100644 --- a/dist/ba_data/python/bascenev1lib/game/deathmatch.py +++ b/dist/ba_data/python/bascenev1lib/game/deathmatch.py @@ -9,9 +9,11 @@ from __future__ import annotations from typing import TYPE_CHECKING +from typing_extensions import override +import bascenev1 as bs + from bascenev1lib.actor.playerspaz import PlayerSpaz from bascenev1lib.actor.scoreboard import Scoreboard -import bascenev1 as bs if TYPE_CHECKING: from typing import Any, Sequence @@ -38,6 +40,7 @@ class DeathMatchGame(bs.TeamGameActivity[Player, Team]): # Print messages when players die since it matters here. announce_player_deaths = True + @override @classmethod def get_available_settings( cls, sessiontype: type[bs.Session] @@ -87,12 +90,14 @@ class DeathMatchGame(bs.TeamGameActivity[Player, Team]): 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]: assert bs.app.classic is not None @@ -116,16 +121,20 @@ class DeathMatchGame(bs.TeamGameActivity[Player, Team]): bs.MusicType.EPIC if self._epic_mode else bs.MusicType.TO_THE_DEATH ) + @override def get_instance_description(self) -> str | Sequence: return 'Crush ${ARG1} of your enemies.', self._score_to_win + @override def get_instance_description_short(self) -> str | Sequence: return 'kill ${ARG1} enemies', self._score_to_win + @override def on_team_join(self, team: Team) -> None: if self.has_begun(): self._update_scoreboard() + @override def on_begin(self) -> None: super().on_begin() self.setup_standard_time_limit(self._time_limit) @@ -137,6 +146,7 @@ class DeathMatchGame(bs.TeamGameActivity[Player, Team]): ) self._update_scoreboard() + @override def handlemessage(self, msg: Any) -> Any: if isinstance(msg, bs.PlayerDiedMessage): # Augment standard behavior. @@ -197,6 +207,7 @@ class DeathMatchGame(bs.TeamGameActivity[Player, Team]): team, team.score, self._score_to_win ) + @override def end_game(self) -> None: results = bs.GameResults() for team in self.teams: diff --git a/dist/ba_data/python/bascenev1lib/game/easteregghunt.py b/dist/ba_data/python/bascenev1lib/game/easteregghunt.py index 4eb2d5b..e7159f8 100644 --- a/dist/ba_data/python/bascenev1lib/game/easteregghunt.py +++ b/dist/ba_data/python/bascenev1lib/game/easteregghunt.py @@ -10,6 +10,9 @@ from __future__ import annotations import random from typing import TYPE_CHECKING +from typing_extensions import override +import bascenev1 as bs + from bascenev1lib.actor.bomb import Bomb from bascenev1lib.actor.playerspaz import PlayerSpaz from bascenev1lib.actor.spazbot import SpazBotSet, BouncyBot, SpazBotDiedMessage @@ -17,7 +20,6 @@ from bascenev1lib.actor.onscreencountdown import OnScreenCountdown from bascenev1lib.actor.scoreboard import Scoreboard from bascenev1lib.actor.respawnicon import RespawnIcon from bascenev1lib.gameutils import SharedObjects -import bascenev1 as bs if TYPE_CHECKING: from typing import Any @@ -51,11 +53,13 @@ class EasterEggHuntGame(bs.TeamGameActivity[Player, Team]): scoreconfig = bs.ScoreConfig(label='Score', scoretype=bs.ScoreType.POINTS) # We're currently hard-coded for one map. + @override @classmethod def get_supported_maps(cls, sessiontype: type[bs.Session]) -> list[str]: return ['Tower D'] # We support teams, free-for-all, and co-op sessions. + @override @classmethod def supports_session_type(cls, sessiontype: type[bs.Session]) -> bool: return ( @@ -93,11 +97,13 @@ class EasterEggHuntGame(bs.TeamGameActivity[Player, Team]): bs.MusicType.EPIC if self._epic_mode else bs.MusicType.FORWARD_MARCH ) + @override def on_team_join(self, team: Team) -> None: if self.has_begun(): self._update_scoreboard() # Called when our game actually starts. + @override def on_begin(self) -> None: from bascenev1lib.maps import TowerD @@ -118,6 +124,7 @@ class EasterEggHuntGame(bs.TeamGameActivity[Player, Team]): self._spawn_evil_bunny() # Overriding the default character spawning. + @override def spawn_player(self, player: Player) -> bs.Actor: spaz = self.spawn_player_spaz(player) spaz.connect_controls_to_player() @@ -191,6 +198,7 @@ class EasterEggHuntGame(bs.TeamGameActivity[Player, Team]): self._eggs.append(Egg(position=(xpos, ypos, zpos))) # Various high-level game events come through this method. + @override def handlemessage(self, msg: Any) -> Any: # Respawn dead players. if isinstance(msg, bs.PlayerDiedMessage): @@ -231,6 +239,7 @@ class EasterEggHuntGame(bs.TeamGameActivity[Player, Team]): for team in self.teams: self._scoreboard.set_team_value(team, team.score) + @override def end_game(self) -> None: results = bs.GameResults() for team in self.teams: @@ -271,9 +280,11 @@ class Egg(bs.Actor): }, ) + @override def exists(self) -> bool: return bool(self.node) + @override def handlemessage(self, msg: Any) -> Any: if isinstance(msg, bs.DieMessage): if self.node: diff --git a/dist/ba_data/python/bascenev1lib/game/elimination.py b/dist/ba_data/python/bascenev1lib/game/elimination.py index 8244a6f..5628fae 100644 --- a/dist/ba_data/python/bascenev1lib/game/elimination.py +++ b/dist/ba_data/python/bascenev1lib/game/elimination.py @@ -10,9 +10,11 @@ from __future__ import annotations import logging from typing import TYPE_CHECKING +from typing_extensions import override +import bascenev1 as bs + from bascenev1lib.actor.spazfactory import SpazFactory from bascenev1lib.actor.scoreboard import Scoreboard -import bascenev1 as bs if TYPE_CHECKING: from typing import Any, Sequence @@ -157,6 +159,7 @@ class Icon(bs.Actor): if lives == 0: bs.timer(0.6, self.update_for_lives) + @override def handlemessage(self, msg: Any) -> Any: if isinstance(msg, bs.DieMessage): self.node.delete() @@ -194,6 +197,7 @@ class EliminationGame(bs.TeamGameActivity[Player, Team]): allow_mid_activity_joins = False + @override @classmethod def get_available_settings( cls, sessiontype: type[bs.Session] @@ -238,12 +242,14 @@ class EliminationGame(bs.TeamGameActivity[Player, Team]): ) 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]: assert bs.app.classic is not None @@ -269,6 +275,7 @@ class EliminationGame(bs.TeamGameActivity[Player, Team]): bs.MusicType.EPIC if self._epic_mode else bs.MusicType.SURVIVAL ) + @override def get_instance_description(self) -> str | Sequence: return ( 'Last team standing wins.' @@ -276,6 +283,7 @@ class EliminationGame(bs.TeamGameActivity[Player, Team]): else 'Last one standing wins.' ) + @override def get_instance_description_short(self) -> str | Sequence: return ( 'last team standing wins' @@ -283,6 +291,7 @@ class EliminationGame(bs.TeamGameActivity[Player, Team]): else 'last one standing wins' ) + @override def on_player_join(self, player: Player) -> None: player.lives = self._lives_per_player @@ -299,6 +308,7 @@ class EliminationGame(bs.TeamGameActivity[Player, Team]): if self.has_begun(): self._update_icons() + @override def on_begin(self) -> None: super().on_begin() self._start_time = bs.time() @@ -473,6 +483,7 @@ class EliminationGame(bs.TeamGameActivity[Player, Team]): return points[-1][1] return None + @override def spawn_player(self, player: Player) -> bs.Actor: actor = self.spawn_player_spaz(player, self._get_spawn_point(player)) if not self._solo_mode: @@ -499,6 +510,7 @@ class EliminationGame(bs.TeamGameActivity[Player, Team]): position=player.node.position, ).autoretain() + @override def on_player_leave(self, player: Player) -> None: super().on_player_leave(player) player.icons = [] @@ -522,6 +534,7 @@ class EliminationGame(bs.TeamGameActivity[Player, Team]): def _get_total_team_lives(self, team: Team) -> int: return sum(player.lives for player in team.players) + @override def handlemessage(self, msg: Any) -> Any: if isinstance(msg, bs.PlayerDiedMessage): # Augment standard behavior. @@ -592,6 +605,7 @@ class EliminationGame(bs.TeamGameActivity[Player, Team]): and any(player.lives > 0 for player in team.players) ] + @override def end_game(self) -> None: if self.has_ended(): return diff --git a/dist/ba_data/python/bascenev1lib/game/football.py b/dist/ba_data/python/bascenev1lib/game/football.py index 5a286a6..1c99555 100644 --- a/dist/ba_data/python/bascenev1lib/game/football.py +++ b/dist/ba_data/python/bascenev1lib/game/football.py @@ -1,5 +1,6 @@ # Released under the MIT License. See LICENSE for details. # +# pylint: disable=too-many-lines """Implements football games (both co-op and teams varieties).""" # ba_meta require api 8 @@ -12,6 +13,9 @@ import random import logging from typing import TYPE_CHECKING +from typing_extensions import override +import bascenev1 as bs + from bascenev1lib.actor.bomb import TNTSpawner from bascenev1lib.actor.playerspaz import PlayerSpaz from bascenev1lib.actor.scoreboard import Scoreboard @@ -39,7 +43,6 @@ from bascenev1lib.actor.spazbot import ( StickyBot, ExplodeyBot, ) -import bascenev1 as bs if TYPE_CHECKING: from typing import Any, Sequence @@ -128,11 +131,13 @@ class FootballTeamGame(bs.TeamGameActivity[Player, Team]): bs.BoolSetting('Epic Mode', default=False), ] + @override @classmethod def supports_session_type(cls, sessiontype: type[bs.Session]) -> bool: # We only support two-team play. return issubclass(sessiontype, bs.DualTeamSession) + @override @classmethod def get_supported_maps(cls, sessiontype: type[bs.Session]) -> list[str]: assert bs.app.classic is not None @@ -170,6 +175,7 @@ class FootballTeamGame(bs.TeamGameActivity[Player, Team]): bs.MusicType.EPIC if self._epic_mode else bs.MusicType.FOOTBALL ) + @override def get_instance_description(self) -> str | Sequence: touchdowns = self._score_to_win / 7 @@ -181,6 +187,7 @@ class FootballTeamGame(bs.TeamGameActivity[Player, Team]): return 'Score ${ARG1} touchdowns.', touchdowns return 'Score a touchdown.' + @override def get_instance_description_short(self) -> str | Sequence: touchdowns = self._score_to_win / 7 touchdowns = math.ceil(touchdowns) @@ -188,6 +195,7 @@ class FootballTeamGame(bs.TeamGameActivity[Player, Team]): return 'score ${ARG1} touchdowns', touchdowns return 'score a touchdown' + @override def on_begin(self) -> None: super().on_begin() self.setup_standard_time_limit(self._time_limit) @@ -224,6 +232,7 @@ class FootballTeamGame(bs.TeamGameActivity[Player, Team]): self._update_scoreboard() self._chant_sound.play() + @override def on_team_join(self, team: Team) -> None: self._update_scoreboard() @@ -285,6 +294,7 @@ class FootballTeamGame(bs.TeamGameActivity[Player, Team]): bs.cameraflash(duration=10.0) self._update_scoreboard() + @override def end_game(self) -> None: results = bs.GameResults() for team in self.teams: @@ -298,6 +308,7 @@ class FootballTeamGame(bs.TeamGameActivity[Player, Team]): team, team.score, self._score_to_win ) + @override def handlemessage(self, msg: Any) -> Any: if isinstance(msg, FlagPickedUpMessage): assert isinstance(msg.flag, FootballFlag) @@ -379,9 +390,11 @@ class FootballCoopGame(bs.CoopGameActivity[Player, Team]): default_music = bs.MusicType.FOOTBALL # FIXME: Need to update co-op games to use getscoreconfig. + @override def get_score_type(self) -> str: return 'time' + @override def get_instance_description(self) -> str | Sequence: touchdowns = self._score_to_win / 7 touchdowns = math.ceil(touchdowns) @@ -389,6 +402,7 @@ class FootballCoopGame(bs.CoopGameActivity[Player, Team]): return 'Score ${ARG1} touchdowns.', touchdowns return 'Score a touchdown.' + @override def get_instance_description_short(self) -> str | Sequence: touchdowns = self._score_to_win / 7 touchdowns = math.ceil(touchdowns) @@ -444,6 +458,7 @@ class FootballCoopGame(bs.CoopGameActivity[Player, Team]): self._flag_respawn_light: bs.Actor | None = None self._flag: FootballFlag | None = None + @override def on_transition_in(self) -> None: super().on_transition_in() self._scoreboard = Scoreboard() @@ -480,6 +495,7 @@ class FootballCoopGame(bs.CoopGameActivity[Player, Team]): ) self._chant_sound.play() + @override def on_begin(self) -> None: # FIXME: Split this up a bit. # pylint: disable=too-many-statements @@ -795,11 +811,13 @@ class FootballCoopGame(bs.CoopGameActivity[Player, Team]): if i == 0: bs.cameraflash(duration=10.0) + @override def end_game(self) -> None: bs.setmusic(None) self._bots.final_celebrate() bs.timer(0.001, bs.Call(self.do_end, 'defeat')) + @override def on_continue(self) -> None: # Subtract one touchdown from the bots and get them moving again. assert self._bot_team is not None @@ -897,6 +915,7 @@ class FootballCoopGame(bs.CoopGameActivity[Player, Team]): }, ) + @override def handlemessage(self, msg: Any) -> Any: """handle high-level game messages""" if isinstance(msg, bs.PlayerDiedMessage): @@ -959,6 +978,7 @@ class FootballCoopGame(bs.CoopGameActivity[Player, Team]): del player # Unused. self._player_has_punched = True + @override def spawn_player(self, player: Player) -> bs.Actor: spaz = self.spawn_player_spaz( player, position=self.map.get_start_position(player.team.id) diff --git a/dist/ba_data/python/bascenev1lib/game/hockey.py b/dist/ba_data/python/bascenev1lib/game/hockey.py index b61b7df..64487e5 100644 --- a/dist/ba_data/python/bascenev1lib/game/hockey.py +++ b/dist/ba_data/python/bascenev1lib/game/hockey.py @@ -9,11 +9,13 @@ from __future__ import annotations from typing import TYPE_CHECKING +from typing_extensions import override +import bascenev1 as bs + from bascenev1lib.actor.playerspaz import PlayerSpaz from bascenev1lib.actor.scoreboard import Scoreboard from bascenev1lib.actor.powerupbox import PowerupBoxFactory from bascenev1lib.gameutils import SharedObjects -import bascenev1 as bs if TYPE_CHECKING: from typing import Any, Sequence @@ -58,6 +60,7 @@ class Puck(bs.Actor): ) bs.animate(self.node, 'mesh_scale', {0: 0, 0.2: 1.3, 0.26: 1}) + @override def handlemessage(self, msg: Any) -> Any: if isinstance(msg, bs.DieMessage): if self.node: @@ -152,10 +155,12 @@ class HockeyGame(bs.TeamGameActivity[Player, Team]): bs.BoolSetting('Epic Mode', default=False), ] + @override @classmethod def supports_session_type(cls, sessiontype: type[bs.Session]) -> bool: return issubclass(sessiontype, bs.DualTeamSession) + @override @classmethod def get_supported_maps(cls, sessiontype: type[bs.Session]) -> list[str]: assert bs.app.classic is not None @@ -231,16 +236,19 @@ class HockeyGame(bs.TeamGameActivity[Player, Team]): bs.MusicType.EPIC if self._epic_mode else bs.MusicType.HOCKEY ) + @override def get_instance_description(self) -> str | Sequence: if self._score_to_win == 1: return 'Score a goal.' return 'Score ${ARG1} goals.', self._score_to_win + @override def get_instance_description_short(self) -> str | Sequence: if self._score_to_win == 1: return 'score a goal' return 'score ${ARG1} goals', self._score_to_win + @override def on_begin(self) -> None: super().on_begin() @@ -281,6 +289,7 @@ class HockeyGame(bs.TeamGameActivity[Player, Team]): self._update_scoreboard() self._chant_sound.play() + @override def on_team_join(self, team: Team) -> None: self._update_scoreboard() @@ -364,6 +373,7 @@ class HockeyGame(bs.TeamGameActivity[Player, Team]): bs.cameraflash(duration=10.0) self._update_scoreboard() + @override def end_game(self) -> None: results = bs.GameResults() for team in self.teams: @@ -375,6 +385,7 @@ class HockeyGame(bs.TeamGameActivity[Player, Team]): for team in self.teams: self._scoreboard.set_team_value(team, team.score, winscore) + @override def handlemessage(self, msg: Any) -> Any: # Respawn dead players if they're still in the game. if isinstance(msg, bs.PlayerDiedMessage): diff --git a/dist/ba_data/python/bascenev1lib/game/keepaway.py b/dist/ba_data/python/bascenev1lib/game/keepaway.py index bc80b77..416723e 100644 --- a/dist/ba_data/python/bascenev1lib/game/keepaway.py +++ b/dist/ba_data/python/bascenev1lib/game/keepaway.py @@ -11,6 +11,9 @@ import logging from enum import Enum from typing import TYPE_CHECKING +from typing_extensions import override +import bascenev1 as bs + from bascenev1lib.actor.playerspaz import PlayerSpaz from bascenev1lib.actor.scoreboard import Scoreboard from bascenev1lib.actor.flag import ( @@ -19,7 +22,6 @@ from bascenev1lib.actor.flag import ( FlagDiedMessage, FlagPickedUpMessage, ) -import bascenev1 as bs if TYPE_CHECKING: from typing import Any, Sequence @@ -86,12 +88,14 @@ class KeepAwayGame(bs.TeamGameActivity[Player, Team]): ] scoreconfig = bs.ScoreConfig(label='Time Held') + @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]: assert bs.app.classic is not None @@ -129,18 +133,23 @@ class KeepAwayGame(bs.TeamGameActivity[Player, Team]): bs.MusicType.EPIC if self._epic_mode else bs.MusicType.KEEP_AWAY ) + @override def get_instance_description(self) -> str | Sequence: return 'Carry the flag for ${ARG1} seconds.', self._hold_time + @override def get_instance_description_short(self) -> str | Sequence: return 'carry the flag for ${ARG1} seconds', self._hold_time + @override def create_team(self, sessionteam: bs.SessionTeam) -> Team: return Team(timeremaining=self._hold_time) + @override def on_team_join(self, team: Team) -> None: self._update_scoreboard() + @override def on_begin(self) -> None: super().on_begin() self.setup_standard_time_limit(self._time_limit) @@ -181,6 +190,7 @@ class KeepAwayGame(bs.TeamGameActivity[Player, Team]): if scoreteam.timeremaining <= 0: self.end_game() + @override def end_game(self) -> None: results = bs.GameResults() for team in self.teams: @@ -268,6 +278,7 @@ class KeepAwayGame(bs.TeamGameActivity[Player, Team]): team, team.timeremaining, self._hold_time, countdown=True ) + @override def handlemessage(self, msg: Any) -> Any: if isinstance(msg, bs.PlayerDiedMessage): # Augment standard behavior. diff --git a/dist/ba_data/python/bascenev1lib/game/kingofthehill.py b/dist/ba_data/python/bascenev1lib/game/kingofthehill.py index 6492d82..907b434 100644 --- a/dist/ba_data/python/bascenev1lib/game/kingofthehill.py +++ b/dist/ba_data/python/bascenev1lib/game/kingofthehill.py @@ -11,11 +11,13 @@ import weakref from enum import Enum from typing import TYPE_CHECKING +from typing_extensions import override +import bascenev1 as bs + from bascenev1lib.actor.flag import Flag from bascenev1lib.actor.playerspaz import PlayerSpaz from bascenev1lib.actor.scoreboard import Scoreboard from bascenev1lib.gameutils import SharedObjects -import bascenev1 as bs if TYPE_CHECKING: from typing import Any, Sequence @@ -84,10 +86,12 @@ class KingOfTheHillGame(bs.TeamGameActivity[Player, Team]): ] scoreconfig = bs.ScoreConfig(label='Time Held') + @override @classmethod def supports_session_type(cls, sessiontype: type[bs.Session]) -> bool: return issubclass(sessiontype, bs.MultiTeamSession) + @override @classmethod def get_supported_maps(cls, sessiontype: type[bs.Session]) -> list[str]: assert bs.app.classic is not None @@ -144,15 +148,19 @@ class KingOfTheHillGame(bs.TeamGameActivity[Player, Team]): bs.MusicType.EPIC if self._epic_mode else bs.MusicType.SCARY ) + @override def get_instance_description(self) -> str | Sequence: return 'Secure the flag for ${ARG1} seconds.', self._hold_time + @override def get_instance_description_short(self) -> str | Sequence: return 'secure the flag for ${ARG1} seconds', self._hold_time + @override def create_team(self, sessionteam: bs.SessionTeam) -> Team: return Team(time_remaining=self._hold_time) + @override def on_begin(self) -> None: super().on_begin() shared = SharedObjects.get() @@ -223,6 +231,7 @@ class KingOfTheHillGame(bs.TeamGameActivity[Player, Team]): if scoring_team.time_remaining <= 0: self.end_game() + @override def end_game(self) -> None: results = bs.GameResults() for team in self.teams: @@ -283,6 +292,7 @@ class KingOfTheHillGame(bs.TeamGameActivity[Player, Team]): team, team.time_remaining, self._hold_time, countdown=True ) + @override def handlemessage(self, msg: Any) -> Any: if isinstance(msg, bs.PlayerDiedMessage): super().handlemessage(msg) # Augment default. diff --git a/dist/ba_data/python/bascenev1lib/game/meteorshower.py b/dist/ba_data/python/bascenev1lib/game/meteorshower.py index 35e0566..a9a70ae 100644 --- a/dist/ba_data/python/bascenev1lib/game/meteorshower.py +++ b/dist/ba_data/python/bascenev1lib/game/meteorshower.py @@ -10,9 +10,11 @@ from __future__ import annotations import random from typing import TYPE_CHECKING +from typing_extensions import override +import bascenev1 as bs + from bascenev1lib.actor.bomb import Bomb from bascenev1lib.actor.onscreentimer import OnScreenTimer -import bascenev1 as bs if TYPE_CHECKING: from typing import Any, Sequence @@ -49,11 +51,13 @@ class MeteorShowerGame(bs.TeamGameActivity[Player, Team]): allow_mid_activity_joins = False # We're currently hard-coded for one map. + @override @classmethod def get_supported_maps(cls, sessiontype: type[bs.Session]) -> list[str]: return ['Rampage'] # We support teams, free-for-all, and co-op sessions. + @override @classmethod def supports_session_type(cls, sessiontype: type[bs.Session]) -> bool: return ( @@ -77,6 +81,7 @@ class MeteorShowerGame(bs.TeamGameActivity[Player, Team]): if self._epic_mode: self.slow_motion = True + @override def on_begin(self) -> None: super().on_begin() @@ -100,6 +105,7 @@ class MeteorShowerGame(bs.TeamGameActivity[Player, Team]): # Check for immediate end (if we've only got 1 player, etc). bs.timer(5.0, self._check_end_game) + @override def on_player_leave(self, player: Player) -> None: # Augment default behavior. super().on_player_leave(player) @@ -108,6 +114,7 @@ class MeteorShowerGame(bs.TeamGameActivity[Player, Team]): self._check_end_game() # overriding the default character spawning.. + @override def spawn_player(self, player: Player) -> bs.Actor: spaz = self.spawn_player_spaz(player) @@ -122,6 +129,7 @@ class MeteorShowerGame(bs.TeamGameActivity[Player, Team]): return spaz # Various high-level game events come through this method. + @override def handlemessage(self, msg: Any) -> Any: if isinstance(msg, bs.PlayerDiedMessage): # Augment standard behavior. @@ -213,6 +221,7 @@ class MeteorShowerGame(bs.TeamGameActivity[Player, Team]): def _decrement_meteor_time(self) -> None: self._meteor_time = max(0.01, self._meteor_time * 0.9) + @override def end_game(self) -> None: cur_time = bs.time() assert self._timer is not None diff --git a/dist/ba_data/python/bascenev1lib/game/ninjafight.py b/dist/ba_data/python/bascenev1lib/game/ninjafight.py index 501e920..98ca8d0 100644 --- a/dist/ba_data/python/bascenev1lib/game/ninjafight.py +++ b/dist/ba_data/python/bascenev1lib/game/ninjafight.py @@ -10,13 +10,15 @@ from __future__ import annotations import random from typing import TYPE_CHECKING +from typing_extensions import override +import bascenev1 as bs + from bascenev1lib.actor.spazbot import ( SpazBotSet, ChargerBot, SpazBotDiedMessage, ) from bascenev1lib.actor.onscreentimer import OnScreenTimer -import bascenev1 as bs if TYPE_CHECKING: from typing import Any @@ -44,6 +46,7 @@ class NinjaFightGame(bs.TeamGameActivity[Player, Team]): ) default_music = bs.MusicType.TO_THE_DEATH + @override @classmethod def get_supported_maps(cls, sessiontype: type[bs.Session]) -> list[str]: # For now we're hard-coding spawn positions and whatnot @@ -51,6 +54,7 @@ class NinjaFightGame(bs.TeamGameActivity[Player, Team]): # a specific map. return ['Courtyard'] + @override @classmethod def supports_session_type(cls, sessiontype: type[bs.Session]) -> bool: # We currently support Co-Op only. @@ -67,6 +71,7 @@ class NinjaFightGame(bs.TeamGameActivity[Player, Team]): self._preset = str(settings['preset']) # Called when our game actually begins. + @override def on_begin(self) -> None: super().on_begin() is_pro = self._preset == 'pro' @@ -123,6 +128,7 @@ class NinjaFightGame(bs.TeamGameActivity[Player, Team]): ) # Called for each spawning player. + @override def spawn_player(self, player: Player) -> bs.Actor: # Let's spawn close to the center. spawn_center = (0, 3, -2) @@ -144,6 +150,7 @@ class NinjaFightGame(bs.TeamGameActivity[Player, Team]): self.end_game() # Called for miscellaneous messages. + @override def handlemessage(self, msg: Any) -> Any: # A player has died. if isinstance(msg, bs.PlayerDiedMessage): @@ -166,6 +173,7 @@ class NinjaFightGame(bs.TeamGameActivity[Player, Team]): # When this is called, we should fill out results and end the game # *regardless* of whether is has been won. (this may be called due # to a tournament ending or other external reason). + @override def end_game(self) -> None: # Stop our on-screen timer so players can see what they got. assert self._timer is not None diff --git a/dist/ba_data/python/bascenev1lib/game/onslaught.py b/dist/ba_data/python/bascenev1lib/game/onslaught.py index 2e8f98e..d464413 100644 --- a/dist/ba_data/python/bascenev1lib/game/onslaught.py +++ b/dist/ba_data/python/bascenev1lib/game/onslaught.py @@ -17,6 +17,9 @@ from enum import Enum, unique from dataclasses import dataclass from typing import TYPE_CHECKING +from typing_extensions import override +import bascenev1 as bs + from bascenev1lib.actor.popuptext import PopupText from bascenev1lib.actor.bomb import TNTSpawner from bascenev1lib.actor.playerspaz import PlayerSpazHurtMessage @@ -45,7 +48,6 @@ from bascenev1lib.actor.spazbot import ( BrawlerBotPro, BomberBotProShielded, ) -import bascenev1 as bs if TYPE_CHECKING: from typing import Any, Sequence @@ -222,6 +224,7 @@ class OnslaughtGame(bs.CoopGameActivity[Player, Team]): self._land_mine_kills = 0 self._tnt_kills = 0 + @override def on_transition_in(self) -> None: super().on_transition_in() customdata = bs.getsession().customdata @@ -286,6 +289,7 @@ class OnslaughtGame(bs.CoopGameActivity[Player, Team]): label=bs.Lstr(resource='scoreText'), score_split=0.5 ) + @override def on_begin(self) -> None: super().on_begin() player_count = len(self.players) @@ -825,6 +829,7 @@ class OnslaughtGame(bs.CoopGameActivity[Player, Team]): break entry_count += 1 + @override def spawn_player(self, player: Player) -> bs.Actor: # We keep track of who got hurt each wave for score purposes. player.has_been_hurt = False @@ -1414,6 +1419,7 @@ class OnslaughtGame(bs.CoopGameActivity[Player, Team]): assert self._scoreboard is not None self._scoreboard.set_team_value(self.teams[0], score, max_score=None) + @override def handlemessage(self, msg: Any) -> Any: if isinstance(msg, PlayerSpazHurtMessage): msg.spaz.getplayer(Player, True).has_been_hurt = True @@ -1526,6 +1532,7 @@ class OnslaughtGame(bs.CoopGameActivity[Player, Team]): def _set_can_end_wave(self) -> None: self._can_end_wave = True + @override def end_game(self) -> None: # Tell our bots to celebrate just to rub it in. assert self._bots is not None @@ -1534,6 +1541,7 @@ class OnslaughtGame(bs.CoopGameActivity[Player, Team]): self.do_end('defeat', delay=2.0) bs.setmusic(None) + @override def on_continue(self) -> None: for player in self.players: if not player.is_alive(): diff --git a/dist/ba_data/python/bascenev1lib/game/race.py b/dist/ba_data/python/bascenev1lib/game/race.py index 2dd20f7..4a35fa4 100644 --- a/dist/ba_data/python/bascenev1lib/game/race.py +++ b/dist/ba_data/python/bascenev1lib/game/race.py @@ -12,11 +12,13 @@ import logging from typing import TYPE_CHECKING from dataclasses import dataclass +from typing_extensions import override +import bascenev1 as bs + from bascenev1lib.actor.bomb import Bomb from bascenev1lib.actor.playerspaz import PlayerSpaz from bascenev1lib.actor.scoreboard import Scoreboard from bascenev1lib.gameutils import SharedObjects -import bascenev1 as bs if TYPE_CHECKING: from typing import Any, Sequence @@ -84,6 +86,7 @@ class RaceGame(bs.TeamGameActivity[Player, Team]): label='Time', lower_is_better=True, scoretype=bs.ScoreType.MILLISECONDS ) + @override @classmethod def get_available_settings( cls, sessiontype: type[bs.Session] @@ -133,10 +136,12 @@ class RaceGame(bs.TeamGameActivity[Player, Team]): ) return settings + @override @classmethod def supports_session_type(cls, sessiontype: type[bs.Session]) -> bool: return issubclass(sessiontype, bs.MultiTeamSession) + @override @classmethod def get_supported_maps(cls, sessiontype: type[bs.Session]) -> list[str]: assert bs.app.classic is not None @@ -179,6 +184,7 @@ class RaceGame(bs.TeamGameActivity[Player, Team]): bs.MusicType.EPIC_RACE if self._epic_mode else bs.MusicType.RACE ) + @override def get_instance_description(self) -> str | Sequence: if ( isinstance(self.session, bs.DualTeamSession) @@ -192,11 +198,13 @@ class RaceGame(bs.TeamGameActivity[Player, Team]): return 'Run ${ARG1} laps.' + t_str, self._laps return 'Run 1 lap.' + t_str + @override def get_instance_description_short(self) -> str | Sequence: if self._laps > 1: return 'run ${ARG1} laps', self._laps return 'run 1 lap' + @override def on_transition_in(self) -> None: super().on_transition_in() shared = SharedObjects.get() @@ -379,9 +387,11 @@ class RaceGame(bs.TeamGameActivity[Player, Team]): except Exception: logging.exception('Error printing lap.') + @override def on_team_join(self, team: Team) -> None: self._update_scoreboard() + @override def on_player_leave(self, player: Player) -> None: super().on_player_leave(player) @@ -442,6 +452,7 @@ class RaceGame(bs.TeamGameActivity[Player, Team]): show_value=False, ) + @override def on_begin(self) -> None: from bascenev1lib.actor.onscreentimer import OnScreenTimer @@ -670,6 +681,7 @@ class RaceGame(bs.TeamGameActivity[Player, Team]): self._flash_mine(m_index) bs.timer(0.95, bs.Call(self._make_mine, m_index)) + @override def spawn_player(self, player: Player) -> bs.Actor: if player.team.finished: # FIXME: This is not type-safe! @@ -758,6 +770,7 @@ class RaceGame(bs.TeamGameActivity[Player, Team]): self.end_game() return + @override def end_game(self) -> None: # Stop updating our time text, and set it to show the exact last # finish time if we have one. (so users don't get upset if their @@ -787,6 +800,7 @@ class RaceGame(bs.TeamGameActivity[Player, Team]): announce_winning_team=isinstance(self.session, bs.DualTeamSession), ) + @override def handlemessage(self, msg: Any) -> Any: if isinstance(msg, bs.PlayerDiedMessage): # Augment default behavior. diff --git a/dist/ba_data/python/bascenev1lib/game/runaround.py b/dist/ba_data/python/bascenev1lib/game/runaround.py index 6640601..b970d35 100644 --- a/dist/ba_data/python/bascenev1lib/game/runaround.py +++ b/dist/ba_data/python/bascenev1lib/game/runaround.py @@ -16,6 +16,9 @@ from enum import Enum from dataclasses import dataclass from typing import TYPE_CHECKING +from typing_extensions import override +import bascenev1 as bs + from bascenev1lib.actor.popuptext import PopupText from bascenev1lib.actor.bomb import TNTSpawner from bascenev1lib.actor.scoreboard import Scoreboard @@ -40,7 +43,6 @@ from bascenev1lib.actor.spazbot import ( BomberBotPro, BrawlerBotPro, ) -import bascenev1 as bs if TYPE_CHECKING: from typing import Any, Sequence @@ -194,6 +196,7 @@ class RunaroundGame(bs.CoopGameActivity[Player, Team]): self._flawless_bonus: int | None = None self._wave_update_timer: bs.Timer | None = None + @override def on_transition_in(self) -> None: super().on_transition_in() self._scoreboard = Scoreboard( @@ -211,6 +214,7 @@ class RunaroundGame(bs.CoopGameActivity[Player, Team]): ) ) + @override def on_begin(self) -> None: super().on_begin() player_count = len(self.players) @@ -571,6 +575,7 @@ class RunaroundGame(bs.CoopGameActivity[Player, Team]): ), ) + @override def on_continue(self) -> None: self._lives = 3 assert self._lives_text is not None @@ -578,6 +583,7 @@ class RunaroundGame(bs.CoopGameActivity[Player, Team]): self._lives_text.node.text = str(self._lives) self._bots.start_moving() + @override def spawn_player(self, player: Player) -> bs.Actor: pos = ( self._spawn_center[0] + random.uniform(-1.5, 1.5), @@ -654,6 +660,7 @@ class RunaroundGame(bs.CoopGameActivity[Player, Team]): ), ).autoretain() + @override def end_game(self) -> None: bs.pushcall(bs.Call(self.do_end, 'defeat')) bs.setmusic(None) @@ -1286,6 +1293,7 @@ class RunaroundGame(bs.CoopGameActivity[Player, Team]): # Revert to normal bot behavior otherwise.. return False + @override def handlemessage(self, msg: Any) -> Any: if isinstance(msg, bs.PlayerScoredMessage): self._score += msg.score diff --git a/dist/ba_data/python/bascenev1lib/game/targetpractice.py b/dist/ba_data/python/bascenev1lib/game/targetpractice.py index c39f970..fdff48f 100644 --- a/dist/ba_data/python/bascenev1lib/game/targetpractice.py +++ b/dist/ba_data/python/bascenev1lib/game/targetpractice.py @@ -10,11 +10,13 @@ from __future__ import annotations import random from typing import TYPE_CHECKING +from typing_extensions import override +import bascenev1 as bs + from bascenev1lib.actor.scoreboard import Scoreboard from bascenev1lib.actor.onscreencountdown import OnScreenCountdown from bascenev1lib.actor.bomb import Bomb from bascenev1lib.actor.popuptext import PopupText -import bascenev1 as bs if TYPE_CHECKING: from typing import Any, Sequence @@ -49,10 +51,12 @@ class TargetPracticeGame(bs.TeamGameActivity[Player, Team]): ] default_music = bs.MusicType.FORWARD_MARCH + @override @classmethod def get_supported_maps(cls, sessiontype: type[bs.Session]) -> list[str]: return ['Doom Shroom'] + @override @classmethod def supports_session_type(cls, sessiontype: type[bs.Session]) -> bool: # We support any teams or versus sessions. @@ -70,10 +74,12 @@ class TargetPracticeGame(bs.TeamGameActivity[Player, Team]): self._enable_impact_bombs = bool(settings['Enable Impact Bombs']) self._enable_triple_bombs = bool(settings['Enable Triple Bombs']) + @override def on_team_join(self, team: Team) -> None: if self.has_begun(): self.update_scoreboard() + @override def on_begin(self) -> None: super().on_begin() self.update_scoreboard() @@ -86,6 +92,7 @@ class TargetPracticeGame(bs.TeamGameActivity[Player, Team]): self._countdown = OnScreenCountdown(60, endcall=self.end_game) bs.timer(4.0, self._countdown.start) + @override def spawn_player(self, player: Player) -> bs.Actor: spawn_center = (0, 3, -5) pos = ( @@ -169,6 +176,7 @@ class TargetPracticeGame(bs.TeamGameActivity[Player, Team]): # Clear out targets that have died. self._targets = [t for t in self._targets if t] + @override def handlemessage(self, msg: Any) -> Any: # When players die, respawn them. if isinstance(msg, bs.PlayerDiedMessage): @@ -188,6 +196,7 @@ class TargetPracticeGame(bs.TeamGameActivity[Player, Team]): for team in self.teams: self._scoreboard.set_team_value(team, team.score) + @override def end_game(self) -> None: results = bs.GameResults() for team in self.teams: @@ -252,9 +261,11 @@ class Target(bs.Actor): bs.animate_array(loc3, 'size', 1, {0.1: [0.0], 0.3: [self._r3 * 2.0]}) bs.getsound('laserReverse').play() + @override def exists(self) -> bool: return bool(self._nodes) + @override def handlemessage(self, msg: Any) -> Any: if isinstance(msg, bs.DieMessage): for node in self._nodes: diff --git a/dist/ba_data/python/bascenev1lib/game/thelaststand.py b/dist/ba_data/python/bascenev1lib/game/thelaststand.py index 5eb32fa..7752c3f 100644 --- a/dist/ba_data/python/bascenev1lib/game/thelaststand.py +++ b/dist/ba_data/python/bascenev1lib/game/thelaststand.py @@ -9,6 +9,9 @@ import logging from dataclasses import dataclass from typing import TYPE_CHECKING +from typing_extensions import override +import bascenev1 as bs + from bascenev1lib.actor.playerspaz import PlayerSpaz from bascenev1lib.actor.bomb import TNTSpawner from bascenev1lib.actor.scoreboard import Scoreboard @@ -29,7 +32,6 @@ from bascenev1lib.actor.spazbot import ( StickyBot, ExplodeyBot, ) -import bascenev1 as bs if TYPE_CHECKING: from typing import Any, Sequence @@ -109,6 +111,7 @@ class TheLastStandGame(bs.CoopGameActivity[Player, Team]): ExplodeyBot: SpawnInfo(0.05, 0.02, 0.002), } + @override def on_transition_in(self) -> None: super().on_transition_in() bs.timer(1.3, self._new_wave_sound.play) @@ -116,6 +119,7 @@ class TheLastStandGame(bs.CoopGameActivity[Player, Team]): label=bs.Lstr(resource='scoreText'), score_split=0.5 ) + @override def on_begin(self) -> None: super().on_begin() @@ -129,6 +133,7 @@ class TheLastStandGame(bs.CoopGameActivity[Player, Team]): position=self._tntspawnpos, respawn_time=10.0 ) + @override def spawn_player(self, player: Player) -> bs.Actor: pos = ( self._spawn_center[0] + random.uniform(-1.5, 1.5), @@ -290,6 +295,7 @@ class TheLastStandGame(bs.CoopGameActivity[Player, Team]): assert self._scoreboard is not None self._scoreboard.set_team_value(self.teams[0], score, max_score=None) + @override def handlemessage(self, msg: Any) -> Any: if isinstance(msg, bs.PlayerDiedMessage): player = msg.getplayer(Player) @@ -327,6 +333,7 @@ class TheLastStandGame(bs.CoopGameActivity[Player, Team]): else: super().handlemessage(msg) + @override def end_game(self) -> None: # Tell our bots to celebrate just to rub it in. self._bots.final_celebrate() diff --git a/dist/ba_data/python/bascenev1lib/mainmenu.py b/dist/ba_data/python/bascenev1lib/mainmenu.py index 6092f0c..8041176 100644 --- a/dist/ba_data/python/bascenev1lib/mainmenu.py +++ b/dist/ba_data/python/bascenev1lib/mainmenu.py @@ -10,6 +10,7 @@ import random import weakref from typing import TYPE_CHECKING +from typing_extensions import override import bascenev1 as bs import bauiv1 as bui @@ -42,7 +43,9 @@ class MainMenuActivity(bs.Activity[bs.Player, bs.Team]): self._language: str | None = None self._update_timer: bs.Timer | None = None self._news: NewsDisplay | None = None + self._attract_mode_timer: bs.Timer | None = None + @override def on_transition_in(self) -> None: # pylint: disable=too-many-locals # pylint: disable=too-many-statements @@ -83,7 +86,7 @@ class MainMenuActivity(bs.Activity[bs.Player, bs.Team]): 'scale': scale, 'position': (0, 10), 'vr_depth': -10, - 'text': '\xa9 2011-2023 Eric Froemling', + 'text': '\xa9 2011-2024 Eric Froemling', }, ) ) @@ -295,6 +298,10 @@ class MainMenuActivity(bs.Activity[bs.Player, bs.Team]): if not (env.demo or env.arcade) and not app.ui_v1.use_toolbars: self._news = NewsDisplay(self) + self._attract_mode_timer = bs.Timer( + 3.12, self._update_attract_mode, repeat=True + ) + # Bring up the last place we were, or start at the main menu otherwise. with bs.ContextRef.empty(): from bauiv1lib import specialoffer @@ -387,7 +394,7 @@ class MainMenuActivity(bs.Activity[bs.Player, bs.Team]): bs.app.ui_v1.set_main_menu_window( MainMenuWindow(transition=None).get_root_widget(), - from_window=None, + from_window=False, # Disable check here. ) # attempt to show any pending offers immediately. @@ -403,6 +410,7 @@ class MainMenuActivity(bs.Activity[bs.Player, bs.Team]): bui.apptimer(2.0, specialoffer.show_offer) bui.apptimer(2.0, try_again) + app.classic.main_menu_did_initial_transition = True def _update(self) -> None: @@ -836,6 +844,26 @@ class MainMenuActivity(bs.Activity[bs.Player, bs.Team]): bui.apptimer(0.5, _start_menu_music) + def _update_attract_mode(self) -> None: + if bui.app.classic is None: + return + + if not bui.app.config.resolve('Show Demos When Idle'): + return + + threshold = 20.0 + + # If we're idle *and* have been in this activity for that long, + # flip over to our cpu demo. + if bui.get_input_idle_time() > threshold and bs.time() > threshold: + bui.app.classic.run_stress_test( + playlist_type='Random', + playlist_name='__default__', + player_count=8, + round_duration=20, + attract_mode=True, + ) + class NewsDisplay: """Wrangles news display.""" @@ -1113,6 +1141,7 @@ class MainMenuSession(bs.Session): self._locked = False self.setactivity(bs.newactivity(MainMenuActivity)) + @override def on_activity_end(self, activity: bs.Activity, results: Any) -> None: if self._locked: bui.unlock_all_input() @@ -1120,6 +1149,7 @@ class MainMenuSession(bs.Session): # Any ending activity leads us into the main menu one. self.setactivity(bs.newactivity(MainMenuActivity)) + @override def on_player_request(self, player: bs.SessionPlayer) -> bool: # Reject all player requests. return False diff --git a/dist/ba_data/python/bascenev1lib/maps.py b/dist/ba_data/python/bascenev1lib/maps.py index 26349f5..d473114 100644 --- a/dist/ba_data/python/bascenev1lib/maps.py +++ b/dist/ba_data/python/bascenev1lib/maps.py @@ -7,7 +7,9 @@ from __future__ import annotations from typing import TYPE_CHECKING +from typing_extensions import override import bascenev1 as bs + from bascenev1lib.gameutils import SharedObjects if TYPE_CHECKING: @@ -22,15 +24,18 @@ class HockeyStadium(bs.Map): name = 'Hockey Stadium' + @override @classmethod def get_play_types(cls) -> list[str]: """Return valid play types for this map.""" return ['melee', 'hockey', 'team_flag', 'keep_away'] + @override @classmethod def get_preview_texture_name(cls) -> str: return 'hockeyStadiumPreview' + @override @classmethod def on_preload(cls) -> Any: data: dict[str, Any] = { @@ -114,15 +119,18 @@ class FootballStadium(bs.Map): name = 'Football Stadium' + @override @classmethod def get_play_types(cls) -> list[str]: """Return valid play types for this map.""" return ['melee', 'football', 'team_flag', 'keep_away'] + @override @classmethod def get_preview_texture_name(cls) -> str: return 'footballStadiumPreview' + @override @classmethod def on_preload(cls) -> Any: data: dict[str, Any] = { @@ -164,6 +172,7 @@ class FootballStadium(bs.Map): gnode.vr_camera_offset = (0, -0.8, -1.1) gnode.vr_near_clip = 0.5 + @override def is_point_near_edge(self, point: bs.Vec3, running: bool = False) -> bool: box_position = self.defs.boxes['edge_box'][0:3] box_scale = self.defs.boxes['edge_box'][6:9] @@ -181,16 +190,19 @@ class Bridgit(bs.Map): name = 'Bridgit' dataname = 'bridgit' + @override @classmethod def get_play_types(cls) -> list[str]: """Return valid play types for this map.""" # print('getting playtypes', cls._getdata()['play_types']) return ['melee', 'team_flag', 'keep_away'] + @override @classmethod def get_preview_texture_name(cls) -> str: return 'bridgitPreview' + @override @classmethod def on_preload(cls) -> Any: data: dict[str, Any] = { @@ -286,6 +298,7 @@ class BigG(bs.Map): name = 'Big G' + @override @classmethod def get_play_types(cls) -> list[str]: """Return valid play types for this map.""" @@ -298,10 +311,12 @@ class BigG(bs.Map): 'conquest', ] + @override @classmethod def get_preview_texture_name(cls) -> str: return 'bigGPreview' + @override @classmethod def on_preload(cls) -> Any: data: dict[str, Any] = { @@ -397,15 +412,18 @@ class Roundabout(bs.Map): name = 'Roundabout' + @override @classmethod def get_play_types(cls) -> list[str]: """Return valid play types for this map.""" return ['melee', 'keep_away', 'team_flag'] + @override @classmethod def get_preview_texture_name(cls) -> str: return 'roundaboutPreview' + @override @classmethod def on_preload(cls) -> Any: data: dict[str, Any] = { @@ -502,15 +520,18 @@ class MonkeyFace(bs.Map): name = 'Monkey Face' + @override @classmethod def get_play_types(cls) -> list[str]: """Return valid play types for this map.""" return ['melee', 'keep_away', 'team_flag'] + @override @classmethod def get_preview_texture_name(cls) -> str: return 'monkeyFacePreview' + @override @classmethod def on_preload(cls) -> Any: data: dict[str, Any] = { @@ -607,6 +628,7 @@ class ZigZag(bs.Map): name = 'Zigzag' + @override @classmethod def get_play_types(cls) -> list[str]: """Return valid play types for this map.""" @@ -618,10 +640,12 @@ class ZigZag(bs.Map): 'king_of_the_hill', ] + @override @classmethod def get_preview_texture_name(cls) -> str: return 'zigzagPreview' + @override @classmethod def on_preload(cls) -> Any: data: dict[str, Any] = { @@ -715,15 +739,18 @@ class ThePad(bs.Map): name = 'The Pad' + @override @classmethod def get_play_types(cls) -> list[str]: """Return valid play types for this map.""" return ['melee', 'keep_away', 'team_flag', 'king_of_the_hill'] + @override @classmethod def get_preview_texture_name(cls) -> str: return 'thePadPreview' + @override @classmethod def on_preload(cls) -> Any: data: dict[str, Any] = { @@ -804,15 +831,18 @@ class DoomShroom(bs.Map): name = 'Doom Shroom' + @override @classmethod def get_play_types(cls) -> list[str]: """Return valid play types for this map.""" return ['melee', 'keep_away', 'team_flag'] + @override @classmethod def get_preview_texture_name(cls) -> str: return 'doomShroomPreview' + @override @classmethod def on_preload(cls) -> Any: data: dict[str, Any] = { @@ -881,6 +911,7 @@ class DoomShroom(bs.Map): gnode.vignette_outer = (0.76, 0.76, 0.76) gnode.vignette_inner = (0.95, 0.95, 0.99) + @override def is_point_near_edge(self, point: bs.Vec3, running: bool = False) -> bool: xpos = point.x zpos = point.z @@ -900,15 +931,18 @@ class LakeFrigid(bs.Map): name = 'Lake Frigid' + @override @classmethod def get_play_types(cls) -> list[str]: """Return valid play types for this map.""" return ['melee', 'keep_away', 'team_flag', 'race'] + @override @classmethod def get_preview_texture_name(cls) -> str: return 'lakeFrigidPreview' + @override @classmethod def on_preload(cls) -> Any: data: dict[str, Any] = { @@ -987,15 +1021,18 @@ class TipTop(bs.Map): name = 'Tip Top' + @override @classmethod def get_play_types(cls) -> list[str]: """Return valid play types for this map.""" return ['melee', 'keep_away', 'team_flag', 'king_of_the_hill'] + @override @classmethod def get_preview_texture_name(cls) -> str: return 'tipTopPreview' + @override @classmethod def on_preload(cls) -> Any: data: dict[str, Any] = { @@ -1065,15 +1102,18 @@ class CragCastle(bs.Map): name = 'Crag Castle' + @override @classmethod def get_play_types(cls) -> list[str]: """Return valid play types for this map.""" return ['melee', 'keep_away', 'team_flag', 'conquest'] + @override @classmethod def get_preview_texture_name(cls) -> str: return 'cragCastlePreview' + @override @classmethod def on_preload(cls) -> Any: data: dict[str, Any] = { @@ -1158,15 +1198,18 @@ class TowerD(bs.Map): name = 'Tower D' + @override @classmethod def get_play_types(cls) -> list[str]: """Return valid play types for this map.""" return [] + @override @classmethod def get_preview_texture_name(cls) -> str: return 'towerDPreview' + @override @classmethod def on_preload(cls) -> Any: data: dict[str, Any] = { @@ -1256,6 +1299,7 @@ class TowerD(bs.Map): gnode.vignette_outer = (0.7, 0.73, 0.7) gnode.vignette_inner = (0.95, 0.95, 0.95) + @override def is_point_near_edge(self, point: bs.Vec3, running: bool = False) -> bool: # see if we're within edge_box boxes = self.defs.boxes @@ -1281,6 +1325,7 @@ class HappyThoughts(bs.Map): name = 'Happy Thoughts' + @override @classmethod def get_play_types(cls) -> list[str]: """Return valid play types for this map.""" @@ -1292,10 +1337,12 @@ class HappyThoughts(bs.Map): 'king_of_the_hill', ] + @override @classmethod def get_preview_texture_name(cls) -> str: return 'alwaysLandPreview' + @override @classmethod def on_preload(cls) -> Any: data: dict[str, Any] = { @@ -1310,6 +1357,7 @@ class HappyThoughts(bs.Map): } return data + @override @classmethod def get_music_type(cls) -> bs.MusicType: return bs.MusicType.FLYING @@ -1397,15 +1445,18 @@ class StepRightUp(bs.Map): name = 'Step Right Up' + @override @classmethod def get_play_types(cls) -> list[str]: """Return valid play types for this map.""" return ['melee', 'keep_away', 'team_flag', 'conquest'] + @override @classmethod def get_preview_texture_name(cls) -> str: return 'stepRightUpPreview' + @override @classmethod def on_preload(cls) -> Any: data: dict[str, Any] = { @@ -1477,15 +1528,18 @@ class Courtyard(bs.Map): name = 'Courtyard' + @override @classmethod def get_play_types(cls) -> list[str]: """Return valid play types for this map.""" return ['melee', 'keep_away', 'team_flag'] + @override @classmethod def get_preview_texture_name(cls) -> str: return 'courtyardPreview' + @override @classmethod def on_preload(cls) -> Any: data: dict[str, Any] = { @@ -1576,6 +1630,7 @@ class Courtyard(bs.Map): gnode.vignette_outer = (0.6, 0.6, 0.64) gnode.vignette_inner = (0.95, 0.95, 0.93) + @override def is_point_near_edge(self, point: bs.Vec3, running: bool = False) -> bool: # count anything off our ground level as safe (for our platforms) # see if we're within edge_box @@ -1593,15 +1648,18 @@ class Rampage(bs.Map): name = 'Rampage' + @override @classmethod def get_play_types(cls) -> list[str]: """Return valid play types for this map.""" return ['melee', 'keep_away', 'team_flag'] + @override @classmethod def get_preview_texture_name(cls) -> str: return 'rampagePreview' + @override @classmethod def on_preload(cls) -> Any: data: dict[str, Any] = { @@ -1681,6 +1739,7 @@ class Rampage(bs.Map): gnode.vignette_outer = (0.62, 0.64, 0.69) gnode.vignette_inner = (0.97, 0.95, 0.93) + @override def is_point_near_edge(self, point: bs.Vec3, running: bool = False) -> bool: box_position = self.defs.boxes['edge_box'][0:3] box_scale = self.defs.boxes['edge_box'][6:9] diff --git a/dist/ba_data/python/bascenev1lib/tutorial.py b/dist/ba_data/python/bascenev1lib/tutorial.py index e51c4a8..86aa641 100644 --- a/dist/ba_data/python/bascenev1lib/tutorial.py +++ b/dist/ba_data/python/bascenev1lib/tutorial.py @@ -19,6 +19,7 @@ import logging from collections import deque from typing import TYPE_CHECKING +from typing_extensions import override import bascenev1 as bs from bascenev1lib.actor.spaz import Spaz @@ -234,11 +235,13 @@ class TutorialActivity(bs.Activity[Player, Team]): self._read_entries_timer: bs.Timer | None = None self._entry_timer: bs.Timer | None = None + @override def on_transition_in(self) -> None: super().on_transition_in() bs.setmusic(bs.MusicType.CHAR_SELECT, continuous=True) self.map = self._map_type() + @override def on_begin(self) -> None: super().on_begin() @@ -2513,6 +2516,7 @@ class TutorialActivity(bs.Activity[Player, Team]): self._skip_text.color = (1, 1, 1) self._issued_warning = False + @override def on_player_join(self, player: Player) -> None: super().on_player_join(player) @@ -2527,6 +2531,7 @@ class TutorialActivity(bs.Activity[Player, Team]): bs.Call(self._player_pressed_button, player), ) + @override def on_player_leave(self, player: Player) -> None: if not all(self.players): logging.error( diff --git a/dist/ba_data/python/bauiv1/__init__.py b/dist/ba_data/python/bauiv1/__init__.py index cd49fca..4f633f3 100644 --- a/dist/ba_data/python/bauiv1/__init__.py +++ b/dist/ba_data/python/bauiv1/__init__.py @@ -47,6 +47,7 @@ from babase import ( do_once, fade_screen, get_display_resolution, + get_input_idle_time, get_ip_address_type, get_low_level_config_value, get_max_graphics_quality, @@ -156,6 +157,7 @@ __all__ = [ 'do_once', 'fade_screen', 'get_display_resolution', + 'get_input_idle_time', 'get_ip_address_type', 'get_low_level_config_value', 'get_max_graphics_quality', diff --git a/dist/ba_data/python/bauiv1/_subsystem.py b/dist/ba_data/python/bauiv1/_subsystem.py index 3c6b1e7..34d7f5a 100644 --- a/dist/ba_data/python/bauiv1/_subsystem.py +++ b/dist/ba_data/python/bauiv1/_subsystem.py @@ -8,7 +8,9 @@ import logging import inspect from typing import TYPE_CHECKING +from typing_extensions import override import babase + import _bauiv1 if TYPE_CHECKING: @@ -82,6 +84,7 @@ class UIV1Subsystem(babase.AppSubsystem): """Current ui scale for the app.""" return self._uiscale + @override def on_app_loading(self) -> None: from bauiv1._uitypes import UIController, ui_upkeep diff --git a/dist/ba_data/python/bauiv1/_uitypes.py b/dist/ba_data/python/bauiv1/_uitypes.py index 5cbd14e..2f93f22 100644 --- a/dist/ba_data/python/bauiv1/_uitypes.py +++ b/dist/ba_data/python/bauiv1/_uitypes.py @@ -9,6 +9,7 @@ import weakref from dataclasses import dataclass from typing import TYPE_CHECKING +from typing_extensions import override import babase import _bauiv1 @@ -264,12 +265,14 @@ class TextWidgetStringEditAdapter(babase.StringEditAdapter): description, initial_text, max_length, screen_space_center ) + @override def _do_apply(self, new_text: str) -> None: if self.widget: _bauiv1.textwidget( edit=self.widget, text=new_text, adapter_finished=True ) + @override def _do_cancel(self) -> None: if self.widget: _bauiv1.textwidget(edit=self.widget, adapter_finished=True) diff --git a/dist/ba_data/python/bauiv1lib/account/viewer.py b/dist/ba_data/python/bauiv1lib/account/viewer.py index 914bd93..9046a80 100644 --- a/dist/ba_data/python/bauiv1lib/account/viewer.py +++ b/dist/ba_data/python/bauiv1lib/account/viewer.py @@ -7,9 +7,11 @@ from __future__ import annotations from typing import TYPE_CHECKING import logging -from bauiv1lib.popup import PopupWindow, PopupMenuWindow +from typing_extensions import override import bauiv1 as bui +from bauiv1lib.popup import PopupWindow, PopupMenuWindow + if TYPE_CHECKING: from typing import Any @@ -596,6 +598,7 @@ class AccountViewerWindow(PopupWindow): self._transitioning_out = True bui.containerwidget(edit=self.root_widget, transition='out_scale') + @override def on_popup_cancel(self) -> None: bui.getsound('swish').play() self._transition_out() diff --git a/dist/ba_data/python/bauiv1lib/achievements.py b/dist/ba_data/python/bauiv1lib/achievements.py index 1c8cc67..df2345d 100644 --- a/dist/ba_data/python/bauiv1lib/achievements.py +++ b/dist/ba_data/python/bauiv1lib/achievements.py @@ -4,6 +4,8 @@ from __future__ import annotations +from typing_extensions import override + from bauiv1lib.popup import PopupWindow import bauiv1 as bui @@ -229,6 +231,7 @@ class AchievementsWindow(PopupWindow): self._transitioning_out = True bui.containerwidget(edit=self.root_widget, transition='out_scale') + @override def on_popup_cancel(self) -> None: bui.getsound('swish').play() self._transition_out() diff --git a/dist/ba_data/python/bauiv1lib/characterpicker.py b/dist/ba_data/python/bauiv1lib/characterpicker.py index b4bdd63..92194e2 100644 --- a/dist/ba_data/python/bauiv1lib/characterpicker.py +++ b/dist/ba_data/python/bauiv1lib/characterpicker.py @@ -7,6 +7,8 @@ from __future__ import annotations import math from typing import TYPE_CHECKING +from typing_extensions import override + from bauiv1lib.popup import PopupWindow import bauiv1 as bui @@ -208,6 +210,7 @@ class CharacterPicker(PopupWindow): self._transitioning_out = True bui.containerwidget(edit=self.root_widget, transition='out_scale') + @override def on_popup_cancel(self) -> None: bui.getsound('swish').play() self._transition_out() diff --git a/dist/ba_data/python/bauiv1lib/colorpicker.py b/dist/ba_data/python/bauiv1lib/colorpicker.py index 904c9e3..3088743 100644 --- a/dist/ba_data/python/bauiv1lib/colorpicker.py +++ b/dist/ba_data/python/bauiv1lib/colorpicker.py @@ -6,6 +6,8 @@ from __future__ import annotations from typing import TYPE_CHECKING +from typing_extensions import override + from bauiv1lib.popup import PopupWindow import bauiv1 as bui @@ -170,6 +172,7 @@ class ColorPicker(PopupWindow): self._delegate.color_picker_closing(self) bui.containerwidget(edit=self.root_widget, transition='out_scale') + @override def on_popup_cancel(self) -> None: if not self._transitioning_out: bui.getsound('swish').play() @@ -338,6 +341,7 @@ class ColorPickerExact(PopupWindow): self._delegate.color_picker_closing(self) bui.containerwidget(edit=self.root_widget, transition='out_scale') + @override def on_popup_cancel(self) -> None: if not self._transitioning_out: bui.getsound('swish').play() diff --git a/dist/ba_data/python/bauiv1lib/fileselector.py b/dist/ba_data/python/bauiv1lib/fileselector.py index 1726311..e9cf0ca 100644 --- a/dist/ba_data/python/bauiv1lib/fileselector.py +++ b/dist/ba_data/python/bauiv1lib/fileselector.py @@ -10,6 +10,8 @@ import logging from threading import Thread from typing import TYPE_CHECKING +from typing_extensions import override + import bauiv1 as bui if TYPE_CHECKING: @@ -204,6 +206,7 @@ class FileSelectorWindow(bui.Window): self._callback = callback self._path = path + @override def run(self) -> None: try: starttime = time.time() diff --git a/dist/ba_data/python/bauiv1lib/gather/abouttab.py b/dist/ba_data/python/bauiv1lib/gather/abouttab.py index b61ed33..1257c4b 100644 --- a/dist/ba_data/python/bauiv1lib/gather/abouttab.py +++ b/dist/ba_data/python/bauiv1lib/gather/abouttab.py @@ -6,6 +6,8 @@ from __future__ import annotations from typing import TYPE_CHECKING +from typing_extensions import override + from bauiv1lib.gather import GatherTab import bauiv1 as bui @@ -16,6 +18,7 @@ if TYPE_CHECKING: class AboutGatherTab(GatherTab): """The about tab in the gather UI""" + @override def on_activate( self, parent_widget: bui.Widget, diff --git a/dist/ba_data/python/bauiv1lib/gather/manualtab.py b/dist/ba_data/python/bauiv1lib/gather/manualtab.py index e09968b..d7f3205 100644 --- a/dist/ba_data/python/bauiv1lib/gather/manualtab.py +++ b/dist/ba_data/python/bauiv1lib/gather/manualtab.py @@ -6,13 +6,13 @@ from __future__ import annotations import logging -from threading import Thread -from typing import TYPE_CHECKING, cast - from enum import Enum +from threading import Thread from dataclasses import dataclass +from typing import TYPE_CHECKING, cast from bauiv1lib.gather import GatherTab +from typing_extensions import override import bauiv1 as bui import bascenev1 as bs @@ -42,6 +42,7 @@ class _HostLookupThread(Thread): self._port = port self._call = call + @override def run(self) -> None: result: str | None try: @@ -101,6 +102,7 @@ class ManualGatherTab(GatherTab): self._party_edit_port_text: bui.Widget | None = None self._no_parties_added_text: bui.Widget | None = None + @override def on_activate( self, parent_widget: bui.Widget, @@ -180,10 +182,12 @@ class ManualGatherTab(GatherTab): return self._container + @override def save_state(self) -> None: assert bui.app.classic is not None bui.app.ui_v1.window_states[type(self)] = State(sub_tab=self._sub_tab) + @override def restore_state(self) -> None: assert bui.app.classic is not None state = bui.app.ui_v1.window_states.get(type(self)) @@ -771,6 +775,7 @@ class ManualGatherTab(GatherTab): text=bui.Lstr(resource='gatherWindow.noPartiesAddedText'), ) + @override def on_deactivate(self) -> None: self._access_check_timer = None diff --git a/dist/ba_data/python/bauiv1lib/gather/nearbytab.py b/dist/ba_data/python/bauiv1lib/gather/nearbytab.py index 146fcc7..7393f6b 100644 --- a/dist/ba_data/python/bauiv1lib/gather/nearbytab.py +++ b/dist/ba_data/python/bauiv1lib/gather/nearbytab.py @@ -7,10 +7,12 @@ from __future__ import annotations import weakref from typing import TYPE_CHECKING -from bauiv1lib.gather import GatherTab +from typing_extensions import override import bauiv1 as bui import bascenev1 as bs +from bauiv1lib.gather import GatherTab + if TYPE_CHECKING: from typing import Any @@ -104,6 +106,7 @@ class NearbyGatherTab(GatherTab): self._net_scanner: NetScanner | None = None self._container: bui.Widget | None = None + @override def on_activate( self, parent_widget: bui.Widget, @@ -156,5 +159,6 @@ class NearbyGatherTab(GatherTab): bui.widget(edit=scrollw, autoselect=True, up_widget=tab_button) return self._container + @override def on_deactivate(self) -> None: self._net_scanner = None diff --git a/dist/ba_data/python/bauiv1lib/gather/privatetab.py b/dist/ba_data/python/bauiv1lib/gather/privatetab.py index e66cd6c..8b6cf7f 100644 --- a/dist/ba_data/python/bauiv1lib/gather/privatetab.py +++ b/dist/ba_data/python/bauiv1lib/gather/privatetab.py @@ -13,6 +13,7 @@ from enum import Enum from dataclasses import dataclass from typing import TYPE_CHECKING, cast +from typing_extensions import override from efro.dataclassio import dataclass_from_dict, dataclass_to_dict from bacommon.net import ( PrivateHostingState, @@ -81,6 +82,7 @@ class PrivateGatherTab(GatherTab): logging.exception('Error building hosting config.') self._hostingconfig = PrivateHostingConfig() + @override def on_activate( self, parent_widget: bui.Widget, @@ -253,6 +255,7 @@ class PrivateGatherTab(GatherTab): return hcfg + @override def on_deactivate(self) -> None: self._update_timer = None @@ -995,10 +998,12 @@ class PrivateGatherTab(GatherTab): self._debug_server_comm('got connect response error') bui.getsound('error').play() + @override def save_state(self) -> None: assert bui.app.classic is not None bui.app.ui_v1.window_states[type(self)] = copy.deepcopy(self._state) + @override def restore_state(self) -> None: assert bui.app.classic is not None state = bui.app.ui_v1.window_states.get(type(self)) diff --git a/dist/ba_data/python/bauiv1lib/gather/publictab.py b/dist/ba_data/python/bauiv1lib/gather/publictab.py index e161943..83b3b53 100644 --- a/dist/ba_data/python/bauiv1lib/gather/publictab.py +++ b/dist/ba_data/python/bauiv1lib/gather/publictab.py @@ -13,6 +13,7 @@ from enum import Enum from dataclasses import dataclass from typing import TYPE_CHECKING, cast +from typing_extensions import override from bauiv1lib.gather import GatherTab import bauiv1 as bui import bascenev1 as bs @@ -247,6 +248,7 @@ class AddrFetchThread(Thread): super().__init__() self._call = call + @override def run(self) -> None: sock: socket.socket | None = None try: @@ -284,6 +286,7 @@ class PingThread(Thread): self._port = port self._call = call + @override def run(self) -> None: assert bui.app.classic is not None bui.app.classic.ping_thread_count += 1 @@ -392,6 +395,7 @@ class PublicGatherTab(GatherTab): self._pending_party_infos: list[dict[str, Any]] = [] self._last_sub_scroll_height = 0.0 + @override def on_activate( self, parent_widget: bui.Widget, @@ -478,9 +482,11 @@ class PublicGatherTab(GatherTab): ) return self._container + @override def on_deactivate(self) -> None: self._update_timer = None + @override def save_state(self) -> None: # Save off a small number of parties with the lowest ping; we'll # display these immediately when our UI comes back up which should @@ -496,6 +502,7 @@ class PublicGatherTab(GatherTab): have_valid_server_list=self._have_valid_server_list, ) + @override def restore_state(self) -> None: assert bui.app.classic is not None state = bui.app.ui_v1.window_states.get(type(self)) diff --git a/dist/ba_data/python/bauiv1lib/getremote.py b/dist/ba_data/python/bauiv1lib/getremote.py index 194725f..0cea25a 100644 --- a/dist/ba_data/python/bauiv1lib/getremote.py +++ b/dist/ba_data/python/bauiv1lib/getremote.py @@ -4,6 +4,8 @@ from __future__ import annotations +from typing_extensions import override + from bauiv1lib.popup import PopupWindow import bauiv1 as bui @@ -77,6 +79,7 @@ class GetBSRemoteWindow(PopupWindow): self._transitioning_out = True bui.containerwidget(edit=self.root_widget, transition='out_scale') + @override def on_popup_cancel(self) -> None: bui.getsound('swish').play() self._transition_out() diff --git a/dist/ba_data/python/bauiv1lib/iconpicker.py b/dist/ba_data/python/bauiv1lib/iconpicker.py index e3cace2..260bbde 100644 --- a/dist/ba_data/python/bauiv1lib/iconpicker.py +++ b/dist/ba_data/python/bauiv1lib/iconpicker.py @@ -7,6 +7,8 @@ from __future__ import annotations import math from typing import TYPE_CHECKING +from typing_extensions import override + from bauiv1lib.popup import PopupWindow import bauiv1 as bui @@ -186,6 +188,7 @@ class IconPicker(PopupWindow): self._transitioning_out = True bui.containerwidget(edit=self.root_widget, transition='out_scale') + @override def on_popup_cancel(self) -> None: bui.getsound('swish').play() self._transition_out() diff --git a/dist/ba_data/python/bauiv1lib/mainmenu.py b/dist/ba_data/python/bauiv1lib/mainmenu.py index 4562a7f..bec93e9 100644 --- a/dist/ba_data/python/bauiv1lib/mainmenu.py +++ b/dist/ba_data/python/bauiv1lib/mainmenu.py @@ -431,13 +431,15 @@ class MainMenuWindow(bui.Window): # media players but this works for now). if bs.is_in_replay(): b_size = 50.0 - b_buffer = 10.0 + b_buffer_1 = 50.0 + b_buffer_2 = 10.0 t_scale = 0.75 assert bui.app.classic is not None uiscale = bui.app.ui_v1.uiscale if uiscale is bui.UIScale.SMALL: b_size *= 0.6 - b_buffer *= 1.0 + b_buffer_1 *= 0.8 + b_buffer_2 *= 1.0 v_offs = -40 t_scale = 0.5 elif uiscale is bui.UIScale.MEDIUM: @@ -467,8 +469,8 @@ class MainMenuWindow(bui.Window): btn = bui.buttonwidget( parent=self._root_widget, position=( - h - b_size - b_buffer, - v - b_size - b_buffer + v_offs, + h - b_size - b_buffer_1, + v - b_size - b_buffer_2 + v_offs, ), button_type='square', size=(b_size, b_size), @@ -481,8 +483,8 @@ class MainMenuWindow(bui.Window): draw_controller=btn, text='-', position=( - h - b_size * 0.5 - b_buffer, - v - b_size * 0.5 - b_buffer + 5 * t_scale + v_offs, + h - b_size * 0.5 - b_buffer_1, + v - b_size * 0.5 - b_buffer_2 + 5 * t_scale + v_offs, ), h_align='center', v_align='center', @@ -491,7 +493,7 @@ class MainMenuWindow(bui.Window): ) btn = bui.buttonwidget( parent=self._root_widget, - position=(h + b_buffer, v - b_size - b_buffer + v_offs), + position=(h + b_buffer_1, v - b_size - b_buffer_2 + v_offs), button_type='square', size=(b_size, b_size), label='', @@ -503,14 +505,27 @@ class MainMenuWindow(bui.Window): draw_controller=btn, text='+', position=( - h + b_size * 0.5 + b_buffer, - v - b_size * 0.5 - b_buffer + 5 * t_scale + v_offs, + h + b_size * 0.5 + b_buffer_1, + v - b_size * 0.5 - b_buffer_2 + 5 * t_scale + v_offs, ), h_align='center', v_align='center', size=(0, 0), scale=3.0 * t_scale, ) + self._pause_resume_button = btn = bui.buttonwidget( + parent=self._root_widget, + position=(h - b_size * 0.5, v - b_size - b_buffer_2 + v_offs), + button_type='square', + size=(b_size, b_size), + label=bui.charstr( + bui.SpecialChar.PLAY_BUTTON + if bs.is_replay_paused() + else bui.SpecialChar.PAUSE_BUTTON + ), + autoselect=True, + on_activate_call=bui.Call(self._pause_or_resume_replay), + ) def _refresh_not_in_game( self, positions: list[tuple[float, float, float]] @@ -1034,6 +1049,20 @@ class MainMenuWindow(bui.Window): ), ) + def _pause_or_resume_replay(self) -> None: + if bs.is_replay_paused(): + bs.resume_replay() + bui.buttonwidget( + edit=self._pause_resume_button, + label=bui.charstr(bui.SpecialChar.PAUSE_BUTTON), + ) + else: + bs.pause_replay() + bui.buttonwidget( + edit=self._pause_resume_button, + label=bui.charstr(bui.SpecialChar.PLAY_BUTTON), + ) + def _quit(self) -> None: # pylint: disable=cyclic-import from bauiv1lib.confirm import QuitWindow @@ -1110,7 +1139,7 @@ class MainMenuWindow(bui.Window): session = bs.get_foreground_host_session() return getattr(session, 'benchmark_type', None) == 'cpu' or ( bui.app.classic is not None - and bui.app.classic.stress_test_reset_timer is not None + and bui.app.classic.stress_test_update_timer is not None ) def _confirm_end_game(self) -> None: diff --git a/dist/ba_data/python/bauiv1lib/playlist/share.py b/dist/ba_data/python/bauiv1lib/playlist/share.py index d52d1e7..480aba5 100644 --- a/dist/ba_data/python/bauiv1lib/playlist/share.py +++ b/dist/ba_data/python/bauiv1lib/playlist/share.py @@ -7,6 +7,8 @@ from __future__ import annotations import time from typing import TYPE_CHECKING +from typing_extensions import override + from bauiv1lib.promocode import PromoCodeWindow import bauiv1 as bui @@ -55,6 +57,7 @@ class SharePlaylistImportWindow(PromoCodeWindow): edit=self._root_widget, transition=self._transition_out ) + @override def _do_enter(self) -> None: plus = bui.app.plus assert plus is not None diff --git a/dist/ba_data/python/bauiv1lib/playoptions.py b/dist/ba_data/python/bauiv1lib/playoptions.py index ea58e4d..b2d42b2 100644 --- a/dist/ba_data/python/bauiv1lib/playoptions.py +++ b/dist/ba_data/python/bauiv1lib/playoptions.py @@ -7,6 +7,7 @@ from __future__ import annotations import logging from typing import TYPE_CHECKING +from typing_extensions import override import bascenev1 as bs import bauiv1 as bui @@ -440,6 +441,7 @@ class PlayOptionsWindow(PopupWindow): self._transitioning_out = True bui.containerwidget(edit=self.root_widget, transition=transition) + @override def on_popup_cancel(self) -> None: bui.getsound('swish').play() self._transition_out() diff --git a/dist/ba_data/python/bauiv1lib/popup.py b/dist/ba_data/python/bauiv1lib/popup.py index 34c591c..b53cfc9 100644 --- a/dist/ba_data/python/bauiv1lib/popup.py +++ b/dist/ba_data/python/bauiv1lib/popup.py @@ -7,6 +7,8 @@ from __future__ import annotations import weakref from typing import TYPE_CHECKING +from typing_extensions import override + import bauiv1 as bui if TYPE_CHECKING: @@ -275,6 +277,7 @@ class PopupMenuWindow(PopupWindow): delegate.popup_menu_closing(self) bui.containerwidget(edit=self.root_widget, transition='out_scale') + @override def on_popup_cancel(self) -> None: if not self._transitioning_out: bui.getsound('swish').play() diff --git a/dist/ba_data/python/bauiv1lib/promocode.py b/dist/ba_data/python/bauiv1lib/promocode.py index 3cf745b..09bcf86 100644 --- a/dist/ba_data/python/bauiv1lib/promocode.py +++ b/dist/ba_data/python/bauiv1lib/promocode.py @@ -5,9 +5,14 @@ from __future__ import annotations import time +import logging +from typing import TYPE_CHECKING import bauiv1 as bui +if TYPE_CHECKING: + from typing import Any + class PromoCodeWindow(bui.Window): """Window for entering promo codes.""" @@ -167,9 +172,6 @@ class PromoCodeWindow(bui.Window): if not self._root_widget or self._root_widget.transitioning_out: return - plus = bui.app.plus - assert plus is not None - bui.containerwidget( edit=self._root_widget, transition=self._transition_out ) @@ -179,11 +181,43 @@ class PromoCodeWindow(bui.Window): AdvancedSettingsWindow(transition='in_left').get_root_widget(), from_window=self._root_widget, ) + + code: Any = bui.textwidget(query=self._text_field) + assert isinstance(code, str) + + bui.app.create_async_task(_run_code(code)) + + +async def _run_code(code: str) -> None: + from bacommon.cloud import PromoCodeMessage + + plus = bui.app.plus + assert plus is not None + + try: + # If we're signed in with a V2 account, ship this to V2 server. + if plus.accounts.primary is not None: + with plus.accounts.primary: + response = await plus.cloud.send_message_async( + PromoCodeMessage(code) + ) + # If V2 handled it, we're done. + if response.valid: + # Support simple message printing from v2 server. + if response.message is not None: + bui.screenmessage(response.message, color=(0, 1, 0)) + return + + # If V2 didn't accept it (or isn't signed in) kick it over to V1. plus.add_v1_account_transaction( { 'type': 'PROMO_CODE', 'expire_time': time.time() + 5, - 'code': bui.textwidget(query=self._text_field), + 'code': code, } ) plus.run_v1_account_transactions() + except Exception: + logging.exception('Error sending promo code.') + bui.screenmessage('Error sending code (see log).', color=(1, 0, 0)) + bui.getsound('error').play() diff --git a/dist/ba_data/python/bauiv1lib/qrcode.py b/dist/ba_data/python/bauiv1lib/qrcode.py index 3d92eaf..467bab7 100644 --- a/dist/ba_data/python/bauiv1lib/qrcode.py +++ b/dist/ba_data/python/bauiv1lib/qrcode.py @@ -3,9 +3,11 @@ """Provides functionality for displaying QR codes.""" from __future__ import annotations -from bauiv1lib.popup import PopupWindow +from typing_extensions import override import bauiv1 as bui +from bauiv1lib.popup import PopupWindow + class QRCodeWindow(PopupWindow): """Popup window that shows a QR code.""" @@ -58,6 +60,7 @@ class QRCodeWindow(PopupWindow): self._transitioning_out = True bui.containerwidget(edit=self.root_widget, transition='out_scale') + @override def on_popup_cancel(self) -> None: bui.getsound('swish').play() self._transition_out() diff --git a/dist/ba_data/python/bauiv1lib/resourcetypeinfo.py b/dist/ba_data/python/bauiv1lib/resourcetypeinfo.py index c40b708..6c3fc58 100644 --- a/dist/ba_data/python/bauiv1lib/resourcetypeinfo.py +++ b/dist/ba_data/python/bauiv1lib/resourcetypeinfo.py @@ -4,6 +4,8 @@ from __future__ import annotations +from typing_extensions import override + from bauiv1lib.popup import PopupWindow import bauiv1 as bui @@ -53,6 +55,7 @@ class ResourceTypeInfoWindow(PopupWindow): self._transitioning_out = True bui.containerwidget(edit=self.root_widget, transition='out_scale') + @override def on_popup_cancel(self) -> None: bui.getsound('swish').play() self._transition_out() diff --git a/dist/ba_data/python/bauiv1lib/settings/advanced.py b/dist/ba_data/python/bauiv1lib/settings/advanced.py index 61c72cb..3828537 100644 --- a/dist/ba_data/python/bauiv1lib/settings/advanced.py +++ b/dist/ba_data/python/bauiv1lib/settings/advanced.py @@ -94,7 +94,7 @@ class AdvancedSettingsWindow(bui.Window): self._scroll_width = self._width - (100 + 2 * x_inset) self._scroll_height = self._height - 115.0 self._sub_width = self._scroll_width * 0.95 - self._sub_height = 766.0 + self._sub_height = 808.0 if self._show_always_use_internal_keyboard: self._sub_height += 62 @@ -489,6 +489,17 @@ class AdvancedSettingsWindow(bui.Window): maxwidth=430, ) + v -= 42 + self._show_demos_when_idle_check_box = ConfigCheckBox( + parent=self._subcontainer, + position=(50, v), + size=(self._sub_width - 100, 30), + configkey='Show Demos When Idle', + displayname=bui.Lstr(resource=f'{self._r}.showDemosWhenIdleText'), + scale=1.0, + maxwidth=430, + ) + v -= 42 self._disable_camera_shake_check_box = ConfigCheckBox( parent=self._subcontainer, @@ -575,12 +586,18 @@ class AdvancedSettingsWindow(bui.Window): up_widget=self._always_use_internal_keyboard_check_box.widget, ) else: - bui.widget( - edit=self._modding_guide_button, - up_widget=self._kick_idle_players_check_box.widget, + # ew. + next_widget_up = ( + self._disable_gyro_check_box.widget + if self._disable_gyro_check_box is not None + else self._disable_camera_shake_check_box.widget ) bui.widget( - edit=self._kick_idle_players_check_box.widget, + edit=self._modding_guide_button, + up_widget=next_widget_up, + ) + bui.widget( + edit=next_widget_up, down_widget=self._modding_guide_button, ) @@ -803,6 +820,8 @@ class AdvancedSettingsWindow(bui.Window): sel_name = 'Benchmarks' elif sel == self._kick_idle_players_check_box.widget: sel_name = 'KickIdlePlayers' + elif sel == self._show_demos_when_idle_check_box.widget: + sel_name = 'ShowDemosWhenIdle' elif sel == self._show_game_ping_check_box.widget: sel_name = 'ShowPing' elif sel == self._disable_camera_shake_check_box.widget: @@ -870,6 +889,8 @@ class AdvancedSettingsWindow(bui.Window): sel = self._benchmarks_button elif sel_name == 'KickIdlePlayers': sel = self._kick_idle_players_check_box.widget + elif sel_name == 'ShowDemosWhenIdle': + sel = self._show_demos_when_idle_check_box.widget elif sel_name == 'ShowPing': sel = self._show_game_ping_check_box.widget elif sel_name == 'DisableCameraShake': diff --git a/dist/ba_data/python/bauiv1lib/teamnamescolors.py b/dist/ba_data/python/bauiv1lib/teamnamescolors.py index 2334e09..3fe9545 100644 --- a/dist/ba_data/python/bauiv1lib/teamnamescolors.py +++ b/dist/ba_data/python/bauiv1lib/teamnamescolors.py @@ -6,6 +6,8 @@ from __future__ import annotations from typing import TYPE_CHECKING, cast +from typing_extensions import override + from bauiv1lib.popup import PopupWindow from bauiv1lib.colorpicker import ColorPicker import bauiv1 as bui @@ -217,6 +219,7 @@ class TeamNamesColorsWindow(PopupWindow): self._transitioning_out = True bui.containerwidget(edit=self.root_widget, transition=transition) + @override def on_popup_cancel(self) -> None: bui.getsound('swish').play() self._transition_out() diff --git a/dist/ba_data/python/bauiv1lib/tournamententry.py b/dist/ba_data/python/bauiv1lib/tournamententry.py index d00c37d..90996bc 100644 --- a/dist/ba_data/python/bauiv1lib/tournamententry.py +++ b/dist/ba_data/python/bauiv1lib/tournamententry.py @@ -7,6 +7,8 @@ from __future__ import annotations import logging from typing import TYPE_CHECKING +from typing_extensions import override + from bauiv1lib.popup import PopupWindow import bauiv1 as bui @@ -749,6 +751,7 @@ class TournamentEntryWindow(PopupWindow): if self._on_close_call is not None: self._on_close_call() + @override def on_popup_cancel(self) -> None: bui.getsound('swish').play() self._on_cancel() diff --git a/dist/ba_data/python/bauiv1lib/tournamentscores.py b/dist/ba_data/python/bauiv1lib/tournamentscores.py index 9d75631..ebd44de 100644 --- a/dist/ba_data/python/bauiv1lib/tournamentscores.py +++ b/dist/ba_data/python/bauiv1lib/tournamentscores.py @@ -6,6 +6,8 @@ from __future__ import annotations from typing import TYPE_CHECKING +from typing_extensions import override + from bauiv1lib.popup import PopupWindow import bauiv1 as bui @@ -244,6 +246,7 @@ class TournamentScoresWindow(PopupWindow): if self._on_close_call is not None: self._on_close_call() + @override def on_popup_cancel(self) -> None: bui.getsound('swish').play() self._transition_out() diff --git a/dist/ba_data/python/bauiv1lib/trophies.py b/dist/ba_data/python/bauiv1lib/trophies.py index b746d05..e605ad7 100644 --- a/dist/ba_data/python/bauiv1lib/trophies.py +++ b/dist/ba_data/python/bauiv1lib/trophies.py @@ -6,6 +6,8 @@ from __future__ import annotations from typing import TYPE_CHECKING +from typing_extensions import override + from bauiv1lib import popup import bauiv1 as bui @@ -213,6 +215,7 @@ class TrophiesWindow(popup.PopupWindow): self._transitioning_out = True bui.containerwidget(edit=self.root_widget, transition='out_scale') + @override def on_popup_cancel(self) -> None: bui.getsound('swish').play() self._transition_out() diff --git a/dist/ba_data/python/efro/dataclassio/_base.py b/dist/ba_data/python/efro/dataclassio/_base.py index 6ef3710..afe19de 100644 --- a/dist/ba_data/python/efro/dataclassio/_base.py +++ b/dist/ba_data/python/efro/dataclassio/_base.py @@ -70,6 +70,13 @@ class IOExtendedData: Can be overridden to migrate old data formats to new, etc. """ + def did_input(self) -> None: + """Called on a class instance after created from data. + + Can be useful to correct values from the db, etc. in the + type-safe form. + """ + def _is_valid_for_codec(obj: Any, codec: Codec) -> bool: """Return whether a value consists solely of json-supported types. diff --git a/dist/ba_data/python/efro/dataclassio/_inputter.py b/dist/ba_data/python/efro/dataclassio/_inputter.py index d6650a6..9707552 100644 --- a/dist/ba_data/python/efro/dataclassio/_inputter.py +++ b/dist/ba_data/python/efro/dataclassio/_inputter.py @@ -64,11 +64,23 @@ class _Inputter(Generic[T]): # For special extended data types, call their 'will_output' callback. tcls = self._cls + if issubclass(tcls, IOExtendedData): + is_ext = True tcls.will_input(values) + else: + is_ext = False out = self._dataclass_from_input(self._cls, '', values) assert isinstance(out, self._cls) + + if is_ext: + # mypy complains that we're no longer returning a T + # if we operate on out directly. + out2 = out + assert isinstance(out2, IOExtendedData) + out2.did_input() + return out def _value_from_input( diff --git a/dist/ba_data/python/efro/dataclassio/extras.py b/dist/ba_data/python/efro/dataclassio/extras.py index 327f829..c54b0c0 100644 --- a/dist/ba_data/python/efro/dataclassio/extras.py +++ b/dist/ba_data/python/efro/dataclassio/extras.py @@ -7,6 +7,8 @@ from __future__ import annotations import dataclasses from typing import TYPE_CHECKING +from typing_extensions import override + if TYPE_CHECKING: from typing import Any @@ -32,6 +34,7 @@ class DataclassDiff: self._obj1 = obj1 self._obj2 = obj2 + @override def __repr__(self) -> str: return dataclass_diff(self._obj1, self._obj2) diff --git a/dist/ba_data/python/efro/error.py b/dist/ba_data/python/efro/error.py index 7f90216..f0e561b 100644 --- a/dist/ba_data/python/efro/error.py +++ b/dist/ba_data/python/efro/error.py @@ -6,6 +6,8 @@ from __future__ import annotations from typing import TYPE_CHECKING import errno +from typing_extensions import override + if TYPE_CHECKING: from typing import Any @@ -82,6 +84,7 @@ class RemoteError(Exception): super().__init__(msg) self._peer_desc = peer_desc + @override def __str__(self) -> str: s = ''.join(str(arg) for arg in self.args) # Indent so we can more easily tell what is the remote part when diff --git a/dist/ba_data/python/efro/log.py b/dist/ba_data/python/efro/log.py index 77b8999..680d94e 100644 --- a/dist/ba_data/python/efro/log.py +++ b/dist/ba_data/python/efro/log.py @@ -15,6 +15,7 @@ from dataclasses import dataclass, field from typing import TYPE_CHECKING, Annotated from threading import Thread, current_thread, Lock +from typing_extensions import override from efro.util import utc_now from efro.call import tpartial from efro.terminal import Clr @@ -306,6 +307,7 @@ class LogHandler(logging.Handler): """Submit a call to be run in the logging background thread.""" self._event_loop.call_soon_threadsafe(call) + @override def emit(self, record: logging.LogRecord) -> None: # pylint: disable=too-many-branches if __debug__: diff --git a/dist/ba_data/python/efro/message/_protocol.py b/dist/ba_data/python/efro/message/_protocol.py index 04c1dea..8daa8d8 100644 --- a/dist/ba_data/python/efro/message/_protocol.py +++ b/dist/ba_data/python/efro/message/_protocol.py @@ -386,6 +386,7 @@ class MessageProtocol: f'\n' f'from typing import TYPE_CHECKING{ovld}{ovld2}\n' f'\n' + # f'from typing_extensions import override\n' f'{import_lines}' f'\n' f'if TYPE_CHECKING:\n' diff --git a/dist/ba_data/python/efro/util.py b/dist/ba_data/python/efro/util.py index 8bc4542..f581542 100644 --- a/dist/ba_data/python/efro/util.py +++ b/dist/ba_data/python/efro/util.py @@ -174,17 +174,26 @@ def empty_weakref(objtype: type[T]) -> weakref.ref[T]: # Just create an object and let it die. Is there a cleaner way to do this? # return weakref.ref(_EmptyObj()) # type: ignore + # Sharing a single ones seems at least a bit better. return _g_empty_weak_ref # type: ignore -def data_size_str(bytecount: int) -> str: +def data_size_str(bytecount: int, compact: bool = False) -> str: """Given a size in bytes, returns a short human readable string. - This should be 6 or fewer chars for most all sane file sizes. + In compact mode this should be 6 or fewer chars for most all + sane file sizes. """ # pylint: disable=too-many-return-statements + + # Special case: handle negatives. + if bytecount < 0: + val = data_size_str(-bytecount, compact=compact) + return f'-{val}' + if bytecount <= 999: - return f'{bytecount} B' + suffix = 'B' if compact else 'bytes' + return f'{bytecount} {suffix}' kbytecount = bytecount / 1024 if round(kbytecount, 1) < 10.0: return f'{kbytecount:.1f} KB' @@ -197,7 +206,7 @@ def data_size_str(bytecount: int) -> str: return f'{mbytecount:.0f} MB' gbytecount = bytecount / (1024 * 1024 * 1024) if round(gbytecount, 1) < 10.0: - return f'{mbytecount:.1f} GB' + return f'{gbytecount:.1f} GB' return f'{gbytecount:.0f} GB' @@ -623,7 +632,7 @@ def check_non_optional(obj: T | None) -> T: Use assert_non_optional for a more efficient (but less safe) equivalent. """ if obj is None: - raise TypeError('Got None value in check_non_optional.') + raise ValueError('Got None value in check_non_optional.') return obj