1.7.5 master sync

This commit is contained in:
Ayush Saini 2022-07-16 17:59:14 +05:30
parent 421c488c6a
commit 62103ea678
61 changed files with 2001 additions and 1364 deletions

View file

@ -50,6 +50,7 @@
"5PH3X",
"99",
"@sametsunal",
"_DraXX",
"_Fami",
"Omar a",
"Bruno A.",
@ -75,7 +76,7 @@
"adan",
"Adeel (AdeZ {@adez_})",
"Adel",
"Rio adi",
"Rio Adi",
"Rayhan Adiansyah",
"Yonas Adiel",
"admin",
@ -194,6 +195,7 @@
"Arin",
"Arroz",
"ARSHAD",
"ArshiyDLn",
"Artem",
"Valentino Artizzu",
"Arxyma",
@ -237,6 +239,7 @@
"Bank",
"Ibrahim Baraka",
"Kamil Barański",
"Leonan Barcelos",
"Bardiaghasedipour",
"William Barnak",
"William Barnakk",
@ -248,6 +251,7 @@
"Bashkot",
"Matthias Bastron",
"Ralph Bathmann",
"Bato",
"Florian Bauernfeind",
"David BAUMANN",
"bayanus",
@ -273,6 +277,7 @@
"Anton Bang Berner",
"Felix Bernhard",
"Davide Bigotto",
"bilibili@Medic药",
"Bima",
"Biytremni",
"Blackcat2960",
@ -316,6 +321,7 @@
"Buskebam",
"Buto11",
"ByAdrianYT",
"Semih Byzts",
"Mohamad Hossein BZ",
"Christoffer Bünner",
"mvp aka cactus",
@ -382,6 +388,7 @@
"D",
"Dada",
"Daivaras",
"Dajo6596YT",
"Dakkat",
"Mikkel Damgaard",
"Danco",
@ -398,6 +405,7 @@
"Dasto",
"David",
"Davide",
"DavidPlayzLol",
"DavidPlayzloll",
"DaymanLP",
"Ddávid",
@ -445,6 +453,7 @@
"Dustin",
"Paul Duvernay",
"Emir İslam Dündar",
"E.R.A.L",
"Ebutahapro07tr",
"Edson",
"Glen Edwards",
@ -458,6 +467,7 @@
"Rezk ElAdawy",
"ElCatCaesar",
"ElderLink",
"elfree",
"Elian",
"Elsans320_YT",
"Elskoser",
@ -466,6 +476,7 @@
"Emil",
"Kürti Emil",
"Muhammed emir",
"Muhammet Emir",
"EmirSametEr",
"emm",
"EnderDust123",
@ -686,8 +697,9 @@
"Tobias Dencker Israelsen",
"Kegyes István",
"Itamar",
"ivan",
"Ivan",
"iViietZ",
"JaaJ",
"Al jabbar",
"Jacek",
"Jack556",
@ -729,6 +741,7 @@
"joke",
"Jonatas",
"Jop",
"JoseANG3L",
"Joseetion",
"Joseph",
"Joshep",
@ -771,6 +784,7 @@
"Mani kelidari",
"Kelmine",
"Kenjie",
"Kerfix",
"Kerim",
"Khaild1717",
"muh khairul",
@ -852,8 +866,10 @@
"Linux44313",
"LiteBalt",
"LittleNyanCat",
"Lizz",
"Lizzetc",
"Lkham",
"Lobinhofs",
"Loex",
"Loko",
"Longkencok",
@ -871,6 +887,7 @@
"Ludicrouswizard",
"satrio ludji",
"Ludovico",
"Luis (GalaxyM4)",
"Jose Luis",
"luislinares",
"luispro25",
@ -933,6 +950,7 @@
"Ihsan Maulana ( @ihsanm27)",
"Federico Mazzone",
"Andrea Mazzucchelli",
"Medic",
"Medic别闹我有药",
"German Medin",
"Martin Medina",
@ -1104,12 +1122,14 @@
"pett",
"Petulakulina",
"Pez",
"PGIGM",
"Đào Xuân Phi",
"Philip",
"Philipp",
"piga",
"Stefano Pigozzi",
"Mario Donato Pilla",
"Pinchidino",
"Danilo \"Logan\" Pirrone",
"PivotStickfigure12",
"Pixelcube",
@ -1321,6 +1341,7 @@
"sun.4810",
"Samet Sunal",
"sundar",
"Suprcat",
"Indo sus",
"Sven",
"Shannon Sy",
@ -1329,6 +1350,7 @@
"Sz™",
"Jorge Luis Sánchez",
"Daniel Sýkora",
"Aleksandar Tadic",
"Arung Taftazani",
"taha",
"Rasim Eren TAHMAZ",
@ -1394,6 +1416,7 @@
"Kontantin Tsvetkov",
"Tudikk",
"Jan Tymll",
"Zacker Tz",
"Zoltán Tóth",
"uDinnoo",
"Cristian Ugalde",

View file

@ -327,6 +327,7 @@
"achievementsRemainingText": "الإنجازات المتبقية",
"achievementsText": "الإنجازات",
"achievementsUnavailableForOldSeasonsText": "المعذرة، الإنجازات للمواسم القديمة غير متوفرة",
"activatedText": "${THING} تم تفعيله.",
"addGameWindow": {
"getMoreGamesText": "الحصول على المزيد من الألعاب",
"titleText": "إضافة لعبة"
@ -625,7 +626,9 @@
"epicDescriptionFilterText": "${DESCRIPTION} بحركة ملحمية بطيئة",
"epicNameFilterText": "الملحمي ${NAME}",
"errorAccessDeniedText": "تم الرفض",
"errorDeviceTimeIncorrectText": "توقف وقت جهازك بمقدار ${HOURS} ساعة.\nهذا قد يتسبب بمشاكل.\nمن فضلك قم بالتحقق من اعدادات الوقت.",
"errorOutOfDiskSpaceText": "انتهت مساحة التخزين",
"errorSecureConnectionFailText": "تعذر انشاء اتصال سحابي أمن; قد تفشل وظائف الشبكة.",
"errorText": "خطا",
"errorUnknownText": "خطا غير معروف",
"exitGameText": "هل تريد الخروج من ${APP_NAME}؟",
@ -788,7 +791,7 @@
"ticketPack4Text": "جمبو تذكرة حزمة",
"ticketPack5Text": "ماموث تذكرة حزمة",
"ticketPack6Text": "تذكرة حزمة هائلة",
"ticketsFromASponsorText": "احصل على تذاكر ${COUNT}\nمن مقدم مشروع القرار",
"ticketsFromASponsorText": "شاهد اعلانا\nمن التذاكر ${COUNT} للحصول على",
"ticketsText": "${COUNT} بطاقات",
"titleText": "أحصل على تذاكر",
"unavailableLinkAccountText": "عذرا، لا تتوفر عمليات الشراء على هذا النظام الأساسي.\nوكحل بديل، يمكنك ربط هذا الحساب بحساب في\nمنصة أخرى وجعل عمليات الشراء هناك.",
@ -799,6 +802,7 @@
"youHaveText": "لديك ${COUNT} تذاكر"
},
"googleMultiplayerDiscontinuedText": "عذرًا ، خدمة جوجل متعددة اللاعبين لم تعد متاحة.\n أنا أعمل على بديل بأسرع وقت ممكن.\n حتى ذلك الحين ، يرجى تجربة طريقة اتصال أخرى.\n -إريك",
"googlePlayPurchasesNotAvailableText": "عمليات شراء جوجل بلاي غير متوفرة.\nقد تحتاج لتحديث تطبيق المتجر.",
"googlePlayText": "جوجل بلاي",
"graphicsSettingsWindow": {
"alwaysText": "دائما",
@ -1109,7 +1113,10 @@
"playlistsText": "قائمة العب",
"pleaseRateText": "يرجى اتخاذ لحظة وتقييمه ${APP_NAME} اذا كنت تستمتع بلعبة\nاو كتابة مراجعة فهاذا يوفر معلومات مفيدة ويوفر التطوير\nفي المستقبل\n\n!شكرًا\nاريك—",
"pleaseWaitText": "الرجاء الانتظار . . .",
"pluginsDetectedText": "تم العتور على اضافات جديدة. فعلها او عدلها في الاعدادات",
"pluginClassLoadErrorText": "فشل في تشغيل الاضافة '${PLUGIN}': ${ERROR}",
"pluginInitErrorText": "خطأ في بدء الاضافة '${PLUGIN}: ${ERROR}",
"pluginsDetectedText": "تم العثور على اضافات جديدة. اعد التشغيل لتفعيلها، او قم بتخصيصها في الاعدادات.",
"pluginsRemovedText": "${NUM} لم تعد الاضافة موجودة.",
"pluginsText": "اضافات",
"practiceText": "تدريب",
"pressAnyButtonPlayAgainText": "اضغط اي زر للعب مجددا...",
@ -1296,8 +1303,8 @@
"comingSoonText": "قريبا...",
"extrasText": "إضافات",
"freeBombSquadProText": "فرقة القنبلة الآن أصبحت مجانية، لكن بما أنك اشتريتها\nبطاقات كشكر لك ${COUNT} ستتلقى فرقة القنبلة القنبلة للمحترفين و\n!استمتع بالميزات الجديدة، وشكرًا لدعمك\n-إيريك",
"holidaySpecialText": "عطلة خاصة",
"howToSwitchCharactersText": "(انتقل إلى \"${SETTINGS} -> ${PLAYER_PROFILES}\" لتعيين وتخصيص الأحرف)",
"holidaySpecialText": "خاص بالعطل",
"howToSwitchCharactersText": "(توجه الى \"${SETTINGS} -> ${PLAYER_PROFILES}\" لتخصيص الشخصيات)",
"howToUseIconsText": "(إنشاء ملفات تعريف لاعب العالمية (في إطار الحساب) لاستخدام هذه)",
"howToUseMapsText": "(استخدم هذه الخرائط في فرقك الخاصة / قوائم التشغيل المجانية للجميع)",
"iconsText": "رموز",
@ -1321,7 +1328,7 @@
"winterSpecialText": "عرض الشتاء",
"youOwnThisText": "- انت تملك هذا -"
},
"storeDescriptionText": "8 لاعب حفله لعبة الجنون!\n\nتفجير أصدقائك (أو الكمبيوتر) في البطولة من الألعاب المصغرة المتفجرة مثل القبض على العلم، منفذها هوكي، وملحمة بطيئة الحركة الموت الموت!\n\nضوابط بسيطة ودعم وحدة تحكم واسعة تجعل من السهل لمدة تصل إلى 8 أشخاص للحصول على في العمل. يمكنك حتى استخدام الأجهزة النقالة الخاصة بك عن طريق التحكم عن طريق الحرة 'بومبسكاد البعيد' التطبيق!\n\nالقنابل بعيدا!\n\nتحقق من www.froemling.net/bombsquad لمزيد من المعلومات.",
"storeDescriptionText": "لعبة لأكثر من 8 لاعبين!\n\nالعب مع اصدقائك (او الحاسوب) في بطولات من الميني جيمز المتفجرة كــإمساك بالعلم، الهوكي و المعركة البطيئة!\n\nتحكم بسيط و لعب باجهزة تحكم يجعلها سهلة لأكثر من 8 لاعبين ليدخلو المعركة; تستطيع ايضا استعمال اجهزة الهاتف كاجهزة تحكم من خلال التطبيق المجاني 'BombSquad Remote' !\n\nوقت رمي القنابل!\n\nتفقد www.froemling.net/bombsquad للمزيد من المعلومات.",
"storeDescriptions": {
"blowUpYourFriendsText": ".فجر أصدقائك",
"competeInMiniGamesText": "تنافس في الألعاب المصغرة بدءا من السباق للطيران.",
@ -1364,31 +1371,31 @@
"translations": {
"characterNames": {
"Agent Johnson": "العميل جونسون",
"B-9000": "الآلي الخارق",
"Bernard": "الدب القطبي",
"B-9000": "B-9000",
"Bernard": "بيرنارد",
"Bones": "هيكل عظمي",
"Butch": "بوتش",
"Easter Bunny": "أرنوب",
"Easter Bunny": "أرنب عيد الفصح",
"Flopsy": "فلوبسي",
"Frosty": "مُقاتل ثلجي",
"Frosty": "فروستي",
"Gretel": "جريتل",
"Grumbledorf": "المشعوذ",
"Jack Morgan": "خير الدين بارباروسا",
"Kronk": "عدنان",
"Grumbledorf": "الساحر",
"Jack Morgan": "جاك مورجان (قرصان)",
"Kronk": "كرونك",
"Lee": "لي",
"Lucky": "سعيد الحظ",
"Mel": "الطباخ",
"Mel": "ميل",
"Middle-Man": "الرجل المتوسط",
"Minimus": "أدنى لا",
"Pascal": "البطريق الكبير",
"Pixel": "حسناء",
"Pascal": "پاسكال",
"Pixel": "بيكسل",
"Sammy Slam": "سامي سلام",
"Santa Claus": "الشيخ",
"Snake Shadow": "مُحارب في الصحراء",
"Spaz": "Spaz",
"Taobao Mascot": "التميمه تاوباو",
"Santa Claus": "سانتا كلوس",
"Snake Shadow": "نينجا",
"Spaz": "سپاز",
"Taobao Mascot": "ماسكوت تاوباو",
"Todd McBurton": "تود بيرتون",
"Zoe": "ليلى",
"Zoe": "زوي",
"Zola": "زولا"
},
"coopLevelNames": {
@ -1843,6 +1850,8 @@
"winsPlayerText": "${NAME} !يفوز",
"winsTeamText": "${NAME} !يفوز فريق",
"winsText": "${NAME} !يفوز",
"workspaceSyncErrorText": "فشل في مزامنة ${WORKSPACE}. القي نظرة على السجل للتفاصيل.",
"workspaceSyncReuseText": "لا يمكن مزامنة ${WORKSPACE}. اعادة استخدام النسخة المتزامنة السابقة.",
"worldScoresUnavailableText": "(النتيجة العالمية غير متوفرة (اتصل بالانترنت",
"worldsBestScoresText": "افضل نتيجة للعالم",
"worldsBestTimesText": "افضل اوقات العالم",

View file

@ -330,6 +330,7 @@
"achievementsRemainingText": "未完成成就:",
"achievementsText": "成就",
"achievementsUnavailableForOldSeasonsText": "抱歉,往届的成就细节不可用。",
"activatedText": "${THING} 已激活",
"addGameWindow": {
"getMoreGamesText": "获取更多游戏模式…",
"titleText": "添加比赛"
@ -629,7 +630,9 @@
"epicDescriptionFilterText": "史诗级慢动作 ${DESCRIPTION}。",
"epicNameFilterText": "史诗级${NAME}",
"errorAccessDeniedText": "访问被拒绝",
"errorDeviceTimeIncorrectText": "您的系统时间与服务器时间相差了${HOURS}小时\n这可能会出现一些问题\n请检查您的系统时间或时区",
"errorOutOfDiskSpaceText": "磁盘空间不足",
"errorSecureConnectionFailText": "无法建立安全的云链接,网络可能会连接失败",
"errorText": "错误",
"errorUnknownText": "未知错误",
"exitGameText": "退出${APP_NAME}",
@ -795,7 +798,7 @@
"ticketPack4Text": "巨型点券包",
"ticketPack5Text": "猛犸象点券包",
"ticketPack6Text": "终极点券包",
"ticketsFromASponsorText": "从赞助商处\n获得${COUNT}点券",
"ticketsFromASponsorText": "观看广告\n白嫖${COUNT}个点券",
"ticketsText": "${COUNT}点券",
"titleText": "获得点券",
"unavailableLinkAccountText": "对不起,该平台上不可进行购买。\n您可将此帐户链接到另一个\n平台以进行购买。",
@ -806,6 +809,7 @@
"youHaveText": "你拥有${COUNT}点券"
},
"googleMultiplayerDiscontinuedText": "抱歉Google的多人游戏服务已不可用。\n我将尽快更换新的替代服务。\n在此之前请尝试其他连接方法。\n-Eric",
"googlePlayPurchasesNotAvailableText": "Google商店购买不可用!\n请安装谷歌框架或更新Google服务。",
"googlePlayText": "Google Play",
"graphicsSettingsWindow": {
"alwaysText": "总是",
@ -1122,7 +1126,10 @@
"playlistsText": "列表",
"pleaseRateText": "如果你喜欢 ${APP_NAME},请考虑花一点时间\n来评价一下它或为它写一篇评论。这将为我们提供\n有用的反馈建议为游戏的未来开发给予支持。\n\n感谢您\n-eric",
"pleaseWaitText": "请稍等...",
"pluginClassLoadErrorText": "加载'${PLUGIN}'插件时出错了耶: ${ERROR}",
"pluginInitErrorText": "初始化'${PLUGIN}'插件失败了啦: ${ERROR}",
"pluginsDetectedText": "新插件安装成功,请重启游戏或在设置中设置它们~",
"pluginsRemovedText": "有${NUM}个插件被删除了...",
"pluginsText": "插件",
"practiceText": "练习",
"pressAnyButtonPlayAgainText": "按任意按钮再玩一次...",
@ -1863,6 +1870,8 @@
"winsPlayerText": "${NAME}获胜!",
"winsTeamText": "${NAME}获胜!",
"winsText": "${NAME}获胜!",
"workspaceSyncErrorText": "同步${WORKSPACE}出错了啦,详情见日志文件",
"workspaceSyncReuseText": "同步${WORKSPACE}时出错,正在使用以前同步的数据....",
"worldScoresUnavailableText": "全球得分不可用。",
"worldsBestScoresText": "全球最高得分",
"worldsBestTimesText": "全球最佳时间",

View file

@ -324,6 +324,7 @@
"achievementsRemainingText": "未完成的成就",
"achievementsText": "成就",
"achievementsUnavailableForOldSeasonsText": "抱歉,往屆成就細節不可用",
"activatedText": "${THING}已激活",
"addGameWindow": {
"getMoreGamesText": "獲取更多比賽模式",
"titleText": "新增比賽"
@ -621,7 +622,9 @@
"epicDescriptionFilterText": "史詩級慢動作${DESCRIPTION}",
"epicNameFilterText": "史詩級${NAME}",
"errorAccessDeniedText": "訪問被拒絕",
"errorDeviceTimeIncorrectText": "您的系統時間與BS伺服器相差了${HOURS}小時\n這可能會出現一些問題\n請檢查你的系統時間或時區",
"errorOutOfDiskSpaceText": "磁盤空間不足",
"errorSecureConnectionFailText": "無法安全的連接到伺服器,網絡功能可能會失效",
"errorText": "錯誤",
"errorUnknownText": "未知錯誤",
"exitGameText": "退出${APP_NAME}?",
@ -1105,7 +1108,10 @@
"playlistsText": "遊戲列表",
"pleaseRateText": "如果你喜歡${APP_NAME},請考慮花一些時間\n為Bombsquad寫一篇評論。這將為我們\n提供一些有用的反饋建議為遊戲的未來開發給予支持\n\nThanks!\n-Eric",
"pleaseWaitText": "請稍等....",
"pluginsDetectedText": "檢測到新插件。 在設置中啟用/配置它們。",
"pluginClassLoadErrorText": "加載插件類'${PLUGIN}'時出錯:${ERROR}",
"pluginInitErrorText": "初始化插件'${PLUGIN}'時出錯:${ERROR}",
"pluginsDetectedText": "檢測到新插件。重新啓動以激活,或在設置中配置這些插件",
"pluginsRemovedText": "${NUM}個插件已丟失",
"pluginsText": "外掛程式",
"practiceText": "練習",
"pressAnyButtonPlayAgainText": "按任意鍵再玩一次...",
@ -1839,6 +1845,8 @@
"winsPlayerText": "${NAME}贏了!",
"winsTeamText": "${NAME}贏了!",
"winsText": "${NAME}贏了!",
"workspaceSyncErrorText": "同步${WORKSPACE}時出錯,詳情日志已輸出",
"workspaceSyncReuseText": "無法同步${WORKSPACE},重複使用以前的同步版本",
"worldScoresUnavailableText": "無法讀入全球分數",
"worldsBestScoresText": "全球最佳分數",
"worldsBestTimesText": "全球最佳時間",

View file

@ -329,6 +329,7 @@
"achievementsRemainingText": "Preostala Postignuća:",
"achievementsText": "Postignuća",
"achievementsUnavailableForOldSeasonsText": "Žao nam je,postignuća pojedinosti nisu dostupni za stare sezone.",
"activatedText": "${THING} uključeno.",
"addGameWindow": {
"getMoreGamesText": "Još igara...",
"titleText": "Dodaj igru"
@ -1117,7 +1118,10 @@
"playlistsText": "Liste igara",
"pleaseRateText": "Ako ti se sviđa ${APP_NAME}, molim te razmisli o tome\nda ga ocijeniš ili napišeš recenziju.\nOvako šalješ korisnu povratnu informaciju koja pomaže u daljnjem razvoju.\n\nHvala!\n-eric",
"pleaseWaitText": "Molimo sačekajte...",
"pluginClassLoadErrorText": "Pogreška učitavanja dodatka klase '${PLUGIN}': ${ERROR}",
"pluginInitErrorText": "Pogreška iniciranja dodatka '${PLUGIN}': ${ERROR}",
"pluginsDetectedText": "Novi dodatak(ci) detektiran(i) Resetiraj da ih ukljućiš ili ih konfiguriraj u postavkima",
"pluginsRemovedText": "${NUM} dodatak/ci više nisu pronađeni.",
"pluginsText": "Dodatci",
"practiceText": "Vježba",
"pressAnyButtonPlayAgainText": "Pritisni bilo koju tipku za ponovnu igru...",
@ -1858,6 +1862,8 @@
"winsPlayerText": "${NAME} pobjeđuje!",
"winsTeamText": "${NAME} pobjeđuju!",
"winsText": "${NAME} pobjeđuje!",
"workspaceSyncErrorText": "Pogreška sinkroniziranja ${WORKSPACE}. Pogledaj log za više detalja.",
"workspaceSyncReuseText": "Nemoguće sinkronizirati ${WORKSPACE}. Ponovno korištenje stare verzije",
"worldScoresUnavailableText": "Svjetski rezultati nedostupni.",
"worldsBestScoresText": "Najbolji svjetski rezultati",
"worldsBestTimesText": "Najbolja svjetska prolazna vremena",

View file

@ -331,6 +331,7 @@
"achievementsRemainingText": "Achievementů Zbývá:",
"achievementsText": "Achievementy",
"achievementsUnavailableForOldSeasonsText": "Promiňte, ale detaily úspěchu nejsou pro starší sezóny zpřístupněny.",
"activatedText": "${THING} aktivováno.",
"addGameWindow": {
"getMoreGamesText": "Získat Více Her...",
"titleText": "Přidat Hru"
@ -633,7 +634,9 @@
"epicDescriptionFilterText": "${DESCRIPTION} V epickém slow motionu",
"epicNameFilterText": "Epické ${NAME}",
"errorAccessDeniedText": "přístup zamítnut",
"errorDeviceTimeIncorrectText": "Čas vašeho zařízení je mimo o ${HOURS} h.\nTo pravděpodobně způsobí problémy.\nProsím zkontrolujte nastavení času a časového pásma.",
"errorOutOfDiskSpaceText": "není místo na disku",
"errorSecureConnectionFailText": "Unable to establish secure cloud connection; network functionality may fail.",
"errorText": "Chyba",
"errorUnknownText": "neznámá chyba",
"exitGameText": "Ukončit ${APP_NAME} ???",
@ -1123,7 +1126,10 @@
"playlistsText": "Playlisty",
"pleaseRateText": "Jestliže Vás ${APP_NAME} baví, zvažte prosím, jestli si nechcete udělat\nchvilku na ohodnocení nebo napsání recenze. Poskytuje to užitečnou\nzpětnou vazbu a pomáhá podporovat budoucí vývoj.\n\nDíky!\n-eric",
"pleaseWaitText": "Prosím čekejte...",
"pluginsDetectedText": "Nové plugin(y) nalezeny. Zapněte/konfigurujte je v nastavení.",
"pluginClassLoadErrorText": "Chyba při načítání třídy pluginu '${PLUGIN}': ${ERROR}",
"pluginInitErrorText": "Chyba při inicializaci pluginu '${PLUGIN}': ${ERROR}",
"pluginsDetectedText": "Nové plugin(y) nalezeny. Pro aktivaci restartujte, nebo konfigurujte v nastavení.",
"pluginsRemovedText": "${NUM} plugin(y) nenalezeny.",
"pluginsText": "Pluginy",
"practiceText": "Cvičení",
"pressAnyButtonPlayAgainText": "Stiskněte libovolné tlačítko pro opakování hry...",
@ -1864,6 +1870,8 @@
"winsPlayerText": "${NAME} Vyhrál!",
"winsTeamText": "${NAME} Vyhrál!",
"winsText": "${NAME} Vyhrál!",
"workspaceSyncErrorText": "Chyba synchronizace ${WORKSPACE}. Podrobnosti v logu.",
"workspaceSyncReuseText": "Nelze synchronizovat ${WORKSPACE}. Opětovné použití předchozí synchronizované verze.",
"worldScoresUnavailableText": "Světové skóre nepřístupné.",
"worldsBestScoresText": "Světové nejlepší skóre",
"worldsBestTimesText": "Světové nejlepší časy",

View file

@ -624,7 +624,9 @@
"epicDescriptionFilterText": "${DESCRIPTION} In epic slow motion.",
"epicNameFilterText": "Epic ${NAME}",
"errorAccessDeniedText": "access denied",
"errorDeviceTimeIncorrectText": "Your device's time is off by ${HOURS} hours.\nThis is likely to cause problems.\nPlease check your time and time-zone settings.",
"errorOutOfDiskSpaceText": "out of disk space",
"errorSecureConnectionFailText": "Unable to establish secure cloud connection; network functionality may fail.",
"errorText": "Error",
"errorUnknownText": "unknown error",
"exitGameText": "Exit ${APP_NAME}?",
@ -787,7 +789,7 @@
"ticketPack4Text": "Jumbo Ticket Pack",
"ticketPack5Text": "Mammoth Ticket Pack",
"ticketPack6Text": "Ultimate Ticket Pack",
"ticketsFromASponsorText": "Get ${COUNT} tickets\nfrom a sponsor",
"ticketsFromASponsorText": "Watch an ad\nfor ${COUNT} tickets",
"ticketsText": "${COUNT} Tickets",
"titleText": "Get Tickets",
"unavailableLinkAccountText": "Sorry, purchases are not available on this platform.\nAs a workaround, you can link this account to an account on\nanother platform and make purchases there.",
@ -798,6 +800,7 @@
"youHaveText": "you have ${COUNT} tickets"
},
"googleMultiplayerDiscontinuedText": "Sorry, Googles multiplayer service is no longer available.\nI am working on a replacement as fast as possible.\nUntil then, please try another connection method.\n-Eric",
"googlePlayPurchasesNotAvailableText": "Google Play purchases are not available.\nYou may need to update your store app.",
"googlePlayText": "Google Play",
"graphicsSettingsWindow": {
"alwaysText": "Always",

View file

@ -325,6 +325,7 @@
"achievementsRemainingText": "Ang Mga Natitirang Nakamtan:",
"achievementsText": "Achievements",
"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…",
"titleText": "Magdagdag Ng Laro"
@ -623,9 +624,11 @@
"epicDescriptionFilterText": "${DESCRIPTION} sa isang epic na slow motion.",
"epicNameFilterText": "Epikong ${NAME}",
"errorAccessDeniedText": "walang pahintulot",
"errorDeviceTimeIncorrectText": "Ang oras ng iyong device ay naka-off nang ${HOURS} na oras.\nIto ay malamang na magdulot ng mga ibat ibang problema.\nPakisuri ang iyong mga setting ng oras at time-zone.",
"errorOutOfDiskSpaceText": "Wala sa puwang ng disk",
"errorSecureConnectionFailText": "Hindi mapatunayan ng secure na “cloud connection”; maaaring mabigo ang pagpapagana ng network.",
"errorText": "Error",
"errorUnknownText": "hindi batid na error",
"errorUnknownText": "error na di malaman",
"exitGameText": "Exit mula sa ${APP_NAME}?",
"exportSuccessText": "Na-export ang '${NAME}'.",
"externalStorageText": "External na Storage",
@ -786,7 +789,7 @@
"ticketPack4Text": "Malaking Pakete Ng Tiket",
"ticketPack5Text": "Mas Malaking Pakete Ng Tiket",
"ticketPack6Text": "Pinakamalaking Pakete Ng Tiket",
"ticketsFromASponsorText": "Kumuha ng ${COUNT} na tiket\nmula sa isang sponsor",
"ticketsFromASponsorText": "Manood ng Ad\npara makuha ang ${COUNT} na tiket",
"ticketsText": "${COUNT} Tiket",
"titleText": "Kumuha Ng Tiket",
"unavailableLinkAccountText": "Pasensya na, hindi available ang pagbili sa platform na ito.\nBilang isang solusyon, maaari mong i-link ang account na ito sa isang account na nasa\nisa pang platform at bumili doon.",
@ -797,6 +800,7 @@
"youHaveText": "mayroon kang ${COUNT} na tiket"
},
"googleMultiplayerDiscontinuedText": "Pasensya na, hindi na available ang multiplayer na serbisyo ng Google.\nGumagawa ako ng kapalit sa lalong madaling panahon.\nHanggang doon, mangyaring sumubok ng ibang paraan ng koneksyon.\n-Eric",
"googlePlayPurchasesNotAvailableText": "Hindi puwede ang mga pagbili sa Google Play nito.\nMaaaring kailanganin mong i-update ang iyong store app.",
"googlePlayText": "Google Play",
"graphicsSettingsWindow": {
"alwaysText": "Palagi",
@ -836,11 +840,11 @@
"pickUpInfoTextScale": 0.5,
"powerupBombDescriptionText": "Payagan kang maglabas ng tatlong bomba\nsa isang hilera sa halip na isa lamang.",
"powerupBombNameText": "Tatlong-Bomba",
"powerupCurseDescriptionText": "Malamang na gusto mong iwasan ang mga ito.\n ...o ikaw?",
"powerupCurseDescriptionText": "Malamang na gusto mong iwasan ang mga ganito.\n ...o sige lang?",
"powerupCurseNameText": "Sumpa",
"powerupHealthDescriptionText": "Ibangon ka sa buong kalusugan.\nHindi mo naisip nito.",
"powerupHealthNameText": "Medikal-Pakete",
"powerupIceBombsDescriptionText": "Mas mahina kaysa sa mga normal na bomba\nngunit nagyelo ang iyong mga kalaban\nat partikular na malutong.",
"powerupIceBombsDescriptionText": "Mas mahina kaysa sa mga normal na bomba\nngunit nagyelo ang iyong mga kalaban\nat partikular na mabasag.",
"powerupIceBombsNameText": "Bombang-Yelo",
"powerupImpactBombsDescriptionText": "Medyo mahina kaysa sa regular\nbomba, ngunit sumasabog ito kapag nabagsak.",
"powerupImpactBombsNameText": "Gatilyong-Bomba",
@ -1089,7 +1093,7 @@
"titleText": "Maglaro",
"twoToEightPlayersText": "2-8 na manlalaro"
},
"playerCountAbbreviatedText": "${COUNT}n.m",
"playerCountAbbreviatedText": "${COUNT}p",
"playerDelayedJoinText": "Papasok si ${PLAYER} sa simula ng susunod na round.",
"playerInfoText": "Impormasyon ng Manlalaro",
"playerLeftText": "Umalis si ${PLAYER} sa laro.",
@ -1110,7 +1114,10 @@
"playlistsText": "Mga Playlist",
"pleaseRateText": "Kung nae-enjoy mo ang ${APP_NAME}, mangyaring isaalang-alang ang \npagkuha na sandali lang at i-rate ito o pagsulat ng isang pagsusuri. Nagbibigay ito ngkapaki-pakinabang na feedback at \ntumutulong sa pagsuporta sa pag-unlad sa hinaharap.\n\nsalamat!\n-eric",
"pleaseWaitText": "Hintay lang…",
"pluginClassLoadErrorText": "Error sa paglo-load ang '${PLUGIN}' na klaseng plugin : ${ERROR}",
"pluginInitErrorText": "Error sa pagsisimula ang '${PLUGIN}' na plugin: ${ERROR}",
"pluginsDetectedText": "May nakitang bagong (mga) plugin. I-restart para i-activate ang mga ito, o i-configure ang mga ito sa mga setting.",
"pluginsRemovedText": "Hindi na nahanapan ang ${NUM} ng (mga) plugin.",
"pluginsText": "Mga Plugin",
"practiceText": "Pagsasagawa",
"pressAnyButtonPlayAgainText": "Pindutin ang anumang button para maglaro muli...",
@ -1453,7 +1460,7 @@
"kill ${ARG1} enemies": "patayin ang ${ARG1} na mga kalaban.",
"last one standing wins": "kung sino ang huling nakatayo ang siyang mananalo",
"last team standing wins": "kung sino ang huling katayuan ng team ang siyang mananalo",
"return ${ARG1} flags": "ibalik ang ${ARG1} na mga bandera",
"return ${ARG1} flags": "Magnakaw ng ${ARG1} na mga bandera",
"return 1 flag": "ibalik ang 1 bandila",
"run ${ARG1} laps": "Tumakbo ng ${ARG1} ikot",
"run 1 lap": "tumakbo ng 1 ikot",
@ -1774,7 +1781,7 @@
"randomName3Text": "Stephen",
"randomName4Text": "Joshua",
"randomName5Text": "Villar",
"skipConfirmText": "Sure ka na i-skip ang tutorial? Tap o pindutin para ma i-confirm.",
"skipConfirmText": "Sure ka ba na i-skip ang tutorial? Tap o pindutin para ma i-confirm.",
"skipVoteCountText": "${COUNT}/${TOTAL} boto na gustong laktawan",
"skippingText": "Nilalaktawan ang tutorial",
"toSkipPressAnythingText": "(i-tap o pindutin ang anuman para laktawan ang tutorial)"
@ -1844,6 +1851,8 @@
"winsPlayerText": "Nanalo si ${NAME}!",
"winsTeamText": "Nanalo ang ${NAME}!",
"winsText": "${NAME} Nanalo!",
"workspaceSyncErrorText": "Error sa pag-sync ng ${WORKSPACE}. Tingnan ang log para sa mga detalye.",
"workspaceSyncReuseText": "Hindi ma-sync ang ${WORKSPACE}. Muling paggamit ng nakaraang naka-sync na bersyon.",
"worldScoresUnavailableText": "Ang scores sa buong mundo ay hindi pa handa",
"worldsBestScoresText": "Pinakamahusay na Iskor ng Mundo",
"worldsBestTimesText": "Pinakamahusay na Oras sa Mundo",

View file

@ -661,7 +661,9 @@
"epicDescriptionFilterText": "${DESCRIPTION} Ín ípic slúw mztíon.",
"epicNameFilterText": "${NAME} Epícz",
"errorAccessDeniedText": "acczlr dnfflz",
"errorDeviceTimeIncorrectText": "Yowruc cowier oowefjwoefj ${HOURS} horewif.\noitwocweojowerjiowejjjfwoef.\nPefjwe wehapeocjjgwghwe w e weofwoefjwe.",
"errorOutOfDiskSpaceText": "orz of dkzk spzlfz",
"errorSecureConnectionFailText": "Uweorcjwoef ojcowe werryyeoi nowe; jcnwoeroidfowdffdj.dfsdf.",
"errorText": "Errórz",
"errorUnknownText": "unknznlz errzzz",
"exitGameText": "$Excej ${APP_NAME}",
@ -749,6 +751,7 @@
"getFriendInviteCodeText": "Gz Frjor Infivo Cdz",
"googlePlayDescriptionText": "Invlt Gglglz Plzlz plzlfer tz yzr prtaryz:",
"googlePlayInviteText": "Invtlzz",
"googlePlayPurchasesNotAvailableText": "JGowej eo owe weoirjweorjf\nYou we r wcowehpaw ecowjeoiwjrperpp.",
"googlePlayReInviteText": "Tnz arzs ${COUNT} Gflf Plzz Plalfjdsf oin weojf wfeoyowiufe\nwhiz ewofij woijdoisoic o asodf wfiawje;aoew aoiwfj.\nTIncowjf oweofjao wotonwei oiacoiw eotijweto tat.",
"googlePlaySeeInvitesText": "Szz Invtlfz",
"googlePlayText": "Gzzlg Plz",
@ -841,7 +844,7 @@
"ticketPack4Text": "Jmfpmf Tkckf CPkf",
"ticketPack5Text": "Mmthf Tckff Pckc",
"ticketPack6Text": "Ulzjtla Tkckdf Pck",
"ticketsFromASponsorText": "Gzt ${COUNT} tkckjtz\nfzn a sobfhrkz",
"ticketsFromASponsorText": "woejwoe c woeijweo\ncowjeo ${COUNT} weocjw",
"ticketsText": "${COUNT} Tckrtzz",
"titleText": "Gtz Tckrtz",
"unavailableLinkAccountText": "Srryrc, cpofj caodn oiwrj coi weo joweijwoief.\nIf wc woiejf w, cocwi eoiaoth aciw jeojfow iejoijwef\npcoj weo faona omowio wejfowijerwe.",
@ -852,6 +855,7 @@
"youHaveText": "yz hv ${COUNT} tickrrz"
},
"googleMultiplayerDiscontinuedText": "Sowoer Gojf wel wouwen weoioc long wf won.\nI wow oe wefwjr pif g wfpawouja c oeij fw ocjaoiejowr.\nUntil. cowier oa j fapefij cpoypt ao coonnec awoiery.\n-Ercff",
"googlePlayPurchasesNotAvailableText": "Weofiiwj woe woerd wieofjwe\ncome foe rj eofiaj cpwoe jrowjer..dd",
"googlePlayText": "Gzzggl Plzz",
"graphicsSettingsWindow": {
"alwaysText": "Azlwáys",

View file

@ -332,6 +332,7 @@
"achievementsRemainingText": "Trofei rimasti:",
"achievementsText": "Trofei",
"achievementsUnavailableForOldSeasonsText": "Mi dispiace, ma gli obbiettivi non sono disponibili per le stagioni vecchie.",
"activatedText": "${THING} attivato.",
"addGameWindow": {
"getMoreGamesText": "Ottieni Giochi...",
"titleText": "Aggiungi Partita"
@ -1162,7 +1163,10 @@
"playlistsText": "Scalette",
"pleaseRateText": "Se ti sta piacendo ${APP_NAME}, prenditi\nun momento per valutarlo o scriverci su una recensione.\nQuesto aiuterà a supportare futuri sviluppi.\n\nGrazie!\n-eric",
"pleaseWaitText": "Attendi...",
"pluginClassLoadErrorText": "Errore nel caricamento di plugin classe '${PLUGIN}': ${ERROR}",
"pluginInitErrorText": "Errore inizializzazione plugin '${PLUGIN}': ${ERROR}",
"pluginsDetectedText": "nuovo/i plugin rilevato. Riavvia per attivarli, o configurali nelle impostazioni.",
"pluginsRemovedText": "${NUM} plugin non trovati.",
"pluginsText": "Plugin",
"practiceText": "Allenamento",
"pressAnyButtonPlayAgainText": "Premi un pulsante qualunque per rigiocare...",
@ -1943,6 +1947,8 @@
"winsPlayerText": "${NAME} Ha Vinto!",
"winsTeamText": "${NAME} Ha Vinto!",
"winsText": "${NAME} vince!",
"workspaceSyncErrorText": "Errore nella sincronizzazione di ${WORKSPACE}. Controlla il registro per dettagli.",
"workspaceSyncReuseText": "Impossibile sincronizzare ${WORKSPACE}. Riutilizzo della versione sincronizzata precedente.",
"worldScoresUnavailableText": "Punteggi mondiali non disponibili.",
"worldsBestScoresText": "I punteggi migliori del mondo",
"worldsBestTimesText": "I tempi migliori del mondo",

View file

@ -326,6 +326,7 @@
"achievementsRemainingText": "دستاورد های باقیمانده:",
"achievementsText": "دستاوردها",
"achievementsUnavailableForOldSeasonsText": "ببخشید، دستاوردهای مخصوص فصل گذشته در دسترس نیستند",
"activatedText": "${THING} فعال شد.",
"addGameWindow": {
"getMoreGamesText": "...بازی های بیشتر",
"titleText": "افزودن بازی"
@ -625,7 +626,9 @@
"epicDescriptionFilterText": "در حماسهٔ حرکت آهسته ${DESCRIPTION}",
"epicNameFilterText": "${NAME} حماسهٔ",
"errorAccessDeniedText": "دسترسی رد شد",
"errorDeviceTimeIncorrectText": "ساعت گوشی ${HOURS} ساعت خاموش بوده‌است.\nممکن است مشکل به‌وجود بیاید.\nلطفاً ساعت و منطقهٔ زمانی گوشی‌تان را بررسی کنید.",
"errorOutOfDiskSpaceText": "حافظه جا ندارد",
"errorSecureConnectionFailText": "قادر به ایجاد اتصال ابری امن نیست. عملکرد شبکه ممکن است خراب شود.",
"errorText": "خطا",
"errorUnknownText": "خطای ناشناخته",
"exitGameText": "؟${APP_NAME} خروج از",
@ -788,7 +791,7 @@
"ticketPack4Text": "پک خیلی بزرگ بلیط",
"ticketPack5Text": "پک ماموتی بلیط",
"ticketPack6Text": "پک نهایی بلیط",
"ticketsFromASponsorText": "بلیط از${COUNT}\nیه اسپانسر بگیرید",
"ticketsFromASponsorText": "در ازای ${COUNT} بلیت\nیک آگهی ببینید",
"ticketsText": "بلیط${COUNT}",
"titleText": "بلیط بگیرید",
"unavailableLinkAccountText": ".ببخشید،خرید به وسیله ی این دستگاه در دسترس نمیباشد\nمیتوانید حسابتان بر روی این دستگاه را به حسابی در دستگاهی\n.دیگر متصل کنید و آنجا خرید خود را انجام دهید",
@ -1109,7 +1112,10 @@
"playlistsText": "لیست بازی ها",
"pleaseRateText": "خوشتان آمده، لطفاً چند لحظه‌ای وقت بگذارید و ${APP_NAME} اگر از\nآن را رتبه‌بندی کنید یا مروری بر آن بنویسید. این کار بازخوردهای مفیدی\n.را به همراه دارد و به پشتیبانی از توسعه‌ها در آینده کمک خواهد کرد\n\n!با تشکر\nاریک—",
"pleaseWaitText": "…لطفاً صبر کنید",
"pluginsDetectedText": ".افزونه(ها)ی جدید شناسایی شد. آن‌ها را در تنظیمات فعال/پیکربندی کنید",
"pluginClassLoadErrorText": "${ERROR} :«${PLUGIN}» خطا در بارگیری دسته‌بندی افزونهٔ",
"pluginInitErrorText": "${ERROR} :«${PLUGIN}» خطا در راه‌اندازی افزونهٔ",
"pluginsDetectedText": "افزونه(ها)ی جدید شناسایی شد. آن‌ها را در تنظیمات فعال، یا پیکربندی کنید.",
"pluginsRemovedText": "${NUM} افزونه دیگر یافت نمی‌شود.",
"pluginsText": "افزونه‌ها",
"practiceText": "تمرین",
"pressAnyButtonPlayAgainText": "…فشردن هر دکمه‌ای برای بازی دوباره",
@ -1847,6 +1853,8 @@
"winsPlayerText": "${NAME} برنده شد",
"winsTeamText": "${NAME} برنده شد",
"winsText": "${NAME} برنده شد",
"workspaceSyncErrorText": "خطا در همگام‌سازی ${WORKSPACE}. برای جزئیات به لاگ مراجعه کنید.",
"workspaceSyncReuseText": "نمی‌توان ${WORKSPACE} را همگام‌سازی کرد. استفادهٔ مجدد از نسخهٔ همگام‌سازی‌شده قبلی.",
"worldScoresUnavailableText": "امتیاز های جهانی قابل دسترس نیستند.",
"worldsBestScoresText": "بهترین امتیازهای جهانی",
"worldsBestTimesText": "بهترین زمان های جهانی",

View file

@ -331,6 +331,7 @@
"achievementsRemainingText": "Conquistas restantes:",
"achievementsText": "Conquistas",
"achievementsUnavailableForOldSeasonsText": "Desculpe, algumas conquistas não estão disponíveis em temporadas antigas.",
"activatedText": "${THING} foi ativado.",
"addGameWindow": {
"getMoreGamesText": "Mais jogos...",
"titleText": "Adicionar jogo"
@ -657,7 +658,9 @@
"epicDescriptionFilterText": "${DESCRIPTION} em câmera lenta épica.",
"epicNameFilterText": "${NAME} épico(a)",
"errorAccessDeniedText": "acesso negado",
"errorDeviceTimeIncorrectText": "A hora do seu dispositivo está atrasada/adiantada em ${HOURS} horas.\nIsso poderá causar problemas.\nPor favor cheque as suas configurações de hora e fuso horário.",
"errorOutOfDiskSpaceText": "pouco espaço em disco",
"errorSecureConnectionFailText": "Não foi possível estabelecer uma conexão segura à nuvem; a funcionalidade da rede pode falhar.",
"errorText": "Erro",
"errorUnknownText": "erro desconhecido",
"exitGameText": "Sair do ${APP_NAME}?",
@ -832,7 +835,7 @@
"ticketPack4Text": "Pacote jumbo de bilhetes",
"ticketPack5Text": "Pacote mamute de bilhete",
"ticketPack6Text": "Pacote ultimate de bilhetes",
"ticketsFromASponsorText": "Ganhe ${COUNT} bilhetes\nde um patrocinador",
"ticketsFromASponsorText": "Assista a um anúncio \ne ganhe ${COUNT} bilhetes",
"ticketsText": "${COUNT} bilhetes",
"titleText": "Obter bilhetes",
"unavailableLinkAccountText": "Desculpe, as compras não estão disponíveis nesta plataforma.\nComo solução alternativa, você pode vincular esta conta para\noutra conta de outra plataforma e fazer compras lá.",
@ -843,6 +846,7 @@
"youHaveText": "você possui ${COUNT} bilhetes"
},
"googleMultiplayerDiscontinuedText": "Desculpe, o serviço multijogador do Google não está mais disponível.\nEstou trabalhando em uma substituição o mais rápido possível.\nAté lá, tente outro método de conexão.\n-Eric",
"googlePlayPurchasesNotAvailableText": "Compras pela Google Play não estão disponíveis.\nTalvez seja necessário atualizar sua loja para isso.",
"googlePlayText": "Google Play",
"graphicsSettingsWindow": {
"alwaysText": "Sempre",
@ -1183,7 +1187,10 @@
"playlistsText": "Playlists",
"pleaseRateText": "Se você está curtindo ${APP_NAME}, por favor, dê um tempinho\npara avaliar e comentar. Isso nos dá uma opinião útil\ne ajuda no desenvolvimento do jogo.\n\nobrigado!\n-eric",
"pleaseWaitText": "Por favor, aguarde...",
"pluginClassLoadErrorText": "Erro ao carregar a classe de um plugin '${PLUGIN}': ${ERROR}",
"pluginInitErrorText": "Erro ao inicializar plugin '${PLUGIN}': ${ERROR}",
"pluginsDetectedText": "Novo(s) plugin(s) detetados. Reinicie o jogo para ativá-los ou configure-os nas configurações.",
"pluginsRemovedText": "${NUM} plugin(s) não foram encontrados.",
"pluginsText": "Plugins",
"practiceText": "Praticar",
"pressAnyButtonPlayAgainText": "Aperte qualquer botão para jogar novamente...",
@ -1970,6 +1977,8 @@
"winsPlayerText": "${NAME} venceu!",
"winsTeamText": "${NAME} venceu!",
"winsText": "${NAME} ganhou!",
"workspaceSyncErrorText": "Erro ao sincronizar ${WORKSPACE}. Veja o log para mais detalhes.",
"workspaceSyncReuseText": "Não pôde sincronizar ${WORKSPACE}. Reusando uma versão anterior sincronizada.",
"worldScoresUnavailableText": "Pontuações mundiais indisponíveis.",
"worldsBestScoresText": "Melhores pontuações do mundo",
"worldsBestTimesText": "Melhores tempos do mundo",

View file

@ -330,6 +330,7 @@
"achievementsRemainingText": "Realizări Rămase:",
"achievementsText": "Realizări",
"achievementsUnavailableForOldSeasonsText": "Scuze, dar detaliile realizărilor din sezoanele trecute sunt indisponibile.",
"activatedText": "${THING} a fost activat(ă).",
"addGameWindow": {
"getMoreGamesText": "Ia mai multe MiniJocuri...",
"titleText": "Adaugă un joc"
@ -349,14 +350,14 @@
"autoText": "Automat",
"backText": "Înapoi",
"banThisPlayerText": "Interzice Accesul Acestui Jucător",
"bestOfFinalText": "Finala Primul-La-${COUNT}",
"bestOfSeriesText": "Seriile Primul-La-${COUNT}:",
"bestOfUseFirstToInstead": 1,
"bestOfFinalText": "Finala Cel-Mai-Bun-Din-${COUNT}",
"bestOfSeriesText": "Seriile Cel-Mai-Bun-Din-${COUNT}:",
"bestOfUseFirstToInstead": 0,
"bestRankText": "Cea mai bună poziție a ta este: #${RANK}",
"bestRatingText": "Cea mai bună notă a ta este ${RATING}",
"bombBoldText": "BOMBĂ",
"bombText": "Bombă",
"boostText": "Crește-ți",
"boostText": "Crește",
"bsRemoteConfigureInAppText": "${REMOTE_APP_NAME} este configurat în aplicația în sine.",
"buttonText": "buton",
"canWeDebugText": "Ai vrea ca BombSquad să trimită automat bug-uri,\ncrash-uri și informații de bază programatorului jocului?\n\nAceste informații nu conțin date personale, ci doar\najută la îmbunătățirea jocului.",
@ -629,7 +630,9 @@
"epicDescriptionFilterText": "${DESCRIPTION} În slow motion epic.",
"epicNameFilterText": "${NAME} Epic",
"errorAccessDeniedText": "acces respins",
"errorDeviceTimeIncorrectText": "Ceasul dispozitivului tău este dat înainte sau înapoi cu ${HOURS} ore.\nAcestu lucru ar putea cauza niște probleme.\nTe rog să verifici setările ceasului și ale fusului orar.",
"errorOutOfDiskSpaceText": "ai rămas fără memorie",
"errorSecureConnectionFailText": "Nu s-a putut stabili o conexiune sigură cloud; funcționalitatea internetului ar putea eșua.",
"errorText": "Eroare",
"errorUnknownText": "eroare necunoscută",
"exitGameText": "Ieși din ${APP_NAME}?",
@ -705,10 +708,10 @@
"friendHasSentPromoCodeText": "${COUNT} Bilete pe ${APP_NAME} de la ${NAME}",
"friendPromoCodeAwardText": "Vei primi ${COUNT} de bilete de fiecare dată când este folosit.",
"friendPromoCodeExpireText": "Codul va expira în ${EXPIRE_HOURS} de ore şi este valabil doar pentru jucătorii noi.",
"friendPromoCodeInstructionsText": "Pentru a-l utiliza, deschide ${APP_NAME} și accesează „Setări-> Avansat-> Introdu codul”.\nConsultă bombsquadgame.com pentru linkuri de descărcare pentru toate platformele acceptate.",
"friendPromoCodeInstructionsText": "Pentru a-l utiliza, deschide ${APP_NAME} și accesează „Setări-> Avansat-> Introdu un cod”.\nConsultă bombsquadgame.com pentru linkuri de descărcare pentru toate platformele acceptate.",
"friendPromoCodeRedeemLongText": "Poate fi introdus pentru ${COUNT} de bilete gratuite de către un maxim de ${MAX_USES} de persoane.",
"friendPromoCodeRedeemShortText": "Poate fi introdus în joc pentru ${COUNT} de bilete.",
"friendPromoCodeWhereToEnterText": "(în „Setări-> Avansat-> Introdu codul”)",
"friendPromoCodeWhereToEnterText": "(în \"Setări-> Avansat-> Introdu un cod\")",
"getFriendInviteCodeText": "Cere un Cod pentru Prieteni",
"googlePlayDescriptionText": "Invită jucători Google Play la petrecerea ta:",
"googlePlayInviteText": "Invită",
@ -794,7 +797,7 @@
"ticketPack4Text": "Super-Pachet de Bilete",
"ticketPack5Text": "Pachet de Bilete MAMUT",
"ticketPack6Text": "Pachet de Bilete Suprem",
"ticketsFromASponsorText": "Ia ${COUNT} bilete\nde la un sponsor",
"ticketsFromASponsorText": "Vizionează o reclamă\npentru ${COUNT} bilete",
"ticketsText": "${COUNT} de Bilete",
"titleText": "Ia Bilete",
"unavailableLinkAccountText": "Scuze, dar achizițiile nu sunt disponibile pe această platformă.\nCa soluție, poți conecta acest cont la un alt cont de pe\no altă platformă și să faci cumpărături acolo.",
@ -805,6 +808,7 @@
"youHaveText": "ai ${COUNT} (de) bilete"
},
"googleMultiplayerDiscontinuedText": "Ne pare rău, serviciul multiplayer de pe Google nu mai este disponibil.\nLucrez la un înlocuitor cât mai repede posibil.\nPână atunci, te rog să încerci o altă metodă de conectare.\n-Eric",
"googlePlayPurchasesNotAvailableText": "Achizițiile de pe Google Play nu sunt disponibile.\nÎncearcă să actualizezi Magazin Play și să încerci din nou.",
"googlePlayText": "Google Play",
"graphicsSettingsWindow": {
"alwaysText": "Întotdeauna",
@ -881,7 +885,7 @@
"buttonText": "buton",
"cantKickHostError": "Nu poți da afară hostul server-ului.",
"chatBlockedText": "${NAME} este blocat în chat timp de ${TIME} (de) secunde.",
"connectedToGameText": "a intrat în „${NAME}”",
"connectedToGameText": "Ai intrat în '${NAME}'",
"connectedToPartyText": "Ai intrat în server-ul lui ${NAME}!",
"connectingToPartyText": "Se conectează...",
"connectionFailedHostAlreadyInPartyText": "Conexiunea a eşuat; hostul este în alt joc.",
@ -915,7 +919,7 @@
"kickIdlePlayersKickedText": "${NAME} a fost dat afară deoarece era inactiv.",
"kickIdlePlayersWarning1Text": "${NAME} va fi dat afară în următoarele ${COUNT} secunde dacă continuă să fie inactiv.",
"kickIdlePlayersWarning2Text": "(Poți dezactiva această setare în Setări -> Avansat)",
"leftGameText": "A ieșit din ${NAME}",
"leftGameText": "Ai ieșit din '${NAME}'",
"leftPartyText": "Ai ieşit din server-ul lui ${NAME}.",
"noMusicFilesInFolderText": "Folder-ul nu conține niciun fişier de muzică.",
"playerJoinedPartyText": "${NAME} a intrat în joc!",
@ -1026,9 +1030,9 @@
"modeArcadeText": "Mod Pentru Arcade",
"modeClassicText": "Modul Clasic",
"modeDemoText": "Modul Demonstrativ",
"mostValuablePlayerText": "Cel mai valoros jucător este:",
"mostViolatedPlayerText": "Cel mai ucis jucător este:",
"mostViolentPlayerText": "Cel mai violent jucător este:",
"mostValuablePlayerText": "Cel mai valoros jucător este",
"mostViolatedPlayerText": "Cel mai ucis jucător este",
"mostViolentPlayerText": "Cel mai violent jucător este",
"moveText": "Te miști din",
"multiKillText": "${COUNT} ucideri!!!",
"multiPlayerCountText": "${COUNT} jucători",
@ -1119,7 +1123,10 @@
"playlistsText": "Liste de Jocuri",
"pleaseRateText": "Dacă îți place ${APP_NAME}, te rog să stai\no clipă și să evaluezi jocul, scriind o recenzie pentru acesta.\nAceasta furnizează feedback folositor și ajută la dezvoltarea jocului în viitor.\n\nmulțumesc!\n-Eric",
"pleaseWaitText": "Te rog să aștepți...",
"pluginClassLoadErrorText": "Nu s-a putut încărca plugin-ul '${PLUGIN}': ${ERROR}",
"pluginInitErrorText": "Nu s-a putut iniția plugin-ul '${PLUGIN}': ${ERROR}",
"pluginsDetectedText": "Plugin(uri) noi detectate. Restartează jocul pentru a le activa / configura în setări.",
"pluginsRemovedText": "${NUM} plugin(uri) nu mai există.",
"pluginsText": "Plugin-uri",
"practiceText": "Antrenament",
"pressAnyButtonPlayAgainText": "Apasă orice buton pentru a juca din nou...",
@ -1134,7 +1141,7 @@
"pressToSelectProfileText": "apasă ${BUTTONS} pentru a-ți selecta profilul",
"pressToSelectTeamText": "apasă ${BUTTONS} pentru a-ți selecta echipa",
"promoCodeWindow": {
"codeText": "Codul:",
"codeText": "Codul",
"codeTextDescription": "Cod Promoțional",
"enterText": "Trimite-l"
},
@ -1246,7 +1253,7 @@
"disableThisNotice": "(Poți dezactiva această notificare în setările avansate)",
"enablePackageModsDescriptionText": "Permite mai multe capabilități pentru modare, dar dezactivează net-play-ul)",
"enablePackageModsText": "Activează Moduri cu Pachete Locale",
"enterPromoCodeText": "Codul:",
"enterPromoCodeText": "Introdu un cod",
"forTestingText": "Notă: aceste valori sunt doar pentru teste şi vor fi resetate când jocul va fi închis.",
"helpTranslateText": "Translațiile din ${APP_NAME} sunt un efort depus de comunitate.\nDacă ai dori să te implici la translatarea/corectarea unei limbi,\nurmează linkul de mai jos. Mulțumiri anticipate!",
"kickIdlePlayersText": "Dă Afară Jucătorii Inactivi",
@ -1713,12 +1720,12 @@
"Don't run all the time. Really. You will fall off cliffs.": "Nu fugi tot timpul. Pe bune. Vei cădea de pe hărți.",
"Don't spin for too long; you'll become dizzy and fall.": "Nu te învârti prea mult timp; vei ameți și vei fi pus la pământ.",
"Hold any button to run. (Trigger buttons work well if you have them)": "Ține apăsat orice buton pentru a fugi. (Butoanele 'Trigger' funcționează bine şi ele dacă le ai)",
"Hold down any button to run. You'll get places faster\nbut won't turn very well, so watch out for cliffs.": "Pentru a fugi,ține apăsat orice buton. Vei ajunge mai repede unde vrei,\ndar nu vei lua curba prea bine, așa că ai grijă să nu cazi de pe hărți.",
"Hold down any button to run. You'll get places faster\nbut won't turn very well, so watch out for cliffs.": "Pentru a fugi, ține apăsat orice buton. Vei ajunge mai repede unde vrei,\ndar nu vei lua curba prea bine, așa că ai grijă să nu cazi de pe hărți.",
"Ice bombs are not very powerful, but they freeze\nwhoever they hit, leaving them vulnerable to shattering.": "Bombele de gheață nu sunt foarte puternice, dar îngheață\npe oricine lovesc, lăsând victimele vulnerabile la spargere.",
"If someone picks you up, punch them and they'll let go.\nThis works in real life too.": "Dacă cineva te ia pe sus, dă-i un pumn și îți va da drumul.\n (Apropo, merge și în viața reală)",
"If someone picks you up, punch them and they'll let go.\nThis works in real life too.": "Dacă cineva te ia pe sus, dă-i un pumn și îți va da drumul.\n (Apropo, merge și în viața reală)",
"If you are short on controllers, install the '${REMOTE_APP_NAME}' app\non your mobile devices to use them as controllers.": "Dacă nu ai destule controllere, instalează aplicația '${REMOTE_APP_NAME}' \npe dispozitivele tale mobile pentru a le folosi drept controllere.",
"If you are short on controllers, install the 'BombSquad Remote' app\non your iOS or Android devices to use them as controllers.": "Dacă nu ai destule controllere, instalează 'BombSquad Remote' pe\nun dispozitiv Android sau iOS pentru a-l folosi ca pe un controller.",
"If you get a sticky-bomb stuck to you, jump around and spin in circles. You might\nshake the bomb off, or if nothing else your last moments will be entertaining.": "Dacă o bombă lipicioasă se lipește de tine, sari și rotește-te în cercuri. S-ar putea \nsă scapi de aceasta, dar dacă nu, vei face un spectacol minunat din ultimele tale momente rămase în viață.",
"If you get a sticky-bomb stuck to you, jump around and spin in circles. You might\nshake the bomb off, or if nothing else your last moments will be entertaining.": "Dacă se atinge vreodată o bombă lipicioasă de tine, sari și rotește-te în cercuri. S-ar putea \nsă scapi de aceasta, dar dacă nu, vei face un spectacol minunat din ultimele tale momente rămase în viață.",
"If you kill an enemy in one hit you get double points for it.": "Dacă omori un inamic dintr-o singură lovitură vei primi puncte duble.",
"If you pick up a curse, your only hope for survival is to\nfind a health powerup in the next few seconds.": "Dacă atingi un blestem, singura ta şansă de a trăi este să\nprinzi o trusă de prim-ajutor în următoarele 5 secunde.",
"If you stay in one place, you're toast. Run and dodge to survive..": "Dacă stai într-un singur loc, vei muri ca un fraier. Ferește-te și aleargă din loc în loc pentru a supraviețui..",
@ -1863,6 +1870,8 @@
"winsPlayerText": "${NAME} Câștigă!",
"winsTeamText": "${NAME} Câștigă!",
"winsText": "${NAME} Câștigă!",
"workspaceSyncErrorText": "Nu s-a putut sincroniza ${WORKSPACE}. Vezi jurnalul de activități pentru detalii.",
"workspaceSyncReuseText": "Nu se poate sincroniza ${WORKSPACE}. Se va folosi ultima versiune sincronizată.",
"worldScoresUnavailableText": "Scorurile globale sunt indisponibile.",
"worldsBestScoresText": "Top Scoruri Mondiale",
"worldsBestTimesText": "Top Timpi Mondiali",

View file

@ -39,7 +39,7 @@
"testAccountWarningCardboardText": "Внимание: вы подписываете в с \"тест\" счета.\nЭти данные будут заменены со счетами Google сразу\nони становятся поддерживается в картонные приложений.\n\nСейчас вы будете зарабатывать все билеты в игре.\n(вы , тем не менее , получить Pro обновление BombSquad бесплатно )",
"testAccountWarningOculusText": "Внимание: вы входите под акканутом \"test\".\nОни будут заменены аккаунтами Oculus позже в этом\nгоду, что позволит совершать покупки билетов и многое другое.\n\nНа данный момент вам придется зарабатывать билеты в игре.\n(однако, вы получаете обновление BombSquad Pro бесплатно)",
"testAccountWarningText": "Внимание: вы заходите под аккаунтом \"test\".\nЭтот аккаунт привязан к конкретному устройству и может\nпериодически сбрасываться. (так что лучше особо\nне тратить время на сбор/разблокировку добра в нем)\n\nЧтобы использовать настоящий аккаунт (Game-Center,\nGoogle Plus и т.д.), запустите платную версию. Это также\nпозволит сохранять свой прогресс в облаке и делать его\nдоступным для разных устройств.",
"ticketsText": "Билетов: ${COUNT}",
"ticketsText": "Количество билетов: ${COUNT}",
"titleText": "Аккаунт",
"unlinkAccountsInstructionsText": "Выберите аккаунт, который хотите отвязать",
"unlinkAccountsText": "Отвязать аккаунты",
@ -49,14 +49,14 @@
"youAreSignedInAsText": "Вы вошли как:"
},
"achievementChallengesText": "Достижения",
"achievementText": "Медаль",
"achievementText": "Достижение",
"achievements": {
"Boom Goes the Dynamite": {
"description": "Убейте 3 плохих парней с помощью TNT",
"descriptionComplete": "С помощью TNT убито 3 плохих парней",
"descriptionFull": "Убейте 3 плохих парней с помощью TNT на уровне ${LEVEL}",
"descriptionFullComplete": "3 плохих парней убито с помощью TNT на уровне ${LEVEL}",
"name": "Сейчас бабахнет"
"name": "Динамит сейчас взорвётся!"
},
"Boxer": {
"description": "Победите без использования бомб",
@ -134,7 +134,7 @@
"descriptionComplete": "С карты сброшено 3 негодяя",
"descriptionFull": "Сбросьте 3 негодяев с карты на уровне ${LEVEL}",
"descriptionFullComplete": "3 негодяя сброшено с карты на уровне ${LEVEL}",
"name": "Давай отсюда"
"name": "Пора учится летать"
},
"Onslaught God": {
"description": "Наберите 5000 очков",
@ -180,7 +180,7 @@
},
"Pro Football Shutout": {
"description": "Победите в сухую",
"descriptionComplete": "Победил в сухую",
"descriptionComplete": "Уровень был пройден в сухую",
"descriptionFull": "Выиграйте матч ${LEVEL} в сухую",
"descriptionFullComplete": "Победа в матче ${LEVEL} в сухую",
"name": "${LEVEL} в сухую"
@ -194,9 +194,9 @@
},
"Pro Onslaught Victory": {
"description": "Победите все волны",
"descriptionComplete": обеждены все волны",
"descriptionComplete": ройдены все волны",
"descriptionFull": "Победите все волны на уровне ${LEVEL}",
"descriptionFullComplete": обеждены все волны на уровне ${LEVEL}",
"descriptionFullComplete": ройдены все волны на ${LEVEL}",
"name": "Победа на уровне ${LEVEL}"
},
"Pro Runaround Victory": {
@ -215,7 +215,7 @@
},
"Rookie Football Victory": {
"description": "Выиграйте матч",
"descriptionComplete": "Матч выигран",
"descriptionComplete": "Матч пройден в вашу пользу",
"descriptionFull": "Выиграйте матч ${LEVEL}",
"descriptionFullComplete": "Выигран матч ${LEVEL}",
"name": "Победа в матче ${LEVEL}"
@ -250,7 +250,7 @@
},
"Sharing is Caring": {
"descriptionFull": "Успешно поделиться игрой с другом",
"descriptionFullComplete": "Игра успешно поделена с другом",
"descriptionFullComplete": "Игра успешно передана другу",
"name": "Делиться - значит заботиться"
},
"Stayin' Alive": {
@ -309,7 +309,7 @@
},
"Uber Football Victory": {
"description": "Выиграйте матч",
"descriptionComplete": "Матч выигран",
"descriptionComplete": "Матч пройден в вашу пользу",
"descriptionFull": "Выиграйте матч ${LEVEL}",
"descriptionFullComplete": "Выигран матч ${LEVEL}",
"name": "Победа в матче ${LEVEL}"
@ -329,9 +329,10 @@
"name": "Победа на уровне ${LEVEL}"
}
},
"achievementsRemainingText": "Оставшиеся медали:",
"achievementsText": "Медали",
"achievementsRemainingText": "Осталось достижений:",
"achievementsText": "Достижения",
"achievementsUnavailableForOldSeasonsText": "К сожалению, подробности достижений не доступны для старых сезонов.",
"activatedText": "${THING} активировано.",
"addGameWindow": {
"getMoreGamesText": "Еще игр...",
"titleText": "Добавить игру"
@ -340,7 +341,7 @@
"alreadySignedInText": "На вашем аккаунте играют на другом устройстве;\nпожалуйста зайдите с другого аккаунта или закройте\nигру на другом устройстве и попытайтесь снова.",
"apiVersionErrorText": "Невозможно загрузить модуль ${NAME}; он предназначен для API версии ${VERSION_USED}; здесь требуется версия ${VERSION_REQUIRED}.",
"audioSettingsWindow": {
"headRelativeVRAudioInfoText": "(Режим \"Авто\" включает его только когда подключены наушники)",
"headRelativeVRAudioInfoText": "(Режим \"Авто\" активируется только при подключении наушников)",
"headRelativeVRAudioText": "Позиционно-зависимое ВР-аудио",
"musicVolumeText": "Громкость музыки",
"soundVolumeText": "Громкость звука",
@ -374,37 +375,37 @@
"completeThisLevelToProceedText": "Чтобы продолжить, нужно\nпройти этот уровень!",
"completionBonusText": "Бонус за прохождение",
"configControllersWindow": {
"configureControllersText": "Настройка контроллеров",
"configureControllersText": "Настройка геймпада",
"configureGamepadsText": "Настройка контроллеров",
"configureKeyboard2Text": "Настройка клавиатуры P2",
"configureKeyboardText": "Настройка клавиатуры",
"configureMobileText": "Мобильные устройства как контроллеры",
"configureMobileText": "Использовать мобильные устройства в качестве геймпадов",
"configureTouchText": "Настройка сенсорного экрана",
"ps3Text": "Контроллеры PS3",
"titleText": "Контроллеры",
"wiimotesText": "Контроллеры Wii",
"xbox360Text": "Контроллеры Xbox 360"
"ps3Text": "Геймпады PS3™",
"titleText": "Геймпады",
"wiimotesText": "Пульт Wii™",
"xbox360Text": "Геймпады Xbox 360™"
},
"configGamepadSelectWindow": {
"androidNoteText": "Внимание: поддержка контроллеров различается в зависимости от устройства и версии Android.",
"pressAnyButtonText": "Нажмите любую кнопку на контроллере,\n который хотите настроить...",
"titleText": "Настроить контроллер"
"androidNoteText": "Внимание: поддержка геймпада различается в зависимости от устройства и версии Android.",
"pressAnyButtonText": "Нажмите любую кнопку на геймпаде,\n которую хотите настроить...",
"titleText": "Настроить геймпад"
},
"configGamepadWindow": {
"advancedText": "Дополнительно",
"advancedTitleText": "Дополнительные настройки контроллера",
"advancedTitleText": "Дополнительные настройки геймпада",
"analogStickDeadZoneDescriptionText": "(Включите, если персонаж продолжает двигаться после того, как стик отпущен)",
"analogStickDeadZoneText": "Мертвая зона аналогового стика",
"appliesToAllText": "(относится ко всем контроллерам данного типа)",
"appliesToAllText": "(настроить ко всем геймпадам данного типа)",
"autoRecalibrateDescriptionText": "(включить, если персонаж не двигается на полной скорости)",
"autoRecalibrateText": "Авто-перекалибровка аналоговых стиков",
"axisText": "ось",
"clearText": "очистить",
"dpadText": "D-Pad",
"extraStartButtonText": "Дополнительная кнопка \"Старт",
"extraStartButtonText": "Настроить дополнительную кнопку \"Start\"",
"ifNothingHappensTryAnalogText": "Если ничего не происходит, попробуйте вместо этого присвоить аналоговому стику.",
"ifNothingHappensTryDpadText": "Если ничего не происходит, попробуйте вместо этого присвоить D-Pad.",
"ignoreCompletelyDescriptionText": "(не дайте этому контроллеру воздействовать на игру, или меню)",
"ignoreCompletelyDescriptionText": "(запретить геймпаду воздействовать на игру, или меню)",
"ignoreCompletelyText": "Игнорировать полностью",
"ignoredButton1Text": "Игнорируемая кнопка 1",
"ignoredButton2Text": "Игнорируемая кнопка 2",
@ -422,13 +423,13 @@
"runTrigger1Text": "Триггер для бега 1",
"runTrigger2Text": "Триггер для бега 2",
"runTriggerDescriptionText": "(аналоговые триггеры позволяют бегать с разной скоростью)",
"secondHalfText": "Используйте это для настройки второй половины\nустройства 'два контроллера в одном',\nкоторое показывается как один контроллер.",
"secondHalfText": "Используйте эту опцию для настройки второй\nполовины устройства \"два геймпада в одном\",\nдля использования в качестве одного геймпада.",
"secondaryEnableText": "Включить",
"secondaryText": "Вторичный контроллер",
"secondaryText": "Второй геймпад",
"startButtonActivatesDefaultDescriptionText": "(выключить, если ваша кнопка \"старт\" работает больше в качестве кнопки \"меню\")",
"startButtonActivatesDefaultText": "Кнопка Старт активирует стандартный виджет",
"titleText": "Настройка контроллера",
"twoInOneSetupText": "Настройка контроллера 2-в-1",
"titleText": "Настройка геймпада",
"twoInOneSetupText": "Настройка геймпада 2-в-1",
"uiOnlyDescriptionText": "(Запретить этому контроллеру присоединяться к игре)",
"uiOnlyText": "Ограничить только для меню",
"unassignedButtonsRunText": "Все неприсвоенные кнопки для бега",
@ -481,8 +482,8 @@
"challengesText": "Испытания",
"currentBestText": "Последний рекорд",
"customText": "Другое",
"entryFeeText": "Участие",
"forfeitConfirmText": "Так просто сдаться?",
"entryFeeText": "Участвовать",
"forfeitConfirmText": "Сдаться?",
"forfeitNotAllowedYetText": "Вы не можете покинуть это состязание.",
"forfeitText": "Сдаться",
"multipliersText": "Множители",
@ -651,14 +652,16 @@
"epicDescriptionFilterText": "${DESCRIPTION} в эпическом замедленном действии.",
"epicNameFilterText": "${NAME} в эпическом режиме",
"errorAccessDeniedText": "доступ запрещен",
"errorDeviceTimeIncorrectText": "Время на устройстве отстает на ${HOURS} часов.\nЭто вызывает проблемы.\nПожалуйста, проверьте настройки времени и часового пояса.",
"errorOutOfDiskSpaceText": "нет места на диске",
"errorSecureConnectionFailText": "Ошибка установки безопасного облачного соединения; сетевые функции могут дать сбой.",
"errorText": "Ошибка",
"errorUnknownText": "неизвестная ошибка",
"exitGameText": "Выйти из ${APP_NAME}?",
"exportSuccessText": "'${NAME}' экспортирован.",
"externalStorageText": "Внешняя память",
"failText": "Провал",
"fatalErrorText": "Ой, что-то потерялось или сломалось.\nПопытайтесь переустановить приложение или\nобратитесь к ${EMAIL} за помощью.",
"fatalErrorText": "Файлы игры повреждены или отсутствуют.\nПопробуйте переустановить приложение или\nобратитесь к ${EMAIL} за помощью.",
"fileSelectorWindow": {
"titleFileFolderText": "Выберите файл или папку",
"titleFileText": "Выберите файл",
@ -670,7 +673,7 @@
"finalScoresText": "Финальные очки",
"finalTimeText": "Финальное время",
"finishingInstallText": "Завершается установка, минутку...",
"fireTVRemoteWarningText": "* Для лучшего результата используйте\nигровые контроллеры или установите\nприложение '${REMOTE_APP_NAME}'\nна ваших телефонах и планшетах.",
"fireTVRemoteWarningText": "* Для лучшего результата используйте\nгеймпады или установите\nприложение '${REMOTE_APP_NAME}'\nна ваших телефонах и планшетах.",
"firstToFinalText": "Финал до ${COUNT} очков",
"firstToSeriesText": "Серия до ${COUNT} очков",
"fiveKillText": "ПЯТЕРЫХ ЗА РАЗ!!!",
@ -708,7 +711,7 @@
"addressFetchErrorText": "<ошибка запроса адресов>",
"appInviteInfoText": "Пригласите друзей поиграть в BombSquad и они\nполучат ${COUNT} бесплатных билетов. Ты получишь\n${YOU_COUNT} за каждого друга.",
"appInviteMessageText": "${NAME} отправил вам ${COUNT} билетов в ${APP_NAME}",
"appInviteSendACodeText": "Отправьте им код",
"appInviteSendACodeText": "Отправить им код",
"appInviteTitleText": "Приглашение в ${APP_NAME}",
"bluetoothAndroidSupportText": "(работает с любым устройством, поддерживающим Bluetooth)",
"bluetoothDescriptionText": "Создать/войти в лобби через Bluetooth:",
@ -829,7 +832,7 @@
"ticketPack4Text": "Огромная пачка билетов",
"ticketPack5Text": "Слоновая пачка билетов",
"ticketPack6Text": "Максимальная пачка билетов",
"ticketsFromASponsorText": "Получить ${COUNT} билетов\nот спонсора",
"ticketsFromASponsorText": "Посмотреть рекламу,\nчтобы получить ${COUNT} билетов",
"ticketsText": "Билетов: ${COUNT}",
"titleText": "Получить билеты",
"unavailableLinkAccountText": "Извините, но на этой платформе покупки недоступны.\nВ качестве решения, вы можете привязать этот аккаунт \nк аккаунту на другой платформе, и совершать покупки там.",
@ -840,6 +843,7 @@
"youHaveText": "У вас ${COUNT} билетов"
},
"googleMultiplayerDiscontinuedText": "Простите, сервис многопользовательской игры Google больше не поддерживается.\nЯ работаю над заменой так быстро, насколько это возможно.\nДо тех пор, пожалуйста выберете другой способ подключения.\n-Эрик",
"googlePlayPurchasesNotAvailableText": "Покупки в Google Play недоступны.\nВозможно, вам необходимо обновить приложение магазина.",
"googlePlayText": "Google Play",
"graphicsSettingsWindow": {
"alwaysText": "Всегда",
@ -1166,7 +1170,10 @@
"playlistsText": "Плей-листы",
"pleaseRateText": "Если вам нравится игра ${APP_NAME}, пожалуйста, подумайте о том,\nчтобы оценить ее или написать рецензию. Это обеспечивает полезную\nобратную связь и помогает поддержать дальнейшую разработку.\n\nСпасибо!\n- Эрик",
"pleaseWaitText": "Пожалуйста, подождите...",
"pluginClassLoadErrorText": "Ошибка при попытке загрузить класс плагина '${PLUGIN}': ${ERROR}",
"pluginInitErrorText": "Ошибка при инициализации плагина '${PLUGIN}': ${ERROR}",
"pluginsDetectedText": "Обнаружены новые плагины! Перезапустите игру, чтобы активировать их, или настройте их в настройках.",
"pluginsRemovedText": "${NUM} плагин(ов) больше не найдены.",
"pluginsText": "Плагины",
"practiceText": "Тренировка",
"pressAnyButtonPlayAgainText": "Нажмите любую кнопку чтобы играть снова...",
@ -1188,10 +1195,10 @@
},
"promoSubmitErrorText": "Ошибка отправки кода, проверьте своё интернете соединение",
"ps3ControllersWindow": {
"macInstructionsText": "Выключите питание на задней панели PS3, убедитесь, что Bluetooth\nвключен на вашем компьютере, а затем подключите контроллер к Маку\nс помощью кабеля USB для синхронизации. Теперь можно использовать\nкнопку контроллера 'PS' чтобы подключить его к Маку\nв проводном (USB) или беспроводном (Bluetooth) режиме.\n\nНа некоторых Маках при синхронизации может потребоваться код доступа.\nВ этом случае обратитесь к следующей инструкции или к гуглу.\n\n\n\n\nКонтроллеры PS3, связанные по беспроводной сети, должны появиться\nв списке устройств в Настройках системы -> Bluetooth. Возможно, вам придется\nудалить их из этого списка, если вы хотите снова использовать их с PS3.\n\nТакже всегда отключайте их от Bluetooth, когда он не используется,\nиначе будут садиться батарейки.\n\nBluetooth должен обрабатывать до 7 подключенных устройств,\nхотя у вас может получиться по-другому.",
"ouyaInstructionsText": "Чтобы использовать контроллер PS3 с OUYA, просто подключите его один раз\nс помощью кабеля USB для синхронизации. Это может отключить другие\nконтроллеры, тогда нужно перезагрузить OUYA и отсоединить кабель USB.\n\nПосле этого можно использовать кнопку 'PS' контроллера для беспроводного\nподключения. После игры нажмите и удерживайте кнопку 'PS' в течение\n10 секунд чтобы выключить контроллер, в противном случае он может\nостаться включенным и разрядит батарейки.",
"macInstructionsText": "Выключите питание на задней панели PS3, убедитесь, что Bluetooth\nвключен на вашем компьютере, а затем подключите геймпад к Mac\nс помощью кабеля USB для синхронизации. Теперь можно использовать\nкнопку геймпад 'PS' чтобы подключить его к Mac\nв проводном (USB) или беспроводном (Bluetooth) режиме.\n\nНа некоторых системах Mac при синхронизации может потребоватьсякод доступа.\nВ этом случае обратитесь к следующей инструкции или к гуглу.\n\n\n\n\nГеймпады PS3, связанные по беспроводной сети, должны появиться\nв списке устройств в Настройках системы -> Bluetooth. Возможно, вам придется\nудалить их из этого списка, если вы хотите снова использовать их с PS3.\n\nТакже всегда отключайте их от Bluetooth, когда он не используется,\nиначе будут садиться батарейки.\n\nBluetooth должен обрабатывать до 7 подключенных устройств,\nхотя у вас может получиться по-другому.",
"ouyaInstructionsText": "Чтобы использовать геймпад PS3 с OUYA, просто подключите его один раз\nс помощью кабеля USB для синхронизации. Это может отключить другие\nгеймпады, тогда нужно перезагрузить OUYA и отсоединить кабель USB.\n\nПосле этого можно использовать кнопку 'PS' геймпада для беспроводного\nподключения. После игры нажмите и удерживайте кнопку 'PS' в течение\n10 секунд чтобы выключить геймпад, в противном случае он может\nостаться включенным и разрядит батарейки.",
"pairingTutorialText": "видео-тьюториал по синхронизации",
"titleText": "Использование контроллеров PS3 с ${APP_NAME}:"
"titleText": "Использование геймпада PS3 с ${APP_NAME}:"
},
"publicBetaText": "ОТКРЫТАЯ БЕТА-ВЕРСИЯ",
"punchBoldText": "УДАР",
@ -1201,7 +1208,7 @@
"purchasingText": "Покупка...",
"quitGameText": "Выйти из ${APP_NAME}?",
"quittingIn5SecondsText": "Выход через 5 секунд...",
"randomPlayerNamesText": "Дима, Кузя, Вован, Маха, Русский, Какуля, Бибер, Борька, Няшка, Толян, Ержан, Дибисяра, Вася, Морген, Серёга, Ваня, Кеша, Жорик, Стёпа, Эдгар, Циган",
"randomPlayerNamesText": "Дима, Кузя, Вован, Маха, Русский, Какуля, Бибер, Борька, Няшка, Толян, Ержан, Дибисяра, Вася, Морген, Серёга, Ваня, Кеша, Жорик, Стёпа, Эдгар, Цыган, Олег, Егор, Ёршик",
"randomText": "Случайный",
"rankText": "Ранг",
"ratingText": "Рейтинг",
@ -1508,9 +1515,9 @@
"Get the flag to the enemy end zone.": "Отнесите флаг в зону защиты противника.",
"How fast can you defeat the ninjas?": "Как быстро вы сможете победить ниндзя?",
"Kill a set number of enemies to win.": "Убейте заданное число врагов, чтобы выиграть.",
"Last one standing wins.": "Побеждает последний стоящий на ногах.",
"Last remaining alive wins.": "Побеждает последний в живых.",
"Last team standing wins.": "Побеждает последняя стоящая на ногах команда.",
"Last one standing wins.": "Побеждает последний живой игрок.",
"Last remaining alive wins.": "Побеждает последний живой игрок.",
"Last team standing wins.": "Побеждает последняя живая команда.",
"Prevent enemies from reaching the exit.": "Не дайте врагам дойти до выхода.",
"Reach the enemy flag to score.": "Доберитесь до вражеского флага чтобы набрать очки.",
"Return the enemy flag to score.": "Принесите вражеский флаг на базу, чтобы набрать очки.",
@ -1536,8 +1543,8 @@
"Touch the enemy flag.": "Коснитесь вражеского флага.",
"carry the flag for ${ARG1} seconds": "пронесите флаг в течение ${ARG1} секунд",
"kill ${ARG1} enemies": "убейте ${ARG1} врагов",
"last one standing wins": "побеждает последний стоящий на ногах",
"last team standing wins": "побеждает последняя стоящая на ногах команда",
"last one standing wins": "побеждает последний живой игрок",
"last team standing wins": "побеждает последняя живая команда",
"return ${ARG1} flags": "принесите ${ARG1} флагов на базу",
"return 1 flag": "принесите 1 флаг на базу",
"run ${ARG1} laps": "пробегите ${ARG1} кругов",
@ -1874,7 +1881,7 @@
},
"twoKillText": "ДВОИХ ЗА РАЗ!",
"unavailableText": "недоступно",
"unconfiguredControllerDetectedText": "Обнаружен ненастроенный контроллер:",
"unconfiguredControllerDetectedText": "Обнаружен ненастроенный геймпад:",
"unlockThisInTheStoreText": "Это должно быть разблокировано в магазине.",
"unlockThisProfilesText": "Чтобы создать более ${NUM} профиль, Вам необходимо:",
"unlockThisText": "Чтобы разблокировать это, вам нужно:",
@ -1940,13 +1947,15 @@
"winsPlayerText": "Победил ${NAME}!",
"winsTeamText": "Победили ${NAME}!",
"winsText": "${NAME} выиграл!",
"workspaceSyncErrorText": "Ошибка при попытке синхронизации ${WORKSPACE}. Посмотрите лог для информации.",
"workspaceSyncReuseText": "Не может синронизировать ${WORKSPACE}. Будет использоватся прошлая синхронизация.",
"worldScoresUnavailableText": "Мировые результаты недоступны.",
"worldsBestScoresText": "Лучшие в мире очки",
"worldsBestTimesText": "Лучшее в мире время",
"xbox360ControllersWindow": {
"getDriverText": "Скачать драйвер",
"macInstructions2Text": "Для использования контроллеров по беспроводной связи, вам также\nпотребуется ресивер, который поставляется с \"беспроводным контроллером\nXbox 360 для Windows\". Один ресивер позволяет подключить до 4 контроллеров.\n\nВнимание: ресиверы сторонних производителей не будут работать с этим драйвером,\nубедитесь, что на вашем ресивере написано \"Microsoft\", а не \"XBOX 360\".\nMicrosoft больше не продает их отдельно, так что вам нужно будет найти\nресивер в комплекте с контроллером, либо искать на ebay.\n\nЕсли вы считаете это полезным, можете отправить денег разработчику\nдрайвера на его сайте.",
"macInstructionsText": "Для использования контроллеров Xbox 360 необходимо\nустановить драйвер Mac, доступный по ссылке ниже.\nОн работает и с проводными и беспроводными контроллерами.",
"macInstructions2Text": "Для использования контроллеров по беспроводной связи, вам также\nпотребуется ресивер, который поставляется с \"беспроводным геймпадом\nXbox 360 для Windows\". Один ресивер позволяет подключить до 4 контроллеров.\n\nВнимание: ресиверы сторонних производителей не будут работать с этим драйвером,\nубедитесь, что на вашем ресивере написано \"Microsoft\", а не \"XBOX 360\".\nMicrosoft больше не продает их отдельно, так что вам нужно будет найти\nресивер в комплекте с геймпадом, либо искать на ebay.\n\nЕсли вы считаете это полезным, можете отправить денег разработчику\nдрайвера на его сайте.",
"macInstructionsText": "Для использования геймпада Xbox 360 необходимо\nустановить драйвер Mac, доступный по ссылке ниже.\nОн работает и с проводными и беспроводными геймпадами.",
"macInstructionsTextScale": 0.8,
"ouyaInstructionsText": "Для использования проводных контроллеров Xbox 360 в BombSquad,\nпросто подключите их к USB-порту вашего устройства. Для нескольких\nконтроллеров можно использовать концентратор USB.\n\nДля использования беспроводных контроллеров вам понадобится беспроводной\nресивер который поставляется в наборе \"беспроводного геймпада Xbox 360\nдля Windows\" или продается отдельно. Каждый ресивер подключается\nк порту USB и позволяет подключать до 4 беспроводных контроллеров.",
"titleText": "Использование контроллеров Xbox 360 в ${APP_NAME}:"

View file

@ -328,6 +328,7 @@
"achievementsRemainingText": "Неизвршена достигнућа:",
"achievementsText": "Достигнућа",
"achievementsUnavailableForOldSeasonsText": "Извини, информације нису доступне за старије сезоне.",
"activatedText": "${THING} Активиран",
"addGameWindow": {
"getMoreGamesText": "Додај више игри...",
"titleText": "Додај игру"
@ -627,7 +628,9 @@
"epicDescriptionFilterText": "${DESCRIPTION} у епско успореној игри.",
"epicNameFilterText": "Епски ${NAME}",
"errorAccessDeniedText": "приступ одбијен",
"errorDeviceTimeIncorrectText": "Време на твом уређају је нетачно за ${HOURS} сати.\nОво може изазвати проблеме.\nМолимо вас проверите подешавања времена и временску зону.",
"errorOutOfDiskSpaceText": "нема места на диску",
"errorSecureConnectionFailText": "Немогуће успоставити везу са клаудом; функција мреже може престати.",
"errorText": "Грешка",
"errorUnknownText": "непозната грешка",
"exitGameText": "Изађи из \"${APP_NAME}\"?",
@ -1119,7 +1122,10 @@
"playlistsText": "Листе игара",
"pleaseRateText": "Ако уживате у \"${APP_NAME}\" игри, молимо вас да издвојите\nмало времена да оцените апликацију или напишете коментар.\nОво нам даје корисне информације и помаже у будућем развоју.\n\nХвала!\n-Ерик",
"pleaseWaitText": "Молимо вас сачекајте...",
"pluginsDetectedText": "Нови додатак/ци пронађен/и. Укључи/подеси их у подешавањима.",
"pluginClassLoadErrorText": "Грешка учитавања плугина класе '${PLUGIN}': ${ERROR}",
"pluginInitErrorText": "Грешка иницијације плугина '${PLUGIN}': ${ERROR}",
"pluginsDetectedText": "Нови додатак/ци пронађени. Рестартуј да их активираш,или конфигуришеш у подешавањима.",
"pluginsRemovedText": "${NUM} додатак/ више не постоје.",
"pluginsText": "Додаци",
"practiceText": "Вежбање",
"pressAnyButtonPlayAgainText": "Притисни било које дугме да одиграш опет...",
@ -1855,6 +1861,8 @@
"winsPlayerText": "${NAME} је победио!",
"winsTeamText": "${NAME} је победио!",
"winsText": "${NAME} је победио!",
"workspaceSyncErrorText": "Грешка синхронизовања ${WORKSPACE}. Погледајте лог за више детаља.",
"workspaceSyncReuseText": "Синхронизовање ${WORKSPACE} неуспешно. Коришћење претходно синхронизоване верзије.",
"worldScoresUnavailableText": "Светски резултати недоступни.",
"worldsBestScoresText": "Најбољи резултати на свету",
"worldsBestTimesText": "Најбоља времена на свету",

View file

@ -32,7 +32,7 @@
"signInWithTestAccountInfoText": "(Cuenta de prueba, usa la cuenta del dispositivo para avanzar)",
"signInWithTestAccountText": "Registrarse con una Cuenta de Prueba",
"signInWithV2InfoText": "(una cuenta que funciona en todas las plataformas)",
"signInWithV2Text": "Inicie cesión con una cuenta de bosquad",
"signInWithV2Text": "Inicie sesión con tú cuenta de BombSquad",
"signOutText": "Cerrar Sesión",
"signingInText": "Iniciando sesión...",
"signingOutText": "Cerrando sesión...",
@ -332,6 +332,7 @@
"achievementsRemainingText": "Logros pendientes:",
"achievementsText": "Logros",
"achievementsUnavailableForOldSeasonsText": "Lo sentimos, los logros específicos no están disponibles para temporadas anteriores.",
"activatedText": "${THING} activado.",
"addGameWindow": {
"getMoreGamesText": "Más Juegos...",
"titleText": "Agregar Juego"
@ -651,7 +652,9 @@
"epicDescriptionFilterText": "${DESCRIPTION} En cámara lenta épica.",
"epicNameFilterText": "${NAME} - Modo épico",
"errorAccessDeniedText": "acceso negado",
"errorDeviceTimeIncorrectText": "La hora actual de tu dispositivo es ${HOURS} horas.\nEsto podría causar problemas.\nPorfavor verifica la hora y zona horaria en ajustes.",
"errorOutOfDiskSpaceText": "insuficiente espacio en disco",
"errorSecureConnectionFailText": "No se puede establecer una conexión segura en la nube; La red podría estar fallando",
"errorText": "Error",
"errorUnknownText": "error desconocido",
"exitGameText": "¿Salir de ${APP_NAME}?",
@ -838,6 +841,7 @@
"youHaveText": "tienes ${COUNT} boletos"
},
"googleMultiplayerDiscontinuedText": "Lo siento, Google's multijugador servicio ya no esta mas disponible.\nEstoy trabajando en un reemplazo lo mas rapido posible.\nHasta entonces, por favor intente con otro metodo de conexión.\n-Eric",
"googlePlayPurchasesNotAvailableText": "Las compras de Google Play no están disponibles.\nEs posible que deba actualizar la aplicación de su tienda.",
"googlePlayText": "Google Play",
"graphicsSettingsWindow": {
"alwaysText": "Siempre",
@ -1079,8 +1083,8 @@
"modeArcadeText": "Modo Arcade",
"modeClassicText": "Modo Clásico",
"modeDemoText": "Modo De Demostración",
"mostValuablePlayerText": "Jugador más valorado",
"mostViolatedPlayerText": "Jugador más agredido",
"mostValuablePlayerText": "Jugador más Valorado",
"mostViolatedPlayerText": "Jugador más Violado",
"mostViolentPlayerText": "Jugador más Violento",
"moveText": "Mover",
"multiKillText": "¡¡¡${COUNT}-COMBO!!!",
@ -1178,7 +1182,10 @@
"playlistsText": "Listas de Juego",
"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 al cargar la clase del plugin '${PLUGIN}': ${ERROR}",
"pluginInitErrorText": "Error al iniciar el plugin '${PLUGIN}': ${ERROR}",
"pluginsDetectedText": "Nuevos complemento(s) detectados. Reinicie para activarlos, o configúrelos en la configuración",
"pluginsRemovedText": "${NUM} plugin(s) ya no se encuentran.",
"pluginsText": "Plugins",
"practiceText": "Práctica",
"pressAnyButtonPlayAgainText": "Oprime cualquier botón jugar de nuevo...",
@ -1960,6 +1967,8 @@
"winsPlayerText": "¡${NAME} Gana!",
"winsTeamText": "¡${NAME} Gana!!",
"winsText": "¡${NAME} Gana!",
"workspaceSyncErrorText": "Error al sincronizar ${WORKSPACE}. Mira el registro para mas detalles.",
"workspaceSyncReuseText": "No se puede sincronizar ${WORKSPACE}. Reusando la versión sincronizada anterior.",
"worldScoresUnavailableText": "Puntuaciones globales no disponibles.",
"worldsBestScoresText": "Mejores puntuaciones Mundiales",
"worldsBestTimesText": "Mejores tiempos Mundiales",

View file

@ -324,6 +324,7 @@
"achievementsRemainingText": "மீதமுள்ள சாதனைகள்:",
"achievementsText": "சாதனைகள்",
"achievementsUnavailableForOldSeasonsText": "மன்னிக்கவும், சாதனை விவரங்கள் பழைய பருவங்களுக்கு கிடைக்கவில்லை.",
"activatedText": "${THING} செயல்படுத்தப்பட்டது.",
"addGameWindow": {
"getMoreGamesText": "மேலும் விளையாட்டுகளைப் பெறுங்கள்...",
"titleText": "விளையாட்டைச் சேர்"
@ -623,7 +624,9 @@
"epicDescriptionFilterText": "${DESCRIPTION} காவிய மெதுவான இயக்கத்தில்.",
"epicNameFilterText": "காவியம் ${NAME}",
"errorAccessDeniedText": "அணுகல் மறுக்கப்பட்டது",
"errorDeviceTimeIncorrectText": "உங்கள் சாதனத்தின் நேரம் ${HOURS} மணிநேரம் உள்ளது.\nஇதனால் பிரச்னைகள் ஏற்பட வாய்ப்புள்ளது.\nஉங்கள் நேரம் மற்றும் நேர மண்டல அமைப்புகளைச் சரிபார்க்கவும்.",
"errorOutOfDiskSpaceText": "வட்டு இடத்திற்கு வெளியே",
"errorSecureConnectionFailText": "பாதுகாப்பான கிளவுட் இணைப்பை நிறுவ முடியவில்லை; பிணைய செயல்பாடு தோல்வியடையலாம்.",
"errorText": "பிழை",
"errorUnknownText": "அறியப்படாத பிழை",
"exitGameText": "${APP_NAME} இலிருந்து வெளியேறவா?",
@ -786,7 +789,7 @@
"ticketPack4Text": "ஜம்போ டிக்கெட் பேக்",
"ticketPack5Text": "மம்மத் டிக்கெட் பேக்",
"ticketPack6Text": "அல்டிமேட் டிக்கெட் பேக்",
"ticketsFromASponsorText": "${COUNT} டிக்கெட்டுகளைப் பெறுங்கள்\nஒரு ஸ்பான்சரிடமிருந்து",
"ticketsFromASponsorText": "ஒரு விளம்பரத்தைப் பார்த்து\n${COUNT} டிக்கெட்டுகளை பெறுங்கள்",
"ticketsText": "${COUNT} டிக்கெட்டுகள்",
"titleText": "டிக்கெட்டுகளைப் பெறுங்கள்",
"unavailableLinkAccountText": "மன்னிக்கவும், இந்த தளத்தில் கொள்முதல் கிடைக்கவில்லை.\nஒரு தீர்வாக, இந்தக் கணக்கை ஒரு கணக்குடன் இணைக்கலாம்\nமற்றொரு தளம் மற்றும் அங்கு கொள்முதல் செய்யுங்கள்.",
@ -797,6 +800,7 @@
"youHaveText": "உங்களிடம் ${COUNT} டிக்கெட்டுகள் உள்ளன"
},
"googleMultiplayerDiscontinuedText": "மன்னிக்கவும், கூகுளின் மல்டிபிளேயர் சேவை இனி கிடைக்காது.\nநான் முடிந்தவரை விரைவாக மாற்றுவதற்கு வேலை செய்கிறேன்.\nஅதுவரை, வேறு இணைப்பு முறையை முயற்சிக்கவும்.\n-எரிக்",
"googlePlayPurchasesNotAvailableText": "Google Play வாங்குதல்கள் கிடைக்கவில்லை.\nஉங்கள் ஸ்டோர் பயன்பாட்டைப் புதுப்பிக்க வேண்டியிருக்கலாம்.",
"googlePlayText": "கூகுள் பிளே",
"graphicsSettingsWindow": {
"alwaysText": "எப்போதும்",
@ -1108,7 +1112,10 @@
"playlistsText": "பிளேலிஸ்ட்கள்",
"pleaseRateText": "நீங்கள் ${APP_NAME} ஐ அனுபவிக்கிறீர்கள் என்றால், தயவுசெய்து எடுப்பதைக் கவனியுங்கள்\nதருணம் மற்றும் மதிப்பிடுதல் அல்லது விமர்சனம் எழுதுதல். இது வழங்குகிறது\nபயனுள்ள கருத்து மற்றும் எதிர்கால வளர்ச்சிக்கு உதவுகிறது.\n\nநன்றி!\n-எரிக்",
"pleaseWaitText": "தயவுசெய்து காத்திருங்கள்...",
"pluginClassLoadErrorText": "செருகுநிரல் வகுப்பை ஏற்றுவதில் பிழை '${PLUGIN}': ${ERROR}",
"pluginInitErrorText": "'${PLUGIN}' செருகுநிரலைத் தொடங்குவதில் பிழை: ${ERROR}",
"pluginsDetectedText": "புதிய செருகுநிரல்(கள்) கண்டறியப்பட்டது. அவற்றைச் செயல்படுத்த மீண்டும் தொடங்கவும் அல்லது அமைப்புகளில் உள்ளமைக்கவும்.",
"pluginsRemovedText": "${NUM} செருகுநிரல்(கள்) இனி காணப்படவில்லை.",
"pluginsText": "செருகுநிரல்கள்",
"practiceText": "பயிற்சி",
"pressAnyButtonPlayAgainText": "மீண்டும் விளையாட எந்த பட்டனையும் அழுத்தவும்...",
@ -1844,6 +1851,8 @@
"winsPlayerText": "${NAME} வெற்றி!",
"winsTeamText": "${NAME} வெற்றி!",
"winsText": "${NAME} வெற்றி!",
"workspaceSyncErrorText": "${WORKSPACE} ஐ ஒத்திசைப்பதில் பிழை. விவரங்களுக்கு பதிவைப் பார்க்கவும்.",
"workspaceSyncReuseText": "${WORKSPACE} ஐ ஒத்திசைக்க முடியவில்லை. முந்தைய ஒத்திசைக்கப்பட்ட பதிப்பை மீண்டும் பயன்படுத்துகிறது.",
"worldScoresUnavailableText": "உலக ஸ்கோர் கிடைக்கவில்லை.",
"worldsBestScoresText": "உலகின் சிறந்த மதிப்பெண்கள்",
"worldsBestTimesText": "உலகின் சிறந்த நேரங்கள்",

View file

@ -326,6 +326,7 @@
"achievementsRemainingText": "Kalan Başarılar;",
"achievementsText": "Başarı",
"achievementsUnavailableForOldSeasonsText": "Üzgünüz, başarı detayları eski sezonlar için kullanılamaz.",
"activatedText": "${THING} aktifleştirildi.",
"addGameWindow": {
"getMoreGamesText": "Daha Çok Oyun...",
"titleText": "Oyun Ekle"
@ -624,7 +625,9 @@
"epicDescriptionFilterText": "${DESCRIPTION} epik ağırçekim.",
"epicNameFilterText": "Epik ${NAME}",
"errorAccessDeniedText": "erişim reddedildi",
"errorDeviceTimeIncorrectText": "Eyvah! Cihazın ile sunucu arasındaki zaman farkı ${HOURS} saat.\nBu bazı sorunlara yol açabilir.\nLütfen saat ve saat dilimi ayarlarını kontrol et.",
"errorOutOfDiskSpaceText": "disk alanı doldu",
"errorSecureConnectionFailText": "Güvenli bulut bağlantısı kurulamadı; ağ işlevi başarısız olabilir.",
"errorText": "Hata",
"errorUnknownText": "bilinmeyen hata",
"exitGameText": ık ${APP_NAME}?",
@ -787,7 +790,7 @@
"ticketPack4Text": "Dev Bilet Paketi",
"ticketPack5Text": "Muazzam Bilet Paketi",
"ticketPack6Text": "En Üst Düzey Bilet Paketi",
"ticketsFromASponsorText": "Bir sponsordan\n${COUNT} Bilet Al",
"ticketsFromASponsorText": "${COUNT} Bilet için\nbir reklam izle",
"ticketsText": "${COUNT} Bilet",
"titleText": "Bilet Al",
"unavailableLinkAccountText": "Üzgünüz, Satın almalar bu patformda kullanılamaz.\nBu geçici oldugu gibi, bu hesabını diğer platformlardaki\nhesablara bağlayabilir oradan satın alma işlemi yapbilirsin.",
@ -798,6 +801,7 @@
"youHaveText": "${COUNT} biletin var"
},
"googleMultiplayerDiscontinuedText": "Üzgünüz, Google'ın çok oyunculu servisi şu anda çalışmıyor.\nBir yer değişimi için olabildiğince hızlı çalışıyorum.\nO zamana kadar, lütfen başka bir bağlantı yöntemi deneyin.\n-Eric",
"googlePlayPurchasesNotAvailableText": "Google Play satın alma işlemleri mevcut değildir.\nMağaza uygulamanızı güncellemeniz gerekebilir.",
"googlePlayText": "Google Play",
"graphicsSettingsWindow": {
"alwaysText": "Her Zaman",
@ -1108,7 +1112,10 @@
"playlistsText": "ÇalmaListesi",
"pleaseRateText": "Eğer ${APP_NAME}'dan zevk aldıysan, lütfen oyunu değerlerdir\nve yorumunu yaz. Bu oyunun daha fazla geliştirilmesine\nyardımcı olur.\n\nteşekkürler!\n-eric",
"pleaseWaitText": "Lütfen bekle...",
"pluginClassLoadErrorText": "'${PLUGIN}' eklenti sınıfı yüklenirken hata oluştu: ${ERROR}",
"pluginInitErrorText": "'${PLUGIN}' eklentisi başlatılırken hata oluştu: ${ERROR}",
"pluginsDetectedText": "Yeni eklentiler tespit edildi. Onları etkinleştirmek veya ayarlarda yapılandırmak için oyunu yeniden başlatın.",
"pluginsRemovedText": "${NUM} eklenti(ler) artık bulunmuyor.",
"pluginsText": "Eklentiler",
"practiceText": "Alıştırma",
"pressAnyButtonPlayAgainText": "Tekrar oynamak için bir tuşa basın...",
@ -1844,6 +1851,8 @@
"winsPlayerText": "${NAME} Kazandı!",
"winsTeamText": "${NAME} Kazandı!",
"winsText": "${NAME} Kazandı!",
"workspaceSyncErrorText": "${WORKSPACE} eşitlemesinde hata oluştu. Detaylar için günlüğü inceleyin.",
"workspaceSyncReuseText": "${WORKSPACE} eşitlenemiyor. Önceden eşitlenmiş sürüm kullanılıyor.",
"worldScoresUnavailableText": "Global Skorlar Mevcut Değil.",
"worldsBestScoresText": "Global En-İyi Skorlar",
"worldsBestTimesText": "Global En-İyi Süreler",

View file

@ -5,27 +5,27 @@
"achievementProgressText": "Obietivi: ${COUNT} de ${TOTAL}",
"campaignProgressText": "Progreso canpagna [Defìsiłe]: ${PROGRESS}",
"changeOncePerSeason": "A te połi canbiar sto dato soło na volta par stajon.",
"changeOncePerSeasonError": "Par modifegar (${NUM} days) A te ghè da spetar ła stajon pròsema.",
"changeOncePerSeasonError": "Par canbiarlo te ghè da spetar ła pròsema stajon (${NUM} days).",
"customName": "Nome parsonałizà",
"linkAccountsEnterCodeText": "Insarisi còdaze",
"linkAccountsGenerateCodeText": "Jènara còdaze",
"linkAccountsInfoText": "(sparpagna progresi infrà dispozitivi defarenti)",
"linkAccountsInstructionsNewText": "Par cołegar do account, jènara un còdaze inte'l primo\ndispozitivo e insarìseło intel segondo. I dati del\nsegondo account i vegnarà sparpagnài so tuti do i account\n(i dati del primo account i ndarà perdesti).\n\nA te połi cołegar fin a ${COUNT} account.\n\nINPORTANTE: cołega soło i account de to propiedà.\nSe A te cołeghi account de calche to amigo, A no sarè\npì boni de zugar online intel mèdemo momento.",
"linkAccountsInfoText": "(sparpagna progresi infrà dispozidivi defarenti)",
"linkAccountsInstructionsNewText": "Par cołegar do account, jènara un còdaze inte'l primo\ndispozidivo e insarìseło intel segondo. I dati del\nsegondo account i vegnarà sparpagnài so tuti do i account\n(i dati del primo account i ndarà perdesti).\n\nTe połi cołegar fin a ${COUNT} account.\n\nINPORTANTE: cołega soło i account de to propiedà.\nSe te cołeghi account de calche to amigo, no sarè\npì boni de zugar online intel mèdemo momento.",
"linkAccountsText": "Cołega account",
"linkedAccountsText": "Account cołegài:",
"nameChangeConfirm": "Vutu canbiar el nome del to account co ${NAME}?",
"resetProgressConfirmNoAchievementsText": "A te si drio ełimenar i to progresi so ła modałidà\ncooparadiva e i to punteji łogałi (ma miga i to biłieti).\nSta asion no ła połe mìa èsar anułada. Vutu ndar vanti?",
"resetProgressConfirmText": "A te si drio ełimenar i to progresi so ła\nmodałidà cooparadiva, i to obietivi e i to punteji\nłogałi (ma miga i to biłieti). Sta asion\nno ła połe mìa èsar anułada. Vutu ndar vanti?",
"resetProgressConfirmNoAchievementsText": "Te si drio ełimenar i to progresi so ła modałidà\ncooparadiva e i to punteji łogałi (ma miga i to biłieti).\nSta asion no ła połe èsar anułada. Vutu ndar vanti?",
"resetProgressConfirmText": "Te si drio ełimenar i to progresi so ła\nmodałidà cooparadiva, i to obietivi e i to punteji\nłogałi (ma miga i to biłieti). Sta asion\nno ła połe èsar anułada. Vutu ndar vanti?",
"resetProgressText": "Ełìmena progresi",
"setAccountName": "Inposta un nome utente",
"setAccountNameDesc": "Sełesiona el nome da vizuałizar sol to account.\nA te połi doparar el nome da uno de i to account\ncołegài o crear un nome parsonałizà ma ùnivogo.",
"signInInfoText": "Conétate par tirar sù biłieti, batajar online e\nsparpagnar i to progresi infrà dispozitivi defarenti.",
"signInInfoText": "Conétate par tirar sù biłieti, batajar online e\nsparpagnar i to progresi infrà dispozidivi defarenti.",
"signInText": "Conétate",
"signInWithDeviceInfoText": "(par 'sto dispozitivo A ze disponìbiłe un soło account automàtego)",
"signInWithDeviceText": "Conétate co l'account del dispozitivo",
"signInWithDeviceInfoText": "(par 'sto dispozidivo ze disponìbiłe un soło account automàtego)",
"signInWithDeviceText": "Conétate co l'account del dispozidivo",
"signInWithGameCircleText": "Conétate co Game Circle",
"signInWithGooglePlayText": "Conétate co Google Play",
"signInWithTestAccountInfoText": "(account de proa vecio: in fuduro dòpara i account del dispozitivo)",
"signInWithTestAccountInfoText": "(account de proa vecio: in fuduro dòpara i account del dispozidivo)",
"signInWithTestAccountText": "Conétate co un account de proa",
"signInWithV2InfoText": "(un account che fusiona so tute łe piataforme)",
"signInWithV2Text": "Acedi co un account BombSquad",
@ -38,7 +38,7 @@
"unlinkAccountsText": "Descołega account",
"v2LinkInstructionsText": "Acedi o dòpara 'sto link par crear un account.",
"viaAccount": "(doparando ${NAME})",
"youAreSignedInAsText": "A te zugarè cofà:"
"youAreSignedInAsText": "Te zugarè cofà:"
},
"achievementChallengesText": "Obietivi sfide",
"achievementText": "Obietivo",
@ -323,14 +323,15 @@
},
"achievementsRemainingText": "Obietivi restanti:",
"achievementsText": "Obietivi",
"achievementsUnavailableForOldSeasonsText": "Ne despiaze, i obietivi de łe stajon pasàe no i ze mìa disponìbiłe.",
"achievementsUnavailableForOldSeasonsText": "Ne despiaze, i obietivi de łe stajon pasàe no i ze pì disponìbiłi.",
"activatedText": "${THING} ativà.",
"addGameWindow": {
"getMoreGamesText": "Otien pì łevełi...",
"titleText": "Zonta zugo"
},
"allowText": "Parmeti",
"alreadySignedInText": "El to account el ze in dòparo inte nantro\ndispozitivo: canbia account o sara sù el zugo\ninte chełaltro to dispozitivo e proa danovo.",
"apiVersionErrorText": "A no ze mìa posìbiłe cargar el mòduło ${NAME}, el se refarise a ła varsion ${VERSION_USED}. A serve invese ła ${VERSION_REQUIRED}.",
"alreadySignedInText": "El to account el ze in dòparo inte nantro\ndispozidivo: canbia account o sara sù el zugo\ninte chełaltro to dispozidivo e proa danovo.",
"apiVersionErrorText": "Inposìbiłe cargar el mòduło ${NAME}, el se refarise a ła varsion ${VERSION_USED}. Serve invese ła ${VERSION_REQUIRED}.",
"audioSettingsWindow": {
"headRelativeVRAudioInfoText": "(Ativa \"Auto\" soło co A te tachi sù łe fonarołe par ła realtà virtuałe)",
"headRelativeVRAudioText": "Àudio par fonarołe VR",
@ -354,19 +355,19 @@
"buttonText": "boton",
"canWeDebugText": "Ghetu caro che BombSquad el reporte in automàtego bai,\nblochi e informasion baze só'l so dòparo a'l dezviłupador?\n\n'Sti dati no i contien miga informasion parsonałi ma i ło\njuta a far ndar ben el zugo sensa blochi o bai.",
"cancelText": "Anuła",
"cantConfigureDeviceText": "Ne despiaze, ${DEVICE} no'l ze mìa configuràbiłe.",
"cantConfigureDeviceText": "Ne despiaze, ${DEVICE} no'l ze miga configuràbiłe.",
"challengeEndedText": "'Sta sfida ła ze fenìa.",
"chatMuteText": "Siłensia ciacołada",
"chatMutedText": "Ciacołada siłensiada",
"chatUnMuteText": "Reativa son",
"choosingPlayerText": "<sernisi zugador>",
"completeThisLevelToProceedText": "A te ghè da conpletar 'sto\nłeveło par ndar vanti!",
"completeThisLevelToProceedText": "Par ndar vanti te ghè\nda conpletar 'sto łeveło!",
"completionBonusText": "Premio de concruzion",
"configControllersWindow": {
"configureControllersText": "Configura controładori",
"configureKeyboard2Text": "Configura botonera P2",
"configureKeyboardText": "Configura botonera",
"configureMobileText": "Dòpara dispozitivi cofà controładori",
"configureMobileText": "Dòpara dispozidivi cofà controładori",
"configureTouchText": "Configura schermo tàtiłe",
"ps3Text": "Controładori PS3",
"titleText": "Controładori",
@ -374,7 +375,7 @@
"xbox360Text": "Controładori Xbox 360"
},
"configGamepadSelectWindow": {
"androidNoteText": "Nota: ła conpatibiłidà par i controładori ła muda drio dispozitivo e varsion de Android.",
"androidNoteText": "Nota: ła conpatibiłidà par i controładori ła muda drio dispozidivo e varsion de Android.",
"pressAnyButtonText": "Struca un boton calsìase de'l controłador\nche A te vołi configurar...",
"titleText": "Configura controładori"
},
@ -418,13 +419,13 @@
"twoInOneSetupText": "Configurasion controłador 2 so 1",
"uiOnlyDescriptionText": "(par evitar che 'sto controłador el posa zontarse inte na partìa)",
"uiOnlyText": "Łìmida el dòparo de'l menù",
"unassignedButtonsRunText": "Tuti i botoni mìa defenìi i fà córar",
"unsetText": "<mìa defenìo>",
"unassignedButtonsRunText": "Tuti i botoni miga defenìi i fà córar",
"unsetText": "<miga defenìo>",
"vrReorientButtonText": "Boton de repozision VR"
},
"configKeyboardWindow": {
"configuringText": "Configurasion ${DEVICE}",
"keyboard2NoteText": "Nota: ła parte pì granda de łe botonere łe połe rejistrar\nsoło un fià de comandi a'l colpo. Donca, par zugar in du,\nA se ndarìa mejo doparar do botonere defarenti. Tien daconto\nche A te gavarè da defenir in tuti i cazi botoni unìvoghi\npar i tuti do i zugadori."
"keyboard2NoteText": "Nota: ła parte pì granda de łe botonere łe połe rejistrar\nsoło un fià de comandi a'l colpo. Donca, par zugar in du,\nse ndarìa mejo doparar do botonere defarenti. Tien daconto\nche te gavarè da defenir in tuti i cazi botoni unìvoghi\npar i tuti do i zugadori."
},
"configTouchscreenWindow": {
"actionControlScaleText": "Grandesa controło",
@ -445,13 +446,13 @@
"connectMobileDevicesWindow": {
"amazonText": "Amazon Store",
"appStoreText": "App Store",
"bestResultsText": "Par zugar co rezultài pì boni A te serve na rede wifi ràpida.\nA te połi redùzar el retardo (lag) de ła rede zugando visin al\ntrazmetidor del segnałe, desconetendo altri dispozitivi sensa\nfiło o conetendo lospitador del zugo co un cavo ethernet.",
"explanationText": "Par doparar un tełèfono o un tołeto cofà un controłador sensa fiło,\ninstàłaghe rento lapl \"${REMOTE_APP_NAME}\". Par zugar a ${APP_NAME}\nA te połi conétar col wifi tuti i dispozitivi che A te vołi! A ze gratis!",
"bestResultsText": "Par zugar co rezultài pì boni te serve na rede wifi ràpida.\nTe połi redùzar el retardo (lag) de ła rede zugando visin al\ntrazmetidor del segnałe, desconetendo altri dispozidivi sensa\nfiło o conetendo lospitador del zugo co un cavo ethernet.",
"explanationText": "Par doparar un tełèfono o un tołeto cofà un controłador sensa fiło,\ninstàłaghe rento lapl \"${REMOTE_APP_NAME}\". Par zugar a ${APP_NAME}\nte połi conétar col wifi tuti i dispozidivi che te vołi! Ze agratis!",
"forAndroidText": "par Android:",
"forIOSText": "par iOS:",
"getItForText": "Descarga ${REMOTE_APP_NAME} par iOS so l'AppStore de\nApple o par Android so'l Store de Google Play o l'Amazon Store",
"googlePlayText": "Google Play",
"titleText": "Doparar dispozitivi mòbiłi cofà controładori:"
"titleText": "Doparar dispozidivi mòbiłi cofà controładori:"
},
"continuePurchaseText": "Vutu ndar vanti par ${PRICE}?",
"continueText": "Sì!",
@ -466,7 +467,7 @@
"currentBestText": "El mejo par deso",
"customText": "E in pì...",
"entryFeeText": "Costo",
"forfeitConfirmText": "Vutu dalvero dàrgheła vinta?",
"forfeitConfirmText": "Vutu dabon dàrgheła vinta?",
"forfeitNotAllowedYetText": "Deso A no te połi miga dàrgheła vinta suito.",
"forfeitText": "Dàgheła vinta",
"multipliersText": "Moltiplegadori",
@ -533,7 +534,7 @@
"stressTestRoundDurationText": "Durada turno",
"stressTestTitleText": "Proa soto sforso",
"titleText": "Prestasion e Proe soto sforso",
"totalReloadTimeText": "Tenpo totałe recargamento: ${TIME} (par i detaji varda el rejistro)"
"totalReloadTimeText": "Tenpo totałe recargamento: ${TIME} (par i detaji varda el rejistro eventi)"
},
"defaultGameListNameText": "Łista de zugo \"${PLAYMODE}\" predefenìa",
"defaultNewGameListNameText": "Ła me łista de zugo ${PLAYMODE}",
@ -623,7 +624,7 @@
"errorAccessDeniedText": "aceso refudà",
"errorOutOfDiskSpaceText": "spasio so'l disco fenìo",
"errorText": "Eror",
"errorUnknownText": "eror mìa conosesto",
"errorUnknownText": "eror miga conosesto",
"exitGameText": "Vutu ndar fora da ${APP_NAME}?",
"exportSuccessText": "'${NAME}' esportà.",
"externalStorageText": "Memoria esterna",
@ -646,7 +647,7 @@
"fiveKillText": "BEN 5 SASINÀI!!!!",
"flawlessWaveText": "Ondada parfeta!",
"fourKillText": "EŁIMENÀI: 4!!!",
"friendScoresUnavailableText": "Punteji de i amighi mìa disponìbiłi.",
"friendScoresUnavailableText": "Punteji de i amighi miga disponìbiłi.",
"gameCenterText": "GameCenter",
"gameCircleText": "GameCircle",
"gameLeadersText": "Clasìfega de'l łeveło ${COUNT}",
@ -668,14 +669,14 @@
},
"gamesToText": "${WINCOUNT} a ${LOSECOUNT}",
"gatherWindow": {
"aboutDescriptionLocalMultiplayerExtraText": "Recòrdate: se A te ghè bastansa controładori, caun\ndispozitivo de un grupo el połe ospitar pì zugadori.",
"aboutDescriptionText": "Dòpara ste sesion par far sù un grupo.\n\nI grupi i te parmete de zugar partìe e tornèi\nco i to amighi infrà i vari dispozitivi.\n\nDòpara el boton ${PARTY} insima a drita par\nciacołar e interajir col to grupo.\n(so un controłador, struca ${BUTTON} co A te si inte un menù)",
"aboutDescriptionLocalMultiplayerExtraText": "Recòrdate: se te ghè bastansa controładori, tuti i\ndispozidivi de un grupo i połe ospitar pì zugadori.",
"aboutDescriptionText": "Dòpara 'ste sesion par far sù un grupo.\n\nI grupi i te parmete de zugar partìe e tornèi\nco i to amighi infrà i vari dispozidivi.\n\nDòpara el boton ${PARTY} insima a drita par\nciacołar e interajir col to grupo.\n(so un controłador, struca ${BUTTON} co te si inte un menù)",
"aboutText": "Info",
"addressFetchErrorText": "<eror rancurando sù i ndarisi>",
"appInviteMessageText": "L'utente ${NAME} el te ga mandà ${COUNT} biłieti so ${APP_NAME}",
"appInviteSendACodeText": "Màndaghe un còdaze",
"appInviteTitleText": "Invido a proar ${APP_NAME}",
"bluetoothAndroidSupportText": "(el funsiona so tuti i dispozitivi Android co el bluetooth)",
"bluetoothAndroidSupportText": "(el funsiona so tuti i dispozidivi Android co el bluetooth)",
"bluetoothDescriptionText": "Òspita/zóntate so un grupo co'l bluetooth:",
"bluetoothHostText": "Òspita",
"bluetoothJoinText": "Zóntate",
@ -708,8 +709,8 @@
"googlePlayText": "Google Play",
"googlePlayVersionOnlyText": "(soło inte ła varsion Android / Google Play)",
"hostPublicPartyDescriptionText": "Òspita un grupo pùblego",
"hostingUnavailableText": "Ospitamento mìa disponìbiłe",
"inDevelopmentWarningText": "Nota:\n\nEl zugo in rede ła ze na funsion nova e in dezviłupo.\nPar deso, A ze dalvero racomandà che tuti i\nzugadori i sie tuti so ła mèdema rede wifi.",
"hostingUnavailableText": "Ospitamento miga disponìbiłe",
"inDevelopmentWarningText": "Nota:\n\nEl zugo in rede ła ze na funsion nova e in dezviłupo.\nPar deso, ze dabon racomandà che tuti i\nzugadori i sipie tuti so ła mèdema rede wifi.",
"internetText": "Internet",
"inviteAFriendText": "I to amighi zełi sensa zugo? Invìdełi a\nproarlo e i resevarà ${COUNT} biłieti gratùidi.",
"inviteFriendsText": "Invida amighi",
@ -734,7 +735,7 @@
"otherVersionsText": "(par altre varsion)",
"partyCodeText": "Còdaze de'l grupo",
"partyInviteAcceptText": "Và ben",
"partyInviteDeclineText": "Mìa deso",
"partyInviteDeclineText": "Miga deso",
"partyInviteGooglePlayExtraText": "(varda el paneło 'Google Play' inte ła fenestra 'Crea grupo')",
"partyInviteIgnoreText": "Ignora",
"partyInviteText": "A te ze rivà un invido de ${NAME}\npar zontarte inte'l só grupo!",
@ -744,7 +745,7 @@
"partyStatusCheckingText": "Varìfega condision...",
"partyStatusJoinableText": "deso el to grupo el połe èsar catà da internet",
"partyStatusNoConnectionText": "A no ze miga posìbiłe conétarse a'l server",
"partyStatusNotJoinableText": "el to grupo no'l połe mìa èsar catà da internet",
"partyStatusNotJoinableText": "el to grupo no'l połe miga èsar catà da internet",
"partyStatusNotPublicText": "el to grupo no'l ze miga pùblego",
"pingText": "łatensa",
"portText": "Porta",
@ -763,12 +764,12 @@
"startStopHostingMinutesText": "Te połi tacar o fermar de ospitar grupi łìbaramente par n'antri ${MINUTES} menuti.",
"stopHostingText": "Ferma ospitamento",
"titleText": "Crea grupo",
"wifiDirectDescriptionBottomText": "Se tuti i dispozitivi i gà ła sesion 'Wifi direto', i sarà boni de dopararlo par\ncatarse e conétarse infrà de łori. Na volta che tuti i dispozitivi i sarà conetesti,\nA te podarè crear grupi inte ła sesion 'Rede łogałe', cofà ła fuse na rede wifi normałe.\n\nPar rezultài pì boni, mejo che lospitador del wifi direto el sipie lòspite anca del grupo so ${APP_NAME}.",
"wifiDirectDescriptionTopText": "El Wifi direto el połe èsar doparà par conétar diretamente dispozitivi Android\nsensa pasar par ła rede wifi. El funsiona mejo so varsion Android 4.2 o pì resenti.\n\nPar dopararlo, verzi łe inpostasion wifi e controła ła funsion 'Wifi direto'.",
"wifiDirectDescriptionBottomText": "Se tuti i dispozidivi i gà ła sesion 'Wifi direto', i sarà boni de dopararlo par\ncatarse e conétarse infrà de łori. Na volta che tuti i dispozidivi i sarà conetesti,\nte podarè crear grupi inte ła sesion 'Rede łogałe', cofà ła fuse na rede wifi normałe.\n\nPar rezultài pì boni, mejo che lospitador del wifi direto el sipie lòspite anca del grupo so ${APP_NAME}.",
"wifiDirectDescriptionTopText": "El Wifi direto el połe èsar doparà par conétar diretamente dispozidivi Android\nsensa pasar par ła rede wifi. El funsiona mejo so varsion Android 4.2 o pì resenti.\n\nPar dopararlo, verzi łe inpostasion wifi e controła ła funsion 'Wifi direto'.",
"wifiDirectOpenWiFiSettingsText": "Verzi inpostasion wifi",
"wifiDirectText": "Wifi direto",
"worksBetweenAllPlatformsText": "(el funsiona intrà tute łe piataforme)",
"worksWithGooglePlayDevicesText": "(el funsiona co tuti i dispozitivi co ła varsion del zugo de Google Play par Android)",
"worksWithGooglePlayDevicesText": "(el funsiona co tuti i dispozidivi co ła varsion del zugo de Google Play par Android)",
"youHaveBeenSentAPromoCodeText": "A te ze stà mandà un còdaze promosionałe de ${APP_NAME}:"
},
"getTicketsWindow": {
@ -788,8 +789,8 @@
"ticketsText": "${COUNT} biłieti",
"titleText": "Otien biłieti",
"unavailableLinkAccountText": "Ne despiaze, A no se połe miga cronpar so 'sta piataforma.\nVołendo, cofà sołusion, A te połi cołegar 'sto account co\nuno inte n'antra piataforma e cronpar calcosa da łà.",
"unavailableTemporarilyText": "'Sta funsion no ła ze mìa disponìbiłe par deso: proa danovo pì tardi.",
"unavailableText": "Ne despiaze, 'sta funsion no ła ze mìa disponìbiłe.",
"unavailableTemporarilyText": "'Sta funsion no ła ze miga disponìbiłe par deso: proa danovo pì tardi.",
"unavailableText": "Ne despiaze, 'sta funsion no ła ze miga disponìbiłe.",
"versionTooOldText": "Ne despiaze, 'sta varsion ła ze masa vecia: ajorna el zugo co cheła nova.",
"youHaveShortText": "A te ghè ${COUNT}",
"youHaveText": "A te ghè ${COUNT} biłieti"
@ -817,13 +818,13 @@
"helpWindow": {
"bombInfoText": "- Bonbe -\nPì forti de i crogni, ma łe połe\nfarte małe anca a ti. Dopàrełe\nben tràndoghełe doso a i nemighi\nprima che łe salte par aria.",
"canHelpText": "${APP_NAME} el połe jutarte!",
"controllersInfoText": "A te połi zugar a ${APP_NAME} co i amighi co na rede o, se gavì\ncontroładori che basta, połì zugar tuti insenbre so el mèdemo\ndispozitivo: ${APP_NAME} el ghin suporta racuanti. Połì senpre doparar\ni tełèfoni cofà controładori co lapl gratùida '${REMOTE_APP_NAME}'.\nPar info in pì varda so Inpostasion > Controładori.",
"controllersInfoTextRemoteOnly": "Te połi zugar a ${APP_NAME} co i to amighi doparando na rede,\no zugar tuti so'l mèdemo dispozitivo doparando i tełèfoni cofà\ncontroładori co l'apl gratùida '${REMOTE_APP_NAME}'.",
"controllersInfoText": "Te połi zugar a ${APP_NAME} co i amighi co na rede o, se gavì\ncontroładori che basta, połì zugar tuti insenbre so el mèdemo\ndispozidivo: ${APP_NAME} el ghin suporta racuanti. Połì senpre doparar\ni tełèfoni cofà controładori co lapl gratùida '${REMOTE_APP_NAME}'.\nPar info in pì varda so Inpostasion > Controładori.",
"controllersInfoTextRemoteOnly": "Te połi zugar a ${APP_NAME} co i to amighi doparando na rede,\no zugar tuti so'l mèdemo dispozidivo doparando i tełèfoni cofà\ncontroładori co l'apl gratùida '${REMOTE_APP_NAME}'.",
"controllersText": "Controładori",
"controlsSubtitleText": "El to amighévołe parsonajo de ${APP_NAME} el gà un fià de funsion de baze:",
"controlsText": "Controłi",
"devicesInfoText": "Ła varsion VR de ${APP_NAME} ła połe èsar zugada so na rede anca\nco ła varsion normałe de l'app, donca tira fora tełèfoni, tołeti\ne computers e taca a zugar! Podarìa èsar ùtiłe conétar na varsion\nnormałe de'l zugo a cheła VR anca soło par parmétarghe a łe parsone\nde poder ndarghe drio a'l zugo da fora.",
"devicesText": "Dispozitivi",
"devicesText": "Dispozidivi",
"friendsGoodText": "A ze senpre bona roba vèrghene. Se se ła gode de pì co pì zugadori\ne ${APP_NAME} el ghin suporta fin a 8, e 'sta roba ła ne mena a:",
"friendsText": "Amighi",
"jumpInfoText": "- Salto -\nSalta par traversar buzi cełi,\npar trar robe pì alte, o par\nfar védar tuta ła to ałegresa.",
@ -858,18 +859,18 @@
},
"holdAnyButtonText": "<tien strucà un boton calsìase>",
"holdAnyKeyText": "<tien strucà un boton calsìase>",
"hostIsNavigatingMenusText": "- ${HOST} l'è drio navegar intrà i menù a zbregabałon! -",
"hostIsNavigatingMenusText": "- ${HOST} ze drio navegar intrà i menù a zbregabałon! -",
"importPlaylistCodeInstructionsText": "Dòpara 'sto còdaze par inportar 'sta łista de zugo ndove che te vołi:",
"importPlaylistSuccessText": "Łista de zugo '${NAME}' a ${TYPE} inportada",
"importText": "Inporta",
"importingText": "Inportasion...",
"inGameClippedNameText": "rento el zugo:\n\"${NAME}\"",
"installDiskSpaceErrorText": "EROR: A no ze mìa posìbiłe fenir linstałasion.\nEl to dispozitivo el podarìa èsar sensa spasio.\nŁìbara un fià de memoria e proa danovo.",
"installDiskSpaceErrorText": "EROR: inposìbiłe fenir linstałasion.\nEl to dispozidivo el podarìa èsar sensa spasio.\nŁìbara un fià de memoria e proa danovo.",
"internal": {
"arrowsToExitListText": "struca ${LEFT} o ${RIGHT} par ndar fora da ła serie",
"buttonText": "boton",
"cantKickHostError": "A no te połi miga parar vìa l'ospitador.",
"chatBlockedText": "${NAME} l'è stà tajà fora da ła chat par ${TIME} segondi.",
"chatBlockedText": "${NAME} ze stà tajà fora da ła chat par ${TIME} segondi.",
"connectedToGameText": "A te te si zontà so '${NAME}'",
"connectedToPartyText": "A te te si zontà so'l grupo de ${NAME}!",
"connectingToPartyText": "Conesion...",
@ -890,10 +891,10 @@
"corruptFileText": "A ze stà catài fora file coronpesti. Proa reinstałar el zugo o manda na mail a: ${EMAIL}",
"errorPlayingMusicText": "Eror de reprodusion muzegałe: ${MUSIC}",
"errorResettingAchievementsText": "A no ze miga posìbiłe reinpostar i obietivi in łinea. Proa danovo pì tardi.",
"hasMenuControlText": "${NAME} l'à el controło de'l menù",
"hasMenuControlText": "${NAME} gà el controło de'l menù",
"incompatibleNewerVersionHostText": "L'ospitador el gà na varsion de'l zugo pì resente.\nAjórneło anca ti a ła varsion ùltema e proa danovo.",
"incompatibleVersionHostText": "L'ospitador el gà na varsion de'l zugo defarente.\nSegùreve de ver tuti do l'apl ajornada e provè danovo.",
"incompatibleVersionPlayerText": "${NAME} l'à na varsion de'l zugo defarente.\nSegùreve de ver tuti do l'apl ajornada e provè danovo.",
"incompatibleVersionPlayerText": "${NAME} gà na varsion de'l zugo defarente.\nSegùreve de ver tuti do l'apl ajornada e provè danovo.",
"invalidAddressErrorText": "Eror: ndariso miga vàłido.",
"invalidNameErrorText": "Eror: nome miga vàłido.",
"invalidPortErrorText": "Eror: porta miga vàłida.",
@ -901,20 +902,20 @@
"invitationsSentText": "Mandài ${COUNT} invidi.",
"joinedPartyInstructionsText": "Calchedun el se gà zontà inte'l to grupo.\nVà so \"Zuga\" par tacar na partìa.",
"keyboardText": "Botonera",
"kickIdlePlayersKickedText": "A ze stà parà vìa ${NAME} par masa sonera.",
"kickIdlePlayersWarning1Text": "Se ła só sonera ła sèvita, ${NAME} l'vegnarà parà vìa tenpo ${COUNT} segondi.",
"kickIdlePlayersKickedText": "Ze stà parà fora ${NAME} par masa sonera.",
"kickIdlePlayersWarning1Text": "Se ła só sonera ła sèvita, ${NAME} vegnarà parà fora tenpo ${COUNT} segondi.",
"kickIdlePlayersWarning2Text": "(A te połi dezativarlo so Inpostasion > Avansàe)",
"leftGameText": "A te si ndà fora da '${NAME}'.",
"leftPartyText": "A te si ndà fora da'l grupo de ${NAME}.",
"noMusicFilesInFolderText": "Ła carteła no ła gà rento gnaun file muzegałe.",
"playerJoinedPartyText": "${NAME} l'se gà zontà inte'l grupo!",
"playerLeftPartyText": "${NAME} l'è ndà fora da'l grupo!",
"playerLeftPartyText": "${NAME} gà mołà el grupo!",
"rejectingInviteAlreadyInPartyText": "Invido refudà (dezà inte un grupo).",
"serverRestartingText": "El server el ze drio retacarse. Reconétate infrà na scianta...",
"serverShuttingDownText": "El server el ze drio stuarse...",
"signInErrorText": "Eror in entrada.",
"signInNoConnectionText": "A no ze mìa posìbiłe ndar rento. (gnauna conesion a internet?)",
"telnetAccessDeniedText": "EROR: l'utente no'l gà mìa parmeso l'aceso co telnet.",
"signInNoConnectionText": "Inposìbiłe acédar. (sensa conesion internet?)",
"telnetAccessDeniedText": "EROR: l'utente no'l gà miga parmeso l'aceso co telnet.",
"timeOutText": "(tocarà a ti tenpo ${TIME} segondi)",
"touchScreenJoinWarningText": "A te te si zontà co'l touchscreen.\nSe ła ze stà na capeła, struca 'Menù > Moła łeveło'.",
"touchScreenText": "TouchScreen",
@ -931,9 +932,9 @@
"keyboardChangeInstructionsText": "Struca spasio do 'olte par mudar botonera.",
"keyboardNoOthersAvailableText": "A no ghe ze miga altre botonere disponìbiłi.",
"keyboardSwitchText": "Pasajo a ła botonera \"${NAME}\".",
"kickOccurredText": "${NAME} l'è stà parà vìa.",
"kickOccurredText": "${NAME} ze stà parà fora.",
"kickQuestionText": "Vutu parar vìa ${NAME}?",
"kickText": "Para a",
"kickText": "Para fora",
"kickVoteCantKickAdminsText": "I aministradori no i połe mìa èsar parài vìa.",
"kickVoteCantKickSelfText": "A no te połi mìa pararte vìa da soło.",
"kickVoteFailedNotEnoughVotersText": "A ghe ze masa pochi zugadori par na votasion.",
@ -996,8 +997,8 @@
"howToPlayText": "Come zugar",
"justPlayerText": "(Soło par ${NAME})",
"leaveGameText": "Moła łeveło",
"leavePartyConfirmText": "Vutu dalvero ndar fora da'l grupo?",
"leavePartyText": "Va fora da'l grupo",
"leavePartyConfirmText": "Vutu dabon ndar fora da'l grupo?",
"leavePartyText": "Moła grupo",
"quitText": "Sortisi",
"resumeText": "Continua",
"settingsText": "Inpostasion"
@ -1020,9 +1021,9 @@
"multiKillText": "CAENA DE ${COUNT} FATI FORA!!!!!",
"multiPlayerCountText": "${COUNT} zugadori",
"mustInviteFriendsText": "Nota: A te ghè da invidar i amighi\nso'l paneło \"${GATHER}\" o picar pì\ncontroładori par zugar in multizugador.",
"nameBetrayedText": "${NAME} l'à tradìo ${VICTIM}.",
"nameDiedText": "${NAME} l'è crepà.",
"nameKilledText": "${NAME} l'à copà ${VICTIM}.",
"nameBetrayedText": "${NAME} gà tradìo ${VICTIM}.",
"nameDiedText": "${NAME} ze crepà.",
"nameKilledText": "${NAME} gà copà ${VICTIM}.",
"nameNotEmptyText": "El nome no'l połe miga star vodo!",
"nameScoresText": "Un ponto par ${NAME}!",
"nameSuicideKidFriendlyText": "${NAME} par zbałio L ze crepà.",
@ -1037,8 +1038,8 @@
"nextLevelText": "Łeveło seguente",
"noAchievementsRemainingText": "- gnaun",
"noContinuesText": "(sensa continui)",
"noExternalStorageErrorText": "Inte sto dispozitivo A no ze stà catada gnauna memoria esterna",
"noGameCircleText": "Eror: A no te si miga conetesto co GameCircle",
"noExternalStorageErrorText": "So sto dispozidivo no ze stà catada gnauna memoria esterna",
"noGameCircleText": "Eror: no te si miga conetesto co GameCircle",
"noScoresYetText": "Gnancora gnaun puntejo.",
"noThanksText": "Nò, grasie",
"noTournamentsInTestBuildText": "AVERTENSA: i punteji de'l tornèo de 'sta varsion de proa i vegnarà ignorài.",
@ -1087,7 +1088,7 @@
"playerCountAbbreviatedText": "${COUNT}z",
"playerDelayedJoinText": "Co tacarà el turno che'l vien A se zontarà anca ${PLAYER}.",
"playerInfoText": "Informasion zugador",
"playerLeftText": "${PLAYER} l'à mołà el łeveło.",
"playerLeftText": "${PLAYER} gà mołà el łeveło.",
"playerLimitReachedText": "Nùmaro màsemo de ${COUNT} zugadori: A no połe zontarse pì nesun.",
"playerProfilesWindow": {
"cantDeleteAccountProfileText": "A no te połi miga ełimenar el profiło prinsipałe de'l to account.",
@ -1105,7 +1106,10 @@
"playlistsText": "Łiste de zugo",
"pleaseRateText": "Se ${APP_NAME} el ze drio piazerte, tote un àtemo par\nłasarghe zó na vałudasion o scrìvarghe zó un comento. 'Ste\nopinion łe tornarà còmode par dezviłupi fuduri de'l zugo.\n\ngrasie!\n-eric",
"pleaseWaitText": "Speta n'àtemo...",
"pluginClassLoadErrorText": "Eror de cargamento de ła clase de estension '${PLUGIN}': ${ERROR}",
"pluginInitErrorText": "Eror de inisiałizasion de l'estension '${PLUGIN}': ${ERROR}",
"pluginsDetectedText": "Estension nove rełevàe. Retaca el zugo par ativarle, o configùrełe so łe inpostasion.",
"pluginsRemovedText": "Estension miga catàe: ${NUM}",
"pluginsText": "Estension",
"practiceText": "Pràtega",
"pressAnyButtonPlayAgainText": "Struca un boton calsìase par zugar danovo...",
@ -1125,7 +1129,7 @@
},
"promoSubmitErrorText": "Eror de trazmision de'l còdaze: controła ła to conesion internet",
"ps3ControllersWindow": {
"macInstructionsText": "Stua ła corente so'l dadrìo de ła to PS3, segùrate che'l\nbluetooth de'l to Mac el sipie ativà, daspò coneti el to controłador\na'l to Mac co un cavo USB par cubiarli. Da deso in vanti A te\npołi doparar el boton \"cao\" de'l controłador par conétarlo co'l to\nMac in modałidà USB (co'l fiło) o bluetooth (sensa fiło).\n\nPar el cubiamento calche Mac el podarìa dimandarte un còdaze.\nSe càpida, varda ła demostrasion seguente o serca juto so Google.\n\n\n\n\nI controładori PS3 conetesti sensa fiło i gavarìa da védarse inte ła łista\nde'l dispozitivo so Prefarense de sistema > Bluetooth. Co te vorè\ndopararli danovo so ła to PS3, te podarisi ver da cavarli vìa da 'sta łista.\n\nSegùrate anca de desconétarli da'l bluetooth co A no te łi\ndòpari o łe só batarìe łe sevitarà a sconsumarse.\n\nEl bluetooth el gavarìa da suportar fin a 7 dispozitivi conetesti,\nanca se ła capasidà ła podarìa variar.",
"macInstructionsText": "Stua ła corente so'l dadrìo de ła to PS3, segùrate che'l\nbluetooth de'l to Mac el sipie ativà, daspò coneti el to controłador\na'l to Mac co un cavo USB par cubiarli. Da deso in vanti te\npołi doparar el boton \"cao\" de'l controłador par conétarlo co'l to\nMac in modałidà USB (co'l fiło) o bluetooth (sensa fiło).\n\nPar el cubiamento calche Mac el podarìa dimandarte un còdaze.\nSe càpida, varda ła demostrasion seguente o serca juto so Google.\n\n\n\n\nI controładori PS3 conetesti sensa fiło i gavarìa da védarse inte ła łista\nde'l dispozidivo so Prefarense de sistema > Bluetooth. Co te vorè\ndopararli danovo so ła to PS3, te podarisi ver da cavarli vìa da 'sta łista.\n\nSegùrate anca de desconétarli da'l bluetooth co no te łi\ndòpari o łe só batarìe łe sevitarà a sconsumarse.\n\nEl bluetooth el gavarìa da suportar fin a 7 dispozidivi conetesti,\nanca se ła capasidà ła podarìa variar.",
"ouyaInstructionsText": "Par doparar un controłador PS3 co ła to OUYA, conéteło co un cavo USB par\ncubiarlo. 'Sta asion ła podarìa desconétar cheł'altri to controładori, donca\nA podarìa servirte retacar ła to OUYA e destacar el cavo USB.\n\nDa deso in vanti A te dovarisi èsar bon de doparar el boton \"cao\" de'l\ncontrołador par conétarlo sensa fiło. Co A te ghè fenìo de zugar, tien\nstrucà el boton \"cao\" par 10 segondi par stuar el controłador, el\npodarìa restar inpisà e stracar ła batarìa.",
"pairingTutorialText": "demostrasion video pa'l cubiamento",
"titleText": "Doparar un controłador PS3 co ${APP_NAME}:"
@ -1153,7 +1157,7 @@
"cant_resolve_host": "A no ze mìa posìbiłe catar fora l'ospitador.",
"capturing": "Drio spetar i zugadori…",
"connected": "Conetesto.",
"description": "Dòpara el to tełèfono o tołeto cofà controłador par BombSquad.\nA połe conétarse so un schermo ùgnoło fin a 8 dispozitivi insenbre, par un feston bueło multizugador!",
"description": "Dòpara el to tełèfono o tołeto cofà controłador par BombSquad.\nPołe conétarse so un schermo ùgnoło fin a 8 dispozidivi insenbre, par un feston bueło multizugador!",
"disconnected": "Desconetesto da'l server.",
"dpad_fixed": "fiso",
"dpad_floating": "mòbiłe",
@ -1206,9 +1210,9 @@
},
"scoreWasText": "(prima ${COUNT})",
"selectText": "Sełesiona",
"seriesWinLine1PlayerText": "L'À VINTO ŁA",
"seriesWinLine1TeamText": "L'À VINTO ŁA",
"seriesWinLine1Text": "L'À VINTO ŁA",
"seriesWinLine1PlayerText": "GÀ VINTO ŁA",
"seriesWinLine1TeamText": "GÀ VINTO ŁA",
"seriesWinLine1Text": "GÀ VINTO ŁA",
"seriesWinLine2Text": "DESFIDA!",
"settingsWindow": {
"accountText": "Account",
@ -1231,7 +1235,7 @@
"enterPromoCodeText": "Insarisi còdaze",
"forTestingText": "Nota: vałori vàłidi soło par proe. Sortendo da l'apl łi vegnarà perdesti.",
"helpTranslateText": "Łe tradusion de ${APP_NAME} łe ze curàe da vołontari.\nSe A te vol darghe na ociada a cheła veneta, struca so'l boton\ncuà soto. Curada da Còdaze Veneto: codazeveneto@gmail.com",
"kickIdlePlayersText": "Para a zugadori in sonera",
"kickIdlePlayersText": "Para fora zugadori in sonera",
"kidFriendlyModeText": "Modałidà bocia (viołensa reduzesta, evc)",
"languageText": "Łengua",
"moddingGuideText": "Guida par modifegasion",
@ -1317,7 +1321,7 @@
"winterSpecialText": "Spesiałe inverno",
"youOwnThisText": "- dezà tuo -"
},
"storeDescriptionText": "Feston bueło a 8 zugadori!\n\nFà saltar par aria i to amighi (o el computer) inte un tornèo de minizughi cofà 'Brinca ła bandiera', 'Scravaso de stełe' o 'Scontri mortałi' in movensa camoma!\n\nFin a 8 parsone łe sarà bone de batajar insenbre, grasie a comandi senpi e a ła conpatibiłidà co tanti controładori. Co l'apl gratùida 'BombSquad Remote' A te połi parfin doparar i to dispozitivi mòbiłi cofà controładori!\n\nE deso... tira fora łe bonbe!\n\nPar informasion in pì daghe na ociada so www.froemling.net/bombsquad.",
"storeDescriptionText": "Feston bueło a 8 zugadori!\n\nFà saltar par aria i to amighi (o el computer) inte un tornèo de minizughi cofà 'Brinca ła bandiera', 'Scravaso de stełe' o 'Scontri mortałi' in movensa camoma!\n\nFin a 8 parsone łe sarà bone de batajar insenbre, grasie a comandi senpi e a ła conpatibiłidà co tanti controładori. Co l'apl gratùida 'BombSquad Remote' te połi parfin doparar i to dispozidivi mòbiłi cofà controładori!\n\nE deso... tira fora łe bonbe!\n\nPar informasion in pì daghe na ociada so www.froemling.net/bombsquad.",
"storeDescriptions": {
"blowUpYourFriendsText": "Fà sciopar par aria i to amighi.",
"competeInMiniGamesText": "Zuga co tanti minizughi: da corse a zvołi.",
@ -1572,7 +1576,7 @@
"An error has occurred; please contact support. (${ERROR})": "A se gà verifegà un eror: contata l'asistensa. (${ERROR})",
"An error has occurred; please contact support@froemling.net.": "A se gà verifegà un eror: contata support@froemling.net.",
"An error has occurred; please try again later.": "A se gà verifegà un eror: proa danovo pì tardi.",
"Are you sure you want to link these accounts?\n\n${ACCOUNT1}\n${ACCOUNT2}\n\nThis cannot be undone!": "Vutu dalvero cołegar 'sti profiłi?\n\n${ACCOUNT1}\n${ACCOUNT2}\n\n'Sta asion no ła połe mìa èsar anułada!",
"Are you sure you want to link these accounts?\n\n${ACCOUNT1}\n${ACCOUNT2}\n\nThis cannot be undone!": "Vutu dabon cołegar 'sti profiłi?\n\n${ACCOUNT1}\n${ACCOUNT2}\n\n'Sta asion no ła połe pì èsar anułada!",
"BombSquad Pro unlocked!": "BombSquad Pro dezblocà!",
"Can't link 2 accounts of this type.": "A no se połe mìa cołegar 2 profiłi de 'sto tipo.",
"Can't link 2 diamond league accounts.": "A no se połe mìa cołegar 2 profiłi de ła łega de damante.",
@ -1588,7 +1592,7 @@
"Invalid tournament entry; score will be ignored.": "Entrada inte'l tornèo mìa vàłida: el puntejo el vegnarà ignorà.",
"Item unlocked!": "Ojeto dezblocà!",
"LINKING DENIED. ${ACCOUNT} contains\nsignificant data that would ALL BE LOST.\nYou can link in the opposite order if you'd like\n(and lose THIS account's data instead)": "COŁEGAMENTO REFUDÀ. ${ACCOUNT} el contien dati\ninportanti che i ndarà PERDESTI.\nSe te vołi A te połi cołegarli in òrdane raverso\n(e pèrdar a'l só posto i dati de 'STO account cuà).",
"Link account ${ACCOUNT} to this account?\nAll existing data on ${ACCOUNT} will be lost.\nThis can not be undone. Are you sure?": "Vutu dalvero cołegar l'account ${ACCOUNT} a 'sto account?\nTuti i dati so ${ACCOUNT} i ndarà perdesti.\n'Sta asion no ła połe mìa èsar anułada: situ seguro?",
"Link account ${ACCOUNT} to this account?\nAll existing data on ${ACCOUNT} will be lost.\nThis can not be undone. Are you sure?": "Vutu dabon cołegar l'account ${ACCOUNT} a 'sto account?\nTuti i dati so ${ACCOUNT} i ndarà perdesti.\n'Sta asion no ła połe pì èsar anułada: vutu ndar vanti?",
"Max number of playlists reached.": "Nùmaro màsemo de łiste de scolto pasà.",
"Max number of profiles reached.": "Nùmaro màsemo de profiłi pasà.",
"Maximum friend code rewards reached.": "Brincà el nùmaro màsemo de premi da'l còdaze amigo.",
@ -1610,11 +1614,11 @@
"This code cannot be used on the account that created it.": "'Sto còdaze no'l połe mìa èsar doparà inte l'account che'l ło gà creà.",
"This is currently unavailable; please try again later.": "Par deso, funsion miga disponìbiłe. Proa danovo pì tardi.",
"This requires version ${VERSION} or newer.": "A ghe serve ła varsion ${VERSION} o una pì nova.",
"Tournaments disabled due to rooted device.": "Tornèi dezativài parvìa de'l dispozitivo co'l root.",
"Tournaments disabled due to rooted device.": "Tornèi dezativài parvìa de'l dispozidivo co'l root.",
"Tournaments require ${VERSION} or newer": "Par i tornèi A serve ła varsion ${VERSION} o una pì resente",
"Unlink ${ACCOUNT} from this account?\nAll data on ${ACCOUNT} will be reset.\n(except for achievements in some cases)": "Vutu descołegar l'account ${ACCOUNT} da 'sto account?\nTuti i dati de ${ACCOUNT} i vegnarà ełimenài.\n(obietivi a parte in calche cazo)",
"WARNING: complaints of hacking have been issued against your account.\nAccounts found to be hacking will be banned. Please play fair.": "AVERTENSA: el to account el ze stà segnałà par el dòparo de truchi.\nI zugaduri catài a doparar truchi i vegnarà blocài. Zuga da gałantomo.",
"Would you like to link your device account to this one?\n\nYour device account is ${ACCOUNT1}\nThis account is ${ACCOUNT2}\n\nThis will allow you to keep your existing progress.\nWarning: this cannot be undone!\n": "Ghetu caro cołegar l'account de'l to dispozitivo co 'sto cuà?\n\nL'account de'l to dispozitivo el ze ${ACCOUNT1}\n'Sto account el ze ${ACCOUNT2}\n\n'Sta oparasion ła te parmetarà de mantegner i to progresi ezistenti.\nAvertensa: 'sta asion no ła połe mìa èsar anułada!",
"Would you like to link your device account to this one?\n\nYour device account is ${ACCOUNT1}\nThis account is ${ACCOUNT2}\n\nThis will allow you to keep your existing progress.\nWarning: this cannot be undone!\n": "Ghetu caro cołegar l'account de'l to dispozidivo co 'sto cuà?\n\nL'account de'l to dispozidivo el ze ${ACCOUNT1}\n'Sto account el ze ${ACCOUNT2}\n\n'Sta oparasion ła te parmetarà de mantegner i to progresi ezistenti.\nAvertensa: 'sta asion no ła połe èsar anułada!",
"You already own this!": "Dezà cronpà!",
"You can join in ${COUNT} seconds.": "A te połi zontarte tenpo ${COUNT} segondi.",
"You don't have enough tickets for this!": "A no te ghè miga biłieti che basta par cronparlo!",
@ -1672,7 +1676,7 @@
"Time Limit": "Łìmide de tenpo"
},
"statements": {
"${TEAM} is disqualified because ${PLAYER} left": "Ła scuadra ${TEAM} ła ze scuałifegada parché ${PLAYER} l'à mołà.",
"${TEAM} is disqualified because ${PLAYER} left": "Ła scuadra ${TEAM} ła ze scuałifegada parché ${PLAYER} gà mołà.",
"Killing ${NAME} for skipping part of the track!": "Copà ${NAME} par ver saltà un toco de'l parcorso!",
"Warning to ${NAME}: turbo / button-spamming knocks you out.": "Avertensa par ${NAME}: el turbo / spam co i botoni el te gà fato trar fora."
},
@ -1689,19 +1693,19 @@
"Curse boxes turn you into a ticking time bomb.\nThe only cure is to quickly grab a health-pack.": "Łe case małedision łe te transforma inte na bonba a tenpo.\nA te połi curarte soło tołendo in presa un pacheto sałude.",
"Despite their looks, all characters' abilities are identical,\nso just pick whichever one you most closely resemble.": "Anca se drio ła siera A no par, łe abiłidà de tuti i parsonaji\nłe ze conpagne, donca tote sù cheło che'l te połe somejar de pì.",
"Don't get too cocky with that energy shield; you can still get yourself thrown off a cliff.": "Mai far masa i gałeti co'l scudo nerjètego: A te połi uncora farte trar baso da na croda.",
"Don't run all the time. Really. You will fall off cliffs.": "No stà córar tuto el tenpo. Dalvero. A te fenirè zó par na croda.",
"Don't run all the time. Really. You will fall off cliffs.": "No stà córar tuto el tenpo. Dabon. Te fenirè zó par na croda.",
"Don't spin for too long; you'll become dizzy and fall.": "No stà ndar torno par masa tenpo: te vegnarà na storniroła e te cascarè.",
"Hold any button to run. (Trigger buttons work well if you have them)": "Tien strucà un boton calsìase par córar. (I griłeti nałòzeghi i ze i pì adati, se A te łi ghè)",
"Hold down any button to run. You'll get places faster\nbut won't turn very well, so watch out for cliffs.": "Tien strucà un boton calsìase par córar. A te te movarè pì in\npresa ma no te voltarè miga masa ben, donca ocio a łe crode.",
"Ice bombs are not very powerful, but they freeze\nwhoever they hit, leaving them vulnerable to shattering.": "Łe bonbe jaso no łe ze miga tanto potenti, ma łe injasa tuto cheło\nche łe brinca, e cheło che'l ze injasà el ze anca fàsiłe da fracasar.",
"If someone picks you up, punch them and they'll let go.\nThis works in real life too.": "Se calchedun el te łeva sù, daghe un crogno che'l te\nmołe zó. 'Sta roba ła funsiona anca inte ła vida vera.",
"If you are short on controllers, install the '${REMOTE_APP_NAME}' app\non your mobile devices to use them as controllers.": "Se A te si a curto de controładori, instała l'apl '${REMOTE_APP_NAME}'\nso i to dispozitivi mòbiłi e dopàrełi cofà controładori.",
"If you are short on controllers, install the '${REMOTE_APP_NAME}' app\non your mobile devices to use them as controllers.": "Se te si a curto de controładori, instała l'apl '${REMOTE_APP_NAME}'\nso i to dispozidivi mòbiłi e dopàrełi cofà controładori.",
"If you get a sticky-bomb stuck to you, jump around and spin in circles. You might\nshake the bomb off, or if nothing else your last moments will be entertaining.": "Se na bonba petaisa ła te se taca doso, salta in volta e và torno: ła gavarìa da cavarse.\nE se no funsionase, i to momenti ùltemi de vida i gavarà almanco dà spetàgoło!",
"If you kill an enemy in one hit you get double points for it.": "Se A te copi un nemigo co un colpo soło A te ciapi el dupio de i punti.",
"If you pick up a curse, your only hope for survival is to\nfind a health powerup in the next few seconds.": "Se A te ciapi na małedision, A te ghè soło na sparansa de salvesa:\ncatar fora un pacheto sałude inte i puchi segondi che A te resta.",
"If you stay in one place, you're toast. Run and dodge to survive..": "Se A se stà fermi inte un posto, A se ze friti. Cori e schiva par soravìvar...",
"If you've got lots of players coming and going, turn on 'auto-kick-idle-players'\nunder settings in case anyone forgets to leave the game.": "Se A te te cati tanti zugadori che i và e vien, e inte'l cazo che calchedun el se dezménteghe de\nmołar el zugo, ativa ła funsion automàtega 'Para a zugadori in sonera' inte łe inpostasion.",
"If your device gets too warm or you'd like to conserve battery power,\nturn down \"Visuals\" or \"Resolution\" in Settings->Graphics": "Se'l to dispozitivo el taca scotar o se A te ghè caro sparagnar batarìa,\ncała ła \"Prospetiva\" o ła \"Resołusion\" so Inpostasion > Gràfega",
"If you've got lots of players coming and going, turn on 'auto-kick-idle-players'\nunder settings in case anyone forgets to leave the game.": "Se te te cati tanti zugadori che và e vien, e inte'l cazo che calchedun el se dezménteghe de\nmołar el zugo, ativa ła funsion automàtega 'Para fora zugadori in sonera' inte łe inpostasion.",
"If your device gets too warm or you'd like to conserve battery power,\nturn down \"Visuals\" or \"Resolution\" in Settings->Graphics": "Se'l to dispozidivo el taca scotar o se te ghè caro sparagnar batarìa,\ncała ła \"Prospetiva\" o ła \"Resołusion\" so Inpostasion > Gràfega",
"If your framerate is choppy, try turning down resolution\nor visuals in the game's graphics settings.": "Se el zugo el và a scati, proa całar ła resołusion\no ła prospetiva inte l'inpostasion gràfega.",
"In Capture-the-Flag, your own flag must be at your base to score, If the other\nteam is about to score, stealing their flag can be a good way to stop them.": "Par far ponto so 'Brinca ła bandiera', A te ghè da portar ła bandiera fin so ła to\nbaze. Se cheł'altra scuadra ła ze drio far ponto, A te połi fermarla anca robàndogheła.",
"In hockey, you'll maintain more speed if you turn gradually.": "Inte l'hockey, voltando gradualmente A te mantien alta ła vełosidà.",
@ -1769,7 +1773,7 @@
"randomName3Text": "Teo",
"randomName4Text": "Cesco",
"randomName5Text": "Stè",
"skipConfirmText": "Vutu dalvero saltar ła demostrasion? Toca o struca par confermar.",
"skipConfirmText": "Vutu dabon saltar ła demostrasion? Toca o struca par confermar.",
"skipVoteCountText": "Voti par saltar ła demostrasion: ${COUNT}/${TOTAL}",
"skippingText": "demostrasion saltada...",
"toSkipPressAnythingText": "(toca o struca un boton par saltar ła demostrasion)"
@ -1787,7 +1791,7 @@
"upgradeText": "Mejora",
"upgradeToPlayText": "Par zugarghe, dezbloca \"${PRO}\" inte ła botega.",
"useDefaultText": "Reinposta",
"usesExternalControllerText": "'Sto zugo el dòpara un controłador esterno cofà dispozitivo de entrada.",
"usesExternalControllerText": "'Sto zugo el dòpara un controłador esterno cofà dispozidivo de entrada.",
"usingItunesText": "Doparar l'apl de mùzega par el son de fondo...",
"validatingTestBuildText": "Confermasion varsion de proa...",
"victoryText": "Vitoria!",
@ -1832,21 +1836,23 @@
"wiimoteSetupWindow": {
"copyrightText": "Deriti d'autor DarwiinRemote",
"listenText": "Scolta",
"macInstructionsText": "Segùrate che ła to Wii ła sipie stuada e el bluetooth de'l\nto Mac ativà, donca stuca so 'Scolta'. Ła conpatibiłidà co'l\nWiimote ła podarìa èsar un fià inserta, donca te podarisi\ncatarte a proar pì 'olte prima de otegner na conesion.\n\nEl bluetooth el gavarìa da suportar fin a 7 dispozitivi conetesti,\nanca se ła capasidà ła podarìa variar.\n\nBombSquad el suporta el controłador Clàsego e i controładori\norijinałi Wiimotes, Nunchuks.\nAnca el novo Wii Remote Plus el funsiona\nma soło sensa acesori.",
"macInstructionsText": "Segùrate che ła to Wii ła sipie stuada e el bluetooth de'l\nto Mac ativà, donca stuca so 'Scolta'. Ła conpatibiłidà co'l\nWiimote ła podarìa èsar un fià inserta, donca te podarisi\ncatarte a proar pì 'olte prima de otegner na conesion.\n\nEl bluetooth el gavarìa da suportar fin a 7 dispozidivi conetesti,\nanca se ła capasidà ła podarìa variar.\n\nBombSquad el suporta el controłador Clàsego e i controładori\norijinałi Wiimotes, Nunchuks.\nAnca el novo Wii Remote Plus el funsiona\nma soło sensa acesori.",
"thanksText": "Grasie a ła scuadra DarwiinRemote\npar verlo rendesto posìbiłe.",
"titleText": "Configurasion Wiimote"
},
"winsPlayerText": "Vitoria de ${NAME}!",
"winsTeamText": "Vitoria de i ${NAME}!",
"winsText": "Vitoria de ${NAME}!",
"workspaceSyncErrorText": "Eror de sincronizasion de ${WORKSPACE}. Varda el rejistro eventi par i detaji.",
"workspaceSyncReuseText": "Inposìbiłe sinconizar ${WORKSPACE}. Dòparo de ła varsion sincronizada presedente.",
"worldScoresUnavailableText": "Punteji mondiałi miga disponìbiłi.",
"worldsBestScoresText": "I punteji mejo de'l mondo",
"worldsBestTimesText": "I tenpi mejo de'l mondo",
"xbox360ControllersWindow": {
"getDriverText": "Descarga el driver",
"macInstructions2Text": "Par doparar sensa fiło i controładori, A te serve anca el resevidor\nche'l riva co l''Xbox 360 Wireless Controller par Windows'.\nUn resevidor el te parmete de conétar fin a 4 controładori.\n\nInportante: i resevidori de terse parti no i funsionarà mìa co 'sto driver;\nsegùrate che'l to resevidor el sipia 'Microsoft' e miga 'XBOX 360'.\nŁa Microsoft no łi vende pì destacài, donca A te servirà par forsa cheło\nvendesto insebre co'l controłador, o senò, proa sercar so Ebay.\n\nSe A te cati ùtiłe el driver, ciapa in considerasion de farghe na\ndonasion a'l só dezviłupador so 'sto sito.",
"macInstructions2Text": "Par doparar sensa fiło i controładori, A te serve anca el resevidor\nche'l riva co l''Xbox 360 Wireless Controller par Windows'.\nUn resevidor el te parmete de conétar fin a 4 controładori.\n\nInportante: i resevidori de terse parti no i funsionarà miga co 'sto driver;\nsegùrate che'l to resevidor el sipia 'Microsoft' e miga 'XBOX 360'.\nŁa Microsoft no łi vende pì destacài, donca te servirà par forsa cheło\nvendesto insebre co'l controłador, o senò, proa sercar so Ebay.\n\nSe te cati ùtiłe el driver, ciapa in considerasion de farghe na\ndonasion a'l só dezviłupador so 'sto sito.",
"macInstructionsText": "Per doparar i controładori co'l fiło de ła Xbox 360, A te ghè\nda instałar el driver Mac disponìbiłe so'l link cuà soto.\nEl funsiona co anbo i controładori, co'l fiło o sensa.",
"ouyaInstructionsText": "Par doparar so Bombsquad un controłador de l'Xbox 360 co'l fiło,\ntàcheło sù inte ła porta USB del to dispozitivo. A te połi anca\ntacar sù pì controładori insenbre doparando un hub USB.\n\nPar doparar i controładori sensa fiło invese, A te serve un resevidor\nde segnałe. A te połi catarlo, o rento ła scàtoła \"Controładori sensa fiło\nXbox 360 par Windows\", o vendesto a parte. Caun resevidor el và tacà so\nna porta USB e el te parmete de conétar fin a 4 controładori.",
"ouyaInstructionsText": "Par doparar so Bombsquad un controłador de l'Xbox 360 co'l fiło,\ntàcheło sù inte ła porta USB del to dispozidivo. Te połi anca\ntacar sù pì controładori insenbre doparando un hub USB.\n\nPar doparar i controładori sensa fiło invese, te serve un resevidor\nde segnałe. Te połi catarlo, o rento ła scàtoła \"Controładori sensa fiło\nXbox 360 par Windows\", o vendesto a parte. Caun resevidor el và tacà so\nna porta USB e el te parmete de conétar fin a 4 controładori.",
"titleText": "Doparar un controłador Xbox 360 co ${APP_NAME}:"
},
"yesAllowText": "Sì, parmeti!",

View file

@ -1,4 +1,4 @@
from .core import contents, where
__all__ = ["contents", "where"]
__version__ = "2022.05.18.1"
__version__ = "2022.06.15"

View file

@ -1362,39 +1362,6 @@ Agu+TGbrIP65y7WZf+a2E/rKS03Z7lNGBjvGTq2TWoF+bCpLagVFjPIhpDGQh2xl
nJ2lYJU6Un/10asIbvPuW/mIPX64b24D5EI=
-----END CERTIFICATE-----
# Issuer: CN=Hellenic Academic and Research Institutions RootCA 2011 O=Hellenic Academic and Research Institutions Cert. Authority
# Subject: CN=Hellenic Academic and Research Institutions RootCA 2011 O=Hellenic Academic and Research Institutions Cert. Authority
# Label: "Hellenic Academic and Research Institutions RootCA 2011"
# Serial: 0
# MD5 Fingerprint: 73:9f:4c:4b:73:5b:79:e9:fa:ba:1c:ef:6e:cb:d5:c9
# SHA1 Fingerprint: fe:45:65:9b:79:03:5b:98:a1:61:b5:51:2e:ac:da:58:09:48:22:4d
# SHA256 Fingerprint: bc:10:4f:15:a4:8b:e7:09:dc:a5:42:a7:e1:d4:b9:df:6f:05:45:27:e8:02:ea:a9:2d:59:54:44:25:8a:fe:71
-----BEGIN CERTIFICATE-----
MIIEMTCCAxmgAwIBAgIBADANBgkqhkiG9w0BAQUFADCBlTELMAkGA1UEBhMCR1Ix
RDBCBgNVBAoTO0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1
dGlvbnMgQ2VydC4gQXV0aG9yaXR5MUAwPgYDVQQDEzdIZWxsZW5pYyBBY2FkZW1p
YyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIFJvb3RDQSAyMDExMB4XDTExMTIw
NjEzNDk1MloXDTMxMTIwMTEzNDk1MlowgZUxCzAJBgNVBAYTAkdSMUQwQgYDVQQK
EztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIENl
cnQuIEF1dGhvcml0eTFAMD4GA1UEAxM3SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJl
c2VhcmNoIEluc3RpdHV0aW9ucyBSb290Q0EgMjAxMTCCASIwDQYJKoZIhvcNAQEB
BQADggEPADCCAQoCggEBAKlTAOMupvaO+mDYLZU++CwqVE7NuYRhlFhPjz2L5EPz
dYmNUeTDN9KKiE15HrcS3UN4SoqS5tdI1Q+kOilENbgH9mgdVc04UfCMJDGFr4PJ
fel3r+0ae50X+bOdOFAPplp5kYCvN66m0zH7tSYJnTxa71HFK9+WXesyHgLacEns
bgzImjeN9/E2YEsmLIKe0HjzDQ9jpFEw4fkrJxIH2Oq9GGKYsFk3fb7u8yBRQlqD
75O6aRXxYp2fmTmCobd0LovUxQt7L/DICto9eQqakxylKHJzkUOap9FNhYS5qXSP
FEDH3N6sQWRstBmbAmNtJGSPRLIl6s5ddAxjMlyNh+UCAwEAAaOBiTCBhjAPBgNV
HRMBAf8EBTADAQH/MAsGA1UdDwQEAwIBBjAdBgNVHQ4EFgQUppFC/RNhSiOeCKQp
5dgTBCPuQSUwRwYDVR0eBEAwPqA8MAWCAy5ncjAFggMuZXUwBoIELmVkdTAGggQu
b3JnMAWBAy5ncjAFgQMuZXUwBoEELmVkdTAGgQQub3JnMA0GCSqGSIb3DQEBBQUA
A4IBAQAf73lB4XtuP7KMhjdCSk4cNx6NZrokgclPEg8hwAOXhiVtXdMiKahsog2p
6z0GW5k6x8zDmjR/qw7IThzh+uTczQ2+vyT+bOdrwg3IBp5OjWEopmr95fZi6hg8
TqBTnbI6nOulnJEWtk2C4AwFSKls9cz4y51JtPACpf1wA+2KIaWuE4ZJwzNzvoc7
dIsXRSZMFpGD/md9zU1jZ/rzAxKWeAaNsWftjj++n08C9bMJL/NMh98qy5V8Acys
Nnq/onN694/BtZqhFLKPM58N7yLcZnuEvUUXBj08yrl3NI/K6s8/MT7jiOOASSXI
l7WdmplNsDz4SgCbZN2fOUvRJ9e4
-----END CERTIFICATE-----
# Issuer: CN=Actalis Authentication Root CA O=Actalis S.p.A./03358520967
# Subject: CN=Actalis Authentication Root CA O=Actalis S.p.A./03358520967
# Label: "Actalis Authentication Root CA"
@ -4528,3 +4495,191 @@ PQQDAwNpADBmAjEAyjzGKnXCXnViOTYAYFqLwZOZzNnbQTs7h5kXO9XMT8oi96CA
y/m0sRtW9XLS/BnRAjEAkfcwkz8QRitxpNA7RJvAKQIFskF3UfN5Wp6OFKBOQtJb
gfM0agPnIjhQW+0ZT0MW
-----END CERTIFICATE-----
# Issuer: CN=DigiCert TLS ECC P384 Root G5 O=DigiCert, Inc.
# Subject: CN=DigiCert TLS ECC P384 Root G5 O=DigiCert, Inc.
# Label: "DigiCert TLS ECC P384 Root G5"
# Serial: 13129116028163249804115411775095713523
# MD5 Fingerprint: d3:71:04:6a:43:1c:db:a6:59:e1:a8:a3:aa:c5:71:ed
# SHA1 Fingerprint: 17:f3:de:5e:9f:0f:19:e9:8e:f6:1f:32:26:6e:20:c4:07:ae:30:ee
# SHA256 Fingerprint: 01:8e:13:f0:77:25:32:cf:80:9b:d1:b1:72:81:86:72:83:fc:48:c6:e1:3b:e9:c6:98:12:85:4a:49:0c:1b:05
-----BEGIN CERTIFICATE-----
MIICGTCCAZ+gAwIBAgIQCeCTZaz32ci5PhwLBCou8zAKBggqhkjOPQQDAzBOMQsw
CQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJjAkBgNVBAMTHURp
Z2lDZXJ0IFRMUyBFQ0MgUDM4NCBSb290IEc1MB4XDTIxMDExNTAwMDAwMFoXDTQ2
MDExNDIzNTk1OVowTjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJ
bmMuMSYwJAYDVQQDEx1EaWdpQ2VydCBUTFMgRUNDIFAzODQgUm9vdCBHNTB2MBAG
ByqGSM49AgEGBSuBBAAiA2IABMFEoc8Rl1Ca3iOCNQfN0MsYndLxf3c1TzvdlHJS
7cI7+Oz6e2tYIOyZrsn8aLN1udsJ7MgT9U7GCh1mMEy7H0cKPGEQQil8pQgO4CLp
0zVozptjn4S1mU1YoI71VOeVyaNCMEAwHQYDVR0OBBYEFMFRRVBZqz7nLFr6ICIS
B4CIfBFqMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49
BAMDA2gAMGUCMQCJao1H5+z8blUD2WdsJk6Dxv3J+ysTvLd6jLRl0mlpYxNjOyZQ
LgGheQaRnUi/wr4CMEfDFXuxoJGZSZOoPHzoRgaLLPIxAJSdYsiJvRmEFOml+wG4
DXZDjC5Ty3zfDBeWUA==
-----END CERTIFICATE-----
# Issuer: CN=DigiCert TLS RSA4096 Root G5 O=DigiCert, Inc.
# Subject: CN=DigiCert TLS RSA4096 Root G5 O=DigiCert, Inc.
# Label: "DigiCert TLS RSA4096 Root G5"
# Serial: 11930366277458970227240571539258396554
# MD5 Fingerprint: ac:fe:f7:34:96:a9:f2:b3:b4:12:4b:e4:27:41:6f:e1
# SHA1 Fingerprint: a7:88:49:dc:5d:7c:75:8c:8c:de:39:98:56:b3:aa:d0:b2:a5:71:35
# SHA256 Fingerprint: 37:1a:00:dc:05:33:b3:72:1a:7e:eb:40:e8:41:9e:70:79:9d:2b:0a:0f:2c:1d:80:69:31:65:f7:ce:c4:ad:75
-----BEGIN CERTIFICATE-----
MIIFZjCCA06gAwIBAgIQCPm0eKj6ftpqMzeJ3nzPijANBgkqhkiG9w0BAQwFADBN
MQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJTAjBgNVBAMT
HERpZ2lDZXJ0IFRMUyBSU0E0MDk2IFJvb3QgRzUwHhcNMjEwMTE1MDAwMDAwWhcN
NDYwMTE0MjM1OTU5WjBNMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQs
IEluYy4xJTAjBgNVBAMTHERpZ2lDZXJ0IFRMUyBSU0E0MDk2IFJvb3QgRzUwggIi
MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCz0PTJeRGd/fxmgefM1eS87IE+
ajWOLrfn3q/5B03PMJ3qCQuZvWxX2hhKuHisOjmopkisLnLlvevxGs3npAOpPxG0
2C+JFvuUAT27L/gTBaF4HI4o4EXgg/RZG5Wzrn4DReW+wkL+7vI8toUTmDKdFqgp
wgscONyfMXdcvyej/Cestyu9dJsXLfKB2l2w4SMXPohKEiPQ6s+d3gMXsUJKoBZM
pG2T6T867jp8nVid9E6P/DsjyG244gXazOvswzH016cpVIDPRFtMbzCe88zdH5RD
nU1/cHAN1DrRN/BsnZvAFJNY781BOHW8EwOVfH/jXOnVDdXifBBiqmvwPXbzP6Po
sMH976pXTayGpxi0KcEsDr9kvimM2AItzVwv8n/vFfQMFawKsPHTDU9qTXeXAaDx
Zre3zu/O7Oyldcqs4+Fj97ihBMi8ez9dLRYiVu1ISf6nL3kwJZu6ay0/nTvEF+cd
Lvvyz6b84xQslpghjLSR6Rlgg/IwKwZzUNWYOwbpx4oMYIwo+FKbbuH2TbsGJJvX
KyY//SovcfXWJL5/MZ4PbeiPT02jP/816t9JXkGPhvnxd3lLG7SjXi/7RgLQZhNe
XoVPzthwiHvOAbWWl9fNff2C+MIkwcoBOU+NosEUQB+cZtUMCUbW8tDRSHZWOkPL
tgoRObqME2wGtZ7P6wIDAQABo0IwQDAdBgNVHQ4EFgQUUTMc7TZArxfTJc1paPKv
TiM+s0EwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcN
AQEMBQADggIBAGCmr1tfV9qJ20tQqcQjNSH/0GEwhJG3PxDPJY7Jv0Y02cEhJhxw
GXIeo8mH/qlDZJY6yFMECrZBu8RHANmfGBg7sg7zNOok992vIGCukihfNudd5N7H
PNtQOa27PShNlnx2xlv0wdsUpasZYgcYQF+Xkdycx6u1UQ3maVNVzDl92sURVXLF
O4uJ+DQtpBflF+aZfTCIITfNMBc9uPK8qHWgQ9w+iUuQrm0D4ByjoJYJu32jtyoQ
REtGBzRj7TG5BO6jm5qu5jF49OokYTurWGT/u4cnYiWB39yhL/btp/96j1EuMPik
AdKFOV8BmZZvWltwGUb+hmA+rYAQCd05JS9Yf7vSdPD3Rh9GOUrYU9DzLjtxpdRv
/PNn5AeP3SYZ4Y1b+qOTEZvpyDrDVWiakuFSdjjo4bq9+0/V77PnSIMx8IIh47a+
p6tv75/fTM8BuGJqIz3nCU2AG3swpMPdB380vqQmsvZB6Akd4yCYqjdP//fx4ilw
MUc/dNAUFvohigLVigmUdy7yWSiLfFCSCmZ4OIN1xLVaqBHG5cGdZlXPU8Sv13WF
qUITVuwhd4GTWgzqltlJyqEI8pc7bZsEGCREjnwB8twl2F6GmrE52/WRMmrRpnCK
ovfepEWFJqgejF0pW8hL2JpqA15w8oVPbEtoL8pU9ozaMv7Da4M/OMZ+
-----END CERTIFICATE-----
# Issuer: CN=Certainly Root R1 O=Certainly
# Subject: CN=Certainly Root R1 O=Certainly
# Label: "Certainly Root R1"
# Serial: 188833316161142517227353805653483829216
# MD5 Fingerprint: 07:70:d4:3e:82:87:a0:fa:33:36:13:f4:fa:33:e7:12
# SHA1 Fingerprint: a0:50:ee:0f:28:71:f4:27:b2:12:6d:6f:50:96:25:ba:cc:86:42:af
# SHA256 Fingerprint: 77:b8:2c:d8:64:4c:43:05:f7:ac:c5:cb:15:6b:45:67:50:04:03:3d:51:c6:0c:62:02:a8:e0:c3:34:67:d3:a0
-----BEGIN CERTIFICATE-----
MIIFRzCCAy+gAwIBAgIRAI4P+UuQcWhlM1T01EQ5t+AwDQYJKoZIhvcNAQELBQAw
PTELMAkGA1UEBhMCVVMxEjAQBgNVBAoTCUNlcnRhaW5seTEaMBgGA1UEAxMRQ2Vy
dGFpbmx5IFJvb3QgUjEwHhcNMjEwNDAxMDAwMDAwWhcNNDYwNDAxMDAwMDAwWjA9
MQswCQYDVQQGEwJVUzESMBAGA1UEChMJQ2VydGFpbmx5MRowGAYDVQQDExFDZXJ0
YWlubHkgUm9vdCBSMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANA2
1B/q3avk0bbm+yLA3RMNansiExyXPGhjZjKcA7WNpIGD2ngwEc/csiu+kr+O5MQT
vqRoTNoCaBZ0vrLdBORrKt03H2As2/X3oXyVtwxwhi7xOu9S98zTm/mLvg7fMbed
aFySpvXl8wo0tf97ouSHocavFwDvA5HtqRxOcT3Si2yJ9HiG5mpJoM610rCrm/b0
1C7jcvk2xusVtyWMOvwlDbMicyF0yEqWYZL1LwsYpfSt4u5BvQF5+paMjRcCMLT5
r3gajLQ2EBAHBXDQ9DGQilHFhiZ5shGIXsXwClTNSaa/ApzSRKft43jvRl5tcdF5
cBxGX1HpyTfcX35pe0HfNEXgO4T0oYoKNp43zGJS4YkNKPl6I7ENPT2a/Z2B7yyQ
wHtETrtJ4A5KVpK8y7XdeReJkd5hiXSSqOMyhb5OhaRLWcsrxXiOcVTQAjeZjOVJ
6uBUcqQRBi8LjMFbvrWhsFNunLhgkR9Za/kt9JQKl7XsxXYDVBtlUrpMklZRNaBA
2CnbrlJ2Oy0wQJuK0EJWtLeIAaSHO1OWzaMWj/Nmqhexx2DgwUMFDO6bW2BvBlyH
Wyf5QBGenDPBt+U1VwV/J84XIIwc/PH72jEpSe31C4SnT8H2TsIonPru4K8H+zMR
eiFPCyEQtkA6qyI6BJyLm4SGcprSp6XEtHWRqSsjAgMBAAGjQjBAMA4GA1UdDwEB
/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTgqj8ljZ9EXME66C6u
d0yEPmcM9DANBgkqhkiG9w0BAQsFAAOCAgEAuVevuBLaV4OPaAszHQNTVfSVcOQr
PbA56/qJYv331hgELyE03fFo8NWWWt7CgKPBjcZq91l3rhVkz1t5BXdm6ozTaw3d
8VkswTOlMIAVRQdFGjEitpIAq5lNOo93r6kiyi9jyhXWx8bwPWz8HA2YEGGeEaIi
1wrykXprOQ4vMMM2SZ/g6Q8CRFA3lFV96p/2O7qUpUzpvD5RtOjKkjZUbVwlKNrd
rRT90+7iIgXr0PK3aBLXWopBGsaSpVo7Y0VPv+E6dyIvXL9G+VoDhRNCX8reU9di
taY1BMJH/5n9hN9czulegChB8n3nHpDYT3Y+gjwN/KUD+nsa2UUeYNrEjvn8K8l7
lcUq/6qJ34IxD3L/DCfXCh5WAFAeDJDBlrXYFIW7pw0WwfgHJBu6haEaBQmAupVj
yTrsJZ9/nbqkRxWbRHDxakvWOF5D8xh+UG7pWijmZeZ3Gzr9Hb4DJqPb1OG7fpYn
Kx3upPvaJVQTA945xsMfTZDsjxtK0hzthZU4UHlG1sGQUDGpXJpuHfUzVounmdLy
yCwzk5Iwx06MZTMQZBf9JBeW0Y3COmor6xOLRPIh80oat3df1+2IpHLlOR+Vnb5n
wXARPbv0+Em34yaXOp/SX3z7wJl8OSngex2/DaeP0ik0biQVy96QXr8axGbqwua6
OV+KmalBWQewLK8=
-----END CERTIFICATE-----
# Issuer: CN=Certainly Root E1 O=Certainly
# Subject: CN=Certainly Root E1 O=Certainly
# Label: "Certainly Root E1"
# Serial: 8168531406727139161245376702891150584
# MD5 Fingerprint: 0a:9e:ca:cd:3e:52:50:c6:36:f3:4b:a3:ed:a7:53:e9
# SHA1 Fingerprint: f9:e1:6d:dc:01:89:cf:d5:82:45:63:3e:c5:37:7d:c2:eb:93:6f:2b
# SHA256 Fingerprint: b4:58:5f:22:e4:ac:75:6a:4e:86:12:a1:36:1c:5d:9d:03:1a:93:fd:84:fe:bb:77:8f:a3:06:8b:0f:c4:2d:c2
-----BEGIN CERTIFICATE-----
MIIB9zCCAX2gAwIBAgIQBiUzsUcDMydc+Y2aub/M+DAKBggqhkjOPQQDAzA9MQsw
CQYDVQQGEwJVUzESMBAGA1UEChMJQ2VydGFpbmx5MRowGAYDVQQDExFDZXJ0YWlu
bHkgUm9vdCBFMTAeFw0yMTA0MDEwMDAwMDBaFw00NjA0MDEwMDAwMDBaMD0xCzAJ
BgNVBAYTAlVTMRIwEAYDVQQKEwlDZXJ0YWlubHkxGjAYBgNVBAMTEUNlcnRhaW5s
eSBSb290IEUxMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE3m/4fxzf7flHh4axpMCK
+IKXgOqPyEpeKn2IaKcBYhSRJHpcnqMXfYqGITQYUBsQ3tA3SybHGWCA6TS9YBk2
QNYphwk8kXr2vBMj3VlOBF7PyAIcGFPBMdjaIOlEjeR2o0IwQDAOBgNVHQ8BAf8E
BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU8ygYy2R17ikq6+2uI1g4
hevIIgcwCgYIKoZIzj0EAwMDaAAwZQIxALGOWiDDshliTd6wT99u0nCK8Z9+aozm
ut6Dacpps6kFtZaSF4fC0urQe87YQVt8rgIwRt7qy12a7DLCZRawTDBcMPPaTnOG
BtjOiQRINzf43TNRnXCve1XYAS59BWQOhriR
-----END CERTIFICATE-----
# Issuer: CN=E-Tugra Global Root CA RSA v3 O=E-Tugra EBG A.S. OU=E-Tugra Trust Center
# Subject: CN=E-Tugra Global Root CA RSA v3 O=E-Tugra EBG A.S. OU=E-Tugra Trust Center
# Label: "E-Tugra Global Root CA RSA v3"
# Serial: 75951268308633135324246244059508261641472512052
# MD5 Fingerprint: 22:be:10:f6:c2:f8:03:88:73:5f:33:29:47:28:47:a4
# SHA1 Fingerprint: e9:a8:5d:22:14:52:1c:5b:aa:0a:b4:be:24:6a:23:8a:c9:ba:e2:a9
# SHA256 Fingerprint: ef:66:b0:b1:0a:3c:db:9f:2e:36:48:c7:6b:d2:af:18:ea:d2:bf:e6:f1:17:65:5e:28:c4:06:0d:a1:a3:f4:c2
-----BEGIN CERTIFICATE-----
MIIF8zCCA9ugAwIBAgIUDU3FzRYilZYIfrgLfxUGNPt5EDQwDQYJKoZIhvcNAQEL
BQAwgYAxCzAJBgNVBAYTAlRSMQ8wDQYDVQQHEwZBbmthcmExGTAXBgNVBAoTEEUt
VHVncmEgRUJHIEEuUy4xHTAbBgNVBAsTFEUtVHVncmEgVHJ1c3QgQ2VudGVyMSYw
JAYDVQQDEx1FLVR1Z3JhIEdsb2JhbCBSb290IENBIFJTQSB2MzAeFw0yMDAzMTgw
OTA3MTdaFw00NTAzMTIwOTA3MTdaMIGAMQswCQYDVQQGEwJUUjEPMA0GA1UEBxMG
QW5rYXJhMRkwFwYDVQQKExBFLVR1Z3JhIEVCRyBBLlMuMR0wGwYDVQQLExRFLVR1
Z3JhIFRydXN0IENlbnRlcjEmMCQGA1UEAxMdRS1UdWdyYSBHbG9iYWwgUm9vdCBD
QSBSU0EgdjMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCiZvCJt3J7
7gnJY9LTQ91ew6aEOErxjYG7FL1H6EAX8z3DeEVypi6Q3po61CBxyryfHUuXCscx
uj7X/iWpKo429NEvx7epXTPcMHD4QGxLsqYxYdE0PD0xesevxKenhOGXpOhL9hd8
7jwH7eKKV9y2+/hDJVDqJ4GohryPUkqWOmAalrv9c/SF/YP9f4RtNGx/ardLAQO/
rWm31zLZ9Vdq6YaCPqVmMbMWPcLzJmAy01IesGykNz709a/r4d+ABs8qQedmCeFL
l+d3vSFtKbZnwy1+7dZ5ZdHPOrbRsV5WYVB6Ws5OUDGAA5hH5+QYfERaxqSzO8bG
wzrwbMOLyKSRBfP12baqBqG3q+Sx6iEUXIOk/P+2UNOMEiaZdnDpwA+mdPy70Bt4
znKS4iicvObpCdg604nmvi533wEKb5b25Y08TVJ2Glbhc34XrD2tbKNSEhhw5oBO
M/J+JjKsBY04pOZ2PJ8QaQ5tndLBeSBrW88zjdGUdjXnXVXHt6woq0bM5zshtQoK
5EpZ3IE1S0SVEgpnpaH/WwAH0sDM+T/8nzPyAPiMbIedBi3x7+PmBvrFZhNb/FAH
nnGGstpvdDDPk1Po3CLW3iAfYY2jLqN4MpBs3KwytQXk9TwzDdbgh3cXTJ2w2Amo
DVf3RIXwyAS+XF1a4xeOVGNpf0l0ZAWMowIDAQABo2MwYTAPBgNVHRMBAf8EBTAD
AQH/MB8GA1UdIwQYMBaAFLK0ruYt9ybVqnUtdkvAG1Mh0EjvMB0GA1UdDgQWBBSy
tK7mLfcm1ap1LXZLwBtTIdBI7zAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQEL
BQADggIBAImocn+M684uGMQQgC0QDP/7FM0E4BQ8Tpr7nym/Ip5XuYJzEmMmtcyQ
6dIqKe6cLcwsmb5FJ+Sxce3kOJUxQfJ9emN438o2Fi+CiJ+8EUdPdk3ILY7r3y18
Tjvarvbj2l0Upq7ohUSdBm6O++96SmotKygY/r+QLHUWnw/qln0F7psTpURs+APQ
3SPh/QMSEgj0GDSz4DcLdxEBSL9htLX4GdnLTeqjjO/98Aa1bZL0SmFQhO3sSdPk
vmjmLuMxC1QLGpLWgti2omU8ZgT5Vdps+9u1FGZNlIM7zR6mK7L+d0CGq+ffCsn9
9t2HVhjYsCxVYJb6CH5SkPVLpi6HfMsg2wY+oF0Dd32iPBMbKaITVaA9FCKvb7jQ
mhty3QUBjYZgv6Rn7rWlDdF/5horYmbDB7rnoEgcOMPpRfunf/ztAmgayncSd6YA
VSgU7NbHEqIbZULpkejLPoeJVF3Zr52XnGnnCv8PWniLYypMfUeUP95L6VPQMPHF
9p5J3zugkaOj/s1YzOrfr28oO6Bpm4/srK4rVJ2bBLFHIK+WEj5jlB0E5y67hscM
moi/dkfv97ALl2bSRM9gUgfh1SxKOidhd8rXj+eHDjD/DLsE4mHDosiXYY60MGo8
bcIHX0pzLz/5FooBZu+6kcpSV3uu1OYP3Qt6f4ueJiDPO++BcYNZ
-----END CERTIFICATE-----
# Issuer: CN=E-Tugra Global Root CA ECC v3 O=E-Tugra EBG A.S. OU=E-Tugra Trust Center
# Subject: CN=E-Tugra Global Root CA ECC v3 O=E-Tugra EBG A.S. OU=E-Tugra Trust Center
# Label: "E-Tugra Global Root CA ECC v3"
# Serial: 218504919822255052842371958738296604628416471745
# MD5 Fingerprint: 46:bc:81:bb:f1:b5:1e:f7:4b:96:bc:14:e2:e7:27:64
# SHA1 Fingerprint: 8a:2f:af:57:53:b1:b0:e6:a1:04:ec:5b:6a:69:71:6d:f6:1c:e2:84
# SHA256 Fingerprint: 87:3f:46:85:fa:7f:56:36:25:25:2e:6d:36:bc:d7:f1:6f:c2:49:51:f2:64:e4:7e:1b:95:4f:49:08:cd:ca:13
-----BEGIN CERTIFICATE-----
MIICpTCCAiqgAwIBAgIUJkYZdzHhT28oNt45UYbm1JeIIsEwCgYIKoZIzj0EAwMw
gYAxCzAJBgNVBAYTAlRSMQ8wDQYDVQQHEwZBbmthcmExGTAXBgNVBAoTEEUtVHVn
cmEgRUJHIEEuUy4xHTAbBgNVBAsTFEUtVHVncmEgVHJ1c3QgQ2VudGVyMSYwJAYD
VQQDEx1FLVR1Z3JhIEdsb2JhbCBSb290IENBIEVDQyB2MzAeFw0yMDAzMTgwOTQ2
NThaFw00NTAzMTIwOTQ2NThaMIGAMQswCQYDVQQGEwJUUjEPMA0GA1UEBxMGQW5r
YXJhMRkwFwYDVQQKExBFLVR1Z3JhIEVCRyBBLlMuMR0wGwYDVQQLExRFLVR1Z3Jh
IFRydXN0IENlbnRlcjEmMCQGA1UEAxMdRS1UdWdyYSBHbG9iYWwgUm9vdCBDQSBF
Q0MgdjMwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAASOmCm/xxAeJ9urA8woLNheSBkQ
KczLWYHMjLiSF4mDKpL2w6QdTGLVn9agRtwcvHbB40fQWxPa56WzZkjnIZpKT4YK
fWzqTTKACrJ6CZtpS5iB4i7sAnCWH/31Rs7K3IKjYzBhMA8GA1UdEwEB/wQFMAMB
Af8wHwYDVR0jBBgwFoAU/4Ixcj75xGZsrTie0bBRiKWQzPUwHQYDVR0OBBYEFP+C
MXI++cRmbK04ntGwUYilkMz1MA4GA1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNp
ADBmAjEA5gVYaWHlLcoNy/EZCL3W/VGSGn5jVASQkZo1kTmZ+gepZpO6yGjUij/6
7W4WAie3AjEA3VoXK3YdZUKWpqxdinlW2Iob35reX8dQj7FbcQwm32pAAOwzkSFx
vmjkI6TZraE3
-----END CERTIFICATE-----

View file

@ -37,6 +37,7 @@ __all__ = [
'Counter',
'Deque',
'DefaultDict',
'NamedTuple',
'OrderedDict',
'TypedDict',
@ -380,6 +381,46 @@ def _is_callable_members_only(cls):
return all(callable(getattr(cls, attr, None)) for attr in _get_protocol_attrs(cls))
def _maybe_adjust_parameters(cls):
"""Helper function used in Protocol.__init_subclass__ and _TypedDictMeta.__new__.
The contents of this function are very similar
to logic found in typing.Generic.__init_subclass__
on the CPython main branch.
"""
tvars = []
if '__orig_bases__' in cls.__dict__:
tvars = typing._collect_type_vars(cls.__orig_bases__)
# Look for Generic[T1, ..., Tn] or Protocol[T1, ..., Tn].
# If found, tvars must be a subset of it.
# If not found, tvars is it.
# Also check for and reject plain Generic,
# and reject multiple Generic[...] and/or Protocol[...].
gvars = None
for base in cls.__orig_bases__:
if (isinstance(base, typing._GenericAlias) and
base.__origin__ in (typing.Generic, Protocol)):
# for error messages
the_base = base.__origin__.__name__
if gvars is not None:
raise TypeError(
"Cannot inherit from Generic[...]"
" and/or Protocol[...] multiple types.")
gvars = base.__parameters__
if gvars is None:
gvars = tvars
else:
tvarset = set(tvars)
gvarset = set(gvars)
if not tvarset <= gvarset:
s_vars = ', '.join(str(t) for t in tvars if t not in gvarset)
s_args = ', '.join(str(g) for g in gvars)
raise TypeError(f"Some type variables ({s_vars}) are"
f" not listed in {the_base}[{s_args}]")
tvars = gvars
cls.__parameters__ = tuple(tvars)
# 3.8+
if hasattr(typing, 'Protocol'):
Protocol = typing.Protocol
@ -476,43 +517,13 @@ else:
return typing._GenericAlias(cls, params)
def __init_subclass__(cls, *args, **kwargs):
tvars = []
if '__orig_bases__' in cls.__dict__:
error = typing.Generic in cls.__orig_bases__
else:
error = typing.Generic in cls.__bases__
if error:
raise TypeError("Cannot inherit from plain Generic")
if '__orig_bases__' in cls.__dict__:
tvars = typing._collect_type_vars(cls.__orig_bases__)
# Look for Generic[T1, ..., Tn] or Protocol[T1, ..., Tn].
# If found, tvars must be a subset of it.
# If not found, tvars is it.
# Also check for and reject plain Generic,
# and reject multiple Generic[...] and/or Protocol[...].
gvars = None
for base in cls.__orig_bases__:
if (isinstance(base, typing._GenericAlias) and
base.__origin__ in (typing.Generic, Protocol)):
# for error messages
the_base = base.__origin__.__name__
if gvars is not None:
raise TypeError(
"Cannot inherit from Generic[...]"
" and/or Protocol[...] multiple types.")
gvars = base.__parameters__
if gvars is None:
gvars = tvars
else:
tvarset = set(tvars)
gvarset = set(gvars)
if not tvarset <= gvarset:
s_vars = ', '.join(str(t) for t in tvars if t not in gvarset)
s_args = ', '.join(str(g) for g in gvars)
raise TypeError(f"Some type variables ({s_vars}) are"
f" not listed in {the_base}[{s_args}]")
tvars = gvars
cls.__parameters__ = tuple(tvars)
_maybe_adjust_parameters(cls)
# Determine if this is a protocol or a concrete subclass.
if not cls.__dict__.get('_is_protocol', None):
@ -613,6 +624,7 @@ if hasattr(typing, "Required"):
# keyword with old-style TypedDict(). See https://bugs.python.org/issue42059
# The standard library TypedDict below Python 3.11 does not store runtime
# information about optional and required keys when using Required or NotRequired.
# Generic TypedDicts are also impossible using typing.TypedDict on Python <3.11.
TypedDict = typing.TypedDict
_TypedDictMeta = typing._TypedDictMeta
is_typeddict = typing.is_typeddict
@ -695,8 +707,16 @@ else:
# Subclasses and instances of TypedDict return actual dictionaries
# via _dict_new.
ns['__new__'] = _typeddict_new if name == 'TypedDict' else _dict_new
# Don't insert typing.Generic into __bases__ here,
# or Generic.__init_subclass__ will raise TypeError
# in the super().__new__() call.
# Instead, monkey-patch __bases__ onto the class after it's been created.
tp_dict = super().__new__(cls, name, (dict,), ns)
if any(issubclass(base, typing.Generic) for base in bases):
tp_dict.__bases__ = (typing.Generic, dict)
_maybe_adjust_parameters(tp_dict)
annotations = {}
own_annotations = ns.get('__annotations__', {})
msg = "TypedDict('Name', {f0: t0, f1: t1, ...}); each t must be a type"
@ -1958,3 +1978,92 @@ else:
if not hasattr(typing, "TypeVarTuple"):
typing._collect_type_vars = _collect_type_vars
typing._check_generic = _check_generic
# Backport typing.NamedTuple as it exists in Python 3.11.
# In 3.11, the ability to define generic `NamedTuple`s was supported.
# This was explicitly disallowed in 3.9-3.10, and only half-worked in <=3.8.
if sys.version_info >= (3, 11):
NamedTuple = typing.NamedTuple
else:
def _caller():
try:
return sys._getframe(2).f_globals.get('__name__', '__main__')
except (AttributeError, ValueError): # For platforms without _getframe()
return None
def _make_nmtuple(name, types, module, defaults=()):
fields = [n for n, t in types]
annotations = {n: typing._type_check(t, f"field {n} annotation must be a type")
for n, t in types}
nm_tpl = collections.namedtuple(name, fields,
defaults=defaults, module=module)
nm_tpl.__annotations__ = nm_tpl.__new__.__annotations__ = annotations
# The `_field_types` attribute was removed in 3.9;
# in earlier versions, it is the same as the `__annotations__` attribute
if sys.version_info < (3, 9):
nm_tpl._field_types = annotations
return nm_tpl
_prohibited_namedtuple_fields = typing._prohibited
_special_namedtuple_fields = frozenset({'__module__', '__name__', '__annotations__'})
class _NamedTupleMeta(type):
def __new__(cls, typename, bases, ns):
assert _NamedTuple in bases
for base in bases:
if base is not _NamedTuple and base is not typing.Generic:
raise TypeError(
'can only inherit from a NamedTuple type and Generic')
bases = tuple(tuple if base is _NamedTuple else base for base in bases)
types = ns.get('__annotations__', {})
default_names = []
for field_name in types:
if field_name in ns:
default_names.append(field_name)
elif default_names:
raise TypeError(f"Non-default namedtuple field {field_name} "
f"cannot follow default field"
f"{'s' if len(default_names) > 1 else ''} "
f"{', '.join(default_names)}")
nm_tpl = _make_nmtuple(
typename, types.items(),
defaults=[ns[n] for n in default_names],
module=ns['__module__']
)
nm_tpl.__bases__ = bases
if typing.Generic in bases:
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:
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])
if typing.Generic in bases:
nm_tpl.__init_subclass__()
return nm_tpl
def NamedTuple(__typename, __fields=None, **kwargs):
if __fields is None:
__fields = kwargs.items()
elif kwargs:
raise TypeError("Either list of fields or keywords"
" can be provided to NamedTuple, not both")
return _make_nmtuple(__typename, __fields, module=_caller())
NamedTuple.__doc__ = typing.NamedTuple.__doc__
_NamedTuple = type.__new__(_NamedTupleMeta, 'NamedTuple', (), {})
# On 3.8+, alter the signature so that it matches typing.NamedTuple.
# The signature of typing.NamedTuple on >=3.8 is invalid syntax in Python 3.7,
# so just leave the signature as it is on 3.7.
if sys.version_info >= (3, 8):
NamedTuple.__text_signature__ = '(typename, fields=None, /, **kwargs)'
def _namedtuple_mro_entries(bases):
assert NamedTuple in bases
return (_NamedTuple,)
NamedTuple.__mro_entries__ = _namedtuple_mro_entries

View file

@ -29,6 +29,7 @@ if TYPE_CHECKING:
from ba._cloud import CloudSubsystem
from bastd.actor import spazappearance
from ba._accountv2 import AccountV2Subsystem
from ba._level import Level
class App:
@ -274,6 +275,7 @@ class App:
# Co-op Campaigns.
self.campaigns: dict[str, ba.Campaign] = {}
self.custom_coop_practice_games: list[str] = []
# Server Mode.
self.server: ba.ServerController | None = None
@ -342,6 +344,7 @@ class App:
from bastd.actor import spazappearance
from ba._generated.enums import TimeType
self._aioloop = _asyncio.setup_asyncio()
cfg = self.config
@ -432,8 +435,6 @@ class App:
# from ba._dependency import test_depset
# test_depset()
if bool(False):
self._test_https()
def _update_state(self) -> None:
if self._app_paused:
@ -527,6 +528,15 @@ class App:
# FIXME: This should not be an actor attr.
activity.paused_text = None
def add_coop_practice_level(self, level: Level) -> None:
"""Adds an individual level to the 'practice' section in Co-op."""
# Assign this level to our catch-all campaign.
self.campaigns['Challenges'].addlevel(level)
# Make note to add it to our challenges UI.
self.custom_coop_practice_games.append(f'Challenges:{level.name}')
def return_to_main_menu_session_gracefully(self,
reset_ui: bool = True) -> None:
"""Attempt to cleanly get back to the main menu."""
@ -643,19 +653,3 @@ class App:
"""
self._initial_login_completed = True
self._update_state()
def _test_https(self) -> None:
"""Testing https support.
(would be nice to get this working on our custom Python builds; need
to wrangle certificates somehow).
"""
import urllib.request
try:
with urllib.request.urlopen('https://example.com') as url:
val = url.read()
_ba.screenmessage('HTTPS SUCCESS!')
print('HTTPS TEST SUCCESS', len(val))
except Exception as exc:
_ba.screenmessage('HTTPS FAIL.')
print('HTTPS TEST FAIL:', exc)

View file

@ -17,6 +17,7 @@ import sys
from efro.dataclassio import (ioprepped, IOAttrs, dataclass_from_json,
dataclass_to_json)
import _ba
if TYPE_CHECKING:
from bacommon.assets import AssetPackageFlavor
@ -165,7 +166,6 @@ def fetch_url(url: str, filename: Path, asset_gather: AssetGather) -> None:
"""
# pylint: disable=consider-using-with
import socket
# We don't want to keep the provided AssetGather alive, but we want
@ -175,7 +175,9 @@ def fetch_url(url: str, filename: Path, asset_gather: AssetGather) -> None:
# Pass a very short timeout to urllib so we have opportunities
# to cancel even with network blockage.
req = urllib.request.urlopen(url, timeout=1)
req = urllib.request.urlopen(url,
context=_ba.app.net.sslcontext,
timeout=1)
file_size = int(req.headers['Content-Length'])
print(f'\nDownloading: {filename} Bytes: {file_size:,}')

View file

@ -11,6 +11,9 @@ from __future__ import annotations
from typing import TYPE_CHECKING
import asyncio
import logging
import time
import os
if TYPE_CHECKING:
import ba
@ -19,6 +22,8 @@ if TYPE_CHECKING:
_asyncio_timer: ba.Timer | None = None
_asyncio_event_loop: asyncio.AbstractEventLoop | None = None
DEBUG_TIMING = os.environ.get('BA_DEBUG_TIMING') == '1'
def setup_asyncio() -> asyncio.AbstractEventLoop:
"""Setup asyncio functionality for the logic thread."""
@ -53,7 +58,18 @@ def setup_asyncio() -> asyncio.AbstractEventLoop:
def run_cycle() -> None:
assert _asyncio_event_loop is not None
_asyncio_event_loop.call_soon(_asyncio_event_loop.stop)
starttime = time.monotonic() if DEBUG_TIMING else 0
_asyncio_event_loop.run_forever()
endtime = time.monotonic() if DEBUG_TIMING else 0
# Let's aim to have nothing take longer than 1/120 of a second.
if DEBUG_TIMING:
warn_time = 1.0 / 120
duration = endtime - starttime
if duration > warn_time:
logging.warning(
'Asyncio loop step took %.4fs; ideal max is %.4f',
duration, warn_time)
global _asyncio_timer # pylint: disable=invalid-name
_asyncio_timer = _ba.Timer(1.0 / 30.0,

View file

@ -28,10 +28,16 @@ class Campaign:
Category: **App Classes**
"""
def __init__(self, name: str, sequential: bool = True):
def __init__(self,
name: str,
sequential: bool = True,
levels: list[ba.Level] | None = None):
self._name = name
self._levels: list[ba.Level] = []
self._sequential = sequential
self._levels: list[ba.Level] = []
if levels is not None:
for level in levels:
self.addlevel(level)
@property
def name(self) -> str:
@ -43,12 +49,15 @@ class Campaign:
"""Whether this Campaign's levels must be played in sequence."""
return self._sequential
def addlevel(self, level: ba.Level) -> None:
def addlevel(self, level: ba.Level, index: int | None = None) -> None:
"""Adds a ba.Level to the Campaign."""
if level.campaign is not None:
raise RuntimeError('Level already belongs to a campaign.')
level.set_campaign(self, len(self._levels))
self._levels.append(level)
if index is None:
self._levels.append(level)
else:
self._levels.insert(index, level)
@property
def levels(self) -> list[ba.Level]:
@ -91,9 +100,8 @@ class Campaign:
def init_campaigns() -> None:
"""Fill out initial default Campaigns."""
# pylint: disable=too-many-statements
# pylint: disable=cyclic-import
from ba import _level
from ba._level import Level
from bastd.game.onslaught import OnslaughtGame
from bastd.game.football import FootballCoopGame
from bastd.game.runaround import RunaroundGame
@ -109,244 +117,218 @@ def init_campaigns() -> None:
# FIXME: Once translations catch up, we can convert these to use the
# generic display-name '${GAME} Training' type stuff.
campaign = Campaign('Easy')
campaign.addlevel(
_level.Level('Onslaught Training',
gametype=OnslaughtGame,
settings={'preset': 'training_easy'},
preview_texture_name='doomShroomPreview'))
campaign.addlevel(
_level.Level('Rookie Onslaught',
gametype=OnslaughtGame,
settings={'preset': 'rookie_easy'},
preview_texture_name='courtyardPreview'))
campaign.addlevel(
_level.Level('Rookie Football',
gametype=FootballCoopGame,
settings={'preset': 'rookie_easy'},
preview_texture_name='footballStadiumPreview'))
campaign.addlevel(
_level.Level('Pro Onslaught',
gametype=OnslaughtGame,
settings={'preset': 'pro_easy'},
preview_texture_name='doomShroomPreview'))
campaign.addlevel(
_level.Level('Pro Football',
gametype=FootballCoopGame,
settings={'preset': 'pro_easy'},
preview_texture_name='footballStadiumPreview'))
campaign.addlevel(
_level.Level('Pro Runaround',
gametype=RunaroundGame,
settings={'preset': 'pro_easy'},
preview_texture_name='towerDPreview'))
campaign.addlevel(
_level.Level('Uber Onslaught',
gametype=OnslaughtGame,
settings={'preset': 'uber_easy'},
preview_texture_name='courtyardPreview'))
campaign.addlevel(
_level.Level('Uber Football',
gametype=FootballCoopGame,
settings={'preset': 'uber_easy'},
preview_texture_name='footballStadiumPreview'))
campaign.addlevel(
_level.Level('Uber Runaround',
gametype=RunaroundGame,
settings={'preset': 'uber_easy'},
preview_texture_name='towerDPreview'))
register_campaign(campaign)
register_campaign(
Campaign(
'Easy',
levels=[
Level('Onslaught Training',
gametype=OnslaughtGame,
settings={'preset': 'training_easy'},
preview_texture_name='doomShroomPreview'),
Level('Rookie Onslaught',
gametype=OnslaughtGame,
settings={'preset': 'rookie_easy'},
preview_texture_name='courtyardPreview'),
Level('Rookie Football',
gametype=FootballCoopGame,
settings={'preset': 'rookie_easy'},
preview_texture_name='footballStadiumPreview'),
Level('Pro Onslaught',
gametype=OnslaughtGame,
settings={'preset': 'pro_easy'},
preview_texture_name='doomShroomPreview'),
Level('Pro Football',
gametype=FootballCoopGame,
settings={'preset': 'pro_easy'},
preview_texture_name='footballStadiumPreview'),
Level('Pro Runaround',
gametype=RunaroundGame,
settings={'preset': 'pro_easy'},
preview_texture_name='towerDPreview'),
Level('Uber Onslaught',
gametype=OnslaughtGame,
settings={'preset': 'uber_easy'},
preview_texture_name='courtyardPreview'),
Level('Uber Football',
gametype=FootballCoopGame,
settings={'preset': 'uber_easy'},
preview_texture_name='footballStadiumPreview'),
Level('Uber Runaround',
gametype=RunaroundGame,
settings={'preset': 'uber_easy'},
preview_texture_name='towerDPreview')
],
))
# "hard" mode
campaign = Campaign('Default')
campaign.addlevel(
_level.Level('Onslaught Training',
gametype=OnslaughtGame,
settings={'preset': 'training'},
preview_texture_name='doomShroomPreview'))
campaign.addlevel(
_level.Level('Rookie Onslaught',
gametype=OnslaughtGame,
settings={'preset': 'rookie'},
preview_texture_name='courtyardPreview'))
campaign.addlevel(
_level.Level('Rookie Football',
gametype=FootballCoopGame,
settings={'preset': 'rookie'},
preview_texture_name='footballStadiumPreview'))
campaign.addlevel(
_level.Level('Pro Onslaught',
gametype=OnslaughtGame,
settings={'preset': 'pro'},
preview_texture_name='doomShroomPreview'))
campaign.addlevel(
_level.Level('Pro Football',
gametype=FootballCoopGame,
settings={'preset': 'pro'},
preview_texture_name='footballStadiumPreview'))
campaign.addlevel(
_level.Level('Pro Runaround',
gametype=RunaroundGame,
settings={'preset': 'pro'},
preview_texture_name='towerDPreview'))
campaign.addlevel(
_level.Level('Uber Onslaught',
gametype=OnslaughtGame,
settings={'preset': 'uber'},
preview_texture_name='courtyardPreview'))
campaign.addlevel(
_level.Level('Uber Football',
gametype=FootballCoopGame,
settings={'preset': 'uber'},
preview_texture_name='footballStadiumPreview'))
campaign.addlevel(
_level.Level('Uber Runaround',
gametype=RunaroundGame,
settings={'preset': 'uber'},
preview_texture_name='towerDPreview'))
campaign.addlevel(
_level.Level('The Last Stand',
gametype=TheLastStandGame,
settings={},
preview_texture_name='rampagePreview'))
register_campaign(campaign)
register_campaign(
Campaign(
'Default',
levels=[
Level('Onslaught Training',
gametype=OnslaughtGame,
settings={'preset': 'training'},
preview_texture_name='doomShroomPreview'),
Level('Rookie Onslaught',
gametype=OnslaughtGame,
settings={'preset': 'rookie'},
preview_texture_name='courtyardPreview'),
Level('Rookie Football',
gametype=FootballCoopGame,
settings={'preset': 'rookie'},
preview_texture_name='footballStadiumPreview'),
Level('Pro Onslaught',
gametype=OnslaughtGame,
settings={'preset': 'pro'},
preview_texture_name='doomShroomPreview'),
Level('Pro Football',
gametype=FootballCoopGame,
settings={'preset': 'pro'},
preview_texture_name='footballStadiumPreview'),
Level('Pro Runaround',
gametype=RunaroundGame,
settings={'preset': 'pro'},
preview_texture_name='towerDPreview'),
Level('Uber Onslaught',
gametype=OnslaughtGame,
settings={'preset': 'uber'},
preview_texture_name='courtyardPreview'),
Level('Uber Football',
gametype=FootballCoopGame,
settings={'preset': 'uber'},
preview_texture_name='footballStadiumPreview'),
Level('Uber Runaround',
gametype=RunaroundGame,
settings={'preset': 'uber'},
preview_texture_name='towerDPreview'),
Level('The Last Stand',
gametype=TheLastStandGame,
settings={},
preview_texture_name='rampagePreview')
],
))
# challenges: our 'official' random extra co-op levels
campaign = Campaign('Challenges', sequential=False)
campaign.addlevel(
_level.Level('Infinite Onslaught',
gametype=OnslaughtGame,
settings={'preset': 'endless'},
preview_texture_name='doomShroomPreview'))
campaign.addlevel(
_level.Level('Infinite Runaround',
gametype=RunaroundGame,
settings={'preset': 'endless'},
preview_texture_name='towerDPreview'))
campaign.addlevel(
_level.Level('Race',
displayname='${GAME}',
gametype=RaceGame,
settings={
'map': 'Big G',
'Laps': 3,
'Bomb Spawning': 0
},
preview_texture_name='bigGPreview'))
campaign.addlevel(
_level.Level('Pro Race',
displayname='Pro ${GAME}',
gametype=RaceGame,
settings={
'map': 'Big G',
'Laps': 3,
'Bomb Spawning': 1000
},
preview_texture_name='bigGPreview'))
campaign.addlevel(
_level.Level('Lake Frigid Race',
displayname='${GAME}',
gametype=RaceGame,
settings={
'map': 'Lake Frigid',
'Laps': 6,
'Mine Spawning': 2000,
'Bomb Spawning': 0
},
preview_texture_name='lakeFrigidPreview'))
campaign.addlevel(
_level.Level('Football',
displayname='${GAME}',
gametype=FootballCoopGame,
settings={'preset': 'tournament'},
preview_texture_name='footballStadiumPreview'))
campaign.addlevel(
_level.Level('Pro Football',
displayname='Pro ${GAME}',
gametype=FootballCoopGame,
settings={'preset': 'tournament_pro'},
preview_texture_name='footballStadiumPreview'))
campaign.addlevel(
_level.Level('Runaround',
displayname='${GAME}',
gametype=RunaroundGame,
settings={'preset': 'tournament'},
preview_texture_name='towerDPreview'))
campaign.addlevel(
_level.Level('Uber Runaround',
displayname='Uber ${GAME}',
gametype=RunaroundGame,
settings={'preset': 'tournament_uber'},
preview_texture_name='towerDPreview'))
campaign.addlevel(
_level.Level('The Last Stand',
displayname='${GAME}',
gametype=TheLastStandGame,
settings={'preset': 'tournament'},
preview_texture_name='rampagePreview'))
campaign.addlevel(
_level.Level('Tournament Infinite Onslaught',
displayname='Infinite Onslaught',
gametype=OnslaughtGame,
settings={'preset': 'endless_tournament'},
preview_texture_name='doomShroomPreview'))
campaign.addlevel(
_level.Level('Tournament Infinite Runaround',
displayname='Infinite Runaround',
gametype=RunaroundGame,
settings={'preset': 'endless_tournament'},
preview_texture_name='towerDPreview'))
campaign.addlevel(
_level.Level('Target Practice',
displayname='Pro ${GAME}',
gametype=TargetPracticeGame,
settings={},
preview_texture_name='doomShroomPreview'))
campaign.addlevel(
_level.Level('Target Practice B',
displayname='${GAME}',
gametype=TargetPracticeGame,
settings={
'Target Count': 2,
'Enable Impact Bombs': False,
'Enable Triple Bombs': False
},
preview_texture_name='doomShroomPreview'))
campaign.addlevel(
_level.Level('Meteor Shower',
displayname='${GAME}',
gametype=MeteorShowerGame,
settings={},
preview_texture_name='rampagePreview'))
campaign.addlevel(
_level.Level('Epic Meteor Shower',
displayname='${GAME}',
gametype=MeteorShowerGame,
settings={'Epic Mode': True},
preview_texture_name='rampagePreview'))
campaign.addlevel(
_level.Level('Easter Egg Hunt',
displayname='${GAME}',
gametype=EasterEggHuntGame,
settings={},
preview_texture_name='towerDPreview'))
campaign.addlevel(
_level.Level('Pro Easter Egg Hunt',
displayname='Pro ${GAME}',
gametype=EasterEggHuntGame,
settings={'Pro Mode': True},
preview_texture_name='towerDPreview'))
campaign.addlevel(
_level.Level(
name='Ninja Fight', # (unique id not seen by player)
displayname='${GAME}', # (readable name seen by player)
gametype=NinjaFightGame,
settings={'preset': 'regular'},
preview_texture_name='courtyardPreview'))
campaign.addlevel(
_level.Level(name='Pro Ninja Fight',
displayname='Pro ${GAME}',
gametype=NinjaFightGame,
settings={'preset': 'pro'},
preview_texture_name='courtyardPreview'))
register_campaign(campaign)
register_campaign(
Campaign(
'Challenges',
sequential=False,
levels=[
Level('Infinite Onslaught',
gametype=OnslaughtGame,
settings={'preset': 'endless'},
preview_texture_name='doomShroomPreview'),
Level('Infinite Runaround',
gametype=RunaroundGame,
settings={'preset': 'endless'},
preview_texture_name='towerDPreview'),
Level('Race',
displayname='${GAME}',
gametype=RaceGame,
settings={
'map': 'Big G',
'Laps': 3,
'Bomb Spawning': 0
},
preview_texture_name='bigGPreview'),
Level('Pro Race',
displayname='Pro ${GAME}',
gametype=RaceGame,
settings={
'map': 'Big G',
'Laps': 3,
'Bomb Spawning': 1000
},
preview_texture_name='bigGPreview'),
Level('Lake Frigid Race',
displayname='${GAME}',
gametype=RaceGame,
settings={
'map': 'Lake Frigid',
'Laps': 6,
'Mine Spawning': 2000,
'Bomb Spawning': 0
},
preview_texture_name='lakeFrigidPreview'),
Level('Football',
displayname='${GAME}',
gametype=FootballCoopGame,
settings={'preset': 'tournament'},
preview_texture_name='footballStadiumPreview'),
Level('Pro Football',
displayname='Pro ${GAME}',
gametype=FootballCoopGame,
settings={'preset': 'tournament_pro'},
preview_texture_name='footballStadiumPreview'),
Level('Runaround',
displayname='${GAME}',
gametype=RunaroundGame,
settings={'preset': 'tournament'},
preview_texture_name='towerDPreview'),
Level('Uber Runaround',
displayname='Uber ${GAME}',
gametype=RunaroundGame,
settings={'preset': 'tournament_uber'},
preview_texture_name='towerDPreview'),
Level('The Last Stand',
displayname='${GAME}',
gametype=TheLastStandGame,
settings={'preset': 'tournament'},
preview_texture_name='rampagePreview'),
Level('Tournament Infinite Onslaught',
displayname='Infinite Onslaught',
gametype=OnslaughtGame,
settings={'preset': 'endless_tournament'},
preview_texture_name='doomShroomPreview'),
Level('Tournament Infinite Runaround',
displayname='Infinite Runaround',
gametype=RunaroundGame,
settings={'preset': 'endless_tournament'},
preview_texture_name='towerDPreview'),
Level('Target Practice',
displayname='Pro ${GAME}',
gametype=TargetPracticeGame,
settings={},
preview_texture_name='doomShroomPreview'),
Level('Target Practice B',
displayname='${GAME}',
gametype=TargetPracticeGame,
settings={
'Target Count': 2,
'Enable Impact Bombs': False,
'Enable Triple Bombs': False
},
preview_texture_name='doomShroomPreview'),
Level('Meteor Shower',
displayname='${GAME}',
gametype=MeteorShowerGame,
settings={},
preview_texture_name='rampagePreview'),
Level('Epic Meteor Shower',
displayname='${GAME}',
gametype=MeteorShowerGame,
settings={'Epic Mode': True},
preview_texture_name='rampagePreview'),
Level('Easter Egg Hunt',
displayname='${GAME}',
gametype=EasterEggHuntGame,
settings={},
preview_texture_name='towerDPreview'),
Level('Pro Easter Egg Hunt',
displayname='Pro ${GAME}',
gametype=EasterEggHuntGame,
settings={'Pro Mode': True},
preview_texture_name='towerDPreview'),
Level(
name='Ninja Fight', # (unique id not seen by player)
displayname='${GAME}', # (readable name seen by player)
gametype=NinjaFightGame,
settings={'preset': 'regular'},
preview_texture_name='courtyardPreview'),
Level(name='Pro Ninja Fight',
displayname='Pro ${GAME}',
gametype=NinjaFightGame,
settings={'preset': 'pro'},
preview_texture_name='courtyardPreview')
],
))

View file

@ -56,6 +56,14 @@ class CloudSubsystem:
) -> None:
...
@overload
def send_message_cb(
self,
msg: bacommon.cloud.PingMessage,
on_response: Callable[[bacommon.cloud.PingResponse | Exception], None],
) -> None:
...
def send_message_cb(
self,
msg: Message,

View file

@ -204,6 +204,12 @@ def no_game_circle_message() -> None:
_ba.screenmessage(Lstr(resource='noGameCircleText'), color=(1, 0, 0))
def google_play_purchases_not_available_message() -> None:
from ba._language import Lstr
_ba.screenmessage(Lstr(resource='googlePlayPurchasesNotAvailableText'),
color=(1, 0, 0))
def empty_call() -> None:
pass

View file

@ -44,7 +44,7 @@ class MetadataSubsystem:
"""
def __init__(self) -> None:
self.metascan: ScanResults | None = None
self.scanresults: ScanResults | None = None
self.extra_scan_dirs: list[str] = []
def on_app_running(self) -> None:
@ -58,7 +58,7 @@ class MetadataSubsystem:
Should be called only once at launch."""
app = _ba.app
if self.metascan is not None:
if self.scanresults is not None:
print('WARNING: meta scan run more than once.')
pythondirs = ([app.python_directory_app, app.python_directory_user] +
self.extra_scan_dirs)
@ -133,7 +133,7 @@ class MetadataSubsystem:
def get_scan_results(self) -> ScanResults:
"""Return meta scan results; block if the scan is not yet complete."""
if self.metascan is None:
if self.scanresults is None:
print('WARNING: ba.meta.get_scan_results()'
' called before scan completed.'
' This can cause hitches.')
@ -141,12 +141,12 @@ class MetadataSubsystem:
# Now wait a bit for the scan to complete.
# Eventually error though if it doesn't.
starttime = time.time()
while self.metascan is None:
while self.scanresults is None:
time.sleep(0.05)
if time.time() - starttime > 10.0:
raise TimeoutError(
'timeout waiting for meta scan to complete.')
return self.metascan
return self.scanresults
def get_game_types(self) -> list[type[ba.GameActivity]]:
"""Return available game types."""
@ -206,7 +206,7 @@ class ScanThread(threading.Thread):
# We also, however, immediately make results available.
# This is because the game thread may be blocked waiting
# for them so we can't push a call or we'd get deadlock.
_ba.app.meta.metascan = results
_ba.app.meta.scanresults = results
class DirectoryScan:
@ -288,7 +288,7 @@ class DirectoryScan:
# If we find a module requiring a different api version, warn
# and ignore.
if required_api is not None and required_api < CURRENT_API_VERSION:
if required_api is not None and required_api <= CURRENT_API_VERSION:
self.results.warnings += (
f'Warning: {subpath} requires api {required_api} but'
f' we are running {CURRENT_API_VERSION}; ignoring module.\n')
@ -403,13 +403,13 @@ class DirectoryScan:
if len(lines) > 1:
self.results.warnings += (
'Warning: ' + str(subpath) +
': multiple "# ba_meta api require <NUM>" lines found;'
': multiple "# ba_meta require api <NUM>" lines found;'
' ignoring module.\n')
elif not lines and toplevel and meta_lines:
# If we're a top-level module containing meta lines but
# no valid api require, complain.
# no valid "require api" line found, complain.
self.results.warnings += (
'Warning: ' + str(subpath) +
': no valid "# ba_meta api require <NUM>" line found;'
': no valid "# ba_meta require api <NUM>" line found;'
' ignoring module.\n')
return None

View file

@ -211,7 +211,7 @@ class MusicSubsystem:
return 'Mac' in uas
if entry_type in ('musicFile', 'musicFolder'):
return ('android' in uas
and _ba.android_get_external_storage_path() is not None)
and _ba.android_get_external_files_dir() is not None)
if entry_type == 'default':
return True
return False

View file

@ -3,6 +3,7 @@
"""Networking related functionality."""
from __future__ import annotations
import ssl
import copy
import threading
import weakref
@ -29,14 +30,32 @@ class NetworkSubsystem:
# as it is updated by a background thread.
self.zone_pings_lock = threading.Lock()
# Region IDs mapped to average pings. This will remain empty
# Zone IDs mapped to average pings. This will remain empty
# until enough pings have been run to be reasonably certain
# that a nearby server has been pinged.
self.zone_pings: dict[str, float] = {}
self._sslcontext: ssl.SSLContext | None = None
# For debugging.
self.v1_test_log: str = ''
self.v1_ctest_results: dict[int, str] = {}
self.server_time_offset_hours: float | None = None
@property
def sslcontext(self) -> ssl.SSLContext:
"""Create/return our shared SSLContext.
This can be reused for all standard urllib requests/etc.
"""
# Note: I've run into older Android devices taking upwards of 1 second
# to put together a default SSLContext, so recycling one can definitely
# be a worthwhile optimization. This was suggested to me in this
# thread by one of Python's SSL maintainers:
# https://github.com/python/cpython/issues/94637
if self._sslcontext is None:
self._sslcontext = ssl.create_default_context()
return self._sslcontext
def get_ip_address_type(addr: str) -> socket.AddressFamily:
@ -108,30 +127,36 @@ class MasterServerCallThread(threading.Thread):
self._callback(arg)
def run(self) -> None:
# pylint: disable=too-many-branches, consider-using-with
# pylint: disable=consider-using-with
import urllib.request
import urllib.parse
import urllib.error
import json
from efro.error import is_urllib_network_error
from efro.error import is_urllib_communication_error
from ba import _general
response_data: Any = None
url: str | None = None
try:
self._data = _general.utf8_all(self._data)
_ba.set_thread_name('BA_ServerCallThread')
if self._request_type == 'get':
url = (_ba.get_master_server_address() + '/' + self._request +
'?' + urllib.parse.urlencode(self._data))
response = urllib.request.urlopen(
urllib.request.Request(
(_ba.get_master_server_address() + '/' +
self._request + '?' +
urllib.parse.urlencode(self._data)), None,
{'User-Agent': _ba.app.user_agent_string}),
url, None, {'User-Agent': _ba.app.user_agent_string}),
context=_ba.app.net.sslcontext,
timeout=DEFAULT_REQUEST_TIMEOUT_SECONDS)
elif self._request_type == 'post':
url = _ba.get_master_server_address() + '/' + self._request
response = urllib.request.urlopen(
urllib.request.Request(
_ba.get_master_server_address() + '/' + self._request,
url,
urllib.parse.urlencode(self._data).encode(),
{'User-Agent': _ba.app.user_agent_string}),
context=_ba.app.net.sslcontext,
timeout=DEFAULT_REQUEST_TIMEOUT_SECONDS)
else:
raise TypeError('Invalid request_type: ' + self._request_type)
@ -151,29 +176,18 @@ class MasterServerCallThread(threading.Thread):
raise TypeError(f'invalid responsetype: {self._response_type}')
except Exception as exc:
do_print = False
response_data = None
# Ignore common network errors; note unexpected ones.
if is_urllib_network_error(exc):
pass
elif (self._response_type == MasterServerResponseType.JSON
and isinstance(exc, json.decoder.JSONDecodeError)):
# FIXME: should handle this better; could mean either the
# server sent us bad data or it got corrupted along the way.
pass
else:
do_print = True
if do_print:
# Any other error here is unexpected,
# so let's make a note of it,
if not is_urllib_communication_error(exc, url=url):
print(f'Error in MasterServerCallThread'
f' (response-type={self._response_type},'
f' (url={url},'
f' response-type={self._response_type},'
f' response-data={response_data}):')
import traceback
traceback.print_exc()
response_data = None
if self._callback is not None:
_ba.pushcall(_general.Call(self._run_callback, response_data),
from_other_thread=True)

View file

@ -112,13 +112,15 @@ class PluginSubsystem:
# or workspaces.
if disappeared_plugs:
_ba.playsound(_ba.getsound('shieldDown'))
_ba.screenmessage(Lstr(resource='pluginsRemovedText',
subs=[('${NUM}',
str(len(disappeared_plugs)))]),
color=(1, 1, 0))
_ba.screenmessage(
Lstr(resource='pluginsRemovedText',
subs=[('${NUM}', str(len(disappeared_plugs)))]),
color=(1, 1, 0),
)
plugnames = ', '.join(disappeared_plugs)
_ba.log(
f'{len(disappeared_plugs)} plugin(s) no longer found:'
f' {disappeared_plugs}',
f' {plugnames}.',
to_server=False)
for goneplug in disappeared_plugs:
del _ba.app.config['Plugins'][goneplug]

View file

@ -115,7 +115,7 @@ class WorkspaceSubsystem:
tpartial(
self._errmsg,
Lstr(resource='workspaceSyncReuseText',
subs=[('$WORKSPACE', workspacename)])),
subs=[('${WORKSPACE}', workspacename)])),
from_other_thread=True,
)

View file

@ -17,23 +17,29 @@ def get_human_readable_user_scripts_path() -> str:
This is NOT a valid filesystem path; may be something like "(SD Card)".
"""
from ba import _language
app = _ba.app
path: str | None = app.python_directory_user
if path is None:
return '<Not Available>'
# On newer versions of android, the user's external storage dir is probably
# only visible to the user's processes and thus not really useful printed
# in its entirety; lets print it as <External Storage>/myfilepath.
# These days, on Android, we use getExternalFilesDir() as the base of our
# app's user-scripts dir, which gives us paths like:
# /storage/emulated/0/Android/data/net.froemling.bombsquad/files
# Userspace apps tend to show that as:
# Android/data/net.froemling.bombsquad/files
# We'd like to display it that way, but I'm not sure if there's a clean
# way to get the root of the external storage area (/storage/emulated/0)
# so that we could strip it off. There is
# Environment.getExternalStorageDirectory() but that is deprecated.
# So for now let's just be conservative and trim off recognized prefixes
# and show the whole ugly path as a fallback.
# Note that we used to use externalStorageText resource but gonna try
# without it for now. (simply 'foo' instead of <External Storage>/foo).
if app.platform == 'android':
ext_storage_path: str | None = (
_ba.android_get_external_storage_path())
if (ext_storage_path is not None
and app.python_directory_user.startswith(ext_storage_path)):
path = ('<' +
_language.Lstr(resource='externalStorageText').evaluate() +
'>' + app.python_directory_user[len(ext_storage_path):])
for pre in ['/storage/emulated/0/']:
if path.startswith(pre):
path = path.removeprefix(pre)
break
return path

View file

@ -14,7 +14,7 @@ if TYPE_CHECKING:
# Version is sent to the master-server with all commands. Can be incremented
# if we need to change behavior server-side to go along with client changes.
BACLOUD_VERSION = 6
BACLOUD_VERSION = 7
@ioprepped

View file

@ -76,6 +76,22 @@ class LoginProxyCompleteMessage(Message):
proxyid: Annotated[str, IOAttrs('p')]
@ioprepped
@dataclass
class PingMessage(Message):
"""Standard ping."""
@classmethod
def get_response_types(cls) -> list[type[Response]]:
return [PingResponse]
@ioprepped
@dataclass
class PingResponse(Response):
"""pong."""
@ioprepped
@dataclass
class TestMessage(Message):

View file

@ -4,6 +4,7 @@
from __future__ import annotations
import datetime
from typing import TYPE_CHECKING, Any, Annotated
from dataclasses import dataclass, field
@ -27,6 +28,9 @@ class ServerNodeEntry:
class ServerNodeQueryResponse:
"""A response to a query about server-nodes."""
# The current utc time on the master server.
time: Annotated[datetime.datetime, IOAttrs('t')]
# If present, something went wrong, and this describes it.
error: Annotated[str | None, IOAttrs('e', store_default=False)] = None

View file

@ -5,13 +5,14 @@
from __future__ import annotations
import os
from pathlib import Path
from dataclasses import dataclass
from typing import TYPE_CHECKING, Annotated
from efro.dataclassio import ioprepped, IOAttrs
if TYPE_CHECKING:
from pathlib import Path
pass
@ioprepped
@ -40,15 +41,17 @@ class DirectoryManifest:
paths: list[str] = []
if path.is_dir():
# Build the full list of package-relative paths.
# Build the full list of relative paths.
for basename, _dirnames, filenames in os.walk(path):
for filename in filenames:
fullname = os.path.join(basename, filename)
assert fullname.startswith(pathstr)
paths.append(fullname[len(pathstr) + 1:])
# Make sure we end up with forward slashes no matter
# what the os.* stuff above here was using.
paths.append(Path(fullname[len(pathstr) + 1:]).as_posix())
elif path.exists():
# Just return a single file entry if path is not a dir.
paths.append(pathstr)
paths.append(path.as_posix())
def _get_file_info(filepath: str) -> tuple[str, DirectoryManifestFile]:
sha = hashlib.sha256()
@ -70,6 +73,18 @@ class DirectoryManifest:
with ThreadPoolExecutor(max_workers=cpus) as executor:
return cls(files=dict(executor.map(_get_file_info, paths)))
def validate(self) -> None:
"""Log any odd data in the manifest; for debugging."""
import logging
for fpath, _fentry in self.files.items():
# We want to be dealing in only forward slashes; make sure
# that's the case (wondering if we'll ever see backslashes
# for escape purposes).
if '\\' in fpath:
logging.exception("Found unusual path in manifest: '%s'.",
fpath)
break # 1 error is enough for now.
@classmethod
def get_empty_hash(cls) -> str:
"""Return the hash for an empty file."""

View file

@ -606,6 +606,7 @@ class Spaz(ba.Actor):
"""
assert self.node
self.node.boxing_gloves = True
self._has_boxing_gloves = True
if self._demo_mode: # Preserve old behavior.
self._punch_power_scale = 1.7
self._punch_cooldown = 300
@ -754,7 +755,6 @@ class Spaz(ba.Actor):
ba.WeakCall(self._bomb_wear_off),
timeformat=ba.TimeFormat.MILLISECONDS))
elif msg.poweruptype == 'punch':
self._has_boxing_gloves = True
tex = PowerupBoxFactory.get().tex_punch
self._flash_billboard(tex)
self.equip_boxing_gloves()

View file

@ -5,6 +5,9 @@
# Yes this is a long one..
# pylint: disable=too-many-lines
# ba_meta require api 7
# (see https://ballistica.net/wiki/meta-tag-system)
from __future__ import annotations
import math

View file

@ -5,6 +5,9 @@
# We wear the cone of shame.
# pylint: disable=too-many-lines
# ba_meta require api 7
# (see https://ballistica.net/wiki/meta-tag-system)
from __future__ import annotations
import random

View file

@ -6,7 +6,6 @@
from __future__ import annotations
import copy
from typing import TYPE_CHECKING
import _ba
@ -18,6 +17,8 @@ from bastd.ui.store.browser import StoreBrowserWindow
if TYPE_CHECKING:
from typing import Any
from bastd.ui.coop.tournamentbutton import TournamentButton
class CoopBrowserWindow(ba.Window):
"""Window for browsing co-op levels/games/etc."""
@ -175,8 +176,6 @@ class CoopBrowserWindow(ba.Window):
'Selected Coop Campaign Level', None))
self._selected_custom_level = (cfg.get('Selected Coop Custom Level',
None))
self._selected_challenge_level = (cfg.get(
'Selected Coop Challenge Level', None))
# Don't want initial construction affecting our last-selected.
self._do_selection_callbacks = False
@ -283,6 +282,7 @@ class CoopBrowserWindow(ba.Window):
import bastd.ui.tournamentscores as _unused8
import bastd.ui.tournamententry as _unused9
import bastd.ui.play as _unused10
import bastd.ui.coop.tournamentbutton as _unused11
def _update(self) -> None:
# Do nothing if we've somehow outlived our actual UI.
@ -335,21 +335,21 @@ class CoopBrowserWindow(ba.Window):
# Decrement time on our tournament buttons.
ads_enabled = _ba.have_incentivized_ad()
for tbtn in self._tournament_buttons:
tbtn['time_remaining'] = max(0, tbtn['time_remaining'] - 1)
if tbtn['time_remaining_value_text'] is not None:
tbtn.time_remaining = max(0, tbtn.time_remaining - 1)
if tbtn.time_remaining_value_text is not None:
ba.textwidget(
edit=tbtn['time_remaining_value_text'],
text=ba.timestring(tbtn['time_remaining'],
edit=tbtn.time_remaining_value_text,
text=ba.timestring(tbtn.time_remaining,
centi=False,
suppress_format_warning=True) if
(tbtn['has_time_remaining']
(tbtn.has_time_remaining
and self._tourney_data_up_to_date) else '-')
# Also adjust the ad icon visibility.
if tbtn.get('allow_ads', False) and _ba.has_video_ads():
ba.imagewidget(edit=tbtn['entry_fee_ad_image'],
if tbtn.allow_ads and _ba.has_video_ads():
ba.imagewidget(edit=tbtn.entry_fee_ad_image,
opacity=1.0 if ads_enabled else 0.25)
ba.textwidget(edit=tbtn['entry_fee_text_remaining'],
ba.textwidget(edit=tbtn.entry_fee_text_remaining,
color=(0.6, 0.6, 0.6, 1 if ads_enabled else 0.2))
self._update_hard_mode_lock_image()
@ -363,232 +363,21 @@ class CoopBrowserWindow(ba.Window):
ba.print_exception('Error updating campaign lock.')
def _update_for_data(self, data: list[dict[str, Any]] | None) -> None:
# pylint: disable=too-many-statements
# pylint: disable=too-many-locals
# pylint: disable=too-many-branches
from ba.internal import getcampaign, get_tournament_prize_strings
# If the number of tournaments or challenges in the data differs from
# our current arrangement, refresh with the new number.
if ((data is None and self._tournament_button_count != 0)
or (data is not None and
(len(data) != self._tournament_button_count))):
self._tournament_button_count = len(
data) if data is not None else 0
self._tournament_button_count = (len(data)
if data is not None else 0)
ba.app.config['Tournament Rows'] = self._tournament_button_count
self._refresh()
# Update all of our tourney buttons based on whats in data.
for i, tbtn in enumerate(self._tournament_buttons):
assert data is not None
entry: dict[str, Any] = data[i]
prize_y_offs = (34 if 'prizeRange3' in entry else
20 if 'prizeRange2' in entry else 12)
x_offs = 90
# This seems to be a false alarm.
# pylint: disable=unbalanced-tuple-unpacking
pr1, pv1, pr2, pv2, pr3, pv3 = (
get_tournament_prize_strings(entry))
# pylint: enable=unbalanced-tuple-unpacking
enabled = 'requiredLeague' not in entry
ba.buttonwidget(edit=tbtn['button'],
color=(0.5, 0.7, 0.2) if enabled else
(0.5, 0.5, 0.5))
ba.imagewidget(edit=tbtn['lock_image'],
opacity=0.0 if enabled else 1.0)
ba.textwidget(edit=tbtn['prize_range_1_text'],
text='-' if pr1 == '' else pr1,
position=(tbtn['button_x'] + 365 + x_offs,
tbtn['button_y'] + tbtn['button_scale_y'] -
93 + prize_y_offs))
# We want to draw values containing tickets a bit smaller
# (scratch that; we now draw medals a bit bigger).
ticket_char = ba.charstr(ba.SpecialChar.TICKET_BACKING)
prize_value_scale_large = 1.0
prize_value_scale_small = 1.0
ba.textwidget(edit=tbtn['prize_value_1_text'],
text='-' if pv1 == '' else pv1,
scale=prize_value_scale_large if ticket_char
not in pv1 else prize_value_scale_small,
position=(tbtn['button_x'] + 380 + x_offs,
tbtn['button_y'] + tbtn['button_scale_y'] -
93 + prize_y_offs))
ba.textwidget(edit=tbtn['prize_range_2_text'],
text=pr2,
position=(tbtn['button_x'] + 365 + x_offs,
tbtn['button_y'] + tbtn['button_scale_y'] -
93 - 45 + prize_y_offs))
ba.textwidget(edit=tbtn['prize_value_2_text'],
text=pv2,
scale=prize_value_scale_large if ticket_char
not in pv2 else prize_value_scale_small,
position=(tbtn['button_x'] + 380 + x_offs,
tbtn['button_y'] + tbtn['button_scale_y'] -
93 - 45 + prize_y_offs))
ba.textwidget(edit=tbtn['prize_range_3_text'],
text=pr3,
position=(tbtn['button_x'] + 365 + x_offs,
tbtn['button_y'] + tbtn['button_scale_y'] -
93 - 90 + prize_y_offs))
ba.textwidget(edit=tbtn['prize_value_3_text'],
text=pv3,
scale=prize_value_scale_large if ticket_char
not in pv3 else prize_value_scale_small,
position=(tbtn['button_x'] + 380 + x_offs,
tbtn['button_y'] + tbtn['button_scale_y'] -
93 - 90 + prize_y_offs))
leader_name = '-'
leader_score: str | ba.Lstr = '-'
if entry['scores']:
score = tbtn['leader'] = copy.deepcopy(entry['scores'][0])
leader_name = score[1]
leader_score = (ba.timestring(
score[0] * 10,
centi=True,
timeformat=ba.TimeFormat.MILLISECONDS,
suppress_format_warning=True) if entry['scoreType']
== 'time' else str(score[0]))
else:
tbtn['leader'] = None
ba.textwidget(edit=tbtn['current_leader_name_text'],
text=ba.Lstr(value=leader_name))
self._tournament_leader_score_type = (entry['scoreType'])
ba.textwidget(edit=tbtn['current_leader_score_text'],
text=leader_score)
ba.buttonwidget(edit=tbtn['more_scores_button'],
label=ba.Lstr(resource=self._r + '.seeMoreText'))
out_of_time_text: str | ba.Lstr = (
'-' if 'totalTime' not in entry else ba.Lstr(
resource=self._r + '.ofTotalTimeText',
subs=[('${TOTAL}',
ba.timestring(entry['totalTime'],
centi=False,
suppress_format_warning=True))]))
ba.textwidget(edit=tbtn['time_remaining_out_of_text'],
text=out_of_time_text)
tbtn['time_remaining'] = entry['timeRemaining']
tbtn['has_time_remaining'] = entry is not None
tbtn['tournament_id'] = entry['tournamentID']
tbtn['required_league'] = (None if 'requiredLeague' not in entry
else entry['requiredLeague'])
game = ba.app.accounts_v1.tournament_info[
tbtn['tournament_id']]['game']
if game is None:
ba.textwidget(edit=tbtn['button_text'], text='-')
ba.imagewidget(edit=tbtn['image'],
texture=ba.gettexture('black'),
opacity=0.2)
else:
campaignname, levelname = game.split(':')
campaign = getcampaign(campaignname)
max_players = ba.app.accounts_v1.tournament_info[
tbtn['tournament_id']]['maxPlayers']
txt = ba.Lstr(
value='${A} ${B}',
subs=[('${A}', campaign.getlevel(levelname).displayname),
('${B}',
ba.Lstr(resource='playerCountAbbreviatedText',
subs=[('${COUNT}', str(max_players))]))])
ba.textwidget(edit=tbtn['button_text'], text=txt)
ba.imagewidget(
edit=tbtn['image'],
texture=campaign.getlevel(levelname).get_preview_texture(),
opacity=1.0 if enabled else 0.5)
fee = entry['fee']
if fee is None:
fee_var = None
elif fee == 4:
fee_var = 'price.tournament_entry_4'
elif fee == 3:
fee_var = 'price.tournament_entry_3'
elif fee == 2:
fee_var = 'price.tournament_entry_2'
elif fee == 1:
fee_var = 'price.tournament_entry_1'
else:
if fee != 0:
print('Unknown fee value:', fee)
fee_var = 'price.tournament_entry_0'
tbtn['allow_ads'] = allow_ads = entry['allowAds']
final_fee: int | None = (None if fee_var is None else
_ba.get_v1_account_misc_read_val(
fee_var, '?'))
final_fee_str: str | ba.Lstr
if fee_var is None:
final_fee_str = ''
else:
if final_fee == 0:
final_fee_str = ba.Lstr(
resource='getTicketsWindow.freeText')
else:
final_fee_str = (
ba.charstr(ba.SpecialChar.TICKET_BACKING) +
str(final_fee))
ad_tries_remaining = ba.app.accounts_v1.tournament_info[
tbtn['tournament_id']]['adTriesRemaining']
free_tries_remaining = ba.app.accounts_v1.tournament_info[
tbtn['tournament_id']]['freeTriesRemaining']
# Now, if this fee allows ads and we support video ads, show
# the 'or ad' version.
if allow_ads and _ba.has_video_ads():
ads_enabled = _ba.have_incentivized_ad()
ba.imagewidget(edit=tbtn['entry_fee_ad_image'],
opacity=1.0 if ads_enabled else 0.25)
or_text = ba.Lstr(resource='orText',
subs=[('${A}', ''),
('${B}', '')]).evaluate().strip()
ba.textwidget(edit=tbtn['entry_fee_text_or'], text=or_text)
ba.textwidget(
edit=tbtn['entry_fee_text_top'],
position=(tbtn['button_x'] + 360,
tbtn['button_y'] + tbtn['button_scale_y'] - 60),
scale=1.3,
text=final_fee_str)
# Possibly show number of ad-plays remaining.
ba.textwidget(
edit=tbtn['entry_fee_text_remaining'],
position=(tbtn['button_x'] + 360,
tbtn['button_y'] + tbtn['button_scale_y'] - 146),
text='' if ad_tries_remaining in [None, 0] else
('' + str(ad_tries_remaining)),
color=(0.6, 0.6, 0.6, 1 if ads_enabled else 0.2))
else:
ba.imagewidget(edit=tbtn['entry_fee_ad_image'], opacity=0.0)
ba.textwidget(edit=tbtn['entry_fee_text_or'], text='')
ba.textwidget(
edit=tbtn['entry_fee_text_top'],
position=(tbtn['button_x'] + 360,
tbtn['button_y'] + tbtn['button_scale_y'] - 80),
scale=1.3,
text=final_fee_str)
# Possibly show number of free-plays remaining.
ba.textwidget(
edit=tbtn['entry_fee_text_remaining'],
position=(tbtn['button_x'] + 360,
tbtn['button_y'] + tbtn['button_scale_y'] - 100),
text=('' if (free_tries_remaining in [None, 0]
or final_fee != 0) else
('' + str(free_tries_remaining))),
color=(0.6, 0.6, 0.6, 1))
tbtn.update_for_data(data[i])
def _on_tournament_query_response(self,
data: dict[str, Any] | None) -> None:
@ -715,10 +504,13 @@ class CoopBrowserWindow(ba.Window):
items = [
campaignname + ':Onslaught Training',
campaignname + ':Rookie Onslaught',
campaignname + ':Rookie Football', campaignname + ':Pro Onslaught',
campaignname + ':Pro Football', campaignname + ':Pro Runaround',
campaignname + ':Uber Onslaught', campaignname + ':Uber Football',
campaignname + ':Uber Runaround'
campaignname + ':Rookie Football',
campaignname + ':Pro Onslaught',
campaignname + ':Pro Football',
campaignname + ':Pro Runaround',
campaignname + ':Uber Onslaught',
campaignname + ':Uber Football',
campaignname + ':Uber Runaround',
]
items += [campaignname + ':The Last Stand']
if self._selected_campaign_level is None:
@ -772,6 +564,7 @@ class CoopBrowserWindow(ba.Window):
# pylint: disable=too-many-locals
# pylint: disable=cyclic-import
from bastd.ui.coop.gamebutton import GameButton
from bastd.ui.coop.tournamentbutton import TournamentButton
# (Re)create the sub-container if need be.
if self._subcontainer is not None:
@ -839,7 +632,7 @@ class CoopBrowserWindow(ba.Window):
# Tournaments
self._tournament_buttons: list[dict[str, Any]] = []
self._tournament_buttons: list[TournamentButton] = []
v -= 53
# FIXME shouldn't use hard-coded strings here.
@ -919,10 +712,15 @@ class CoopBrowserWindow(ba.Window):
v2 = -2
is_last_sel = True
self._tournament_buttons.append(
self._tournament_button(sc2, h, v2, is_last_sel))
TournamentButton(sc2,
h,
v2,
is_last_sel,
on_pressed=ba.WeakCall(
self.run_tournament)))
v -= 200
# Custom Games.
# Custom Games. (called 'Practice' in UI these days).
v -= 50
ba.textwidget(parent=w_parent,
position=(h_base + 27, v + 30 + 198),
@ -949,14 +747,13 @@ class CoopBrowserWindow(ba.Window):
if _ba.get_v1_account_misc_read_val(
'easter', False) or _ba.get_purchased('games.easter_egg_hunt'):
items = [
'Challenges:Easter Egg Hunt', 'Challenges:Pro Easter Egg Hunt'
'Challenges:Easter Egg Hunt',
'Challenges:Pro Easter Egg Hunt',
] + items
# add all custom user levels here..
# items += [
# 'User:' + l.getname()
# for l in getcampaign('User').getlevels()
# ]
# If we've defined custom games, put them at the beginning.
if ba.app.custom_coop_practice_games:
items = ba.app.custom_coop_practice_games + items
self._custom_h_scroll = custom_h_scroll = h_scroll = ba.hscrollwidget(
parent=w_parent,
@ -995,19 +792,19 @@ class CoopBrowserWindow(ba.Window):
for i, tbutton in enumerate(self._tournament_buttons):
ba.widget(
edit=tbutton['button'],
edit=tbutton.button,
up_widget=self._tournament_info_button
if i == 0 else self._tournament_buttons[i - 1]['button'],
down_widget=self._tournament_buttons[(i + 1)]['button']
if i == 0 else self._tournament_buttons[i - 1].button,
down_widget=self._tournament_buttons[(i + 1)].button
if i + 1 < len(self._tournament_buttons) else custom_h_scroll)
ba.widget(
edit=tbutton['more_scores_button'],
edit=tbutton.more_scores_button,
down_widget=self._tournament_buttons[(
i + 1)]['current_leader_name_text']
i + 1)].current_leader_name_text
if i + 1 < len(self._tournament_buttons) else custom_h_scroll)
ba.widget(edit=tbutton['current_leader_name_text'],
ba.widget(edit=tbutton.current_leader_name_text,
up_widget=self._tournament_info_button if i == 0 else
self._tournament_buttons[i - 1]['more_scores_button'])
self._tournament_buttons[i - 1].more_scores_button)
for btn in self._custom_buttons:
try:
@ -1037,314 +834,6 @@ class CoopBrowserWindow(ba.Window):
def _enable_selectable_callback(self) -> None:
self._do_selection_callbacks = True
def _tournament_button(self, parent: ba.Widget, x: float, y: float,
select: bool) -> dict[str, Any]:
sclx = 300
scly = 195.0
data: dict[str, Any] = {
'tournament_id': None,
'time_remaining': 0,
'has_time_remaining': False,
'leader': None
}
data['button'] = btn = ba.buttonwidget(
parent=parent,
position=(x + 23, y + 4),
size=(sclx, scly),
label='',
button_type='square',
autoselect=True,
on_activate_call=lambda: self.run(None, tournament_button=data))
ba.widget(edit=btn,
show_buffer_bottom=50,
show_buffer_top=50,
show_buffer_left=400,
show_buffer_right=200)
if select:
ba.containerwidget(edit=parent,
selected_child=btn,
visible_child=btn)
image_width = sclx * 0.85 * 0.75
data['image'] = ba.imagewidget(
parent=parent,
draw_controller=btn,
position=(x + 21 + sclx * 0.5 - image_width * 0.5, y + scly - 150),
size=(image_width, image_width * 0.5),
model_transparent=self.lsbt,
model_opaque=self.lsbo,
texture=ba.gettexture('black'),
opacity=0.2,
mask_texture=ba.gettexture('mapPreviewMask'))
data['lock_image'] = ba.imagewidget(
parent=parent,
draw_controller=btn,
position=(x + 21 + sclx * 0.5 - image_width * 0.25,
y + scly - 150),
size=(image_width * 0.5, image_width * 0.5),
texture=ba.gettexture('lock'),
opacity=0.0)
data['button_text'] = ba.textwidget(parent=parent,
draw_controller=btn,
position=(x + 20 + sclx * 0.5,
y + scly - 35),
size=(0, 0),
h_align='center',
text='-',
v_align='center',
maxwidth=sclx * 0.76,
scale=0.85,
color=(0.8, 1.0, 0.8, 1.0))
header_color = (0.43, 0.4, 0.5, 1)
value_color = (0.6, 0.6, 0.6, 1)
x_offs = 0
ba.textwidget(parent=parent,
draw_controller=btn,
position=(x + 360, y + scly - 20),
size=(0, 0),
h_align='center',
text=ba.Lstr(resource=self._r + '.entryFeeText'),
v_align='center',
maxwidth=100,
scale=0.9,
color=header_color,
flatness=1.0)
data['entry_fee_text_top'] = ba.textwidget(parent=parent,
draw_controller=btn,
position=(x + 360,
y + scly - 60),
size=(0, 0),
h_align='center',
text='-',
v_align='center',
maxwidth=60,
scale=1.3,
color=value_color,
flatness=1.0)
data['entry_fee_text_or'] = ba.textwidget(parent=parent,
draw_controller=btn,
position=(x + 360,
y + scly - 90),
size=(0, 0),
h_align='center',
text='',
v_align='center',
maxwidth=60,
scale=0.5,
color=value_color,
flatness=1.0)
data['entry_fee_text_remaining'] = ba.textwidget(parent=parent,
draw_controller=btn,
position=(x + 360, y +
scly - 90),
size=(0, 0),
h_align='center',
text='',
v_align='center',
maxwidth=60,
scale=0.5,
color=value_color,
flatness=1.0)
data['entry_fee_ad_image'] = ba.imagewidget(
parent=parent,
size=(40, 40),
draw_controller=btn,
position=(x + 360 - 20, y + scly - 140),
opacity=0.0,
texture=ba.gettexture('tv'))
x_offs += 50
ba.textwidget(parent=parent,
draw_controller=btn,
position=(x + 447 + x_offs, y + scly - 20),
size=(0, 0),
h_align='center',
text=ba.Lstr(resource=self._r + '.prizesText'),
v_align='center',
maxwidth=130,
scale=0.9,
color=header_color,
flatness=1.0)
data['button_x'] = x
data['button_y'] = y
data['button_scale_y'] = scly
xo2 = 0
prize_value_scale = 1.5
data['prize_range_1_text'] = ba.textwidget(
parent=parent,
draw_controller=btn,
position=(x + 355 + xo2 + x_offs, y + scly - 93),
size=(0, 0),
h_align='right',
v_align='center',
maxwidth=50,
text='-',
scale=0.8,
color=header_color,
flatness=1.0)
data['prize_value_1_text'] = ba.textwidget(
parent=parent,
draw_controller=btn,
position=(x + 380 + xo2 + x_offs, y + scly - 93),
size=(0, 0),
h_align='left',
text='-',
v_align='center',
maxwidth=100,
scale=prize_value_scale,
color=value_color,
flatness=1.0)
data['prize_range_2_text'] = ba.textwidget(
parent=parent,
draw_controller=btn,
position=(x + 355 + xo2 + x_offs, y + scly - 93),
size=(0, 0),
h_align='right',
v_align='center',
maxwidth=50,
scale=0.8,
color=header_color,
flatness=1.0)
data['prize_value_2_text'] = ba.textwidget(
parent=parent,
draw_controller=btn,
position=(x + 380 + xo2 + x_offs, y + scly - 93),
size=(0, 0),
h_align='left',
text='',
v_align='center',
maxwidth=100,
scale=prize_value_scale,
color=value_color,
flatness=1.0)
data['prize_range_3_text'] = ba.textwidget(
parent=parent,
draw_controller=btn,
position=(x + 355 + xo2 + x_offs, y + scly - 93),
size=(0, 0),
h_align='right',
v_align='center',
maxwidth=50,
scale=0.8,
color=header_color,
flatness=1.0)
data['prize_value_3_text'] = ba.textwidget(
parent=parent,
draw_controller=btn,
position=(x + 380 + xo2 + x_offs, y + scly - 93),
size=(0, 0),
h_align='left',
text='',
v_align='center',
maxwidth=100,
scale=prize_value_scale,
color=value_color,
flatness=1.0)
ba.textwidget(parent=parent,
draw_controller=btn,
position=(x + 620 + x_offs, y + scly - 20),
size=(0, 0),
h_align='center',
text=ba.Lstr(resource=self._r + '.currentBestText'),
v_align='center',
maxwidth=180,
scale=0.9,
color=header_color,
flatness=1.0)
data['current_leader_name_text'] = ba.textwidget(
parent=parent,
draw_controller=btn,
position=(x + 620 + x_offs - (170 / 1.4) * 0.5,
y + scly - 60 - 40 * 0.5),
selectable=True,
click_activate=True,
autoselect=True,
on_activate_call=lambda: self._show_leader(tournament_button=data),
size=(170 / 1.4, 40),
h_align='center',
text='-',
v_align='center',
maxwidth=170,
scale=1.4,
color=value_color,
flatness=1.0)
data['current_leader_score_text'] = ba.textwidget(
parent=parent,
draw_controller=btn,
position=(x + 620 + x_offs, y + scly - 113 + 10),
size=(0, 0),
h_align='center',
text='-',
v_align='center',
maxwidth=170,
scale=1.8,
color=value_color,
flatness=1.0)
data['more_scores_button'] = ba.buttonwidget(
parent=parent,
position=(x + 620 + x_offs - 60, y + scly - 50 - 125),
color=(0.5, 0.5, 0.6),
textcolor=(0.7, 0.7, 0.8),
label='-',
size=(120, 40),
autoselect=True,
up_widget=data['current_leader_name_text'],
text_scale=0.6,
on_activate_call=lambda: self._show_scores(tournament_button=data))
ba.widget(edit=data['current_leader_name_text'],
down_widget=data['more_scores_button'])
ba.textwidget(parent=parent,
draw_controller=btn,
position=(x + 820 + x_offs, y + scly - 20),
size=(0, 0),
h_align='center',
text=ba.Lstr(resource=self._r + '.timeRemainingText'),
v_align='center',
maxwidth=180,
scale=0.9,
color=header_color,
flatness=1.0)
data['time_remaining_value_text'] = ba.textwidget(
parent=parent,
draw_controller=btn,
position=(x + 820 + x_offs, y + scly - 68),
size=(0, 0),
h_align='center',
text='-',
v_align='center',
maxwidth=180,
scale=2.0,
color=value_color,
flatness=1.0)
data['time_remaining_out_of_text'] = ba.textwidget(
parent=parent,
draw_controller=btn,
position=(x + 820 + x_offs, y + scly - 110),
size=(0, 0),
h_align='center',
text='-',
v_align='center',
maxwidth=120,
scale=0.72,
color=(0.4, 0.4, 0.5),
flatness=1.0)
return data
def _switch_to_league_rankings(self) -> None:
# pylint: disable=cyclic-import
from bastd.ui.account import show_sign_in_prompt
@ -1378,100 +867,20 @@ class CoopBrowserWindow(ba.Window):
show_tab=show_tab,
back_location='CoopBrowserWindow').get_root_widget())
def _show_leader(self, tournament_button: dict[str, Any]) -> None:
# pylint: disable=cyclic-import
from bastd.ui.account.viewer import AccountViewerWindow
tournament_id = tournament_button['tournament_id']
# FIXME: This assumes a single player entry in leader; should expand
# this to work with multiple.
if tournament_id is None or tournament_button['leader'] is None or len(
tournament_button['leader'][2]) != 1:
ba.playsound(ba.getsound('error'))
return
ba.playsound(ba.getsound('swish'))
AccountViewerWindow(
account_id=tournament_button['leader'][2][0].get('a', None),
profile_id=tournament_button['leader'][2][0].get('p', None),
position=tournament_button['current_leader_name_text'].
get_screen_space_center())
def _show_scores(self, tournament_button: dict[str, Any]) -> None:
# pylint: disable=cyclic-import
from bastd.ui.tournamentscores import TournamentScoresWindow
tournament_id = tournament_button['tournament_id']
if tournament_id is None:
ba.playsound(ba.getsound('error'))
return
TournamentScoresWindow(
tournament_id=tournament_id,
position=tournament_button['more_scores_button'].
get_screen_space_center())
def is_tourney_data_up_to_date(self) -> bool:
"""Return whether our tourney data is up to date."""
return self._tourney_data_up_to_date
def run(self,
game: str | None,
tournament_button: dict[str, Any] | None = None) -> None:
def run_game(self, game: str) -> None:
"""Run the provided game."""
# pylint: disable=too-many-branches
# pylint: disable=too-many-statements
# pylint: disable=too-many-return-statements
# pylint: disable=cyclic-import
from bastd.ui.confirm import ConfirmWindow
from bastd.ui.tournamententry import TournamentEntryWindow
from bastd.ui.purchase import PurchaseWindow
from bastd.ui.account import show_sign_in_prompt
args: dict[str, Any] = {}
# Do a bit of pre-flight for tournament options.
if tournament_button is not None:
if _ba.get_v1_account_state() != 'signed_in':
show_sign_in_prompt()
return
if not self._tourney_data_up_to_date:
ba.screenmessage(
ba.Lstr(resource='tournamentCheckingStateText'),
color=(1, 1, 0))
ba.playsound(ba.getsound('error'))
return
if tournament_button['tournament_id'] is None:
ba.screenmessage(
ba.Lstr(resource='internal.unavailableNoConnectionText'),
color=(1, 0, 0))
ba.playsound(ba.getsound('error'))
return
if tournament_button['required_league'] is not None:
ba.screenmessage(ba.Lstr(
resource='league.tournamentLeagueText',
subs=[
('${NAME}',
ba.Lstr(
translate=('leagueNames',
tournament_button['required_league'])))
]),
color=(1, 0, 0))
ba.playsound(ba.getsound('error'))
return
if tournament_button['time_remaining'] <= 0:
ba.screenmessage(ba.Lstr(resource='tournamentEndedText'),
color=(1, 0, 0))
ba.playsound(ba.getsound('error'))
return
# Game is whatever the tournament tells us it is.
game = ba.app.accounts_v1.tournament_info[
tournament_button['tournament_id']]['game']
if tournament_button is None and game == 'Easy:The Last Stand':
if game == 'Easy:The Last Stand':
ConfirmWindow(ba.Lstr(resource='difficultyHardUnlockOnlyText',
fallback_resource='difficultyHardOnlyText'),
cancel_button=False,
@ -1479,12 +888,11 @@ class CoopBrowserWindow(ba.Window):
height=130)
return
# Infinite onslaught/runaround require pro; bring up a store link if
# need be.
if tournament_button is None and game in (
'Challenges:Infinite Runaround',
'Challenges:Infinite Onslaught'
) and not ba.app.accounts_v1.have_pro():
# Infinite onslaught/runaround require pro; bring up a store link
# if need be.
if game in ('Challenges:Infinite Runaround',
'Challenges:Infinite Onslaught'
) and not ba.app.accounts_v1.have_pro():
if _ba.get_v1_account_state() != 'signed_in':
show_sign_in_prompt()
else:
@ -1495,7 +903,8 @@ class CoopBrowserWindow(ba.Window):
if game in ['Challenges:Meteor Shower']:
required_purchase = 'games.meteor_shower'
elif game in [
'Challenges:Target Practice', 'Challenges:Target Practice B'
'Challenges:Target Practice',
'Challenges:Target Practice B',
]:
required_purchase = 'games.target_practice'
elif game in ['Challenges:Ninja Fight']:
@ -1503,13 +912,14 @@ class CoopBrowserWindow(ba.Window):
elif game in ['Challenges:Pro Ninja Fight']:
required_purchase = 'games.ninja_fight'
elif game in [
'Challenges:Easter Egg Hunt', 'Challenges:Pro Easter Egg Hunt'
'Challenges:Easter Egg Hunt',
'Challenges:Pro Easter Egg Hunt',
]:
required_purchase = 'games.easter_egg_hunt'
else:
required_purchase = None
if (tournament_button is None and required_purchase is not None
if (required_purchase is not None
and not _ba.get_purchased(required_purchase)):
if _ba.get_v1_account_state() != 'signed_in':
show_sign_in_prompt()
@ -1519,17 +929,57 @@ class CoopBrowserWindow(ba.Window):
self._save_state()
# For tournaments, we pop up the entry window.
if tournament_button is not None:
TournamentEntryWindow(
tournament_id=tournament_button['tournament_id'],
position=tournament_button['button'].get_screen_space_center())
else:
# Otherwise just dive right in.
assert game is not None
if ba.app.launch_coop_game(game, args=args):
ba.containerwidget(edit=self._root_widget,
transition='out_left')
if ba.app.launch_coop_game(game, args=args):
ba.containerwidget(edit=self._root_widget, transition='out_left')
def run_tournament(self, tournament_button: TournamentButton) -> None:
"""Run the provided tournament game."""
from bastd.ui.account import show_sign_in_prompt
from bastd.ui.tournamententry import TournamentEntryWindow
if _ba.get_v1_account_state() != 'signed_in':
show_sign_in_prompt()
return
if not self._tourney_data_up_to_date:
ba.screenmessage(ba.Lstr(resource='tournamentCheckingStateText'),
color=(1, 1, 0))
ba.playsound(ba.getsound('error'))
return
if tournament_button.tournament_id is None:
ba.screenmessage(
ba.Lstr(resource='internal.unavailableNoConnectionText'),
color=(1, 0, 0))
ba.playsound(ba.getsound('error'))
return
if tournament_button.required_league is not None:
ba.screenmessage(
ba.Lstr(
resource='league.tournamentLeagueText',
subs=[('${NAME}',
ba.Lstr(
translate=('leagueNames',
tournament_button.required_league)))
]),
color=(1, 0, 0),
)
ba.playsound(ba.getsound('error'))
return
if tournament_button.time_remaining <= 0:
ba.screenmessage(ba.Lstr(resource='tournamentEndedText'),
color=(1, 0, 0))
ba.playsound(ba.getsound('error'))
return
self._save_state()
assert tournament_button.tournament_id is not None
TournamentEntryWindow(
tournament_id=tournament_button.tournament_id,
position=tournament_button.button.get_screen_space_center())
def _back(self) -> None:
# pylint: disable=cyclic-import
@ -1542,24 +992,6 @@ class CoopBrowserWindow(ba.Window):
ba.app.ui.set_main_menu_window(
PlayWindow(transition='in_left').get_root_widget())
def _restore_state(self) -> None:
try:
sel_name = ba.app.ui.window_states.get(type(self),
{}).get('sel_name')
if sel_name == 'Back':
sel = self._back_button
elif sel_name == 'Scroll':
sel = self._scrollwidget
elif sel_name == 'PowerRanking':
sel = self._league_rank_button_widget
elif sel_name == 'Store':
sel = self._store_button_widget
else:
sel = self._scrollwidget
ba.containerwidget(edit=self._root_widget, selected_child=sel)
except Exception:
ba.print_exception(f'Error restoring state for {self}.')
def _save_state(self) -> None:
cfg = ba.app.config
try:
@ -1580,16 +1012,31 @@ class CoopBrowserWindow(ba.Window):
cfg['Selected Coop Row'] = self._selected_row
cfg['Selected Coop Custom Level'] = self._selected_custom_level
cfg['Selected Coop Challenge Level'] = self._selected_challenge_level
cfg['Selected Coop Campaign Level'] = self._selected_campaign_level
cfg.commit()
def _restore_state(self) -> None:
try:
sel_name = ba.app.ui.window_states.get(type(self),
{}).get('sel_name')
if sel_name == 'Back':
sel = self._back_button
elif sel_name == 'Scroll':
sel = self._scrollwidget
elif sel_name == 'PowerRanking':
sel = self._league_rank_button_widget
elif sel_name == 'Store':
sel = self._store_button_widget
else:
sel = self._scrollwidget
ba.containerwidget(edit=self._root_widget, selected_child=sel)
except Exception:
ba.print_exception(f'Error restoring state for {self}.')
def sel_change(self, row: str, game: str) -> None:
"""(internal)"""
if self._do_selection_callbacks:
if row == 'custom':
self._selected_custom_level = game
if row == 'challenges':
self._selected_challenge_level = game
elif row == 'campaign':
self._selected_campaign_level = game

View file

@ -55,7 +55,7 @@ class GameButton:
position=(x + 23, y + 4),
size=(sclx, scly),
label='',
on_activate_call=ba.Call(window.run, game),
on_activate_call=ba.Call(window.run_game, game),
button_type='square',
autoselect=True,
on_select_call=ba.Call(window.sel_change, row, game))

View file

@ -0,0 +1,561 @@
# Released under the MIT License. See LICENSE for details.
#
"""Defines button for co-op games."""
from __future__ import annotations
from typing import TYPE_CHECKING
import copy
import ba
import _ba
if TYPE_CHECKING:
from typing import Any, Callable
class TournamentButton:
"""Button showing a tournament in coop window."""
def __init__(self, parent: ba.Widget, x: float, y: float, select: bool,
on_pressed: Callable[[TournamentButton], None]) -> None:
self._r = 'coopSelectWindow'
sclx = 300
scly = 195.0
self.on_pressed = on_pressed
self.lsbt = ba.getmodel('level_select_button_transparent')
self.lsbo = ba.getmodel('level_select_button_opaque')
self.allow_ads = False
self.tournament_id: str | None = None
self.time_remaining: int = 0
self.has_time_remaining: bool = False
self.leader: Any = None
self.required_league: str | None = None
self.button = btn = ba.buttonwidget(
parent=parent,
position=(x + 23, y + 4),
size=(sclx, scly),
label='',
button_type='square',
autoselect=True,
# on_activate_call=lambda: self.run(None, tournament_button=data)
on_activate_call=ba.WeakCall(self._pressed))
ba.widget(edit=btn,
show_buffer_bottom=50,
show_buffer_top=50,
show_buffer_left=400,
show_buffer_right=200)
if select:
ba.containerwidget(edit=parent,
selected_child=btn,
visible_child=btn)
image_width = sclx * 0.85 * 0.75
self.image = ba.imagewidget(
parent=parent,
draw_controller=btn,
position=(x + 21 + sclx * 0.5 - image_width * 0.5, y + scly - 150),
size=(image_width, image_width * 0.5),
model_transparent=self.lsbt,
model_opaque=self.lsbo,
texture=ba.gettexture('black'),
opacity=0.2,
mask_texture=ba.gettexture('mapPreviewMask'))
self.lock_image = ba.imagewidget(
parent=parent,
draw_controller=btn,
position=(x + 21 + sclx * 0.5 - image_width * 0.25,
y + scly - 150),
size=(image_width * 0.5, image_width * 0.5),
texture=ba.gettexture('lock'),
opacity=0.0)
self.button_text = ba.textwidget(parent=parent,
draw_controller=btn,
position=(x + 20 + sclx * 0.5,
y + scly - 35),
size=(0, 0),
h_align='center',
text='-',
v_align='center',
maxwidth=sclx * 0.76,
scale=0.85,
color=(0.8, 1.0, 0.8, 1.0))
header_color = (0.43, 0.4, 0.5, 1)
value_color = (0.6, 0.6, 0.6, 1)
x_offs = 0
ba.textwidget(parent=parent,
draw_controller=btn,
position=(x + 360, y + scly - 20),
size=(0, 0),
h_align='center',
text=ba.Lstr(resource=self._r + '.entryFeeText'),
v_align='center',
maxwidth=100,
scale=0.9,
color=header_color,
flatness=1.0)
self.entry_fee_text_top = ba.textwidget(parent=parent,
draw_controller=btn,
position=(x + 360,
y + scly - 60),
size=(0, 0),
h_align='center',
text='-',
v_align='center',
maxwidth=60,
scale=1.3,
color=value_color,
flatness=1.0)
self.entry_fee_text_or = ba.textwidget(parent=parent,
draw_controller=btn,
position=(x + 360,
y + scly - 90),
size=(0, 0),
h_align='center',
text='',
v_align='center',
maxwidth=60,
scale=0.5,
color=value_color,
flatness=1.0)
self.entry_fee_text_remaining = ba.textwidget(parent=parent,
draw_controller=btn,
position=(x + 360,
y + scly - 90),
size=(0, 0),
h_align='center',
text='',
v_align='center',
maxwidth=60,
scale=0.5,
color=value_color,
flatness=1.0)
self.entry_fee_ad_image = ba.imagewidget(parent=parent,
size=(40, 40),
draw_controller=btn,
position=(x + 360 - 20,
y + scly - 140),
opacity=0.0,
texture=ba.gettexture('tv'))
x_offs += 50
ba.textwidget(parent=parent,
draw_controller=btn,
position=(x + 447 + x_offs, y + scly - 20),
size=(0, 0),
h_align='center',
text=ba.Lstr(resource=self._r + '.prizesText'),
v_align='center',
maxwidth=130,
scale=0.9,
color=header_color,
flatness=1.0)
self.button_x = x
self.button_y = y
self.button_scale_y = scly
xo2 = 0
prize_value_scale = 1.5
self.prize_range_1_text = ba.textwidget(
parent=parent,
draw_controller=btn,
position=(x + 355 + xo2 + x_offs, y + scly - 93),
size=(0, 0),
h_align='right',
v_align='center',
maxwidth=50,
text='-',
scale=0.8,
color=header_color,
flatness=1.0)
self.prize_value_1_text = ba.textwidget(
parent=parent,
draw_controller=btn,
position=(x + 380 + xo2 + x_offs, y + scly - 93),
size=(0, 0),
h_align='left',
text='-',
v_align='center',
maxwidth=100,
scale=prize_value_scale,
color=value_color,
flatness=1.0)
self.prize_range_2_text = ba.textwidget(
parent=parent,
draw_controller=btn,
position=(x + 355 + xo2 + x_offs, y + scly - 93),
size=(0, 0),
h_align='right',
v_align='center',
maxwidth=50,
scale=0.8,
color=header_color,
flatness=1.0)
self.prize_value_2_text = ba.textwidget(
parent=parent,
draw_controller=btn,
position=(x + 380 + xo2 + x_offs, y + scly - 93),
size=(0, 0),
h_align='left',
text='',
v_align='center',
maxwidth=100,
scale=prize_value_scale,
color=value_color,
flatness=1.0)
self.prize_range_3_text = ba.textwidget(
parent=parent,
draw_controller=btn,
position=(x + 355 + xo2 + x_offs, y + scly - 93),
size=(0, 0),
h_align='right',
v_align='center',
maxwidth=50,
scale=0.8,
color=header_color,
flatness=1.0)
self.prize_value_3_text = ba.textwidget(
parent=parent,
draw_controller=btn,
position=(x + 380 + xo2 + x_offs, y + scly - 93),
size=(0, 0),
h_align='left',
text='',
v_align='center',
maxwidth=100,
scale=prize_value_scale,
color=value_color,
flatness=1.0)
ba.textwidget(parent=parent,
draw_controller=btn,
position=(x + 620 + x_offs, y + scly - 20),
size=(0, 0),
h_align='center',
text=ba.Lstr(resource=self._r + '.currentBestText'),
v_align='center',
maxwidth=180,
scale=0.9,
color=header_color,
flatness=1.0)
self.current_leader_name_text = ba.textwidget(
parent=parent,
draw_controller=btn,
position=(x + 620 + x_offs - (170 / 1.4) * 0.5,
y + scly - 60 - 40 * 0.5),
selectable=True,
click_activate=True,
autoselect=True,
on_activate_call=ba.WeakCall(self._show_leader),
size=(170 / 1.4, 40),
h_align='center',
text='-',
v_align='center',
maxwidth=170,
scale=1.4,
color=value_color,
flatness=1.0)
self.current_leader_score_text = ba.textwidget(
parent=parent,
draw_controller=btn,
position=(x + 620 + x_offs, y + scly - 113 + 10),
size=(0, 0),
h_align='center',
text='-',
v_align='center',
maxwidth=170,
scale=1.8,
color=value_color,
flatness=1.0)
self.more_scores_button = ba.buttonwidget(
parent=parent,
position=(x + 620 + x_offs - 60, y + scly - 50 - 125),
color=(0.5, 0.5, 0.6),
textcolor=(0.7, 0.7, 0.8),
label='-',
size=(120, 40),
autoselect=True,
up_widget=self.current_leader_name_text,
text_scale=0.6,
on_activate_call=ba.WeakCall(self._show_scores))
ba.widget(edit=self.current_leader_name_text,
down_widget=self.more_scores_button)
ba.textwidget(parent=parent,
draw_controller=btn,
position=(x + 820 + x_offs, y + scly - 20),
size=(0, 0),
h_align='center',
text=ba.Lstr(resource=self._r + '.timeRemainingText'),
v_align='center',
maxwidth=180,
scale=0.9,
color=header_color,
flatness=1.0)
self.time_remaining_value_text = ba.textwidget(
parent=parent,
draw_controller=btn,
position=(x + 820 + x_offs, y + scly - 68),
size=(0, 0),
h_align='center',
text='-',
v_align='center',
maxwidth=180,
scale=2.0,
color=value_color,
flatness=1.0)
self.time_remaining_out_of_text = ba.textwidget(
parent=parent,
draw_controller=btn,
position=(x + 820 + x_offs, y + scly - 110),
size=(0, 0),
h_align='center',
text='-',
v_align='center',
maxwidth=120,
scale=0.72,
color=(0.4, 0.4, 0.5),
flatness=1.0)
def _pressed(self) -> None:
self.on_pressed(self)
def _show_leader(self) -> None:
# pylint: disable=cyclic-import
from bastd.ui.account.viewer import AccountViewerWindow
tournament_id = self.tournament_id
# FIXME: This assumes a single player entry in leader; should expand
# this to work with multiple.
if tournament_id is None or self.leader is None or len(
self.leader[2]) != 1:
ba.playsound(ba.getsound('error'))
return
ba.playsound(ba.getsound('swish'))
AccountViewerWindow(
account_id=self.leader[2][0].get('a', None),
profile_id=self.leader[2][0].get('p', None),
position=self.current_leader_name_text.get_screen_space_center())
def _show_scores(self) -> None:
# pylint: disable=cyclic-import
from bastd.ui.tournamentscores import TournamentScoresWindow
tournament_id = self.tournament_id
if tournament_id is None:
ba.playsound(ba.getsound('error'))
return
TournamentScoresWindow(
tournament_id=tournament_id,
position=self.more_scores_button.get_screen_space_center())
def update_for_data(self, entry: dict[str, Any]) -> None:
"""Update for new incoming data."""
# pylint: disable=too-many-statements
# pylint: disable=too-many-locals
# pylint: disable=too-many-branches
from ba.internal import getcampaign, get_tournament_prize_strings
prize_y_offs = (34 if 'prizeRange3' in entry else
20 if 'prizeRange2' in entry else 12)
x_offs = 90
# This seems to be a false alarm.
# pylint: disable=unbalanced-tuple-unpacking
pr1, pv1, pr2, pv2, pr3, pv3 = (get_tournament_prize_strings(entry))
# pylint: enable=unbalanced-tuple-unpacking
enabled = 'requiredLeague' not in entry
ba.buttonwidget(edit=self.button,
color=(0.5, 0.7, 0.2) if enabled else (0.5, 0.5, 0.5))
ba.imagewidget(edit=self.lock_image, opacity=0.0 if enabled else 1.0)
ba.textwidget(edit=self.prize_range_1_text,
text='-' if pr1 == '' else pr1,
position=(self.button_x + 365 + x_offs, self.button_y +
self.button_scale_y - 93 + prize_y_offs))
# We want to draw values containing tickets a bit smaller
# (scratch that; we now draw medals a bit bigger).
ticket_char = ba.charstr(ba.SpecialChar.TICKET_BACKING)
prize_value_scale_large = 1.0
prize_value_scale_small = 1.0
ba.textwidget(edit=self.prize_value_1_text,
text='-' if pv1 == '' else pv1,
scale=prize_value_scale_large
if ticket_char not in pv1 else prize_value_scale_small,
position=(self.button_x + 380 + x_offs, self.button_y +
self.button_scale_y - 93 + prize_y_offs))
ba.textwidget(edit=self.prize_range_2_text,
text=pr2,
position=(self.button_x + 365 + x_offs, self.button_y +
self.button_scale_y - 93 - 45 + prize_y_offs))
ba.textwidget(edit=self.prize_value_2_text,
text=pv2,
scale=prize_value_scale_large
if ticket_char not in pv2 else prize_value_scale_small,
position=(self.button_x + 380 + x_offs, self.button_y +
self.button_scale_y - 93 - 45 + prize_y_offs))
ba.textwidget(edit=self.prize_range_3_text,
text=pr3,
position=(self.button_x + 365 + x_offs, self.button_y +
self.button_scale_y - 93 - 90 + prize_y_offs))
ba.textwidget(edit=self.prize_value_3_text,
text=pv3,
scale=prize_value_scale_large
if ticket_char not in pv3 else prize_value_scale_small,
position=(self.button_x + 380 + x_offs, self.button_y +
self.button_scale_y - 93 - 90 + prize_y_offs))
leader_name = '-'
leader_score: str | ba.Lstr = '-'
if entry['scores']:
score = self.leader = copy.deepcopy(entry['scores'][0])
leader_name = score[1]
leader_score = (ba.timestring(
score[0] * 10,
centi=True,
timeformat=ba.TimeFormat.MILLISECONDS,
suppress_format_warning=True)
if entry['scoreType'] == 'time' else str(score[0]))
else:
self.leader = None
ba.textwidget(edit=self.current_leader_name_text,
text=ba.Lstr(value=leader_name))
ba.textwidget(edit=self.current_leader_score_text, text=leader_score)
ba.buttonwidget(edit=self.more_scores_button,
label=ba.Lstr(resource=self._r + '.seeMoreText'))
out_of_time_text: str | ba.Lstr = (
'-' if 'totalTime' not in entry else ba.Lstr(
resource=self._r + '.ofTotalTimeText',
subs=[('${TOTAL}',
ba.timestring(entry['totalTime'],
centi=False,
suppress_format_warning=True))]))
ba.textwidget(edit=self.time_remaining_out_of_text,
text=out_of_time_text)
self.time_remaining = entry['timeRemaining']
self.has_time_remaining = entry is not None
self.tournament_id = entry['tournamentID']
self.required_league = (None if 'requiredLeague' not in entry else
entry['requiredLeague'])
game = ba.app.accounts_v1.tournament_info[self.tournament_id]['game']
if game is None:
ba.textwidget(edit=self.button_text, text='-')
ba.imagewidget(edit=self.image,
texture=ba.gettexture('black'),
opacity=0.2)
else:
campaignname, levelname = game.split(':')
campaign = getcampaign(campaignname)
max_players = ba.app.accounts_v1.tournament_info[
self.tournament_id]['maxPlayers']
txt = ba.Lstr(value='${A} ${B}',
subs=[('${A}',
campaign.getlevel(levelname).displayname),
('${B}',
ba.Lstr(resource='playerCountAbbreviatedText',
subs=[('${COUNT}', str(max_players))
]))])
ba.textwidget(edit=self.button_text, text=txt)
ba.imagewidget(
edit=self.image,
texture=campaign.getlevel(levelname).get_preview_texture(),
opacity=1.0 if enabled else 0.5)
fee = entry['fee']
if fee is None:
fee_var = None
elif fee == 4:
fee_var = 'price.tournament_entry_4'
elif fee == 3:
fee_var = 'price.tournament_entry_3'
elif fee == 2:
fee_var = 'price.tournament_entry_2'
elif fee == 1:
fee_var = 'price.tournament_entry_1'
else:
if fee != 0:
print('Unknown fee value:', fee)
fee_var = 'price.tournament_entry_0'
self.allow_ads = allow_ads = entry['allowAds']
final_fee: int | None = (None if fee_var is None else
_ba.get_v1_account_misc_read_val(
fee_var, '?'))
final_fee_str: str | ba.Lstr
if fee_var is None:
final_fee_str = ''
else:
if final_fee == 0:
final_fee_str = ba.Lstr(resource='getTicketsWindow.freeText')
else:
final_fee_str = (ba.charstr(ba.SpecialChar.TICKET_BACKING) +
str(final_fee))
ad_tries_remaining = ba.app.accounts_v1.tournament_info[
self.tournament_id]['adTriesRemaining']
free_tries_remaining = ba.app.accounts_v1.tournament_info[
self.tournament_id]['freeTriesRemaining']
# Now, if this fee allows ads and we support video ads, show
# the 'or ad' version.
if allow_ads and _ba.has_video_ads():
ads_enabled = _ba.have_incentivized_ad()
ba.imagewidget(edit=self.entry_fee_ad_image,
opacity=1.0 if ads_enabled else 0.25)
or_text = ba.Lstr(resource='orText',
subs=[('${A}', ''),
('${B}', '')]).evaluate().strip()
ba.textwidget(edit=self.entry_fee_text_or, text=or_text)
ba.textwidget(edit=self.entry_fee_text_top,
position=(self.button_x + 360,
self.button_y + self.button_scale_y - 60),
scale=1.3,
text=final_fee_str)
# Possibly show number of ad-plays remaining.
ba.textwidget(edit=self.entry_fee_text_remaining,
position=(self.button_x + 360,
self.button_y + self.button_scale_y - 146),
text='' if ad_tries_remaining in [None, 0] else
('' + str(ad_tries_remaining)),
color=(0.6, 0.6, 0.6, 1 if ads_enabled else 0.2))
else:
ba.imagewidget(edit=self.entry_fee_ad_image, opacity=0.0)
ba.textwidget(edit=self.entry_fee_text_or, text='')
ba.textwidget(edit=self.entry_fee_text_top,
position=(self.button_x + 360,
self.button_y + self.button_scale_y - 80),
scale=1.3,
text=final_fee_str)
# Possibly show number of free-plays remaining.
ba.textwidget(
edit=self.entry_fee_text_remaining,
position=(self.button_x + 360,
self.button_y + self.button_scale_y - 100),
text=('' if (free_tries_remaining in [None, 0]
or final_fee != 0) else
('' + str(free_tries_remaining))),
color=(0.6, 0.6, 0.6, 1),
)

View file

@ -207,10 +207,15 @@ class CreditsListWindow(ba.Window):
'\n' + '\n'.join(translation_names.splitlines()[:146]) +
'\n'.join(translation_names.splitlines()[146:]) + '\n'
'\n'
' Shout Out to Awesome Mods / Modders:\n\n'
' Shout Out to Awesome Mods / Modders / Contributors:\n\n'
' BombDash ModPack\n'
' TheMikirog & SoK - BombSquad Joyride Modpack\n'
' Mrmaxmeier - BombSquad-Community-Mod-Manager\n'
' Ritiek Malhotra \n'
' Dliwk\n'
' vishal332008\n'
' itsre3\n'
' Drooopyyy\n'
'\n'
' Holiday theme vector art designed by Freepik\n'
'\n'

View file

@ -705,8 +705,8 @@ class ManualGatherTab(GatherTab):
from_other_thread=True,
)
except Exception as exc:
from efro.error import is_udp_network_error
if is_udp_network_error(exc):
from efro.error import is_udp_communication_error
if is_udp_communication_error(exc):
ba.pushcall(ba.Call(
_safe_set_text, self._checking_state_text,
ba.Lstr(resource='gatherWindow.'

View file

@ -219,9 +219,9 @@ class AddrFetchThread(threading.Thread):
sock.close()
ba.pushcall(ba.Call(self._call, val), from_other_thread=True)
except Exception as exc:
from efro.error import is_udp_network_error
from efro.error import is_udp_communication_error
# Ignore expected network errors; log others.
if is_udp_network_error(exc):
if is_udp_communication_error(exc):
pass
else:
ba.print_exception()
@ -271,8 +271,8 @@ class PingThread(threading.Thread):
ping if accessible else None),
from_other_thread=True)
except Exception as exc:
from efro.error import is_udp_network_error
if is_udp_network_error(exc):
from efro.error import is_udp_communication_error
if is_udp_communication_error(exc):
pass
else:
ba.print_exception('Error on gather ping', once=True)

View file

@ -213,8 +213,8 @@ class OnScreenKeyboardWindow(ba.Window):
# Show change instructions only if we have more than one
# keyboard option.
if (ba.app.meta.metascan is not None
and len(ba.app.meta.metascan.keyboards) > 1):
if (ba.app.meta.scanresults is not None
and len(ba.app.meta.scanresults.keyboards) > 1):
ba.textwidget(
parent=self._root_widget,
h_align='center',
@ -238,8 +238,8 @@ class OnScreenKeyboardWindow(ba.Window):
self._refresh()
def _get_keyboard(self) -> ba.Keyboard:
assert ba.app.meta.metascan is not None
classname = ba.app.meta.metascan.keyboards[self._keyboard_index]
assert ba.app.meta.scanresults is not None
classname = ba.app.meta.scanresults.keyboards[self._keyboard_index]
kbclass = ba.getclass(classname, ba.Keyboard)
return kbclass()
@ -317,11 +317,11 @@ class OnScreenKeyboardWindow(ba.Window):
self._refresh()
def _next_keyboard(self) -> None:
assert ba.app.meta.metascan is not None
assert ba.app.meta.scanresults is not None
self._keyboard_index = (self._keyboard_index + 1) % len(
ba.app.meta.metascan.keyboards)
ba.app.meta.scanresults.keyboards)
self._load_keyboard()
if len(ba.app.meta.metascan.keyboards) < 2:
if len(ba.app.meta.scanresults.keyboards) < 2:
ba.playsound(ba.getsound('error'))
ba.screenmessage(ba.Lstr(resource='keyboardNoOthersAvailableText'),
color=(1, 0, 0))

View file

@ -56,6 +56,7 @@ class AdvancedSettingsWindow(ba.Window):
scale=(2.06 if uiscale is ba.UIScale.SMALL else
1.4 if uiscale is ba.UIScale.MEDIUM else 1.0),
stack_offset=(0, -25) if uiscale is ba.UIScale.SMALL else (0, 0)))
self._prev_lang = ''
self._prev_lang_list: list[str] = []
self._complete_langs_list: list | None = None
@ -423,29 +424,6 @@ class AdvancedSettingsWindow(ba.Window):
v -= self._spacing * 2.1
this_button_width = 410
self._show_user_mods_button = ba.buttonwidget(
parent=self._subcontainer,
position=(self._sub_width / 2 - this_button_width / 2, v - 10),
size=(this_button_width, 60),
autoselect=True,
label=ba.Lstr(resource=self._r + '.showUserModsText'),
text_scale=1.0,
on_activate_call=show_user_scripts)
if self._show_always_use_internal_keyboard:
assert self._always_use_internal_keyboard_check_box is not None
ba.widget(edit=self._always_use_internal_keyboard_check_box.widget,
down_widget=self._show_user_mods_button)
ba.widget(
edit=self._show_user_mods_button,
up_widget=self._always_use_internal_keyboard_check_box.widget)
else:
ba.widget(edit=self._show_user_mods_button,
up_widget=self._kick_idle_players_check_box.widget)
ba.widget(edit=self._kick_idle_players_check_box.widget,
down_widget=self._show_user_mods_button)
v -= self._spacing * 2.0
self._modding_guide_button = ba.buttonwidget(
parent=self._subcontainer,
position=(self._sub_width / 2 - this_button_width / 2, v - 10),
@ -454,8 +432,30 @@ class AdvancedSettingsWindow(ba.Window):
label=ba.Lstr(resource=self._r + '.moddingGuideText'),
text_scale=1.0,
on_activate_call=ba.Call(
ba.open_url,
'http://www.froemling.net/docs/bombsquad-modding-guide'))
ba.open_url, 'http://ballistica.net/wiki/modding-guide'))
if self._show_always_use_internal_keyboard:
assert self._always_use_internal_keyboard_check_box is not None
ba.widget(edit=self._always_use_internal_keyboard_check_box.widget,
down_widget=self._modding_guide_button)
ba.widget(
edit=self._modding_guide_button,
up_widget=self._always_use_internal_keyboard_check_box.widget)
else:
ba.widget(edit=self._modding_guide_button,
up_widget=self._kick_idle_players_check_box.widget)
ba.widget(edit=self._kick_idle_players_check_box.widget,
down_widget=self._modding_guide_button)
v -= self._spacing * 2.0
self._show_user_mods_button = ba.buttonwidget(
parent=self._subcontainer,
position=(self._sub_width / 2 - this_button_width / 2, v - 10),
size=(this_button_width, 60),
autoselect=True,
label=ba.Lstr(resource=self._r + '.showUserModsText'),
text_scale=1.0,
on_activate_call=show_user_scripts)
v -= self._spacing * 2.0

View file

@ -10,6 +10,7 @@ import weakref
from threading import Thread
from typing import TYPE_CHECKING
from efro.error import CleanError
import _ba
import ba
from bastd.ui.settings.testing import TestingWindow
@ -148,10 +149,12 @@ def _run_diagnostics(weakwin: weakref.ref[NetTestingWindow]) -> None:
call()
duration = time.monotonic() - starttime
_print(f'Succeeded in {duration:.2f}s.', color=(0, 1, 0))
except Exception:
except Exception as exc:
import traceback
duration = time.monotonic() - starttime
_print(traceback.format_exc(), color=(1.0, 1.0, 0.3))
msg = (str(exc)
if isinstance(exc, CleanError) else traceback.format_exc())
_print(msg, color=(1.0, 1.0, 0.3))
_print(f'Failed in {duration:.2f}s.', color=(1, 0, 0))
have_error[0] = True
@ -193,6 +196,9 @@ def _run_diagnostics(weakwin: weakref.ref[NetTestingWindow]) -> None:
_print(f'\nContacting V2 master-server ({baseaddr})...')
_print_test_results(lambda: _test_fetch(baseaddr))
_print('\nComparing local time to V2 server...')
_print_test_results(_test_v2_time)
# Get V2 nearby zone
with ba.app.net.zone_pings_lock:
zone_pings = copy.deepcopy(ba.app.net.zone_pings)
@ -206,6 +212,9 @@ def _run_diagnostics(weakwin: weakref.ref[NetTestingWindow]) -> None:
_print(f'\nChecking nearest V2 zone ping ({nearstr})...')
_print_test_results(lambda: _test_nearby_zone_ping(nearest_zone))
_print('\nSending V2 cloud message...')
_print_test_results(_test_v2_cloud_message)
if have_error[0]:
_print('\nDiagnostics complete. Some diagnostics failed.',
color=(10, 0, 0))
@ -271,12 +280,67 @@ def _test_v1_transaction() -> None:
raise RuntimeError(results[0])
def _test_v2_cloud_message() -> None:
from dataclasses import dataclass
import bacommon.cloud
@dataclass
class _Results:
errstr: str | None = None
send_time: float | None = None
response_time: float | None = None
results = _Results()
def _cb(response: bacommon.cloud.PingResponse | Exception) -> None:
# Note: this runs in another thread so need to avoid exceptions.
results.response_time = time.monotonic()
if isinstance(response, Exception):
results.errstr = str(response)
if not isinstance(response, bacommon.cloud.PingResponse):
results.errstr = f'invalid response type: {type(response)}.'
def _send() -> None:
# Note: this runs in another thread so need to avoid exceptions.
results.send_time = time.monotonic()
ba.app.cloud.send_message_cb(bacommon.cloud.PingMessage(), _cb)
# This stuff expects to be run from the logic thread.
ba.pushcall(_send, from_other_thread=True)
wait_start_time = time.monotonic()
while True:
if results.response_time is not None:
break
time.sleep(0.01)
if time.monotonic() - wait_start_time > 10.0:
raise RuntimeError('Timeout waiting for cloud message response')
if results.errstr is not None:
raise RuntimeError(results.errstr)
def _test_v2_time() -> None:
offset = ba.app.net.server_time_offset_hours
if offset is None:
raise RuntimeError('no time offset found;'
' perhaps unable to communicate with v2 server?')
if abs(offset) >= 2.0:
raise CleanError(
f'Your device time is off from world time by {offset:.1f} hours.\n'
'This may cause network operations to fail due to your device\n'
' incorrectly treating SSL certificates as not-yet-valid, etc.\n'
'Check your device time and time-zone settings to fix this.\n')
def _test_fetch(baseaddr: str) -> None:
# pylint: disable=consider-using-with
import urllib.request
response = urllib.request.urlopen(urllib.request.Request(
f'{baseaddr}/ping', None, {'User-Agent': _ba.app.user_agent_string}),
timeout=10.0)
response = urllib.request.urlopen(
urllib.request.Request(f'{baseaddr}/ping', None,
{'User-Agent': _ba.app.user_agent_string}),
context=ba.app.net.sslcontext,
timeout=10.0,
)
if response.getcode() != 200:
raise RuntimeError(
f'Got unexpected response code {response.getcode()}.')

View file

@ -93,7 +93,7 @@ class PluginSettingsWindow(ba.Window):
self._subcontainer = ba.columnwidget(parent=self._scrollwidget,
selection_loops_to_parent=True)
if ba.app.meta.metascan is None:
if ba.app.meta.scanresults is None:
ba.screenmessage('Still scanning plugins; please try again.',
color=(1, 0, 0))
ba.playsound(ba.getsound('error'))

View file

@ -160,7 +160,7 @@ class SoundtrackEntryTypeSelectWindow(ba.Window):
from ba.osmusic import OSMusicPlayer
from bastd.ui.fileselector import FileSelectorWindow
ba.containerwidget(edit=self._root_widget, transition='out_left')
base_path = _ba.android_get_external_storage_path()
base_path = _ba.android_get_external_files_dir()
ba.app.ui.set_main_menu_window(
FileSelectorWindow(
base_path,
@ -173,7 +173,7 @@ class SoundtrackEntryTypeSelectWindow(ba.Window):
def _on_music_folder_press(self) -> None:
from bastd.ui.fileselector import FileSelectorWindow
ba.containerwidget(edit=self._root_widget, transition='out_left')
base_path = _ba.android_get_external_storage_path()
base_path = _ba.android_get_external_files_dir()
ba.app.ui.set_main_menu_window(
FileSelectorWindow(base_path,
callback=self._music_folder_selector_cb,

View file

@ -182,8 +182,9 @@ class TournamentEntryWindow(popup.PopupWindow):
h_align='center',
v_align='center',
scale=0.6,
text=ba.Lstr(resource='watchAVideoText',
fallback_resource='watchAnAdText'),
# Note: AdMob now requires rewarded ad usage
# specifically says 'Ad' in it.
text=ba.Lstr(resource='watchAnAdText'),
maxwidth=95,
color=(0, 1, 0))
ad_plays_remaining_text = (

View file

@ -112,7 +112,7 @@ class TrophiesWindow(popup.PopupWindow):
sub_width: int,
trophy_types: list[list[str]]) -> int:
from ba.internal import get_trophy_string
pts = 0
total_pts = 0
for i, trophy_type in enumerate(trophy_types):
t_count = self._data['t' + trophy_type[0]]
t_mult = self._data['t' + trophy_type[0] + 'm']
@ -157,7 +157,7 @@ class TrophiesWindow(popup.PopupWindow):
h_align='center',
v_align='center')
pts = t_count * t_mult
this_pts = t_count * t_mult
ba.textwidget(parent=self._subcontainer,
position=(sub_width * 0.88,
sub_height - 20 - incr * i),
@ -167,12 +167,12 @@ class TrophiesWindow(popup.PopupWindow):
flatness=1.0,
shadow=0.0,
scale=0.5,
text=eq_text.replace('${NUMBER}', str(pts)),
text=eq_text.replace('${NUMBER}', str(this_pts)),
size=(0, 0),
h_align='center',
v_align='center')
pts += pts
return pts
total_pts += this_pts
return total_pts
def _on_cancel_press(self) -> None:
self._transition_out()

View file

@ -4,6 +4,7 @@
from __future__ import annotations
from typing import TYPE_CHECKING
import errno
if TYPE_CHECKING:
pass
@ -39,12 +40,13 @@ class CommunicationError(Exception):
"""A communication related error has occurred.
This covers anything network-related going wrong in the sending
of data or receiving of a response. This error does not imply
of data or receiving of a response. Basically anything that is out
of our control should get lumped in here. This error does not imply
that data was not received on the other end; only that a full
acknowledgement round trip was not completed.
These errors should be gracefully handled whenever possible, as
occasional network outages are generally unavoidable.
occasional network issues are unavoidable.
"""
@ -55,9 +57,9 @@ class RemoteError(Exception):
occurs remotely. The error string can consist of a remote stack
trace or a simple message depending on the context.
Communication systems should raise more specific error types when
more introspection/control is needed; this is intended somewhat as
a catch-all.
Communication systems should raise more specific error types locally
when more introspection/control is needed; this is intended somewhat
as a catch-all.
"""
def __str__(self) -> str:
@ -69,31 +71,43 @@ class IntegrityError(ValueError):
"""Data has been tampered with or corrupted in some form."""
def is_urllib_network_error(exc: BaseException) -> bool:
"""Is the provided exception from urllib a network-related error?
def is_urllib_communication_error(exc: BaseException, url: str | None) -> bool:
"""Is the provided exception from urllib a communication-related error?
Url, if provided can provide extra context for when to treat an error
as such an error.
This should be passed an exception which resulted from opening or
reading a urllib Request. It returns True for any errors that could
conceivably arise due to unavailable/poor network connections,
firewall/connectivity issues, etc. These issues can often be safely
ignored or presented to the user as general 'network-unavailable'
states.
firewall/connectivity issues, or other issues out of our control.
These errors can often be safely ignored or presented to the user
as general 'network-unavailable' states.
"""
import urllib.error
import http.client
import errno
import socket
if isinstance(
exc,
(urllib.error.URLError, ConnectionError, http.client.IncompleteRead,
http.client.BadStatusLine, socket.timeout)):
if isinstance(exc, (urllib.error.URLError, ConnectionError,
http.client.IncompleteRead, http.client.BadStatusLine,
http.client.RemoteDisconnected, socket.timeout)):
# Special case: although an HTTPError is a subclass of URLError,
# we don't return True for it. It means we have successfully
# communicated with the server but what we are asking for is
# not there/etc.
# we don't consider it a communication error. It generally means we
# have successfully communicated with the server but what we are asking
# for is not there/etc.
if isinstance(exc, urllib.error.HTTPError):
# Special sub-case: appspot.com hosting seems to give 403 errors
# (forbidden) to some countries. I'm assuming for legal reasons?..
# Let's consider that a communication error since its out of our
# control so we don't fill up logs with it.
if exc.code == 403 and url is not None and '.appspot.com' in url:
return True
return False
return True
if isinstance(exc, OSError):
if exc.errno == 10051: # Windows unreachable network error.
return True
@ -106,18 +120,17 @@ def is_urllib_network_error(exc: BaseException) -> bool:
return False
def is_udp_network_error(exc: BaseException) -> bool:
"""Is the provided exception a network-related error?
def is_udp_communication_error(exc: BaseException) -> bool:
"""Should this udp-related exception be considered a communication error?
This should be passed an exception which resulted from creating and
using a socket.SOCK_DGRAM type socket. It should return True for any
errors that could conceivably arise due to unavailable/poor network
connections, firewall/connectivity issues, etc. These issues can often
conditions, firewall/connectivity issues, etc. These issues can often
be safely ignored or presented to the user as general
'network-unavailable' states.
"""
import errno
if isinstance(exc, ConnectionRefusedError):
if isinstance(exc, ConnectionRefusedError | TimeoutError):
return True
if isinstance(exc, OSError):
if exc.errno == 10051: # Windows unreachable network error.
@ -140,8 +153,8 @@ def is_udp_network_error(exc: BaseException) -> bool:
return False
def is_asyncio_streams_network_error(exc: BaseException) -> bool:
"""Is the provided exception a network-related error?
def is_asyncio_streams_communication_error(exc: BaseException) -> bool:
"""Should this streams error be considered a communication error?
This should be passed an exception which resulted from creating and
using asyncio streams. It should return True for any errors that could
@ -149,7 +162,6 @@ def is_asyncio_streams_network_error(exc: BaseException) -> bool:
firewall/connectivity issues, etc. These issues can often be safely
ignored or presented to the user as general 'connection-lost' events.
"""
import errno
import ssl
if isinstance(exc, (

View file

@ -56,6 +56,7 @@ class ErrorResponse(Response):
OTHER = 0
CLEAN = 1
LOCAL = 2
COMMUNICATION = 3
error_message: Annotated[str, IOAttrs('m')]
error_type: Annotated[ErrorType, IOAttrs('e')] = ErrorType.OTHER

View file

@ -25,28 +25,40 @@ class MessageProtocol:
"""Wrangles a set of message types, formats, and response types.
Both endpoints must be using a compatible Protocol for communication
to succeed. To maintain Protocol compatibility between revisions,
all message types must retain the same id, message attr storage names must
not change, newly added attrs must have default values, etc.
all message types must retain the same id, message attr storage
names must not change, newly added attrs must have default values,
etc.
"""
def __init__(self,
message_types: dict[int, type[Message]],
response_types: dict[int, type[Response]],
preserve_clean_errors: bool = True,
log_remote_exceptions: bool = True,
trusted_sender: bool = False) -> None:
receiver_logs_exceptions: bool = True,
receiver_returns_stack_traces: bool = False) -> None:
"""Create a protocol with a given configuration.
Note that common response types are automatically registered
with (unchanging negative ids) so they don't need to be passed
explicitly (but can be if a different id is desired).
If 'preserve_clean_errors' is True, efro.error.CleanError errors
on the remote end will result in the same error raised locally.
All other Exception types come across as efro.error.RemoteError.
If 'preserve_clean_errors' is True, efro.error.CleanError
exceptions raised on the receiver end will result in a matching
CleanError raised back on the sender. All other Exception types
come across as efro.error.RemoteError.
If 'trusted_sender' is True, stringified remote stack traces will
be included in the responses if errors occur.
When 'receiver_logs_exceptions' is True, any uncaught Exceptions
on the receiver end will be logged there via logging.exception()
(in addition to the usual behavior of returning an ErrorResponse
to the sender). This is good to leave enabled if your
intention is to never return ErrorResponses. Looser setups
making routine use of CleanErrors or whatnot may want to
disable this, however.
If 'receiver_returns_stack_traces' is True, stringified stack
traces will be returned to the sender for exceptions occurring
on the receiver end. This can make debugging easier but should
only be used when the client is trusted to see such info.
"""
self.message_types_by_id: dict[int, type[Message]] = {}
self.message_ids_by_type: dict[type[Message], int] = {}
@ -102,9 +114,9 @@ class MessageProtocol:
assert is_ioprepped_dataclass(cls)
assert issubclass(cls, Response)
if cls not in self.response_ids_by_type:
raise ValueError(f'Possible response type {cls}'
f' needs to be included in response_types'
f' for this protocol.')
raise ValueError(
f'Possible response type {cls} needs to be included'
f' in response_types for this protocol.')
# Make sure all registered types have unique base names.
# We can take advantage of this to generate cleaner looking
@ -116,8 +128,8 @@ class MessageProtocol:
' all types are required to have unique names.')
self.preserve_clean_errors = preserve_clean_errors
self.log_remote_exceptions = log_remote_exceptions
self.trusted_sender = trusted_sender
self.receiver_logs_exceptions = receiver_logs_exceptions
self.receiver_returns_stack_traces = receiver_returns_stack_traces
@staticmethod
def encode_dict(obj: dict) -> str:
@ -134,7 +146,9 @@ class MessageProtocol:
def error_to_response(self, exc: Exception) -> Response:
"""Translate an error to a response."""
if self.log_remote_exceptions:
# Log any errors we got during handling if so desired.
if self.receiver_logs_exceptions:
logging.exception('Error handling message.')
# If anything goes wrong, return a ErrorResponse instead.
@ -142,8 +156,9 @@ class MessageProtocol:
return ErrorResponse(error_message=str(exc),
error_type=ErrorResponse.ErrorType.CLEAN)
return ErrorResponse(
error_message=(traceback.format_exc() if self.trusted_sender else
'An unknown error has occurred.'),
error_message=(traceback.format_exc()
if self.receiver_returns_stack_traces else
'An internal error has occurred.'),
error_type=ErrorResponse.ErrorType.OTHER)
def _to_dict(self, message: Any, ids_by_type: dict[type, int],

View file

@ -9,7 +9,7 @@ from __future__ import annotations
import logging
from typing import TYPE_CHECKING, TypeVar
from efro.error import CleanError, RemoteError
from efro.error import CleanError, RemoteError, CommunicationError
from efro.message._message import EmptyResponse, ErrorResponse
if TYPE_CHECKING:
@ -35,7 +35,7 @@ class MessageSender:
def send_raw_message(self, message: str) -> str:
# Actually send the message here.
# MyMessageSender class should provide overloads for send(), send_bg(),
# MyMessageSender class should provide overloads for send(), send_async(),
# etc. to ensure all sending happens with valid types.
obj = MyClass()
obj.msg.send(SomeMessageType())
@ -54,7 +54,12 @@ class MessageSender:
def send_method(
self, call: Callable[[Any, str],
str]) -> Callable[[Any, str], str]:
"""Function decorator for setting raw send method."""
"""Function decorator for setting raw send method.
Send methods take strings and should return strings.
Any Exception raised during the send_method manifests as
a CommunicationError for the message sender.
"""
assert self._send_raw_message_call is None
self._send_raw_message_call = call
return call
@ -62,7 +67,12 @@ class MessageSender:
def send_async_method(
self, call: Callable[[Any, str], Awaitable[str]]
) -> Callable[[Any, str], Awaitable[str]]:
"""Function decorator for setting raw send-async method."""
"""Function decorator for setting raw send-async method.
Send methods take strings and should return strings.
Any Exception raised during the send_method manifests as
a CommunicationError for the message sender.
"""
assert self._send_async_raw_message_call is None
self._send_async_raw_message_call = call
return call
@ -123,7 +133,17 @@ class MessageSender:
raise RuntimeError('send() is unimplemented for this type.')
msg_encoded = self._encode_message(bound_obj, message)
response_encoded = self._send_raw_message_call(bound_obj, msg_encoded)
try:
response_encoded = self._send_raw_message_call(
bound_obj, msg_encoded)
except Exception as exc:
# Any error in the raw send call gets recorded as either
# a local or communication error.
return ErrorResponse(
error_message=f'Error in send async method: {exc}',
error_type=(ErrorResponse.ErrorType.COMMUNICATION
if isinstance(exc, CommunicationError) else
ErrorResponse.ErrorType.LOCAL))
return self._decode_raw_response(bound_obj, message, response_encoded)
async def send_split_part_1_async(self, bound_obj: Any,
@ -139,9 +159,17 @@ class MessageSender:
raise RuntimeError('send_async() is unimplemented for this type.')
msg_encoded = self._encode_message(bound_obj, message)
response_encoded = await self._send_async_raw_message_call(
bound_obj, msg_encoded)
try:
response_encoded = await self._send_async_raw_message_call(
bound_obj, msg_encoded)
except Exception as exc:
# Any error in the raw send call gets recorded as either
# a local or communication error.
return ErrorResponse(
error_message=f'Error in send async method: {exc}',
error_type=(ErrorResponse.ErrorType.COMMUNICATION
if isinstance(exc, CommunicationError) else
ErrorResponse.ErrorType.LOCAL))
return self._decode_raw_response(bound_obj, message, response_encoded)
def send_split_part_2(self, message: Message,
@ -183,8 +211,8 @@ class MessageSender:
# If we got to this point, we successfully communicated
# with the other end so errors represent protocol mismatches
# or other invalid data. For now let's just log it but perhaps
# we'd want to somehow embed it in the ErrorResponse to be raised
# directly to the user later.
# we'd want to somehow embed it in the ErrorResponse to be
# available directly to the user later.
logging.exception('Error decoding raw response')
response = ErrorResponse(
error_message=
@ -208,6 +236,10 @@ class MessageSender:
# Some error occurred. Raise a local Exception for it.
if isinstance(raw_response, ErrorResponse):
if (raw_response.error_type is
ErrorResponse.ErrorType.COMMUNICATION):
raise CommunicationError(raw_response.error_message)
# If something went wrong on our end of the connection,
# don't say it was a remote error.
if raw_response.error_type is ErrorResponse.ErrorType.LOCAL:

View file

@ -13,8 +13,9 @@ from dataclasses import dataclass
from threading import current_thread
from typing import TYPE_CHECKING, Annotated
from efro.error import CommunicationError, is_asyncio_streams_network_error
from efro.util import assert_never
from efro.error import (CommunicationError,
is_asyncio_streams_communication_error)
from efro.dataclassio import (dataclass_to_json, dataclass_from_json,
ioprepped, IOAttrs)
@ -596,7 +597,7 @@ class RPCEndpoint:
if isinstance(exc, _KeepaliveTimeoutError):
return True
return is_asyncio_streams_network_error(exc)
return is_asyncio_streams_communication_error(exc)
def _check_env(self) -> None:
# I was seeing that asyncio stuff wasn't working as expected if

View file

@ -637,9 +637,11 @@ def compact_id(num: int) -> str:
'abcdefghijklmnopqrstuvwxyz')
# NOTE: Even though this is available as part of typing_extensions, keeping
# it in here for now so we don't require typing_extensions as a dependency.
# Once 3.11 rolls around we can kill this and use typing.assert_never.
def assert_never(value: NoReturn) -> NoReturn:
"""Trick for checking exhaustive handling of Enums, etc.
See https://github.com/python/typing/issues/735
"""
assert False, f'Unhandled value: {value} ({type(value).__name__})'

Binary file not shown.