diff --git a/dist/ba_data/data/langdata.json b/dist/ba_data/data/langdata.json index 9621466..c58711e 100644 --- a/dist/ba_data/data/langdata.json +++ b/dist/ba_data/data/langdata.json @@ -9,7 +9,7 @@ "Danish": "Dansk", "Dutch": "Nederlands", "Esperanto": "Esperanto", - "Filipino": "Wikang Tagalog ", + "Filipino": "Wikang Pilipino", "French": "Français", "German": "Deutsch", "Gibberish": "Abuktarika", @@ -43,6 +43,7 @@ "!ParkuristTurist!", "\"9۝ÅℳЇℜρℜѺ۝ƬǀGΞЯ", "\"Unknown\"", + "3alTemp (Temp)", "/in/dev/", "0Globalsters", "1.4.139", @@ -57,6 +58,7 @@ "26885", "3alTemp", "43210", + "4e6yre4ek", "5PH3X", "99", "@sametsunal", @@ -81,6 +83,7 @@ "Gifasa abidjahsi", "Abinav", "Abir", + "ABITDANTON", "Abne", "Abolfadl", "Abolfazl", @@ -106,6 +109,7 @@ "Aga<3", "Carlos Mario Agamez", "ageng", + "Agenteby20", "Dimitris Aggelou", "AGØTI", "ariyan ahir", @@ -114,6 +118,7 @@ "Ahmed", "ahmedzabara", "Collin Ainge", + "Ajnaz", "Akash", "Akbar", "Bekir Akdemir", @@ -145,10 +150,10 @@ "Alexyze", "Algene123456", "Alguien_201", - "ALI", + "Ali", "Mohamed ali", "Shadiq Ali", - "alireza", + "Alireza", "alirezaalidokht", "AliSh8787", "ALISSON", @@ -183,6 +188,7 @@ "Ange Kevin Amlaman", "AMOGUSS85", "amr", + "AN218", "Anandchaursiya", "Anas", "Anastasija", @@ -227,6 +233,7 @@ "Arimaru", "Arin", "arjanex", + "Armando", "Arroz", "ARSHAD", "Arshia", @@ -336,10 +343,11 @@ "BomBillo", "The Bomboler 💣", "bombsquad", + "Wheezy bombsquad", "Bombsquadzueira", "Bomby", "Zeleni bomby", - "Alex BONYOMA", + "Alex Bonyoma", "Book", "Guilherme Borges", "Lucas Borges", @@ -433,12 +441,14 @@ "David Cot", "Nayib Méndez Coto", "Dylan cotten", + "Coutinho", "covcheg", "COVER", "crac", "CrazyBear", "Frederick Cretton", - "crisroco10", + "Crispiymini", + "Crisroco10", "Cristhian", "Cristian", "Cristóbal", @@ -464,6 +474,7 @@ "Daniel", "Daniel3505", "DaniesAlex007", + "Daniozo", "Dančo", "Iman Darius", "DarkAnarcy", @@ -475,8 +486,9 @@ "David", "Davide", "DavidPlayzLol", - "Davidplayzloll", + "DavidPlayzLoll", "DaymanLP", + "DD87", "Ddávid", "Die or Dead", "Привет от детей DeadLine", @@ -496,6 +508,7 @@ "df", "Santanu Dhar", "DHRUVIL", + "DIAbli", "Guilherme Dias", "Diase7en", "ferbie Dicen", @@ -523,6 +536,7 @@ "Gerardo Doro", "DottorMorte", "Doubleknig", + "Dr", "Dragomir", "Dreaming", "Drellsan", @@ -554,6 +568,7 @@ "ElderLink", "elfree", "Elian", + "Elmakyt", "ELMEX95", "Elsans320_YT", "Elskoser", @@ -573,10 +588,13 @@ "Endless_jk", "EnglandFirst", "Enrico", + "Entakos", "enzo", "Era", "Era0S", + "Eric-fan", "Erick", + "Erik", "Erkam", "Jonas Ernst", "NO es", @@ -635,6 +653,7 @@ "FluffyPal", "FLᎧRᏋᏁTIᏁᎧ", "Angelo Fontana", + "Forcedpood", "FortKing", "Freaku", "Golden Freddy", @@ -646,6 +665,7 @@ "FuckIndoDick", "Lukas Funk", "Gustavo FunnyGuard28", + "fyroove", "Erick G", "Roberto G", "George G.", @@ -671,6 +691,7 @@ "krish gator", "gene.mTs", "GeoMatHeo", + "Gerakl", "Gerry", "GG (9.2)", "ggMustaGD0", @@ -712,6 +733,7 @@ "SHOBHIT GUPTA", "Gurad", "Max Guskov", + "Gustavo", "Rachmat Gusti", "Aditya Gwala", "Tódor Gábor", @@ -719,6 +741,7 @@ "Thomas Günther", "Hamza Emin GÜRLER", "Paşa Güven", + "Muhammad H", "H.J.N", "Haasaani", "Hack", @@ -786,13 +809,16 @@ "Umair Hussain", "Hussam", "Huy", + "HyMr", "HYr", "Adrian Höfer", "Davide Iaccarino", + "Ian", "iBearzGaming", "Iboyindyza", "Ibrahim", "Iddrejiot", + "idiomat738", "Ignacio", "IgnUp21", "Igomen15", @@ -820,8 +846,9 @@ "InvisibleDude", "Anestis Ioakimidis", "Dragomir Ioan", - "IorYou4600 (V2)", + "IorYou4600", "Isa", + "IsaacStar ★", "Israelme03", "Tobias Dencker Israelsen", "Kegyes István", @@ -837,11 +864,12 @@ "Jhon Jairo", "jakecato1602@gmail.com", "wahid jamaludin", + "Jameszambrana", "Tarun Jangra", "Aleksandar Janic", "Martin Jansson", "JasimGamer", - "Jason", + "jason", "Javvaed", "Jbo", "JCIBravo", @@ -863,17 +891,20 @@ "jimmy", "Jiren", "jitues", + "JJ", "JM", "Joan", "JoaoVitorBF", - "joaquin", + "Joaquin", "Lex Johannes", "John", "Ksteven john", "Steven john", "Johnny", + "Johnwick", "joke", "Jonatas", + "Jonathan", "Jop", "Jor", "JoseANG3L", @@ -921,6 +952,7 @@ "Kasra", "Kaushik", "KawaiiON", + "kazoo081", "kazooicek", "KD", "Kejuxs", @@ -950,6 +982,7 @@ "kirill", "KirillMasich", "Kittycat41", + "Dominic Klein", "Andrew Kmitto", "Philipp Koch", "Kolmat", @@ -968,8 +1001,10 @@ "krishAngad", "krishuroy", "kroш)", + "Mikuláš Krtička", "Patel krumil", "Krunal", + "Kubixpro1", "kueue", "Alok Kumar", "Parth Kumar", @@ -988,6 +1023,7 @@ "Labrador", "labrosggv", "John Patrick Lachica", + "Osama bin ladin", "laikrai", "m a lakum", "Dmitry Lamperujev", @@ -999,11 +1035,13 @@ "Lazered", "Lazydog", "Elia Lazzari", + "LEANKEAN", "이지민 (Ji-Min Lee)", "legended", "Mick Lemmens", "Leo", "Mr. LeoLeo", + "Leonid", "Lester", "Szajkajkó Levente", "Szajkajó Levente", @@ -1051,6 +1089,7 @@ "Chenging Lu", "Chengming Lu", "João Lucas", + "Pedro Lucas", "Simone Luconi", "Ludicrouswizard", "satrio ludji", @@ -1080,19 +1119,24 @@ "Djawad madi", "MadNightr", "Mads Beier Madsen", + "Magomed", "Mahan", "Ondřej Mahdalík", - "mahdi", + "Mahdi", "mahdimahabadi", + "Mose saef maher", "Mahmoud", + "Mahmoudrabe", "Mahziyar", "maicol", "Majestozão", "Makar", "Maks1212", "Malaysian", + "Amirali Malekshahi", "EMILIO MALQUIN", "MAMAD", + "Mamax400", "Wagdy mamdouh", "Mani", "Manimutharu", @@ -1190,16 +1234,18 @@ "Mohammadhosain", "Mohammadpl", "Mohammed", - "MOHAMMEDTALAL1ST", + "MohammedTalal1st", "1n Mohhaamad", + "Shaygan mohsenian", "Moin", "MONIRIE", - "Carlos Montalvo", + "carlos montalvo", "Ederson Moraes", "Eduardo Moreira", "Danteo Moriarty", "Kabir morya", "Moses", + "Iasonasss Mourelatos", "mr", "mr.Dark", "Mr.Smoothy", @@ -1225,10 +1271,12 @@ "MYSENIOR", "mythbrk00@gmail.com", "محمدحسین MZZ", + "Mzz85moh", "Sajti Márk", "Samuel Mörling", "Luca Müller", - "nacho", + "Nabil", + "Nacho", "Nagaarjun(pongal)", "Nahuelgomez1607", "Nasser", @@ -1267,6 +1315,7 @@ "Frederik Nielsen", "Nifujini", "Nikali2007", + "NikeOvSky", "Nima", "XU NING", "طارق محمد رضا سعيد NinjaStarXD", @@ -1283,6 +1332,7 @@ "NoNameC3698241", "None", "NOOBPEDAR", + "NoobPilotPlayz", "Noobslaya101", "noorjandle1", "Petter Nordlander", @@ -1310,9 +1360,13 @@ "Oscar", "Osmanlı2002", "Osmanys", + "otd", + "oualiabdou", "OyUnBoZaN (NEMUTLUTURKUMDİYENE)", + "pablo", "pack", "PALASH", + "Buddy pall", "Giorgio Palmieri", "Abhinay Pandey", "PangpondTH", @@ -1325,11 +1379,13 @@ "Pastis69", "Sagar patil", "pato", - "patrick", + "Patrick", "paulo", "Dominik Pavešić", "BARLAS PAVLOS-IASON", "Payu", + "PC1003436", + "pc100756", "PC189085", "PC192082", "pc192089", @@ -1342,7 +1398,9 @@ "pebikristia", "Pedro", "Jiren/Juan Pedro", + "João Pedro", "Penta :D", + "Peppone0071", "Peque", "Rode Liliana Miranda Pereira", "Jura Perić", @@ -1360,7 +1418,9 @@ "piga", "Stefano Pigozzi", "Mario Donato Pilla", + "PillTek", "Pinchidino", + "pinguino", "Danilo \"Logan\" Pirrone", "PivotStickfigure12", "Pixelcube", @@ -1374,6 +1434,7 @@ "Anestis Plithos", "Pluisbaard", "Jaideep Kumar PM", + "Pmans", "podolianyn", "Pofani", "poggersCat", @@ -1382,7 +1443,7 @@ "Pooya", "pouriya", "Pouya", - "Pranav", + "pranav", "Luca Preibsch", "Prem", "Fabian Prinz", @@ -1414,6 +1475,7 @@ "raghul", "khaled rahma", "Rayhan Rahmats", + "Raiyan", "Babang Raja", "Ralfreengz", "1. Ramagister", @@ -1465,6 +1527,7 @@ "Ridzuan", "Samuel Rieger", "RieJoemar", + "RiLa", "Rings3467", "Jeroen Rinzema", "RioAdir", @@ -1505,6 +1568,7 @@ "Justin Saephan", "sahel", "Abdullah Saim", + "Saints557", "Audinta Sakti", "Bassam bu salh", "Bsam bu salh", @@ -1513,6 +1577,7 @@ "Matteo Salvini", "Salvo04", "Samen", + "Samir", "Samsep10l", "San", "SaNt0RiNiKits577YT", @@ -1548,6 +1613,8 @@ "shakesm", "Sharvesh", "Nalam Shashwath", + "A sheep", + "A small sheep", "shemas", "Haige Shi", "ShockedGaming", @@ -1555,6 +1622,7 @@ "Shayan Shokry", "Dominik Sikora", "Leonardo Henrique da Silva", + "Lucas Silva", "Sebastian Silva", "silver_volt4", "Aviv Simel", @@ -1620,8 +1688,10 @@ "Shannon Sy", "syaifudib", "Daniel Sykora", + "Syumza", "Sz™", "Jorge Luis Sánchez", + "Kevin Nicola Ríos Sánchez", "Daniel Sýkora", "Aleksandar Tadic", "Arung Taftazani", @@ -1638,6 +1708,7 @@ "Tarma", "tarun", "Tauras", + "TAWILE", "tcnuhgv", "tdho", "Teals53", @@ -1666,6 +1737,7 @@ "TheMikirog", "Theo", "Thiago_TRZ", + "ThirdStar248", "ThisIsBad", "Trevon Thrasher", "Tiberiu", @@ -1678,9 +1750,11 @@ "Thura Tint", "Nishant Tiwari", "tjkffndeupwfbkh", + "Juraj Tlach", "Toloche", "Tom", "Juan Pablo Montoya Tomalá", + "Tomasekvata", "TomasNoobCz", "tomo", "tongtong", @@ -1693,6 +1767,7 @@ "TrialTemp", "Trivago", "El Trolax", + "Quang Truøng", "tseringlama", "Konstantin Tsvetkov", "Kontantin Tsvetkov", @@ -1707,6 +1782,7 @@ "Atchy-Dalama--Ancelly Ulrich", "Syed Umar", "Unknown", + "Unlette659", "Uros", "clarins usap", "utyrrwq", @@ -1722,6 +1798,7 @@ "Dmitry \"SqdDoom\" Verigo", "Deepanshu Verma", "Jop Vernooij", + "Veroyatnolevlev", "Via", "Vickey", "Victor", @@ -1790,21 +1867,25 @@ "amr yasser", "Yatoku", "YellowTractor", + "YelowGlow", "Yasin YILMAZ", "Ymgfr", "yoksoudraft", + "Yones", "Kenneth Yoneyama", "yossef", "youcef", "Youssef", + "ahmed youssef", "Yousuf", "Yovan182Sunbreaker", - "Yrtking", + "YRTKING", "All Star YT", "Dark Fgg5 YT", "Yudhis", "yugo", "yullian", + "YuriBombSquad", "Yuslendo", "NEEROOA Muhammad Yusuf", "Yuuki", @@ -1824,6 +1905,8 @@ "Zangar", "ZaraMax", "zecharaiah", + "ArShAm ZED", + "Zeinab1391", "Daniele Zennaro", "Zenotaiko", "zFliws", @@ -1896,6 +1979,7 @@ "!SW сосут мой член!", "Эмир", "Өмүрзаков Эрсултан", + "Forton123 ютубер", "Ярослав \"Noiseaholic\"", "қуатжан", "اا", @@ -1920,6 +2004,7 @@ "محمد خالد", "امیرحسین دهقان", "امید رضازاده", + "كريستيانو رونالدو", "فاطمه عباس زاده ۸۴", "فاطمه عباس زاده۸۴", "زينب", @@ -2016,6 +2101,6 @@ "Zona-BombSquad", "CrazySquad", "Stazzy", - "Slida" + "slida" ] } diff --git a/dist/ba_data/data/languages/arabic.json b/dist/ba_data/data/languages/arabic.json index 47c5e8d..685f195 100644 --- a/dist/ba_data/data/languages/arabic.json +++ b/dist/ba_data/data/languages/arabic.json @@ -1,6 +1,6 @@ { "accountSettingsWindow": { - "accountNameRules": ".لا يمكن لاسماء الحِسابَات أن تحتوي على رموز تعبيرية أو حروف غير ألفبائية", + "accountNameRules": "لا يمكن أن تحتوي أسماء الحسابات على رموز تعبيرية أو أحرف خاصة أخرى", "accountProfileText": "معلومات اللاعبين", "accountsText": "حسابات", "achievementProgressText": "${TOTAL} من أصل ${COUNT} إنجازاتك: أنجزت", @@ -8,13 +8,13 @@ "changeOncePerSeason": "يمكنك تغييره مرة واحدة في الموسم", "changeOncePerSeasonError": "يجب عليك الانتظار حتى الموسم القادم لتغيير هذا مجددا (${NUM} أيام )", "customName": "الاسم المخصص", - "googlePlayGamesAccountSwitchText": "اذا اردت استخدام حساب غوغل بلاي اخر،\nقم بإستعمال تطبيق غوغل بلاي العاب لتحويله.", + "googlePlayGamesAccountSwitchText": "اذا اردت استخدام حساب غوغل بلاي اخر،\nقم بإستعمال تطبيق العاب غوغل بلاي لتحويله.", "linkAccountsEnterCodeText": "ادخل الرمز", "linkAccountsGenerateCodeText": "انشئ رمز", "linkAccountsInfoText": "(مشاركة تقدمك مع الاجهزة الاخرى)", "linkAccountsInstructionsNewText": "لربط حسابين،- انشئ رمز من الجهاز المراد انشاء الحساب فيه\n- وقم بإدخال الرمز في الجهاز الآخر\n\nالبيانات من الحساب الأول سوف يتم مشاركتها بين الجهازين\n\n من الحسابات كحد أقصى ${COUNT} يمكنك انشاء\n\n تنويه : فقط اربط الحسابات التي تملكها، إذا ربطت حسابك مع الأصدقاء،\n\n .لن يمكنكما اللعب معًا في نفس الوقت", "linkAccountsInstructionsText": "لربط حسابين, انتج كود على احد الحسابين \nو ادخل هذا الكود على الاخر.\nالتقدم و المخزون سيشتركا.\nيمكنك ربط حتى ${COUNT} حسابات.\n\nكن حذراً; هذا لا يمكن استرجاعه", - "linkAccountsText": "ربط حساب", + "linkAccountsText": "ربط الحسابات", "linkedAccountsText": ": حساباتي المرتبطة", "manageAccountText": "إدارة الحساب", "nameChangeConfirm": "هل تريد تغيير اسم حسابك إلى ${NAME}؟", @@ -33,7 +33,7 @@ "signInWithTestAccountText": "تسجيل الدخول بحساب تجريبي", "signInWithText": "تسجيل دخول مع ${SERVICE}", "signInWithV2InfoText": "حساب يعمل على جميع المنصات", - "signInWithV2Text": "قم بتسجيل الدخول باستخدام حساب BombSquad", + "signInWithV2Text": "قم بتسجيل الدخول باستخدام حساب فرقة القنبلة", "signOutText": "تسجيل الخروج", "signingInText": "...جارٍ تسجيل دخولك", "signingOutText": "...جارٍ تسجيل خروجك", @@ -79,7 +79,7 @@ "Free Loader": { "descriptionFull": "ابدأ بلعب الوضع الحر للجميع مع لاعبين أو أكثر", "descriptionFullComplete": "تم بدء لعبة بوضع الحرية للجميع مع لاعِبَيْنْ أو أكثر", - "name": "الفريق المجاني" + "name": "محمل مجاني" }, "Gold Miner": { "description": "اقتل 6 خصوم بأستخدام الألغام الأرضيَّة", @@ -196,7 +196,7 @@ "descriptionComplete": "لقد هزمْتَ كل الموجات", "descriptionFull": "${LEVEL} اهزم كل الموجات في", "descriptionFullComplete": "${LEVEL} لقد هزمتَ كل الموجات في", - "name": "${LEVEL} نصر" + "name": "${LEVEL} إنتصار" }, "Pro Runaround Victory": { "description": "اكمل كل الموجات", @@ -351,7 +351,7 @@ "soundtrackDescriptionText": "(اختر موسيقاك الخاصة لتعمل خلال اللعب)", "titleText": "الصوت" }, - "autoText": "ذاتي الاختيار", + "autoText": "تلقائيا", "backText": "للخلف", "banThisPlayerText": "حظر هاذا الاعب", "bestOfFinalText": "الافضل في ${COUNT}", @@ -601,6 +601,7 @@ "localProfileText": "(الملف الشخصي المحلي)", "nameDescriptionText": "اسم اللاعب", "nameText": "الاسم", + "profileAlreadyExistsText": ".يوجد ملف شخصي بهذا الإسم بالفِعل", "randomText": "عشوائي", "titleEditText": "تعديل الملف الشخصي", "titleNewText": "ملف شخصي جديد", @@ -612,7 +613,7 @@ "cantDeleteDefaultText": "لا يمكنك حذف الصوت الافتراضي.", "cantEditDefaultText": "لا يمكن التعديل على تسجيل الصوت الاساسي. قم بنسخه او أنشئ واحدا جديدا", "cantOverwriteDefaultText": "لا يمكن الكتابة فوق الصوت الافتراضي", - "cantSaveAlreadyExistsText": "يوجد مقطع صوتي بهذا الاسم من قبل.", + "cantSaveAlreadyExistsText": "يوجد مقطع صوتي بهذا الاسم.", "defaultGameMusicText": "<موسيقى اللعبة الافتراضية>", "defaultSoundtrackNameText": "الصوت الافتراضي", "deleteConfirmText": "حذف الموسيقى التصويرية:\n\n'${NAME}'؟", @@ -682,6 +683,8 @@ "duplicateText": "مكرر\nقائمة التشغيل", "editText": "تصحيح\nقائمة التشغيل", "newText": "الجديد\nقائمة التشغيل", + "pointsToWinText": "نقاط للفوز", + "seriesLengthText": "طول السلسلة", "showTutorialText": "عرض البرنامج التعليمي", "shuffleGameOrderText": "ترتيب لعبة المراوغة", "titleText": "تخصيص ${TYPE} قوائم تشغيل" @@ -789,7 +792,7 @@ "titleText": "متعدد الاعبين", "wifiDirectDescriptionBottomText": "إذا كانت جميع الأجهزة تحتوي على لوحة \"واي-في مباشر\"، فيجب أن تكون قادرة على استخدامها للعثور عليها\nوالتواصل مع بعضها البعض. مرة واحدة يتم توصيل جميع الأجهزة، يمكنك تشكيل الأطراف\nهنا باستخدام علامة التبويب \"الشبكة المحلية\"، تماما كما هو الحال مع شبكة واي فاي العادية.\n\nللحصول على أفضل النتائج، يجب أن يكون مضيف واي-في ديريكت أيضا مضيف الطرف ${APP_NAME}.", "wifiDirectDescriptionTopText": "واي فاي المباشر يمكن استخدامها لتوصيل أجهزة الروبوت مباشرة دون\nوالتي تحتاج إلى شبكة واي فاي. هذا يعمل بشكل أفضل على الروبوت 4.2 أو أحدث.\n\nلاستخدامه، افتح إعدادات واي-في وابحث عن \"واي-في ديريكت\" في القائمة.", - "wifiDirectOpenWiFiSettingsText": "افتح إعدادات واي-في", + "wifiDirectOpenWiFiSettingsText": "افتح إعدادات واي-فاي", "wifiDirectText": "واي فاي مباشر", "worksBetweenAllPlatformsText": "(يعمل بين جميع المنصات)", "worksWithGooglePlayDevicesText": "(يعمل مع الأجهزة التي تعمل على جوجل بلاي (أندرويد) نسخة من اللعبة)", @@ -1277,10 +1280,12 @@ "kidFriendlyModeText": "وضع الأطفال (يقلل العنف، إلخ)", "languageText": "لغة", "moddingGuideText": "دليل التعديلات البرمجية", + "moddingToolsText": "أدوات التعديلات البرمجية", "mustRestartText": ".يجب أن تقوم بإعادة تشغيل اللعبة لكي يعمل هذا", "netTestingText": "اختبار الشبكة", "resetText": "إعادة تعيين", "showBombTrajectoriesText": "عرض مسارات القنبلة", + "showDemosWhenIdleText": "عرض العروض التوضيحية عند الخمول", "showDevConsoleButtonText": "إظهار زر وحدة تحكم المطورين", "showInGamePingText": "عرض التأخير الداخلي للعبة", "showPlayerNamesText": "إظهار اسماء اللاعبين", @@ -1301,6 +1306,9 @@ "signInWithGameCenterText": "لاستخدام حساب مركز الألعاب،\nسجل الدخول باستخدام تطبيق مركز الألعاب.", "singleGamePlaylistNameText": "فقط ${GAME}", "singlePlayerCountText": "1 لاعب", + "sizeLargeText": "كبير", + "sizeMediumText": "وسط", + "sizeSmallText": "صغير", "soloNameFilterText": "منفردا ${NAME}", "soundtrackTypeNames": { "CharSelect": "اختر شخصية", @@ -1823,6 +1831,7 @@ "toSkipPressAnythingText": "(إلمس او إضغظ اي شئ لتخطي البرنامج التعليمي)" }, "twoKillText": "!قتل مزدوج", + "uiScaleText": "مقياس واجهة المستخدم", "unavailableText": "غير متوفر", "unconfiguredControllerDetectedText": ":تم الكشف على يد تحكم غير مهيئة", "unlockThisInTheStoreText": "هذا يجب ان يفتح في المتجر", @@ -1836,6 +1845,8 @@ "upgradeText": "احصل على ترقية", "upgradeToPlayText": "في متجر اللعبة لتلعب هذا \"${PRO}\" اشتري", "useDefaultText": "استخدام الإفتراضي", + "userSystemScriptsCreateText": "إنشاء البرامج النصية لنظام المستخدم", + "userSystemScriptsDeleteText": "حذف البرامج النصية لنظام المستخدم", "usesExternalControllerText": "هذه اللعبة تستخدم يد تحكم خارجية للإدخال", "usingItunesText": "...استخدام تطبيق الموسيقى للموسيقى التصويرية", "v2AccountLinkingInfoText": "اذا اردت ربط حسابات V2، قم بالتوجه الى 'ادارة الحساب'.", diff --git a/dist/ba_data/data/languages/belarussian.json b/dist/ba_data/data/languages/belarussian.json index 3fb9bf5..96c1de9 100644 --- a/dist/ba_data/data/languages/belarussian.json +++ b/dist/ba_data/data/languages/belarussian.json @@ -556,6 +556,7 @@ "demoText": "Дэманстрацыя", "denyText": "Адхіліць", "deprecatedText": "Састарэў", + "descriptionText": "Апісанне", "desktopResText": "Дазвол Экрана", "deviceAccountUpgradeText": "Увага:\nВы ўзайшлі ў акаўнт дэвайса\n(${NAME}).\nАкаўнты дэвайса будуць Выдалены ў будучай абнове.", "difficultyEasyText": "Лёгка", @@ -602,6 +603,7 @@ "localProfileText": "(лакальны профіль)", "nameDescriptionText": "Імя Гульца", "nameText": "Імя", + "profileAlreadyExistsText": "Профіль з такім імем ужо існуе.", "randomText": "выпадкова", "titleEditText": "Рэдагаваць Профіль", "titleNewText": "Новы Профіль", @@ -683,6 +685,8 @@ "duplicateText": "Прадубляваць\nПлэйліст", "editText": "Рэдагаваць\nПлэйліст", "newText": "Новы\nПлэйліст", + "pointsToWinText": "Ачкоў Для Перамогі", + "seriesLengthText": "Даўжыня Чарады", "showTutorialText": "Паказаць Туторыял", "shuffleGameOrderText": "Выпадковы Парадак Гульняў", "titleText": "Наладзіць ${TYPE} Плэйлісты" @@ -1287,10 +1291,14 @@ "kidFriendlyModeText": "Дзіцячы Рэжым (менш гвалту і г.д.)", "languageText": "Мова", "moddingGuideText": "Кіраўніцтва па Модынгу", + "moddingToolsText": "Прылады Для Модынгу", "mustRestartText": "Вы павінны перазагрузіць гульню, каб прымяніць новыя налады.", "netTestingText": "Тэсціраванне Сеткі", "resetText": "Скінуць", + "sendInfoText": "Даслаць інфармацыю", "showBombTrajectoriesText": "Паказваць Траекторыi Бомб", + "showDemosWhenIdleText": "Паказваць Дэма ў Рэжыме Чаканьня", + "showDeprecatedLoginTypesText": "Паказаць састарэлыя тыпы ўваходу", "showDevConsoleButtonText": "Паказаць Кнопку Кансолі Распрацоўніка", "showInGamePingText": "Паказываць Пынг Гульні", "showPlayerNamesText": "Паказваць Імёны Гульцоў", @@ -1300,7 +1308,7 @@ "translationFetchErrorText": "статус перакладу недаступны", "translationFetchingStatusText": "праверка статуса перакладу...", "translationInformMe": "Паведаміце мне, калі мая мова мае патрэбу ў абнаўленнях", - "translationNoUpdateNeededText": "гэтая мова абноўлена; ура!", + "translationNoUpdateNeededText": "Бягучая мова актуальная; Ура!", "translationUpdateNeededText": "** гэтая мова патрабуе абнаўлення!! **", "vrTestingText": "VR Тэстіраванне" }, @@ -1311,6 +1319,9 @@ "signInWithGameCenterText": "Каб карыстацца акаўнтам Game Centerб\nувайдзіце з дапамогаю прыкладання Game Center.", "singleGamePlaylistNameText": "Толькі ${GAME}", "singlePlayerCountText": "1 гулец", + "sizeLargeText": "Вялікі", + "sizeMediumText": "Сярэдні", + "sizeSmallText": "Маленькі", "soloNameFilterText": "Сола ${NAME}", "soundtrackTypeNames": { "CharSelect": "Выбар Героя", @@ -1839,6 +1850,7 @@ "toSkipPressAnythingText": "(націсніце, каб прапусціць туторыял)" }, "twoKillText": "ДВА ЗАБОЙСТВЫ!", + "uiScaleText": "Маштаб КІ", "unavailableText": "недаступна", "unconfiguredControllerDetectedText": "Невядомы кантролер знойдзены:", "unlockThisInTheStoreText": "Гэта павінна быць адкрыта ў магазіне.", @@ -1852,6 +1864,8 @@ "upgradeText": "Палепшыць", "upgradeToPlayText": "Адкрыйце \"${PRO}\" у магазіне, каб гуляць у гэта.", "useDefaultText": "Вярнуць Стандартныя", + "userSystemScriptsCreateText": "Стварыць Карыстацкі Сыстэмны Сцэнар", + "userSystemScriptsDeleteText": "Выдаліць Карыстацкі Сыстэмны Сцэнар", "usesExternalControllerText": "Гэта гульня можа выкарыстоўваць знешні кантролер для кіравання.", "usingItunesText": "Выкарыстанне музычнага прыкладання для саўндтрэка ...", "usingItunesTurnRepeatAndShuffleOnText": "Калі ласка, праверце, што ператасаванне і паўтор усяго ў iTunes ўключаны. ", diff --git a/dist/ba_data/data/languages/chinese.json b/dist/ba_data/data/languages/chinese.json index 1fa2c47..0090ecc 100644 --- a/dist/ba_data/data/languages/chinese.json +++ b/dist/ba_data/data/languages/chinese.json @@ -558,6 +558,7 @@ "demoText": "演示", "denyText": "拒绝", "deprecatedText": "已弃用", + "descriptionText": "描述", "desktopResText": "桌面分辨率", "deviceAccountUpgradeText": "紧急警告:\n你正在使用本地账户登录! (${NAME})\n此账户会在未来升级中被删除!!\n~如果想保持账号,请升级到v2账户~", "difficultyEasyText": "简单", @@ -605,6 +606,7 @@ "localProfileText": "(本地档案)", "nameDescriptionText": "角色名称", "nameText": "名称", + "profileAlreadyExistsText": "具有该名称的配置文件已存在", "randomText": "随机", "titleEditText": "编辑档案", "titleNewText": "新建档案", @@ -687,6 +689,8 @@ "editText": "编辑比\n赛列表", "gameListText": "比赛列表", "newText": "新建比\n赛列表", + "pointsToWinText": "获胜点数", + "seriesLengthText": "列表长度", "showTutorialText": "播放教程", "shuffleGameOrderText": "随机比赛模式", "titleText": "自定义${TYPE}列表" @@ -1262,6 +1266,7 @@ }, "scoreWasText": "(是${COUNT})", "selectText": "选择", + "sendInfoDescriptionText": "向开发者发送账户和应用状态信息\n清附上您的姓名和寄送原因", "seriesWinLine1PlayerText": "获得", "seriesWinLine1TeamText": "获得", "seriesWinLine1Text": "获得", @@ -1292,9 +1297,11 @@ "kidFriendlyModeText": "儿童友好模式(低暴力等)", "languageText": "语言", "moddingGuideText": "Mod文档", + "moddingToolsText": "Mod工具", "mustRestartText": "您必须重启游戏来使之生效", "netTestingText": "网络测试", "resetText": "恢复默认值", + "sendInfoText": "发送消息", "showBombTrajectoriesText": "显示炸弹轨迹", "showDemosWhenIdleText": "当游戏空闲时播放演示画面", "showDevConsoleButtonText": "显示开发者控制台按钮", @@ -1307,7 +1314,7 @@ "translationFetchingStatusText": "正在检查翻译进度喵…", "translationInformMe": "中文需要更新翻译时请通知我!", "translationNoUpdateNeededText": "当前语言是最新的;喵呜!", - "translationUpdateNeededText": "**当前语言需要更新!! **", + "translationUpdateNeededText": "**当前语言需要更新!!**", "vrTestingText": "VR测试" }, "shareText": "分享", @@ -1317,6 +1324,9 @@ "signInWithGameCenterText": "使用游戏中心\n应用程序登录。", "singleGamePlaylistNameText": "仅${GAME}", "singlePlayerCountText": "一个玩家", + "sizeLargeText": "大", + "sizeMediumText": "中", + "sizeSmallText": "小", "soloNameFilterText": "单挑模式 ${NAME}", "soundtrackTypeNames": { "CharSelect": "角色选择", @@ -1843,6 +1853,7 @@ "toSkipPressAnythingText": "(点击或按下任何按钮以跳过教程)" }, "twoKillText": "双杀!", + "uiScaleText": "UI缩放", "unavailableText": "不可用", "unconfiguredControllerDetectedText": "检测到未配置的手柄:", "unlockThisInTheStoreText": "这必须在商店中解锁。", @@ -1856,6 +1867,8 @@ "upgradeText": "升级", "upgradeToPlayText": "在游戏商店中解锁\"${PRO}\",以体验该游戏。", "useDefaultText": "使用默认值", + "userSystemScriptsCreateText": "创建用户系统脚本", + "userSystemScriptsDeleteText": "删除用户系统脚本", "usesExternalControllerText": "该游戏使用外部手柄进行输入。", "usingItunesText": "使用音乐应用设置背景音乐……", "usingItunesTurnRepeatAndShuffleOnText": "请确认iTunes中随机播放已开启且重复全部歌曲。", diff --git a/dist/ba_data/data/languages/croatian.json b/dist/ba_data/data/languages/croatian.json index fe4d1db..5599836 100644 --- a/dist/ba_data/data/languages/croatian.json +++ b/dist/ba_data/data/languages/croatian.json @@ -1895,7 +1895,7 @@ "xbox360ControllersWindow": { "getDriverText": "Nabavi driver", "macInstructions2Text": "Da možeš koristiti kontrolere bežično, trebat ćeš i reciever koji\ndolazi u 'Xbox 360 Wireless Controller for Windows' paketu.\nJedan reciever omogućuje ti da povežeš do 4 kontrolera.\n\nVažno: recieveri trećih strana neće raditi s ovim driverom; \nprovjeri da na tvom recieveru piše 'Microsoft', a ne 'XBOX 360'.\nMicrosoft ih više ne prodaje odvojeno, pa ćeš morati nabaviti\njednoga u paketu s kontrolerom ili potraži na ebayu.\n\nAko ti je ovo bilo korisno, molim te razmisli o donaciji\nprogrameru drivera na njegovoj stranici.", - "macInstructionsText": "Da možeš koristiti Xbox 360 kontrolere, morat ćeš instalirati\nMac driver dostupan na poveznici ispod. \nRadi i sa žičnim i s bežičnim kontrolerima.", + "macInstructionsText": "Da možeš koristiti Xbox 360 kontrolere, morat ćeš instalirati\nMac driver dostupan na poveznici ispod. \nRadi i sa žičnim i s bežičnim kontrolerima. ", "ouyaInstructionsText": "Za korištenje žičnih Xbox 360 kontrolera s BombSquadom, jednostavno\nih uključi u USB ulaz tvog uređaja. Možeš koristiti USB hub\nda priključiš više kontrolera.\n\nZa korištenje bežičnih kontrolera trebat ćeš bežični reciever, \nkoji je dostupan kao dio \"Xbox 360 wireless Controller for Windows\" \npaketa ili u slobodnoj prodaji. Svaki se reciever priključuje u USB ulaz i\nomogućuje ti da povežeš do 4 bežična kontrolera.", "titleText": "Korišćenje Xbox 360 kontrolera sa $(APP_NAME)" }, diff --git a/dist/ba_data/data/languages/czech.json b/dist/ba_data/data/languages/czech.json index 1cb0f87..d3a5cd7 100644 --- a/dist/ba_data/data/languages/czech.json +++ b/dist/ba_data/data/languages/czech.json @@ -26,6 +26,7 @@ "setAccountNameDesc": "Vyberte si jméno pro váš účet.\nMůžete použít jedno ze svých již\npoužitých nebo si vytvořit nové.", "signInInfoText": "Přihlašte se, abyste mohli sbírat kupóny, soupeřit online\na sdílet postup mezi zařízeními.", "signInText": "Přihlásit", + "signInWithAnEmailAddressText": "Přihlášení s emailem", "signInWithDeviceInfoText": "(automaticky vytvořený účet dostupný pouze na tomto zařízení)", "signInWithDeviceText": "Přihlásit se s účtem zařízení", "signInWithGameCircleText": "Přihlásit se s přes Game Circle", @@ -562,6 +563,7 @@ "demoText": "Demo", "denyText": "Zakázat", "deprecatedText": "Zastaralé", + "descriptionText": "Popisek", "desktopResText": "Rozlišení plochy", "deviceAccountUpgradeText": "Varování:\nJste přihlášeni na účet dostupný pouze na tomto zařízení (${NAME}).\nTyto účty budou v nastávající aktualizaci odstraněny.\nPřejděte na účet V2, chcete-li si ponechat Váš postup.", "difficultyEasyText": "Lehká", @@ -608,6 +610,7 @@ "localProfileText": "(lokální profil)", "nameDescriptionText": "Přezdívka", "nameText": "Jméno", + "profileAlreadyExistsText": "Profil s tímto jménem již existuje.", "randomText": "náhodně", "titleEditText": "Upravit profil", "titleNewText": "Nový profil", @@ -690,6 +693,8 @@ "duplicateText": "Duplikovat\nplaylist", "editText": "Upravit\nplaylist", "newText": "Nový\nplaylist", + "pointsToWinText": "Bodů Do Výhry", + "seriesLengthText": "Délka Série", "showTutorialText": "Zobrazit návod", "shuffleGameOrderText": "Náhodné seřazení her", "titleText": "Přizpůsobit ${TYPE} playlisty" @@ -728,10 +733,10 @@ "friendHasSentPromoCodeText": "${COUNT} ${APP_NAME} kupónů od ${NAME}", "friendPromoCodeAwardText": "Získáte ${COUNT} kupónů pokaždé, jakmile je použit.", "friendPromoCodeExpireText": "Kód vyprší za ${EXPIRE_HOURS} hodin a je funkční pouze pro nové hráče.", - "friendPromoCodeInstructionsText": "Pro použití otevřete ${APP_NAME} a jděte do „Nastavení->Pokročilé->Vložit kód“\nNa bombsquadgame.com na odkazy k stažení na všechny podporované platformy.", + "friendPromoCodeInstructionsText": "Pro použití otevřete ${APP_NAME} a jděte do „Nastavení->Pokročilé->Poslat info“\nNa bombsquadgame.com na odkazy k stažení na všechny podporované platformy.", "friendPromoCodeRedeemLongText": "Může z něj být získáno ${COUNT} kupónů zdarma až ${MAX_USES} lidmi.", "friendPromoCodeRedeemShortText": "Může být použit pro získání ${COUNT} kupónů do hry.", - "friendPromoCodeWhereToEnterText": "(v „Nastavení->Pokročilé->Vložit Kód“)", + "friendPromoCodeWhereToEnterText": "(v „Nastavení->Pokročilé->Poslat info“)", "getFriendInviteCodeText": "Získat kód pro pozvání přátel", "googlePlayDescriptionText": "Pozvěte Google Play hráče do vaší Party:", "googlePlayInviteText": "Pozvat", @@ -1263,6 +1268,7 @@ }, "scoreWasText": "(předchozí ${COUNT})", "selectText": "Zvolit", + "sendInfoDescriptionText": "Pošle informace stavu aplikace a účtu vývojářovi.\nProsím zahrňte své jméno nebo důvod k poslání.", "seriesWinLine1PlayerText": "VYHRÁVÁ", "seriesWinLine1TeamText": "VYHRÁVÁ", "seriesWinLine1Text": "VYHRÁVÁ", @@ -1281,6 +1287,7 @@ "alwaysUseInternalKeyboardDescriptionText": "(jednoduchá, ovládáním přátelská klávesnice na obrazovce pro úpravu textu)", "alwaysUseInternalKeyboardText": "Vždy použít interní klávesnici", "benchmarksText": "Benchmarky a testy výdrže", + "devToolsText": "Nástroje pro Vývojáře", "disableCameraGyroscopeMotionText": "Vypnout gyroskopický pohyb kamery", "disableCameraShakeText": "Vypnout otřes kamery", "disableThisNotice": "(můžete si toto oznámení vypnout v pokročilých nastaveních)", @@ -1289,17 +1296,20 @@ "enterPromoCodeText": "Zadat kód", "forTestingText": "Poznámka: Tyto hodnoty sou pouze pro test. Obnoví se po restartu.", "helpTranslateText": "Jiné než anglické verze ${APP_NAME} jsou komunitně\npodporovanou záležitostí. Pokud byste chtěli přidat\nnebo opravit překlad, následujte odkaz níže. Předem děkujeme!", - "kickIdlePlayersText": "Vyhazovat neaktivní hráče", + "kickIdlePlayersText": "Vykopnout neaktivní hráče", "kidFriendlyModeText": "Dětský mód (snížené násilí, atd.)", "languageText": "Jazyk", "moddingGuideText": "Příručka módů", + "moddingToolsText": "Modovací možnosti", "mustRestartText": "Musíte restartovat hru, aby se změny projevily.", "netTestingText": "Testování sítě", "resetText": "Obnovit", + "sendInfoText": "Poslat info", "showBombTrajectoriesText": "Ukazovat trajektorii bomb", - "showDemosWhenIdleText": "Zobrazit ukázky při nečinnosti", - "showDevConsoleButtonText": "Ukaž tlačítko vývojářské konzoly", - "showInGamePingText": "Ukazovat ping při hře", + "showDemosWhenIdleText": "Zobrazit demo při nečinnosti", + "showDeprecatedLoginTypesText": "Zobrazit zastaralé způsoby přihlášení", + "showDevConsoleButtonText": "Zobrazit tlačítko vývojářské konzole", + "showInGamePingText": "Zobrazit herní ping", "showPlayerNamesText": "Ukazovat jména hráčů", "showUserModsText": "Zobrazit složku s módy", "titleText": "Pokročilé", @@ -1307,8 +1317,8 @@ "translationFetchErrorText": "stav překladu nedostupný", "translationFetchingStatusText": "zjišťuji stav překladu...", "translationInformMe": "Oznamte mi když bude můj jazyk potřebovat aktualizaci", - "translationNoUpdateNeededText": "tento jazyk je aktuální; hurá!", - "translationUpdateNeededText": "** jazyk potřebuje aktualizovat!! **", + "translationNoUpdateNeededText": "Tento jazyk je aktuální; hurá! :)", + "translationUpdateNeededText": "** Jazyk potřebuje aktualizovat!! **", "vrTestingText": "VR Test" }, "shareText": "Sdílet", @@ -1318,6 +1328,9 @@ "signInWithGameCenterText": "Pro použití účtu Game Center se přihlaste\ns pomocí aplikace Game Center.", "singleGamePlaylistNameText": "Jen ${GAME}", "singlePlayerCountText": "1 hráč", + "sizeLargeText": "Velký", + "sizeMediumText": "Středně velký", + "sizeSmallText": "Malý", "soloNameFilterText": "Sólo ${NAME}", "soundtrackTypeNames": { "CharSelect": "Vybírání postavy", @@ -1844,6 +1857,7 @@ "toSkipPressAnythingText": "(stiskněte cokoliv pro přeskočení návodu)" }, "twoKillText": "DVĚ ZABITÍ!!!", + "uiScaleText": "Velikost UI", "unavailableText": "Nedostupné", "unconfiguredControllerDetectedText": "Zaznamenán nenakonfigurovaný ovladač:", "unlockThisInTheStoreText": "Toto musí být nejdříve odemknuto v obchodě.", @@ -1857,6 +1871,8 @@ "upgradeText": "Přeměnit", "upgradeToPlayText": "Upgradujte na „${PRO}“ v herním obchodě, abyste toto mohli hrát.", "useDefaultText": "Použít výchozí", + "userSystemScriptsCreateText": "Vytvořit Uživatelská Systémová Skripta", + "userSystemScriptsDeleteText": "Smazat Uživatelská Systémová Skripta", "usesExternalControllerText": "Tato hra používá jako vstup externí ovladač.", "usingItunesText": "Používám aplikaci hudby pro soundtrack...", "usingItunesTurnRepeatAndShuffleOnText": "Ujistěte se prosím, že je zaplý shuffle, a opakovat VŠE v iTunes,", diff --git a/dist/ba_data/data/languages/danish.json b/dist/ba_data/data/languages/danish.json index 0a31603..6954230 100644 --- a/dist/ba_data/data/languages/danish.json +++ b/dist/ba_data/data/languages/danish.json @@ -1616,7 +1616,7 @@ "macInstructions2TextScale": 0.76, "macInstructionsText": "For at bruge Xbox 360-controllere skal du installere en\nMacdriver, der er tilgængelig i linket herunder.\nDet virker både med controllere med og uden ledninger.", "macInstructionsTextScale": 0.8, - "ouyaInstructionsText": "For at bruge Xbox 360-controllere til BombSquad, tilslut dem din enheds USB-port.\nDu kan bruge en USB-hub\ntil at forbinde flere controllere. \n\nFor at bruge trådløse controllere skal du bruge en trådløs modtager,\ntilgængelig som en del af \"Xbox 360 wireless Controller for Windows\"-\npakken eller solgt seperat. Hver modtager puttes ind i en USB port\nog tillader dig at forbinde op til 4 trådløse controllers.", + "ouyaInstructionsText": "For at bruge Xbox 360-controllere til BombSquad, tilslut dem din enheds USB-port.\nDu kan bruge en USB-hub\ntil at forbinde flere controllere. \n\nFor at bruge trådløse controllere skal du bruge en trådløs modtager,\ntilgængelig som en del af \"Xbox 360 wireless Controller for Windows\"-\npakken eller solgt seperat. Hver modtager puttes ind i en USB port\nog tillader dig at forbinde op til 4 trådløse controllers. ", "ouyaInstructionsTextScale": 0.8, "titleText": "Hvordan man bruger Xbox 360-controllere med BombSquad:" }, diff --git a/dist/ba_data/data/languages/english.json b/dist/ba_data/data/languages/english.json index 70cec44..a387548 100644 --- a/dist/ba_data/data/languages/english.json +++ b/dist/ba_data/data/languages/english.json @@ -23,6 +23,7 @@ "setAccountNameDesc": "Select the name to display for your account.\nYou can use the name from one of your linked\naccounts or create a unique custom name.", "signInInfoText": "Sign in to collect tickets, compete online,\nand share progress across devices.", "signInText": "Sign In", + "signInWithAnEmailAddressText": "Sign in with an email address", "signInWithDeviceInfoText": "(an automatic account only available from this device)", "signInWithDeviceText": "Sign in with device account", "signInWithText": "Sign in with ${SERVICE}", @@ -550,6 +551,7 @@ "demoText": "Demo", "denyText": "Deny", "deprecatedText": "Deprecated", + "descriptionText": "Description", "desktopResText": "Desktop Res", "deviceAccountUpgradeText": "Warning:\nYou are signed in with a device account (${NAME}).\nDevice accounts will be removed in a future update.\nUpgrade to a V2 Account if you want to keep your progress.", "difficultyEasyText": "Easy", @@ -595,6 +597,7 @@ "localProfileText": "(local profile)", "nameDescriptionText": "Player Name", "nameText": "Name", + "profileAlreadyExistsText": "A profile with that name already exists.", "randomText": "random", "titleEditText": "Edit Profile", "titleNewText": "New Profile", @@ -674,6 +677,8 @@ "duplicateText": "Duplicate\nPlaylist", "editText": "Edit\nPlaylist", "newText": "New\nPlaylist", + "pointsToWinText": "Points To Win", + "seriesLengthText": "Series Length", "showTutorialText": "Show Tutorial", "shuffleGameOrderText": "Shuffle Game Order", "titleText": "Customize ${TYPE} Playlists" @@ -711,10 +716,10 @@ "friendHasSentPromoCodeText": "${COUNT} ${APP_NAME} tickets from ${NAME}", "friendPromoCodeAwardText": "You will receive ${COUNT} tickets each time it is used.", "friendPromoCodeExpireText": "The code will expire in ${EXPIRE_HOURS} hours and only works for new players.", - "friendPromoCodeInstructionsText": "To use it, open ${APP_NAME} and go to \"Settings->Advanced->Enter Code\".\nSee bombsquadgame.com for download links for all supported platforms.", + "friendPromoCodeInstructionsText": "To use it, open ${APP_NAME} and go to \"Settings->Advanced->Send Info\".\nSee bombsquadgame.com for download links for all supported platforms.", "friendPromoCodeRedeemLongText": "It can be redeemed for ${COUNT} free tickets by up to ${MAX_USES} people.", "friendPromoCodeRedeemShortText": "It can be redeemed for ${COUNT} tickets in the game.", - "friendPromoCodeWhereToEnterText": "(in \"Settings->Advanced->Enter Code\")", + "friendPromoCodeWhereToEnterText": "(in \"Settings->Advanced->Send Info\")", "getFriendInviteCodeText": "Get Friend Invite Code", "googlePlayDescriptionText": "Invite Google Play players to your party:", "googlePlayInviteText": "Invite", @@ -1246,6 +1251,7 @@ }, "scoreWasText": "(was ${COUNT})", "selectText": "Select", + "sendInfoDescriptionText": "Sends account and app state info to the developer.\nPlease include your name or reason for sending.", "seriesWinLine1PlayerText": "WINS THE", "seriesWinLine1TeamText": "WINS THE", "seriesWinLine1Text": "WINS THE", @@ -1261,25 +1267,29 @@ }, "settingsWindowAdvanced": { "alwaysUseInternalKeyboardDescriptionText": "(a simple, controller-friendly on-screen keyboard for text editing)", - "alwaysUseInternalKeyboardText": "Always Use Internal Keyboard", + "alwaysUseInternalKeyboardText": "Always use internal keyboard", "benchmarksText": "Benchmarks & Stress-Tests", - "disableCameraGyroscopeMotionText": "Disable Camera Gyroscope Motion", - "disableCameraShakeText": "Disable Camera Shake", + "devToolsText": "Dev Tools", + "disableCameraGyroscopeMotionText": "Disable camera gyroscope motion", + "disableCameraShakeText": "Disable camera shake", "disableThisNotice": "(you can disable this notice in advanced settings)", "enterPromoCodeText": "Enter Code", "forTestingText": "Note: these values are only for testing and will be lost when the app exits.", "helpTranslateText": "${APP_NAME}'s non-English translations are a community\nsupported effort. If you'd like to contribute or correct\na translation, follow the link below. Thanks in advance!", - "kickIdlePlayersText": "Kick Idle Players", + "kickIdlePlayersText": "Kick idle players", "kidFriendlyModeText": "Kid-Friendly Mode (reduced violence, etc)", "languageText": "Language", "moddingGuideText": "Modding Guide", + "moddingToolsText": "Modding Tools", "mustRestartText": "You must restart the game for this to take effect.", "netTestingText": "Network Testing", "resetText": "Reset", + "sendInfoText": "Send Info", "showBombTrajectoriesText": "Show Bomb Trajectories", - "showDemosWhenIdleText": "Show Demos When Idle", - "showDevConsoleButtonText": "Show Dev Console Button", - "showInGamePingText": "Show In-Game Ping", + "showDemosWhenIdleText": "Show demos when idle", + "showDeprecatedLoginTypesText": "Show deprecated login types", + "showDevConsoleButtonText": "Show dev console button", + "showInGamePingText": "Show in-game ping", "showPlayerNamesText": "Show Player Names", "showUserModsText": "Show Mods Folder", "titleText": "Advanced", @@ -1287,8 +1297,8 @@ "translationFetchErrorText": "translation status unavailable", "translationFetchingStatusText": "checking translation status...", "translationInformMe": "Inform me when my language needs updates", - "translationNoUpdateNeededText": "the current language is up to date; woohoo!", - "translationUpdateNeededText": "** the current language needs updates!! **", + "translationNoUpdateNeededText": "The current language is up to date; woohoo!", + "translationUpdateNeededText": "** The current language needs updates!! **", "vrTestingText": "VR Testing" }, "shareText": "Share", @@ -1297,6 +1307,9 @@ "signInForPromoCodeText": "You must sign in to an account for codes to take effect.", "singleGamePlaylistNameText": "Just ${GAME}", "singlePlayerCountText": "1 player", + "sizeLargeText": "Large", + "sizeMediumText": "Medium", + "sizeSmallText": "Small", "soloNameFilterText": "Solo ${NAME}", "soundtrackTypeNames": { "CharSelect": "Character Selection", @@ -1819,6 +1832,7 @@ "toSkipPressAnythingText": "(tap or press anything to skip tutorial)" }, "twoKillText": "DOUBLE KILL!", + "uiScaleText": "UI Scale", "unavailableText": "unavailable", "unconfiguredControllerDetectedText": "Unconfigured controller detected:", "unlockThisInTheStoreText": "This must be unlocked in the store.", @@ -1832,6 +1846,8 @@ "upgradeText": "Upgrade", "upgradeToPlayText": "Unlock \"${PRO}\" in the in-game store to play this.", "useDefaultText": "Use Default", + "userSystemScriptsCreateText": "Create User System Scripts", + "userSystemScriptsDeleteText": "Delete User System Scripts", "usesExternalControllerText": "This game uses an external controller for input.", "usingItunesText": "Using Music App for soundtrack...", "v2AccountLinkingInfoText": "To link V2 accounts, use the 'Manage Account' button.", @@ -1892,14 +1908,6 @@ "worldScoresUnavailableText": "World scores unavailable.", "worldsBestScoresText": "World's Best Scores", "worldsBestTimesText": "World's Best Times", - "xbox360ControllersWindow": { - "getDriverText": "Get Driver", - "macInstructions2Text": "To use controllers wirelessly, you'll also need the receiver that\ncomes with the 'Xbox 360 Wireless Controller for Windows'.\nOne receiver allows you to connect up to 4 controllers.\n\nImportant: 3rd-party receivers will not work with this driver;\nmake sure your receiver says 'Microsoft' on it, not 'XBOX 360'.\nMicrosoft no longer sells these separately, so you'll need to get\nthe one bundled with the controller or else search ebay.\n\nIf you find this useful, please consider a donation to the\ndriver developer at his site.", - "macInstructionsText": "To use Xbox 360 controllers, you'll need to install\nthe Mac driver available at the link below.\nIt works with both wired and wireless controllers.", - "macInstructionsTextScale": 0.8, - "ouyaInstructionsText": "To use wired Xbox 360 controllers with BombSquad, simply\nplug them into your device's USB port. You can use a USB hub\nto connect multiple controllers.\n\nTo use wireless controllers you'll need a wireless receiver,\navailable as part of the \"Xbox 360 wireless Controller for Windows\"\npackage or sold separately. Each receiver plugs into a USB port and\nallows you to connect up to 4 wireless controllers.", - "titleText": "Using Xbox 360 Controllers with ${APP_NAME}:" - }, "yesAllowText": "Yes, Allow!", "yourBestScoresText": "Your Best Scores", "yourBestTimesText": "Your Best Times" diff --git a/dist/ba_data/data/languages/filipino.json b/dist/ba_data/data/languages/filipino.json index 481eac6..1e4e724 100644 --- a/dist/ba_data/data/languages/filipino.json +++ b/dist/ba_data/data/languages/filipino.json @@ -1,29 +1,30 @@ { "accountSettingsWindow": { - "accountNameRules": "Hindi pwede ang pangalan na may emoji o mga ibang special characters", - "accountsText": "Mga Manlalaro", + "accountNameRules": "Bawal ang pangalan na may emoji o mga ibang nakakaibang titik.", + "accountsText": "Mga Account", "achievementProgressText": "Mga Nakamtan: ${COUNT} sa ${TOTAL}", - "campaignProgressText": "Ang Progreso sa Campgain [Mahirap]: ${PROGRESS}", + "campaignProgressText": "Ang Progreso sa Kampanya [Mahirap]: ${PROGRESS}", "changeOncePerSeason": "Mapapalit mo lang ito isang beses kada season", - "changeOncePerSeasonError": "Kailangan mo muna maghintay ng susunod na panahon para mapalitan ito. (${NUM} araw)", - "customName": "Kahit-anong Pangalan", + "changeOncePerSeasonError": "Kailangan mo muna maghintay ng susunod na panahon para mapalitan ito. (${NUM} na araw)", + "customName": "Sariling Pangalan", "googlePlayGamesAccountSwitchText": "Kung gusto mong gamitin ang iba ninyong Google Account, \nkailangan mo gamitin ang Google Play Games app upang maipalit ito.", "linkAccountsEnterCodeText": "Ilagay ang Kowd", "linkAccountsGenerateCodeText": "Gumawa ng Kowd", "linkAccountsInfoText": "(ibahagi ang pag-usad sa ibang mga platform)", - "linkAccountsInstructionsNewText": "Para maiugnay ang dalawang accounts, gumawa ka ng kowd sa una at ilagay \nyung kowd sa pangalawa. Data na galing sa pangalawang account ay \nmaibabahagi sa dalawa.\n(Data na nasa unang account ay mawawala)\n\nMaari kang mag link ng hangang ${COUNT} accounts.\n\nIMPORTANTE: Iugnay lamang ang iyong mga accounts, \ndahil kapag iuugnay mo sa kaibigan mo ang iyong \naccount,hindi kayo makakapaglaro ng sabay.", + "linkAccountsInstructionsNewText": "Para maiugnay ang dalawang account, gumawa ng kowd sa una at ilagay \nyung kowd sa pangalawa. Ang mga datos na galing sa pangalawang account ay \nmaibabahagi sa dalawa.\n(Ang datos na nasa unang account ay mawawala)\n\nMaari kang mag-link ng hanggang ${COUNT} na account.\n\nIMPORTANTE: Iugnay lamang ang iyong mga account na may-ari mo, \ndahil kapag iuugnay mo sa kaibigan mo ang iyong \naccount,hindi kayo makakapaglaro ng sabay.", "linkAccountsInstructionsText": "Para mag-ugnay ng dalawang account, gumawa ng code\nsa isa at ilagay ang code na iyon sa kabila.\nAng iyong pag-usad at imbentaryo ay pagsasamahin.\nMaaari mong i-ugnay hanggang sa ${COUNT} accounts.\n\nBabala lang; hindi na ito maibabalik!", "linkAccountsText": "Ilink ang mga Account", "linkedAccountsText": "Naka-link na mga Account", - "manageAccountText": "I-Pamahalaan ang Account", + "manageAccountText": "Mamahalahin ang Account", "nameChangeConfirm": "Ipalitan ang pangalan ng iyong account sa ${NAME}?", - "resetProgressConfirmNoAchievementsText": "Ibabalik nito sa dati ang iyong pag-usad,\nat lokal na mga high-score (pwera sa mga ticket).\nHindi na ito maibabalik. Ipagpatuloy pa rin?", - "resetProgressConfirmText": "Ibabalik nito sa dati ang iyong pag-usad,\nmga nakamtan, at lokal na mga high-score\n(pwera sa mga ticket). Hindi na ito maibabalik\nulit. Ipagpatuloy pa rin?", - "resetProgressText": "I-reset ang Progreso", - "setAccountName": "I-set ang pangngalan ng Account", + "resetProgressConfirmNoAchievementsText": "Ibabalik nito sa dati ang iyong pag-usad,\nat lokal na mga iskor (parehas lamang ang bilang ng iyong tiket).\nHindi ito maibabalik. Ipagpatuloy pa rin?", + "resetProgressConfirmText": "Maaaring maulit ang iyong pag-usad,\nmga nakamtan, at lokal na mga matataas na iskor\n(ngunit Hindi ang mga tiket). Hindi na ito maibabalik sa dati\nulit. Ipagpatuloy pa rin?", + "resetProgressText": "Magpanibago ng Progreso", + "setAccountName": "Itakda ang Pangalan ng Account", "setAccountNameDesc": "Piliin ang pangalan na ipapakita para sa iyong account.\nMaaari mong gamitin ang pangalan mula sa isa sa iyong mga naka-link \nna account o lumikha ng isang natatanging pasadyang pangalan.", "signInInfoText": "Pumasok o gumawa nang account para kumolekta ng mga ticket, makipagkompetensya online,\nat makabahagi ng pag-usad sa iba't ibang mga device.", "signInText": "Pumasok sa account", + "signInWithAnEmailAddressText": "Pumasok gamit ang email address", "signInWithDeviceInfoText": "(automatic account na magagamit lamang sa device na ito)", "signInWithDeviceText": "Pumasok gamit ang device account", "signInWithGameCircleText": "Pumasok sa account gamit ang Game Circle", @@ -36,27 +37,27 @@ "signOutText": "Umalis", "signingInText": "Pumapasok sa account...", "signingOutText": "Umaalis sa account...", - "ticketsText": "Mga ticket: ${COUNT}", + "ticketsText": "Mga tiket: ${COUNT}", "titleText": "Manlalaro", - "unlinkAccountsInstructionsText": "Pumili ng manlalaro na i-uunlink", - "unlinkAccountsText": "I-unlink ang mga manlalaro", - "unlinkLegacyV1AccountsText": "I-unlink ang mga Legacy (V1) na mga Account", + "unlinkAccountsInstructionsText": "Pumili ng account na tatanggalin dito.", + "unlinkAccountsText": "Tanggalin ang mga account", + "unlinkLegacyV1AccountsText": "Tanggalin ang mga Legacy (V1) na mga Account", "v2LinkInstructionsText": "Gamitin ang link na ito para gumawa ng account o pumasok sa account.", "viaAccount": "(sa pamamagitan ng manlalaro ${NAME})", - "youAreSignedInAsText": "Nakasign-in ka bilang si:" + "youAreSignedInAsText": "Nakapasok bilang si:" }, - "achievementChallengesText": "Mga nakamit na", - "achievementText": "Mga Nakamtan", + "achievementChallengesText": "Mga Hamon sa Pagkamit", + "achievementText": "Nakamtan", "achievements": { "Boom Goes the Dynamite": { - "description": "Magpasabog nang TNT para mamatay ang 3 na salbahe", - "descriptionComplete": "Napasabog ang 3 salbahe gamit ang TNT", - "descriptionFull": "Ipasabog and 3 salbahe gamit ang TNT ${LEVEL}", + "description": "Pasabugin ang mga 3 kalaban gamit ang TNT", + "descriptionComplete": "Pinasabog ang mga 3 kalaban gamit ang TNT", + "descriptionFull": "Sabugin ang mga 3 kalaban gamit ang TNT ${LEVEL}", "descriptionFullComplete": "Napasabog ang 3 kalaban gamit ang TNT sa ${LEVEL}", - "name": "Sabog ang Sabi ng Dinamita" + "name": "Sabog Ang Pinagsasabog" }, "Boxer": { - "description": "Manalo sa laro ng hindi gumagamit ng mga bomba", + "description": "Manalo sa laro nang hindi gumagamit ng mga bomba", "descriptionComplete": "Manalo sa laro ng hindi gumagamit ng bomba", "descriptionFull": "Tapusin ang ${LEVEL} habang hindi gumagamit nang bomba", "descriptionFullComplete": "Natapos ang ${LEVEL} habang hindi gumagamit nang bomba", @@ -65,7 +66,7 @@ "Dual Wielding": { "descriptionFull": "Ikonekta ang dalawang mga controllers (hardware o app)", "descriptionFullComplete": "Nakonektado na ang 2 controllers (hardware o app)", - "name": "Dalawang Manlalaro" + "name": "Dalawang Hawak" }, "Flawless Victory": { "description": "Manalo nang hindi natatamaan ng kalaban", @@ -80,11 +81,11 @@ "name": "Manggagamit" }, "Gold Miner": { - "description": "Pumatay Ng 6 na kalaban gamit ang mga land-mine", + "description": "Magpamatay ka ng 6 na kalaban gamit ang mga land-mine", "descriptionComplete": "Pumatay ng 6 na kalaban gamit ang mga land-mine", "descriptionFull": "Pumatay ng 6 na kalaban gamit ang mga land -mine sa ${LEVEL}", "descriptionFullComplete": "Nakapatay ng 6 na kalaban gamit ang mga land-mine sa ${LEVEL}", - "name": "Minahang Lagayan" + "name": "Minahang Ginto" }, "Got the Moves": { "description": "Manalo ng ‘di sumusuntok o gumagamit ng mga bomba", @@ -96,7 +97,7 @@ "In Control": { "descriptionFull": "Magconnect ng controller (hardware o app)", "descriptionFullComplete": "Nagconnect ng controller (hardware o app)", - "name": "Kumokontrol" + "name": "Nasa-kontrol" }, "Last Stand God": { "description": "Maka-iskor ng 1000 na puntos", @@ -190,17 +191,17 @@ "name": "Kampeon ng ${LEVEL}" }, "Pro Onslaught Victory": { - "description": "Matalo lahat ng mga kaway", - "descriptionComplete": "Natalo lahat ng mga kaway", - "descriptionFull": "Matalo lahat ng mga kaway sa ${LEVEL}", - "descriptionFullComplete": "Natalo lahat ng mga kaway sa ${LEVEL}", + "description": "Matalo lahat ng mga yugto", + "descriptionComplete": "Natalo mo ang lahat ng mga lebel", + "descriptionFull": "Matalo lahat ng mga yugto sa ${LEVEL}", + "descriptionFullComplete": "Natalo mo ang lahat ng mga yugto sa ${LEVEL}", "name": "Kampeon ng ${LEVEL}" }, "Pro Runaround Victory": { - "description": "Tapusin lahat ng mga kaway", - "descriptionComplete": "Natapos lahat ng mga kaway", - "descriptionFull": "Tapusin ang lahat ng mg kaway sa ${LEVEL}", - "descriptionFullComplete": "Tinapos lahat ng mga kaway sa ${LEVEL}", + "description": "Tapusin lahat ng mga yugto", + "descriptionComplete": "Natapos lahat ng mga yugto", + "descriptionFull": "Tapusin ang lahat ng mg yugto sa ${LEVEL}", + "descriptionFullComplete": "Tinapos lahat ng mga yugto sa ${LEVEL}", "name": "Kampeon ng ${LEVEL}" }, "Rookie Football Shutout": { @@ -218,10 +219,10 @@ "name": "Nanalo sa ${LEVEL}" }, "Rookie Onslaught Victory": { - "description": "Italo lahat ang mga kaway", - "descriptionComplete": "Natalo lahat ang mga kaway", - "descriptionFull": "Italo lahat ang mga kaway sa ${LEVEL}", - "descriptionFullComplete": "Natalo lahat ng mga kaway sa ${LEVEL}", + "description": "Italo lahat ang mga yugto", + "descriptionComplete": "Natalo mo lahat ang mga yugto", + "descriptionFull": "Italo lahat ang mga yugto sa ${LEVEL}", + "descriptionFullComplete": "Natalo mo lahat ng mga yugto sa ${LEVEL}", "name": "Nanalo sa ${LEVEL}" }, "Runaround God": { @@ -233,7 +234,7 @@ }, "Runaround Master": { "description": "Pumuntos ng 500", - "descriptionComplete": "Nakapuntos ng 500", + "descriptionComplete": "Naka 500 na iskor", "descriptionFull": "Pumuntos ng 500 sa ${LEVEL}", "descriptionFullComplete": "Nakakuha ng 500 puntos sa ${LEVEL}", "name": "Pinuno ng ${LEVEL}" @@ -255,28 +256,28 @@ "descriptionComplete": "Nanalo nang hindi namatay", "descriptionFull": "Nanalo ${LEVEL} nang hindi namatay", "descriptionFullComplete": "Nanalo sa ${LEVEL} nang hindi namatay", - "name": "Manatiling Buhay" + "name": "Buhay Pa!" }, "Super Mega Punch": { "description": "Magdulot ng 100% damage sa isang suntok", "descriptionComplete": "Nagdulot ng 100% damage sa isang suntok", "descriptionFull": "Magdulot ng 100% damage ng isang suntok sa ${LEVEL}", "descriptionFullComplete": "Nagdulot ng 100% damage sa isang suntok sa ${LEVEL}", - "name": "Sobrang Mega Na Suntok" + "name": "Pinakakirot Na Suntok" }, "Super Punch": { "description": "Magdulot ng 50% damage sa isang suntok", "descriptionComplete": "Nagdulot ng 50% damage sa isang suntok", "descriptionFull": "Magdulot ng 50% damage sa isang suntok sa ${LEVEL}", "descriptionFullComplete": "Nagdulot ng 50% damage sa isang suntok sa ${LEVEL}", - "name": "Sobrang Suntok" + "name": "Masakit Na Suntok" }, "TNT Terror": { "description": "Pumatay ng 6 na kalaban gamit ang TNT", "descriptionComplete": "Pinatay ng 6 na kalaban gamit ang TNT", "descriptionFull": "Pumatay ng 6 na kalaban gamit ang TNT sa ${LEVEL}", "descriptionFullComplete": "Pinatay ng 6 na kalaban gamit ang TNT sa ${LEVEL}", - "name": "Takutan ng TNT" + "name": "Kaguluhan ng TNT" }, "Team Player": { "descriptionFull": "Magsimula ng laro na mayroong 4+ na manlalaro", @@ -284,7 +285,7 @@ "name": "Manlalaro Ng Koponan" }, "The Great Wall": { - "description": "Patayin ang lahat ng salbahe", + "description": "Patayin ang lahat ng kalaban", "descriptionComplete": "Pinigilan ang bawat kalaban", "descriptionFull": "Itigil ang bawat kalaban sa ${LEVEL}", "descriptionFullComplete": "Pinigilan ang bawat kalaban sa ${LEVEL}", @@ -307,28 +308,28 @@ "Uber Football Victory": { "description": "Ipanalo ang laro", "descriptionComplete": "Nanalo ang laro", - "descriptionFull": "I-panalo ang laro sa ${LEVEL}", + "descriptionFull": "Ipanalo ang laro sa ${LEVEL}", "descriptionFullComplete": "Manalo ang laro sa ${LEVEL}", "name": "${LEVEL} Natagumpay" }, "Uber Onslaught Victory": { - "description": "Talunin ang lahat na mga kaway", - "descriptionComplete": "Natalo ang lahat na mga kaway", - "descriptionFull": "Talunin ang lahat na mga kaway sa ${LEVEL}", - "descriptionFullComplete": "Natalo ang lahat na mga kaway sa ${LEVEL}", + "description": "Talunin ang lahat na mga yugto", + "descriptionComplete": "Natalo ang lahat na mga yugto", + "descriptionFull": "Talunin ang lahat na mga yugto sa ${LEVEL}", + "descriptionFullComplete": "Natalo ang lahat na mga yugto sa ${LEVEL}", "name": "Nanalo sa ${LEVEL}" }, "Uber Runaround Victory": { - "description": "Tapusin ang lahat na kaway", - "descriptionComplete": "Natapos ang lahat na mga kaway", - "descriptionFull": "Tapusin ang lahat na mga kaway sa ${LEVEL}", - "descriptionFullComplete": "Natapos ang lahat na mga kaway sa ${LEVEL}", + "description": "Tapusin ang lahat na yugto", + "descriptionComplete": "Natapos ang lahat na mga yugto", + "descriptionFull": "Tapusin ang lahat na mga yugto sa ${LEVEL}", + "descriptionFullComplete": "Natapos ang lahat na mga yugto sa ${LEVEL}", "name": "${LEVEL} Natagumpay" } }, "achievementsRemainingText": "Ang Mga Natitirang Nakamtan:", - "achievementsText": "Achievements", - "achievementsUnavailableForOldSeasonsText": "Pasensya na, hindi available ang mga detalye ng achievements para sa mga lumang seasons.", + "achievementsText": "Mga Nakamtan", + "achievementsUnavailableForOldSeasonsText": "Pasensya na, hindi maibalik ang mga detalye ng achievements para sa mga lumang seasons.", "activatedText": "Na-aktibo ang ${THING}.", "addGameWindow": { "getMoreGamesText": "Kumuha ng Higit pang mga Laro…", @@ -337,7 +338,7 @@ "addToFavoritesText": "Ilagay sa Mga Paborito", "addedToFavoritesText": "Nakalagay na ang '${NAME}' sa Mga Paborito.", "allText": "Lahat", - "allowText": "Payagan", + "allowText": "Payagin", "alreadySignedInText": "Ang iyong account ay naka-sign in mula sa isa pang device;\nMangyaring lumipat ng mga accounts o isara ang laro sa iyong\niba pang mga device at subukan muli.", "apiVersionErrorText": "Hindi ma-load ang module ${NAME}; naka-target ang api-version ${VERSION_USED}; kailangan namin ${VERSION_REQUIRED}", "audioSettingsWindow": { @@ -346,7 +347,7 @@ "musicVolumeText": "Lakas ng Musika", "soundVolumeText": "Lakas ng Tunog", "soundtrackButtonText": "Mga Soundtrack", - "soundtrackDescriptionText": "(I-assign ang iyong sariling musika para magtugtug kapag naglalaro)", + "soundtrackDescriptionText": "(Maglagay ang iyong sariling musika na maitugtog habang naglalaro)", "titleText": "Tugtugan" }, "autoText": "Auto", @@ -370,7 +371,7 @@ "chatMutedText": "Na-mute ang Chat", "chatUnMuteText": "Ipaganahin Mo Na ang Chat", "choosingPlayerText": "", - "codesExplainText": "Binigay ng developer ang mga kowd \npara kilalanin at maitama ang mga problema sa account.", + "codesExplainText": "Binigay ng isang taong gumagawa ng larong ito ang mga kowd \npara kilalanin at maitama ang mga problema sa account.", "completeThisLevelToProceedText": "Tapusin mo muna\nang antas na ito bago tumuloy!", "completionBonusText": "Bonus sa Pagkumpleto nito:", "configControllersWindow": { @@ -458,7 +459,7 @@ "amazonText": "Amazon Appstore", "appStoreText": "App Store", "bestResultsScale": 0.6, - "bestResultsText": "Para sa pinakamahusay na mga resulta, kakailanganin mo ng isang lag-free na wifi network. Kaya mo\nbawasan ang wifi lag sa pamamagitan ng pag-off ng iba pang mga wireless na device, sa pamamagitan ng\nnaglalaro malapit sa iyong wifi router, at sa pamamagitan ng pagkonekta sa\ndirektang host ng laro sa network sa pamamagitan ng ethernet.", + "bestResultsText": "Para sa pinakamahusay na mga resulta, kakailanganin mo ng isang lag-free na wifi network. Kaya mo\nbawasan ang wifi lag sa pamamagitan ng pag-off ng iba pang mga wireless na device, sa pamamagitan ng\nnaglalaro malapit sa iyong wifi router, at sa pamamagitan ng pagkonekta sa\ndirektang pinaghanda ng laro sa network sa pamamagitan ng ethernet.", "explanationText": "Para gumamit ng smart-phone o tablet bilang wireless controller,\ni-install ang \"${REMOTE_APP_NAME}\" na app dito. Anumang bilang ng mga device\nmaaaring kumonekta sa isang larong ${APP_NAME} sa Wi-Fi, at libre ito!", "forAndroidText": "para sa Andriod:", "forIOSText": "para sa iOS:", @@ -474,11 +475,11 @@ "activenessInfoText": "Ang multiplier na ito ay tumataas sa mga araw kung kailan ka\nmaglaro at bumaba sa mga araw na hindi mo ginagawa.", "activityText": "Aktibidad", "campaignText": "Kampanya", - "challengesInfoText": "Makakuha ng mga premyo para sa pagkumpleto ng mga mini-game.\n\nAng mga premyo at antas ng kahirapan ay tumataas\nsa bawat oras na ang isang hamon ay nakumpleto at\nbumaba kapag ang isa ay nag-expire o na-forfeit.", + "challengesInfoText": "Makakuha ng mga premyo para sa pagkumpleto ng mga mini-game.\n\nAng mga premyo at antas ng kahirapan ay tumataas\nsa bawat oras na ang isang hamon ay nakumpleto at\nbumaba kapag ang isa ay nawala na o naparusa.", "challengesText": "Mga Challenges", "currentBestText": "Kasalukuyang Pinakamahusay", "customText": "Kustom", - "entryFeeText": "Pasok", + "entryFeeText": "Pumasok", "forfeitConfirmText": "Isuko ang hamon na ito?", "forfeitNotAllowedYetText": "Ang hamon na ito ay hindi pa maaaring mawala.", "forfeitText": "Forfeit", @@ -502,7 +503,7 @@ "timeRemainingText": "Natitirang Oras", "toRankedText": "Sagad Sa Ranggo", "totalText": "kabuuhan", - "tournamentInfoText": "Makipagkumpitensya para sa matataas na marka sa\nibang mga manlalaro sa iyong liga.\n\nAng mga premyo ay iginagawad sa pinakamataas na iskor\nmga manlalaro kapag nag-expire ang oras ng tournament.", + "tournamentInfoText": "Makipagkumpitensya para sa matataas na marka sa\nibang mga manlalaro sa iyong liga.\n\nAng mga premyo ay iginagawad sa pinakamataas na iskor\nmga manlalaro kapag natapos ang oras ng tournament.", "welcome1Text": "Maligayang pagdating sa ${LEAGUE}. Maaari mong pagbutihin ang iyong\nranggo ng liga sa pamamagitan ng pagkamit ng mga star rating, pagkumpleto\nmga tagumpay, at panalong tropeo sa mga paligsahan.", "welcome2Text": "Maaari ka ring makakuha ng mga tiket mula sa marami sa parehong mga aktibidad.\nMaaaring gamitin ang mga tiket para i-unlock ang mga bagong character, mapa, at\nmini-games, para makapasok sa mga tournament, at higit pa.", "yourPowerRankingText": "Iyong Power Ranking:" @@ -510,13 +511,13 @@ "copyConfirmText": "Nakopya sa clipboard.", "copyOfText": "Kopya ng ${NAME}", "copyText": "I-kopya", - "createEditPlayerText": "", + "createEditPlayerText": "", "createText": "Gumawa", "creditsWindow": { - "additionalAudioArtIdeasText": "Adisyonal Audio, Agang Artwork, Ideya ni ${NAME}", + "additionalAudioArtIdeasText": "Adisyonal na Pagtugtugin, Sining Pang-aga, at Pagharaya ni ${NAME}", "additionalMusicFromText": "Adisyonal musika galing kay ${NAME}", "allMyFamilyText": "Lahat sa aking kaibigan at pamilya ko na tumulong sa play test", - "codingGraphicsAudioText": "Coding, Graphics, at Audio ni ${NAME}", + "codingGraphicsAudioText": "Pagkukudigo, Paggrapikan, at Pagtugtugin ni ${NAME}", "languageTranslationsText": "Pagsasalin Ng Wika:", "legalText": "Ligal:", "publicDomainMusicViaText": "Public-domain music mula sa ${NAME}", @@ -555,71 +556,73 @@ "demoText": "Demo", "denyText": "Tanggihan", "deprecatedText": "Hindi Na Uso", + "descriptionText": "Paglalarawan", "desktopResText": "Desktop Res", - "deviceAccountUpgradeText": "Babala:\nNaka-sign in ka gamit ang isang device account (${NAME}).\nMawawala na yan sa kinakabukasang update.\nMag-upgrade sa isang V2 Account kung gusto mong panatilihin ang iyong pag-unlad.", + "deviceAccountUpgradeText": "Babala:\nNaka-sign in ka gamit ang isang device account (${NAME}).\nMawawala na yan sa kinakabukasang pagbabago.\nMag-upgrade sa isang V2 Account kung gusto mong panatilihin ang iyong pag-unlad.", "difficultyEasyText": "Madali", "difficultyHardOnlyText": "Mahirap Na Mode Lang", "difficultyHardText": "Mahirap", - "difficultyHardUnlockOnlyText": "Itong level na ito ay naka-unlock sa Mahirap na mode lang.\nSa tingin mo ba mayroong ano ang kinakailangan!?!?!", + "difficultyHardUnlockOnlyText": "Itong level na ito ay maibukas sa Mahirap na mode lamang.\nSa tingin mo ba mayroong ka nang kinakailangan!?!?!", "directBrowserToURLText": "Mangyaring idirekta Ang isang web-browser sa sumusunod na URL:", - "disableRemoteAppConnectionsText": "I-disable Ang Mga Remote-App Na Konekyson", + "disableRemoteAppConnectionsText": "Itigil Ang Koneksyon Ng Mga Remote-App", "disableXInputDescriptionText": "Pumayag ng higit sa 4 na controllers ngunit maaaring hindi mabuti ang kalagay", "disableXInputText": "Disable XInput", "disabledText": "Naka-disabled", "discordFriendsText": "Gusto mong may kalaro kang mga tao na gusto mo?\nSumali sa aming Discord server at hanapin mo ang kaibiganin mo!", "discordJoinText": "Sumali sa Discord", - "doneText": "Tapos", + "doneText": "Tapusin", "drawText": "Patas", "duplicateText": "Duplikaduhin", "editGameListWindow": { "addGameText": "Idagdag na\nLaro", "cantOverwriteDefaultText": "Hindi ma-overwrite ang default playlist!", - "cantSaveAlreadyExistsText": "Isang playlist na may ganyang pangalan na!", + "cantSaveAlreadyExistsText": "Mayroon nang isang playlist na may ganyang pangalan!", "cantSaveEmptyListText": "Hindi ma-save ang walang laman na playlist!", - "editGameText": "I-Edit ang\nLaro", + "editGameText": "Ayusin ang\nLaro", "listNameText": "Pangalan Ng Playlist", "nameText": "Pangalan", "removeGameText": "Tanggalin ang\nLaro", "saveText": "I-save Ang Listahan", - "titleText": "Editor Ng Playlist" + "titleText": "Pag-ayusan Ng Playlist" }, "editProfileWindow": { - "accountProfileInfoText": "Itong espesyal na profile ay may\npangalan at icon batay sa iyong account.\n\n${ICONS}\n\nGumawa ng custom na profile para gamitin ng \nmagkaibang pangalan o custom icons.", + "accountProfileInfoText": "Itong espesyal na karakter ay may\npangalan at tatak batay sa iyong account.\n\n\n${ICONS}\n\nGumawa ng sariling karakter para magamit ang iibang pangalan o tatak.", "accountProfileText": "(account profile)", - "availableText": "Ang pangalang \"${NAME}” na ito ay maari mong gamitin pang-icon", + "availableText": "Ang pangalang \"${NAME}” na ito ay maaari mong gamitin pang-tatak", "characterText": "karakter", - "checkingAvailabilityText": "Titignan ko kung maaari mo gamitin ang \"${NAME}\"...", + "checkingAvailabilityText": "Titignan kung maaari mo gamitin ang \"${NAME}\"...", "colorText": "kulay", - "getMoreCharactersText": "Kumuha Ka Pang Mga Karakter...", - "getMoreIconsText": "Kumuha Ka Pang Mga Icons...", - "globalProfileInfoText": "Golbal player profiles ay garantisadong magkaroon ng unique na pangalan worldwide.\nNa-include nila rin ang custom icons.", - "globalProfileText": "(global na profile)", - "highlightText": "highlight", - "iconText": "Mga icon", - "localProfileInfoText": "Ang mga profile Ng lokal na manlalaro ay walang mga icon at ang kanilang mga \npangalan ay hindi garantisadong unique. I-upgrade para maging isang global \nprofile at ma-reserve ng isang unique na pangalan at ma-add ng custom icon.", - "localProfileText": "(lokal na profile)", + "getMoreCharactersText": "Bumili ng Higit pang mga Karakter...", + "getMoreIconsText": "Bumili ng Higit pang Tatak...", + "globalProfileInfoText": "Ang mga karakter ng manlalaro na panglahatan ay garantisadong magkaroon ng magkaiba na pangalan sa buong mundo.\nMayroon din silang mga ibang tatak.", + "globalProfileText": "(karakter para sa mundo)", + "highlightText": "Pangalawang Kulay", + "iconText": "tatak", + "localProfileInfoText": "Ang mga karakter ng lokal na manlalaro\nay wala silang tatak at ang kanilang \nmga pangalan ay hindi garantisadong pambihira. Idakila para maging isang pinakasikat na karakter at makalaan ng isang pambihira na pangalan at madagdag ang iyong sariling tatak para sa buong mundo.", + "localProfileText": "(lokal na karakter)", "nameDescriptionText": "Pangalan Ng Manlalaro", "nameText": "Pangalan", - "randomText": "random", - "titleEditText": "I-Edit Ang Profile", + "profileAlreadyExistsText": "Mayroon nang isang manlalaro na may ganyang pangalan.", + "randomText": "Basta-basta", + "titleEditText": "Ayusin Ang Profile", "titleNewText": "Bagong Profile", - "unavailableText": "hindi available ang \"${NAME}\"; sumubok ng ibang pangalan", - "upgradeProfileInfoText": "Irereserba nito ang pangalan ng iyong manlalaro sa buong mundo\nat nagbibigay-daan sa iyong magtalaga ng custom na icon dito.", - "upgradeToGlobalProfileText": "Upgrade sa Global na Profile" + "unavailableText": "Meron na o Hindi wasto ang \"${NAME}\" na ito; subukin tawagin ng ibang pangalan.", + "upgradeProfileInfoText": "Irereserba nito ang pangalan ng iyong manlalaro sa buong mundo\nat nagbibigay-daan sa iyong magtalaga ng ibang tatak dito.", + "upgradeToGlobalProfileText": "Taasin sa Global na Profile" }, "editSoundtrackWindow": { "cantDeleteDefaultText": "Hindi mo maitanggal Ang default soundtrack", - "cantEditDefaultText": "Hindi ma-edit ang default soundtrack. I-duplicate mo o gumawa Ng bago", - "cantOverwriteDefaultText": "Hindi ma-overwrite ang default soundtrack", - "cantSaveAlreadyExistsText": "Isang soundtrack na may ganyang pangalan na!", - "defaultGameMusicText": "", - "defaultSoundtrackNameText": "Default na Soundtrack", + "cantEditDefaultText": "Hindi pwede ayusin ang orihinal soundtrack.Kopyahin mo nalang o gumawa ng bago", + "cantOverwriteDefaultText": "Hindi maibahin ang orihinal na soundtrack", + "cantSaveAlreadyExistsText": "May isang soundtrack na may ganyang pangalan na!", + "defaultGameMusicText": "", + "defaultSoundtrackNameText": "Orihinal na Soundtrack", "deleteConfirmText": "Itanggal Ang Soundtrack:\n\n'${NAME}'?", "deleteText": "I-delete Ang\nSoundtrack", - "duplicateText": "I-duplicate Ang\nSoundtrack", - "editSoundtrackText": "Editor Ng Soundtrack", - "editText": "I-edit Ang\nSoundtrack", - "fetchingITunesText": "kumuha ng Music App playlists...", + "duplicateText": "Duplikaduhin Ang\nSoundtrack", + "editSoundtrackText": "Ayusan Ng Soundtrack", + "editText": "Ayusin Ang\nSoundtrack", + "fetchingITunesText": "kumuha ng playlist ng Music App...", "musicVolumeZeroWarning": "Paunawa: Ang volume ng musika ay nakatakda sa 0", "nameText": "Pangalan", "newSoundtrackNameText": "Ang Aking Soundtrack ${COUNT}", @@ -634,12 +637,12 @@ "useMusicFileText": "File Ng Musika (mp3, etc)", "useMusicFolderText": "Folder Ng Mga File Ng Musika" }, - "editText": "I-edit", + "editText": "Ayusin", "enabledText": "Nakagana", "endText": "Itigil", - "enjoyText": "Ikasiya!", - "epicDescriptionFilterText": "${DESCRIPTION} sa isang epic na slow motion.", - "epicNameFilterText": "Epikong ${NAME}", + "enjoyText": "Ikasaya!", + "epicDescriptionFilterText": "${DESCRIPTION} sa isang dakilang pabagal na galaw.", + "epicNameFilterText": "Dakilang ${NAME}", "errorAccessDeniedText": "walang pahintulot", "errorDeviceTimeIncorrectText": "Ang oras ng iyong device ay di tama nang ${HOURS} na oras.\nIto ay malamang na magdulot ng mga iba’t ibang problema.\nPakisuri ang mga setting ng iyong oras at time-zone.", "errorOutOfDiskSpaceText": "Wala sa puwang ng disk", @@ -658,70 +661,72 @@ "useThisFolderButtonText": "Gamitin Ang Folder Na Ito" }, "filterText": "Salain", - "finalScoreText": "Huling Marka", - "finalScoresText": "Huling Mga Marka", - "finalTimeText": "Huling Oras", - "finishingInstallText": "Pagtatapos ng pag-install; sandali lang...", + "finalScoreText": "Wakas Na Marka", + "finalScoresText": "Wakas Ng Mga Marka", + "finalTimeText": "Wakas Na Oras", + "finishingInstallText": "Tumatapos ang paglalagay; sandali lang po...", "fireTVRemoteWarningText": "* Para sa isang mas mahusay na karanasan, gamitin\nGame Controllers o i-install ang\n'${REMOTE_APP_NAME}' app sa iyong\nmga telepono at tablet.", - "firstToFinalText": "Una-sa-${COUNT} Wakas", + "firstToFinalText": "Wakas sa Unang Makakuha ng ${COUNT} Puntos", "firstToSeriesText": "Una-sa-${COUNT} Panunuran", "fiveKillText": "LIMANG PAGPATAY!!!", - "flawlessWaveText": "Walang Kapintasan na Kaway!", + "flawlessWaveText": "Hindi Nasaktan!", "fourKillText": "APAT NA PAGPATAY!!!", - "friendScoresUnavailableText": "Hindi available ang marka ng kaibigan", + "friendScoresUnavailableText": "Walang maipakita ang marka ng kaibigan", "gameCenterText": "GameCenter", "gameCircleText": "GameCircle", "gameLeadersText": "Mga Pinuno Ng Pang-${COUNT} na Laro", "gameListWindow": { "cantDeleteDefaultText": "Hindi pwedeng itanggal ang default na playlist.", - "cantEditDefaultText": "Hindi ma-edit ang default playlist! I-duplicate mo o gumawa ng bago.", + "cantEditDefaultText": "Hindi pwede ayusin ang orihinal na playlist! Ipakopya mo o gumawa ng bago.", "cantShareDefaultText": "Hindi pwede ma-share ang default na playlist.", "deleteConfirmText": "Tanggalin ang \"${LIST}\"?", "deleteText": "Tanggalin ang\nPlaylist", - "duplicateText": "I-duplicate ang\nPlaylist", - "editText": "I-edit ang\nPlaylist", + "duplicateText": "Dalawahin ang\nPlaylist", + "editText": "Ayusin ang\nPlaylist", "newText": "Gumawa ng\nPlaylist", + "pointsToWinText": "Puntos Upang Maitupad", + "seriesLengthText": "Haba ng Serye", "showTutorialText": "Ipakita Ang Tutorial", "shuffleGameOrderText": "I-shuffle Ang Kaayusan Ng Laro", - "titleText": "I-customize Ang ${TYPE} Playlists" + "titleText": "Ayusin Ang ${TYPE} Playlists" }, "gameSettingsWindow": { "addGameText": "Maglagay ng Laro" }, - "gamesToText": "${WINCOUNT} na laro sa ${LOSECOUNT}", + "gamesToText": "${WINCOUNT} na laro kontra sa ${LOSECOUNT}", "gatherWindow": { - "aboutDescriptionLocalMultiplayerExtraText": "Tandaan: anumang device sa isang party ay maaaring magkaroon ng higit pa\nkaysa sa isang manlalaro kung mayroon kang sapat na mga controller.", + "aboutDescriptionLocalMultiplayerExtraText": "Tandaan: anumang device sa isang partido ay maaaring magkaroon ng higit pa\nkaysa sa isang manlalaro kung mayroon kang sapat na mga controller.", "aboutDescriptionText": "Gamitin ang mga tab na ito para mag-assemble ng party.\n\nPwede kang maglaro at mag paligsahan kasama ang iyong mga kaibigan sa \niba't ibang device sa pamamagitan ng “Party”.\n\nGamitin ang pindutin na ${PARTY} sa kanang tuktok upang\nmakipag-chat at makipag-ugnayan sa iyong partido.\n(sa isang controller, pindutin ang ${BUTTON} habang nasa isang menu)", "aboutText": "Tungkulin", - "addressFetchErrorText": "", + "addressFetchErrorText": "", "appInviteMessageText": "Pinadalhan ka ni ${NAME} ng ${COUNT} ticket sa ${APP_NAME}", - "appInviteSendACodeText": "Mag-Sent Ka Ng Kowd Sa Kanila", - "appInviteTitleText": "${APP_NAME} App Invite", + "appInviteSendACodeText": "Padalhan Ng Kowd", + "appInviteTitleText": "Paanyaya sa ${APP_NAME}", "bluetoothAndroidSupportText": "(gumagana sa anumang Android device na mayroong Bluetooth)", - "bluetoothDescriptionText": "Mag-host/sumali sa isang party sa pamamagitan ng Bluetooth:", - "bluetoothHostText": "Mag-host gamit ang Bluetooth", + "bluetoothDescriptionText": "Maghanda/sumali sa isang partido sa pamamagitan ng Bluetooth:", + "bluetoothHostText": "Maghanda gamit ang Bluetooth", "bluetoothJoinText": "Sumali gamit ang Bluetooth", "bluetoothText": "Bluetooth", "checkingText": "sinusuri...", "copyCodeConfirmText": "Nakopya na ang kowd sa clipboard.", "copyCodeText": "Kopyahin ang kowd", "dedicatedServerInfoText": "Para sa pinakamahusay na mga resulta, mag-set up ng nakalaang server. Tingnan ang bombsquadgame.com/server para malaman kung paano.", - "disconnectClientsText": "Ididiskonekta nito ang ${COUNT} (mga) manlalaro\nsa iyong party. Sigurado ka ba?", + "disconnectClientsText": "Ididiskonekta nito ang ${COUNT} (mga) manlalaro\nsa iyong partido. Sigurado ka ba?", "earnTicketsForRecommendingAmountText": "Makakatanggap ang mga kaibigan ng ${COUNT} na tiket kung susubukan nila ang laro\n(at makakatanggap ka ng ${YOU_COUNT} para sa bawat gagawa)", - "earnTicketsForRecommendingText": "I-share ang laro\npara makatanggap ng libreng tickets…", + "earnTicketsForRecommendingText": "Ibahagi ang laro\npara makatanggap ng mga libreng tiket…", "emailItText": "I-email", - "favoritesSaveText": "I-save bilang paborito", + "favoritesSaveText": "Ipunin Bilang Paborito", "favoritesText": "Mga Paborito", - "freeCloudServerAvailableMinutesText": "Available ang susunod na libreng cloud server sa loob ng ${MINUTES} (na) minuto.", + "freeCloudServerAvailableMinutesText": "Mayroon ang susunod na libreng cloud server sa loob ng ${MINUTES} (na) minuto.", "freeCloudServerAvailableNowText": "Mayroong libreng cloud server!", - "freeCloudServerNotAvailableText": "Walang available na libreng cloud server.", + "freeCloudServerNotAvailableText": "Walang libreng cloud server sa ngayon.", "friendHasSentPromoCodeText": "${COUNT} ${APP_NAME} na mga tiket mula sa ${NAME}", "friendPromoCodeAwardText": "Makakatanggap ka ng ${COUNT} na tiket sa tuwing ito ay gagamitin.", - "friendPromoCodeExpireText": "E-expire ang kowd sa loob ng ${EXPIRE_HOURS} na oras at gagana lang para sa mga bagong manlalaro.", - "friendPromoCodeInstructionsText": "Upang gamitin ito, buksan ang ${APP_NAME} at pumunta sa \"Mga Setting->Mga Iba Pa->Ilagay ang Kowd\".\nTingnan ang bombsquadgame.com para sa mga link sa pag-download para sa lahat ng sinusuportahang platform.", + "friendPromoCodeExpireText": "Mawawalan-bisa ang kowd sa loob ng ${EXPIRE_HOURS} na oras at gagana lang para sa mga bagong manlalaro.", + "friendPromoCodeInstructionsText": "Upang gamitin ito, buksan ang ${APP_NAME} at pumunta sa \"Mga Setting->Mga Iba Pa->Ilagay ang Impormasyon\".\nTingnan ang bombsquadgame.com para sa mga link sa pag-download para sa lahat ng sinusuportahang platform.", "friendPromoCodeRedeemLongText": "Maaari itong ma-redem ng ${COUNT} na libreng tiket ng hanggang ${MAX_USES} na tao.", "friendPromoCodeRedeemShortText": "Maaari itong ma-redem ng ${COUNT} na tiket sa larong ito.", - "friendPromoCodeWhereToEnterText": "(sa \"Settings->Mga Iba Pa->Ilagay Ang Kowd\")", + "friendPromoCodeWhereToEnterText": "(sa \"Settings->Mga Iba Pa->Ilagay Ang Impormasyon\")", "getFriendInviteCodeText": "Kumuha ng imbitasyon ng kowd ng kaibigan", "googlePlayDescriptionText": "Mag-imbita ng mga manlalaro ng Google Play sa iyong party:", "googlePlayInviteText": "Mag-imbita", @@ -729,14 +734,14 @@ "googlePlaySeeInvitesText": "Ipakita Ang Mga Imbitasyon", "googlePlayText": "Google Play", "googlePlayVersionOnlyText": "(Bersyon ng Android / Google Play)", - "hostPublicPartyDescriptionText": "Mag-host ng Pampublikong Party", - "hostingUnavailableText": "Hindi Available Ang Pagho-host", + "hostPublicPartyDescriptionText": "Maghanda ng Pampublikong Party", + "hostingUnavailableText": "Bawal Muna Ang Paghandahin", "inDevelopmentWarningText": "Note:\n\nAng Network Play ay bago at patuloy na umuunlad na feature.\nSa ngayon, Ito'y lubos na inirerekomenda na ang lahat ng \nmga manlalaro ay nasa parehong Wi-Fi network.", "internetText": "Internet", "inviteAFriendText": "Walang laro ang mga kaibigan mo nito? Mag-imbita mo sila \npara sila'y subukan maglaro ito at makakatanggap sila ng ${COUNT} libreng tiket.", "inviteFriendsText": "Mag-imbita ng mga kaibigan", "joinPublicPartyDescriptionText": "Sumali sa isang Pampublikong Party", - "localNetworkDescriptionText": "Sumali sa malapit na party (LAN, Bluetooth, etc.)", + "localNetworkDescriptionText": "Sumali sa Partidong Malapit Sa'yo! (LAN, Bluetooth, atbp.)", "localNetworkText": "Lokal na Network", "makePartyPrivateText": "Gawing Pribado Ang Party Ko", "makePartyPublicText": "Gawing Publiko Ang Party Ko", @@ -810,16 +815,16 @@ "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.", - "unavailableTemporarilyText": "Hindi available ito. Subukang muli mamaya.", + "unavailableLinkAccountText": "Pasensya na, bawal 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.", + "unavailableTemporarilyText": "Bawal muna ito; subukang muli mamaya.", "unavailableText": "Pasensya na, hindi ito available.", - "versionTooOldText": "Pasensya na, ang bersyon ng laro na ito ay masyadong luma; mangyaring mag-update sa isang mas bagong bersyon.", + "versionTooOldText": "Pasensya na, ang bersyon ng laro na ito ay masyadong luma; mangyaring ibaguhin sa isang pang mas mabagong bersyon.", "youHaveShortText": "mayroon kang ${COUNT}", "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.", - "googlePlayServicesNotAvailableText": "Hindi available ang Google Play Services sa ngayon.\n‘Di magagana ang takbo ng ilang mga app.", + "googlePlayPurchasesNotAvailableText": "Hindi puwede ang mga pagbili sa Google Play nito.\nMaaaring kailanganin mong ibaguhin ang iyong store app.", + "googlePlayServicesNotAvailableText": "Hindi pwede ang Google Play Services sa ngayon.\n‘Di magagana ang takbo ng ilang mga app.", "googlePlayText": "Google Play", "graphicsSettingsWindow": { "alwaysText": "Palagi", @@ -842,7 +847,7 @@ "visualsText": "Biswal" }, "helpWindow": { - "bombInfoText": "- Bomba -\nMas malakas pa sa suntok, pero\nmaaaring magresulta sa matinding pananakit sa sarili.\nPara sa pinakamahusay na mga resulta, itapon patungo\nkalaban bago maubos ang fuse.", + "bombInfoText": "- Bomba -\nMas malakas pa sa suntok, ngunit\nmaaaring magresulta sa matinding pananakit sa sarili.\nPara sa pinakamahusay na mga resulta, ihagis mo patungo sa\nkalaban bago ito'y sumabog.", "bombInfoTextScale": 0.5, "canHelpText": "Makakatulong ang ${APP_NAME}.", "controllersInfoText": "Maaari mong laruin ang ${APP_NAME} kasama ng mga kaibigan sa isang network, o ikaw\nlahat ay maaaring maglaro sa parehong device kung mayroon kang sapat na mga controller.\nSinusuportahan ng ${APP_NAME} ang iba't ibang mga ito; maaari ka ring gumamit ng mga telepono\nbilang mga controller sa pamamagitan ng libreng '${REMOTE_APP_NAME}' app.\nTingnan ang Mga Setting->Controller para sa higit pang impormasyon.", @@ -872,7 +877,7 @@ "powerupLandMinesDescriptionText": "Ang mga ito ay dumating sa mga pakete ng 3;\nKapaki-pakinabang para sa base defense o\npaghinto ng mga mabibilis na kalaban.", "powerupLandMinesNameText": "Mina", "powerupPunchDescriptionText": "Ang iyong mga suntok ay mas mahirap,\nmas mabilis, mas mahusay, at mas malakas.", - "powerupPunchNameText": "Boxing-Gloves", + "powerupPunchNameText": "Guwantes", "powerupShieldDescriptionText": "Pumigil na pagsakit\nkaya hindi mo kailangan.", "powerupShieldNameText": "Enrhiyang-Kalasag", "powerupStickyBombsDescriptionText": "Dumikit sa anumang matamaan nila.\nIto’y naging pagtawanan.", @@ -922,7 +927,7 @@ "errorPlayingMusicText": "Error sa paglalaro ng musika: ${MUSIC}", "errorResettingAchievementsText": "Hindi ma-reset ang mga online na achievements; Subukang muli mamaya.", "hasMenuControlText": "Si ${NAME} lang ay may kontrol sa menu.", - "incompatibleNewerVersionHostText": "Ang host ay nagpapatakbo ng mas bagong bersyon ng laro.\nMag-update sa pinakabagong bersyon at subukang muli.", + "incompatibleNewerVersionHostText": "Ang punong-abala ay nagpapatakbo ng mas mabagong bersyon ng laro.\nMagbago sa pinakabagong bersyon at subukang muli.", "incompatibleVersionHostText": "Ang host ay nagpapatakbo ng ibang bersyon ng laro.\nTiyaking pareho kayong napapanahong bersyon at subukang muli.", "incompatibleVersionPlayerText": "Ang ${NAME} ay nagpapatakbo ng ibang bersyon ng laro.\nTiyaking pareho kayong napapanahong bersyon at subukang muli.", "invalidAddressErrorText": "Error: di-wastong address.", @@ -977,7 +982,7 @@ "killsText": "Pinatay", "kioskWindow": { "easyText": "Madali", - "epicModeText": "Mode na Epic", + "epicModeText": "Dakilang Paraan", "fullMenuText": "Buong Menu", "hardText": "Mahirap", "mediumText": "Katamtaman", @@ -987,7 +992,7 @@ "languageSetText": "Ang wika ay \"${LANGUAGE}\" sa ngayon.", "lapNumberText": "Ikot ${CURRENT}/${TOTAL}", "lastGamesText": "(huling ${COUNT} na laro)", - "leaderboardsText": "Mga Leaderboard", + "leaderboardsText": "Ranggo Ng Mga Pasimuno", "league": { "allTimeText": "Lahat Ng Oras", "currentSeasonText": "Kasalukuyang Season (${NUMBER})", @@ -995,7 +1000,7 @@ "leagueRankText": "Ranggo ng Liga", "leagueText": "Liga", "rankInLeagueText": "#${RANK}, ${NAME} ${SUFFIX} na Liga", - "seasonEndedDaysAgoText": "Natapos ang season noing ${NUMBER} na araw.", + "seasonEndedDaysAgoText": "Tumapos na ang season noong ${NUMBER} na araw.", "seasonEndsDaysText": "Matatapos ang season sa ${NUMBER} (na) araw.", "seasonEndsHoursText": "Matatapos ang season sa ${NUMBER} (na) oras.", "seasonEndsMinutesText": "Matatapos ang season sa ${NUMBER} (na) minuto.", @@ -1087,11 +1092,11 @@ "notUsingAccountText": "Note: hindi pinapansin ang account ng ${SERVICE}.\nPumunta sa 'Account -> Mag-sign in gamit ang ‘${SERVICE}' kung gusto mo itong gamitin.", "nothingIsSelectedErrorText": "Walang napili!", "numberText": "#${NUMBER}", - "offText": "Naka-Off", + "offText": "Nakasara", "okText": "Ok lang", - "onText": "Naka-On", + "onText": "Nakabuksan", "oneMomentText": "Saglit lang…", - "onslaughtRespawnText": "Ang ${PLAYER} ay respawn sa wave na ${WAVE}", + "onslaughtRespawnText": "Ang ${PLAYER} ay respawn sa ika-${WAVE} na yugto", "orText": "${A} o ${B}", "otherText": "Iba Pa…", "outOfText": "(#${RANK} sa ${ALL})", @@ -1105,14 +1110,14 @@ "titleText": "Iyong Party" }, "pausedByHostText": "(naka-pause ng host)", - "perfectWaveText": "Perpekto na Kaway!", + "perfectWaveText": "Perpekto!", "pickUpText": "Pulutin", "playModes": { "coopText": "Kooperatiba", "freeForAllText": "Awayang Rambulan", "multiTeamText": "Maraming Team", "singlePlayerCoopText": "Nag-iisa / Kooperatiba", - "teamsText": "Mga Teams" + "teamsText": "Kampihan" }, "playText": "Maglaro", "playWindow": { @@ -1126,16 +1131,16 @@ "playerLeftText": "Umalis si ${PLAYER} sa laro.", "playerLimitReachedText": "Naabot na ang limitasyon ng manlalaro na ${COUNT}; hindi pinapayagan ang mga ibang manlalaro sumali.", "playerProfilesWindow": { - "cantDeleteAccountProfileText": "Hindi mo matatanggal ang profile ng iyong account.", + "cantDeleteAccountProfileText": "Hindi mo matatanggal ang karakter ng iyong account.", "deleteButtonText": "Itangal ang\nProfile", "deleteConfirmText": "Itangal si ‘${PROFILE}’?", - "editButtonText": "I-edit ang\nProfile", + "editButtonText": "Ayusin ang\nProfile", "explanationText": "(mga pangalan at hitsura ng custom na manlalaro para sa account na ito)", "newButtonText": "Gumawa ng\nProfile", - "titleText": "Profile ng Manlalaro" + "titleText": "Karakter ng Manlalaro" }, "playerText": "Manlalaro", - "playlistNoValidGamesErrorText": "Ang playlist na ito ay hindi naglalaman ng mga wastong naka-unlock na laro.", + "playlistNoValidGamesErrorText": "Ang playlist na ito ay hindi naglalaman ng mga wastong 'di maibukas na laro.", "playlistNotFoundText": "hindi nahanap ang playlist", "playlistText": "Playlist", "playlistsText": "Mga Playlist", @@ -1175,7 +1180,7 @@ }, "punchBoldText": "SUNTOK", "punchText": "Suntok", - "purchaseForText": "Ibili para sa ${PRICE}", + "purchaseForText": "Ipagbili ng ${PRICE}", "purchaseGameText": "Ibili ang laro", "purchasingText": "Nagbabayad...", "quitGameText": "Ihinto ang ${APP_NAME}?", @@ -1184,7 +1189,7 @@ "randomText": "Random", "rankText": "Ranggo", "ratingText": "Marka", - "reachWave2Text": "Abutin ang kaway 2 para ma-ranggo.", + "reachWave2Text": "Abutin ang ika-2 yugto para ma-ranggo.", "readyText": "nakahanda", "recentText": "Makabago", "remoteAppInfoShortText": "Ang ${APP_NAME} ay pinakanakakatuwa kapag nilalaro kasama ng pamilya at mga kaibigan.\nIkonekta ang isa o higit pang mga controller ng hardware o i-install ang\n${REMOTE_APP_NAME} app sa mga phone o tablet upang magamit ang mga ito\nbilang mga controllers.", @@ -1195,8 +1200,8 @@ "button_size": "Laki ng Pindutan", "cant_resolve_host": "Hindi malutas ang host.", "capturing": "Kumukuha…", - "connected": "Konektdo.", - "description": "Gamitin ang iyong mga phone o tablet bilang controller sa BombSquad.\nHanggang 8 device ang maaaring kumonekta nang sabay-sabay para sa epic na lokal na multiplayer na kabaliwan sa isang TV o tablet", + "connected": "Nakakonek.", + "description": "Gamitin ang iyong mga phone o tablet bilang controller sa BombSquad.\nHanggang 8 device ang maaaring kumonekta nang sabay-sabay para sa maringal na lokal na multiplayer na kabaliwan sa isang TV o tablet", "disconnected": "Nadiskonekta ng server.", "dpad_fixed": "nakapirmi", "dpad_floating": "lumulutang", @@ -1209,7 +1214,7 @@ "hardware_buttons": "Mga Pindutan ng Hardware", "join_by_address": "Sumali sa pamamagitan ng Address...", "lag": "Lag: ${SECONDS} na segundo", - "reset": "I-reset sa default", + "reset": "Ibalik sa Orihinal", "run1": "Takbo 1", "run2": "Takbo 2", "searching": "Naghahanap ng mga laro sa BombSquad...", @@ -1230,27 +1235,28 @@ "reportPlayerExplanationText": "Gamitin ang email na ito upang mag-ulat ng pagdaya, hindi naaangkop na pananalita, o iba pang masamang gawin.\nPakilarawan sa ibaba:", "reportThisPlayerCheatingText": "Dumadaya", "reportThisPlayerLanguageText": "Hindi Angkop na Salita", - "reportThisPlayerReasonText": "Ano ang gusto mong iulat?", - "reportThisPlayerText": "Iulat ang Manlalaro na Ito", + "reportThisPlayerReasonText": "Anong dahilan upang sumumbong?", + "reportThisPlayerText": "Isumbong ang Manlalaro na Ito", "requestingText": "Humihiling...", "restartText": "Umulit", "retryText": "Ulitin", "revertText": "Ibalik", "runText": "Tumakbo", - "saveText": "I-save", + "saveText": "Panatilihin", "scanScriptsErrorText": "(Mga) kamalian sa pag-scan ng mga script; Tignan ang log para sa mga detalye.", "scanScriptsMultipleModulesNeedUpdatesText": "Kailangan maibago para sa API ${API} ang ${PATH} at mga ${NUM} na iba pang modules.", "scanScriptsSingleModuleNeedsUpdatesText": "Kailangan maibago para sa api ${API} ang ${PATH}.", - "scoreChallengesText": "Mga Hamon sa Iskor", - "scoreListUnavailableText": "Hindi available ang listahan ng iskor", + "scoreChallengesText": "Mga Hamon Para sa Iskor", + "scoreListUnavailableText": "Hindi maipakita ang listahan ng iskor", "scoreText": "Iskor", "scoreUnits": { "millisecondsText": "Milisegundo", "pointsText": "Puntos", "secondsText": "Segundo" }, - "scoreWasText": "(mula sa ${COUNT})", + "scoreWasText": "(nagmula sa ${COUNT})", "selectText": "Pilihin", + "sendInfoDescriptionText": "Napapadala ang iyong account at impormasyon sa lagay ng app sa mga gumagawa ng larong ito.\nPakiusap na ilagay ang iyong pangalan o ang dahilan ng pagpapadala nito.", "seriesWinLine1PlayerText": "ANG NANALO SA", "seriesWinLine1TeamText": "ANG NANALO SA", "seriesWinLine1Text": "ANG NANALO SA", @@ -1258,44 +1264,48 @@ "settingsWindow": { "accountText": "Account", "advancedText": "Mga Iba Pa", - "audioText": "Audio", + "audioText": "Tugtugan", "controllersText": "Mga Controller", "graphicsText": "Grapika", - "playerProfilesMovedText": "Tandaan: Ang mga Profile ng Manlalaro ay inilipat sa Account window sa pangunahing menu.", + "playerProfilesMovedText": "Paunawa: Ang mga Karakter ng Manlalaro ay inilipat na sa salaan ng mga account sa pangunahing menu.", "titleText": "Mga Setting" }, "settingsWindowAdvanced": { - "alwaysUseInternalKeyboardDescriptionText": "(isang simpleng, controller-friendly na on-screen na keyboard para sa pag-edit ng teksto)", - "alwaysUseInternalKeyboardText": "Laging Gumamit ng Panloob na Keyboard", - "benchmarksText": "Mga Benchmark at Stress-Test", - "disableCameraGyroscopeMotionText": "I-disable ang Camera Gyroscope Motion", - "disableCameraShakeText": "Huwag paganahin ang Camera Shake", - "disableThisNotice": "(maaari mong i-disable ang notice na ito sa mga advanced na setting)", + "alwaysUseInternalKeyboardDescriptionText": "(isang simpleng keyboard na nasa loob lamang ng larong ito para sa pagasasaayos ng teksto)", + "alwaysUseInternalKeyboardText": "Laging gumamit ang keyboard na panloob", + "benchmarksText": "Mga Benchmark at Pagsusuri sa Pagdiin", + "devToolsText": "Kagamitan sa Paggawa", + "disableCameraGyroscopeMotionText": "Itigil ang mosyong dyayroskop ng larawan", + "disableCameraShakeText": "Huwag paganahin ang alog ng larawan", + "disableThisNotice": "(maaari mong ipatigil ang paunwa na ito sa setting \"Mga Iba Pa\")", "enablePackageModsDescriptionText": "(nagpapagana ng mga karagdagang kakayahan sa pag-modding ngunit hindi pinapagana ang net-play)", "enablePackageModsText": "Paganahin ang Lokal na Package Mods", "enterPromoCodeText": "Ilagay ang Kowd", - "forTestingText": "Tandaan: ang mga value na ito ay para lamang sa pagsubok at mawawala kapag lumabas ang app.", + "forTestingText": "Tandaan: ang mga value na ito ay para lamang sa pagsusuri at mawawala ito kapag umalis ka sa larong ito.", "helpTranslateText": "Ang mga pagsasalin na hindi Ingles ng ${APP_NAME} ay isang komunidad\nsuportadong pagsisikap. Kung gusto mong mag-ambag o magtama\nisang pagsasalin, sundan ang link sa ibaba. Salamat!", - "kickIdlePlayersText": "I-kick Ang Mga Idle na Manlalaro", - "kidFriendlyModeText": "Pambata na Mode (binawasan ang karahasan, atbp)", + "kickIdlePlayersText": "Ipalayas ang mga manlalarong hindi gumagalaw", + "kidFriendlyModeText": "Pamamaraang Pambata (walang tinding karahasan, atbp)", "languageText": "Wika", - "moddingGuideText": "Gabay sa Modding", - "mustRestartText": "Dapat mong i-restart ang laro para magka-epekto ito.", + "moddingGuideText": "Gabay sa Pagbabago (Mod)", + "moddingToolsText": "Kagamitan sa Pag-mod", + "mustRestartText": "Ulitin mo ang larong ito upang gumana ang kalalabasan nito.", "netTestingText": "Pagsusuri ng Network", - "resetText": "I-reset", - "showBombTrajectoriesText": "Ipakita ang Mga Trajectory ng Bomba", - "showDemosWhenIdleText": "Ipakita Ang Demo Habang Nakatigil", - "showDevConsoleButtonText": "Ipakita ang Dev Console Button", - "showInGamePingText": "Ipakita ang In-Game Ping", - "showPlayerNamesText": "Ipakita ang Mga Pangalan ng Manlalaro", - "showUserModsText": "Ipakita ang Mods Folder", + "resetText": "Ulitin", + "sendInfoText": "Ipadala ang Impormasyon", + "showBombTrajectoriesText": "Ipakita Kung Saan Lalapag ang Bomba", + "showDemosWhenIdleText": "Ipakita ang demo habang nakatigil", + "showDeprecatedLoginTypesText": "Ipakita ang mga natakwil na uri ng pagpasok sa account", + "showDevConsoleButtonText": "Ipakita ang pindutan ng dev console", + "showInGamePingText": "Ipakita ang leytensi habang naglalaro", + "showPlayerNamesText": "Ipakita ang Pangalan ng Mga Manlalaro", + "showUserModsText": "Ipakita ang Paniklop ng Mga Mod", "titleText": "Advanced", - "translationEditorButtonText": "Editor ng Wika ng ${APP_NAME}.", - "translationFetchErrorText": "hindi available ang katayuan ng wika", - "translationFetchingStatusText": "sinusuri ang status ng lengguwahe…", - "translationInformMe": "Ipaalam sa akin kapag ang aking wika ay nangangailangan ng mga update", - "translationNoUpdateNeededText": "ang kasalukuyang wika ay makabago na muli; yehey!", - "translationUpdateNeededText": "** ang kasalukuyang wika ay nangangailangan ng mga update!! **", + "translationEditorButtonText": "Pag-ayusan ng Wika sa ${APP_NAME}.", + "translationFetchErrorText": "Hindi maipakita ang kalagayan ng wikang ito.", + "translationFetchingStatusText": "sinusuri ang kalagayan ng lengguwahe…", + "translationInformMe": "Ipaalam sa akin kapag ang aking wika ay nangangailangan ng pagbabago", + "translationNoUpdateNeededText": "Ang kasalukuyang wika ay makabago na muli; yehey!", + "translationUpdateNeededText": "** Ang kasalukuyang wika ay nangangailangan ng pagbabago!! **", "vrTestingText": "Testing ng VR" }, "shareText": "I-share", @@ -1305,32 +1315,35 @@ "signInWithGameCenterText": "Para gumamit ng Game Center account,\nmag-sign in gamit ang Game Center app.", "singleGamePlaylistNameText": "${GAME} lang", "singlePlayerCountText": "1 manlalaro", - "soloNameFilterText": "Solo na ${NAME}", + "sizeLargeText": "Malaki", + "sizeMediumText": "Sakto lang", + "sizeSmallText": "Maliit", + "soloNameFilterText": "Pinag-iisang ${NAME}", "soundtrackTypeNames": { "CharSelect": "Pagpili ng Karakter", - "Chosen One": "Napili ng Isa", - "Epic": "Larong Epic na Mode", - "Epic Race": "Epic na Takbuhan", - "FlagCatcher": "Kunin ang Bandila", + "Chosen One": "Piniling Isa", + "Epic": "Larong Madakila", + "Epic Race": "Dakilang Takbuhan", + "FlagCatcher": "Agawang Bandila", "Flying": "Masayang Isip", "Football": "Rugbi", "ForwardMarch": "Pag-atake", "GrandRomp": "Pagsakop", - "Hockey": "Hockey", + "Hockey": "Haki", "Keep Away": "Layuan Mo", "Marching": "Bantayan", "Menu": "Pangunahing Menu", "Onslaught": "Pagsalakay", "Race": "Takbuhan", "Scary": "Hari Ng Burol", - "Scores": "Screen Ng Iskor", - "Survival": "Kaligtasan", + "Scores": "Salaan Ng Iskor", + "Survival": "Pagbabawasan", "ToTheDeath": "Laban Ng Kamatayan", - "Victory": "Final Na Screen Ng Iskor" + "Victory": "Wakas Na Salaan Ng Iskor" }, "spaceKeyText": "space", "statsText": "Katayuan", - "storagePermissionAccessText": "Nangangailangan ito ng access sa storage", + "storagePermissionAccessText": "Nangangailangan ito ng pagpasok sa storage", "store": { "alreadyOwnText": "Nabili mo na ang ${NAME}!", "bombSquadProNameText": "${APP_NAME} Pro", @@ -1340,18 +1353,18 @@ "comingSoonText": "Abangan...", "extrasText": "Padagdag", "freeBombSquadProText": "Ang BombSquad ay libre na ngayon, ngunit dahil ikaw ang orihinal na bumili nito, ikaw na\npagtanggap ng BombSquad Pro upgrade at ${COUNT} na mga tiket bilang pasasalamat.\nTangkilikin ang mga bagong feature, at salamat sa iyong suporta!\n-Eric", - "holidaySpecialText": "Espesyal Na Holiday", - "howToSwitchCharactersText": "(pumunta sa \"${SETTINGS} -> ${PLAYER_PROFILES}\" para magtalaga at mag-customize ng mga character)", - "howToUseIconsText": "(gumawa ng mga global profile ng manlalaro (sa window ng account) para magamit ang mga ito)", + "holidaySpecialText": "Espesyal Ng Holiday", + "howToSwitchCharactersText": "(pumunta sa \"${SETTINGS} -> ${PLAYER_PROFILES}\" para magtalaga at maiba ng mga karakter)", + "howToUseIconsText": "(gumawa ng karakter ng mga manlalaro (sa salaan ng mga account) para magamit ang mga ito)", "howToUseMapsText": "(gamitin ang mga mapa na ito sa sarili mong Mga Team/Rambulan na mga playlist)", - "iconsText": "Mga Icon", + "iconsText": "Mga Tatak", "loadErrorText": "Hindi ma-load ang page.\nSuriin ang iyong koneksyon sa internet.", "loadingText": "Saglit lang…", "mapsText": "Mga Mapa", - "miniGamesText": "Mga MiniGames", + "miniGamesText": "Mga Lalaruhin", "oneTimeOnlyText": "(isang beses lang)", "purchaseAlreadyInProgressText": "Ang isang nabilhin ng item na ito ay isinasagawa na.", - "purchaseConfirmText": "Ibili ang ${ITEM}?", + "purchaseConfirmText": "Bilhin ang ${ITEM}?", "purchaseNotValidError": "Hindi wasto ang nabilhin.\nMakipag-ugnayan kay ${EMAIL} kung ito ay isang error.", "purchaseText": "Bilhin", "saleBundleText": "Diskwento ng Bundle!", @@ -1390,15 +1403,15 @@ "threeKillText": "TRIPLENG PAGPATAY!!", "timeBonusText": "Bonus sa Oras", "timeElapsedText": "Oras Na Lumipas", - "timeExpiredText": "Nag-expire Na Ang Oras!", + "timeExpiredText": "Tapos Na!", "timeSuffixDaysText": "${COUNT}d", "timeSuffixHoursText": "${COUNT}h", "timeSuffixMinutesText": "${COUNT}m", "timeSuffixSecondsText": "${COUNT}s", - "tipText": "Tip", + "tipText": "Isang Payo", "titleText": "BombSquad", "titleVRText": "BombSquad VR", - "topFriendsText": "Pinakamataas na Kaibigan", + "topFriendsText": "Pinakamataas sa mga Kaibigan", "tournamentCheckingStateText": "Sinusuri ang estado ng paligsahan; pakihintay...", "tournamentEndedText": "Natapos na ang paligsahan na ito. Magsisimula ng bago mamaya.", "tournamentEntryText": "Pagpasok sa Paligsahan", @@ -1406,14 +1419,14 @@ "tournamentStandingsText": "Mga Paninindigan sa Paligsahan", "tournamentText": "Paligsahan", "tournamentTimeExpiredText": "Na-expire Na Ang Oras Ng Paligsahan", - "tournamentsDisabledWorkspaceText": "Naka-disable ang mga paligsahan kapag aktibo ang mga workspace. \nHuwag munang paganahin muli ang iyong workspace at i-restart upang makipaglaro sa paligsahan.", + "tournamentsDisabledWorkspaceText": "Hindi ka pwede maglaro ng mga paligsahan kapag aktibo ang mga workspace. \nIsara muna ang iyong workspace at ulitin ang larong ito upang makipaglaro sa paligsahan.", "tournamentsText": "Mga Paligsahan", "translations": { "characterNames": { "Agent Johnson": "Ahente Johnson", "B-9000": "Makina-9000", "Bernard": "Oso", - "Bones": "Buto", + "Bones": "Kalansay", "Butch": "Bakero", "Easter Bunny": "Kuneho", "Flopsy": "Magalaw", @@ -1467,7 +1480,7 @@ "Dodge the falling bombs.": "Iwasan ang bumabagsak na bomba.", "Final glorious epic slow motion battle to the death.": "Huling maluwalhating epic slow motion na labanan hanggang kamatayan.", "Gather eggs!": "Ipunin ng mga itlog!", - "Get the flag to the enemy end zone.": "Kunin ang bandila sa end zone ng kalaban.", + "Get the flag to the enemy end zone.": "Kunin ang bandila patungo sa dulong lugar ng kalaban.", "How fast can you defeat the ninjas?": "Gaano kabilis mo matatalo ang mga ninja?", "Kill a set number of enemies to win.": "Pumatay ng isang set na bilang ng mga kalaban upang manalo.", "Last one standing wins.": "Kung sino ang huling nakatayo ang siyang mananalo.", @@ -1514,8 +1527,8 @@ }, "gameNames": { "Assault": "Pag hawak ng bandila", - "Capture the Flag": "Kunin ang Bandila", - "Chosen One": "Napili na Isa", + "Capture the Flag": "Agawang Bandila", + "Chosen One": "Piniling Isa", "Conquest": "Pagsakop", "Death Match": "Laban ng Kamatayan", "Easter Egg Hunt": "Paghahanap ng mga Easter Egg", @@ -1547,7 +1560,7 @@ "Dutch": "Wikang Olandes", "English": "Wikang Ingles", "Esperanto": "Wikang Esperanto", - "Filipino": "Wikang Tagalog", + "Filipino": "Wikang Pilipino", "Finnish": "Wikang Finnish", "French": "Wikang Pranses", "German": "Wikang Alemanya", @@ -1591,9 +1604,9 @@ "Football Stadium": "Istadyum", "Happy Thoughts": "Masayan na mga Isip", "Hockey Stadium": "Istadyum ng Hockey", - "Lake Frigid": "Yelong Lawa", + "Lake Frigid": "Lawang Yelo", "Monkey Face": "Mukha ng Unggoy", - "Rampage": "Mandaluhong", + "Rampage": "Dalawang Dahilig", "Roundabout": "Paliguy-ligoy", "Step Right Up": "Hakbang Pataas", "The Pad": "Ang Pad", @@ -1602,7 +1615,7 @@ "Zigzag": "Sigsag" }, "playlistNames": { - "Just Epic": "Epic Lang", + "Just Epic": "Mga Madakila", "Just Sports": "Sports Lang" }, "scoreNames": { @@ -1674,8 +1687,8 @@ "You got ${COUNT} tickets!": "Nakakuha ka ng ${COUNT} tickets!", "You got a ${ITEM}!": "Nakakuha ka ng ${ITEM}!", "You have been promoted to a new league; congratulations!": "Na-promote ka sa isang bagong liga; congrats!", - "You must update to a newer version of the app to do this.": "Dapat kang mag-update sa mas bagong bersyon ng app para magawa ito.", - "You must update to the newest version of the game to do this.": "Dapat kang mag-update sa pinakabagong bersyon ng laro upang magawa ito.", + "You must update to a newer version of the app to do this.": "Kailangang magbago sa mas mabagong bersyon ng app para magawa ito.", + "You must update to the newest version of the game to do this.": "Kailangang magbago sa pinakabagong bersyon ng laro upang magawa ito.", "You must wait a few seconds before entering a new code.": "Dapat kang maghintay ng ilang segundo bago maglagay ng bagong code.", "You ranked #${RANK} in the last tournament. Thanks for playing!": "Ikaw ay niraranggo ng #${RANK} sa huling paligsahan. Salamat sa paglalaro!", "Your account was rejected. Are you signed in?": "Tinanggihan ang iyong account. Naka-sign in ka ba?", @@ -1695,7 +1708,7 @@ "Allow Negative Scores": "Payagan ang Mga Negatibong Iskor", "Balance Total Lives": "Balansehin ang Kabuuhang Buhay", "Bomb Spawning": "Paglitaw ng Bomba", - "Chosen One Gets Gloves": "Ang Isa Sa Napili Ay Makukuha Ng Gloves", + "Chosen One Gets Gloves": "Ang Isa Sa Napili Ay Makukuha Mga Guwantes", "Chosen One Gets Shield": "Ang Isa Sa Napili Ay Makukuha Ng Kalasag", "Chosen One Time": "Oras Ng Isa Sa Napili", "Enable Impact Bombs": "I-enable Ang Mga Gatilyong Bomba", @@ -1735,51 +1748,51 @@ "Red": "Pula" }, "tips": { - "A perfectly timed running-jumping-spin-punch can kill in a single hit\nand earn you lifelong respect from your friends.": "Ang isang perpektong naka-time na takbo-talon-ikot-suntok ay maaaring makapatay sa isang hit\nat magkaroon ka ng panghabambuhay na paggalang mula sa iyong mga kaibigan.", - "Always remember to floss.": "Laging tandaan na mag-floss.", - "Create player profiles for yourself and your friends with\nyour preferred names and appearances instead of using random ones.": "Gumawa ng mga profile ng player para sa iyong sarili at sa iyong mga kaibigan\nang iyong mga gustong pangalan at hitsura sa halip na gumamit ng mga random.", - "Curse boxes turn you into a ticking time bomb.\nThe only cure is to quickly grab a health-pack.": "Ginagawa ka ng sumpa sa isang ticking time na bomba.\nAng tanging lunas ay ang mabilisang kumuha ng health-pack.", - "Despite their looks, all characters' abilities are identical,\nso just pick whichever one you most closely resemble.": "Sa kabila ng kanilang hitsura, lahat ng kakayahan ng mga karakter ay magkapareho,\nkaya pumili lang kung alin ang pinakahawig mo.", - "Don't get too cocky with that energy shield; you can still get yourself thrown off a cliff.": "Huwag masyadong maangas sa enerhiya na kalasag na iyon; ihahagis ka nila sa isang platform", - "Don't run all the time. Really. You will fall off cliffs.": "Huwag tumakbo sa lahat ng oras. Talaga. Mahuhulog ka sa platform", - "Don't spin for too long; you'll become dizzy and fall.": "Huwag paikutin nang masyadong mahaba; mahihilo ka at mahulog.", - "Hold any button to run. (Trigger buttons work well if you have them)": "Pindutin ang anumang pindutan upang tumakbo. (Mahusay na gumagana ang mga pindutan ng pag-trigger kung mayroon ka nito)", - "Hold down any button to run. You'll get places faster\nbut won't turn very well, so watch out for cliffs.": "Pindutin nang matagal ang anumang button para tumakbo. Mas mabilis kang makakakuha ng mga lugar\nngunit hindi lumiko nang mahusay, kaya mag-ingat sa matalim na mga gilid", - "Ice bombs are not very powerful, but they freeze\nwhoever they hit, leaving them vulnerable to shattering.": "Ang mga bomba ng yelo ay hindi masyadong malakas, ngunit nagyeyelo\nkung sino man ang kanilang natamaan, na nag-yelo sa kanila na madaling masira.", - "If someone picks you up, punch them and they'll let go.\nThis works in real life too.": "Kung may napulot sa iyo, suntukin mo siya at bibitaw siya.\nGumagana rin ito sa totoong buhay.", - "If you are short on controllers, install the '${REMOTE_APP_NAME}' app\non your mobile devices to use them as controllers.": "Kung kulang ka sa mga controller, i-install ang '${REMOTE_APP_NAME}' app\nsa iyong mga mobile device upang gamitin ang mga ito bilang mga 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.": "Kung nakadikit sa iyo ang isang malagkit na bomba, tumalon at paikutin. Baka \ni-alis mo ang bomba, o kung wala na ang iyong mga huling sandali ay nakakaaliw.", - "If you kill an enemy in one hit you get double points for it.": "Kung pumatay ka ng isang kalaban sa isang hit makakakuha ka ng dobleng puntos para dito.", - "If you pick up a curse, your only hope for survival is to\nfind a health powerup in the next few seconds.": "Kung nakatanggap ka ng sumpa, ang tanging pag-asa mo para mabuhay ay\nmaghanap ng health powerup sa susunod na ilang segundo.", - "If you stay in one place, you're toast. Run and dodge to survive..": "Kung manatili ka sa isang lugar, toasted ka. Tumakbo at umigtad para mabuhay..", - "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.": "Kung marami kang manlalaro na dumarating at pupunta, i-on ang 'auto-kick-ng-idle-na-manlalaro’\nsa ilalim ng mga setting kung sakaling may makakalimutang umalis sa laro.", - "If your device gets too warm or you'd like to conserve battery power,\nturn down \"Visuals\" or \"Resolution\" in Settings->Graphics": "Kung masyadong mainit ang iyong device o gusto mong makatipid ng baterya,\ni-down ang \"Biswal” o \"Resolusyon\" sa Mga Setting->Grapika", - "If your framerate is choppy, try turning down resolution\nor visuals in the game's graphics settings.": "Kung hindi mabuti ang iyong framerate, subukang ibaba ang resolusyon\no mga biswal sa mga setting ng grapika ng laro.", - "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.": "Sa Kunin-ang-Bandila, ang iyong sariling bandila ay dapat nasa iyong base para makaiskor, Kung ang isa\nang team ay malapit nang makapuntos, ang pagnanakaw ng kanilang bandila ay maaaring maging isang magandang paraan upang pigilan sila.", + "A perfectly timed running-jumping-spin-punch can kill in a single hit\nand earn you lifelong respect from your friends.": "Ang isang perpektong oras na takbo-talon-ikot-suntok ay maaaring makapatay sa isang tama\nat magkaroon ka ng panghabambuhay na paggalang mula sa iyong mga kaibigan.", + "Always remember to floss.": "Laging tandaan na magpasikat.", + "Create player profiles for yourself and your friends with\nyour preferred names and appearances instead of using random ones.": "Ang \"Sumpa\" ay ginagawa kang isang sasabuging bomba.\nAng tanging lunas ay ang mabilisang kumuha ng \"Medikal-Pakete\".", + "Curse boxes turn you into a ticking time bomb.\nThe only cure is to quickly grab a health-pack.": "Ang \"Sumpa\" ay ginagawa kang isang masasaboh na bomba.\nAng tanging lunas ay ang mabilisang kumuha ng health-pack.", + "Despite their looks, all characters' abilities are identical,\nso just pick whichever one you most closely resemble.": "Sa kabila ng kanilang hitsura, lahat ng kakayahan ng mga karakter ay magkaparehas lamang,\nkaya pumili lang kung alin ang pinakahawig mo.", + "Don't get too cocky with that energy shield; you can still get yourself thrown off a cliff.": "Huwag masyadong maangas sa paggalaw na mayroon kang enerhiyang kalasag; pwede ka pa rin nilang ihagis mula sa isang plataporma.", + "Don't run all the time. Really. You will fall off cliffs.": "Talagang huwag tumakbo sa lahat ng oras. Mahuhulog ka.", + "Don't spin for too long; you'll become dizzy and fall.": "Huwag paikutin Ang iyong sarili nang masyadong mahaba; mahihilo at matumba ka.", + "Hold any button to run. (Trigger buttons work well if you have them)": "Pindutin ang anumang pindutan upang tumakbo. (Mahusay na gumagana gamit ang mga pindutang kalabitan kung mayroon ka nito)", + "Hold down any button to run. You'll get places faster\nbut won't turn very well, so watch out for cliffs.": "Pindutin nang matagal ang anumang pindutan para tumakbo. Mas mabilis kang makarating\nngunit hindi lumiko nang mahusay, kaya mag-ingat sa matalim na mga gilid", + "Ice bombs are not very powerful, but they freeze\nwhoever they hit, leaving them vulnerable to shattering.": "Ang mga Bombang Yelo ay hindi masyadong malakas, ngunit makatigil at magyeyelo\nkung sino man ang kanilang natamaan na madaling maisira.", + "If someone picks you up, punch them and they'll let go.\nThis works in real life too.": "Kung ikaw ay nahawak, suntukin mo at bibitaw siya.\nNatunay ito sa totoong buhay.", + "If you are short on controllers, install the '${REMOTE_APP_NAME}' app\non your mobile devices to use them as controllers.": "Kung kulang ka sa mga controller, gamitin ang '${REMOTE_APP_NAME}'\nsa iyong mga gadyet upang gamitin ang mga ito bilang mga 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.": "Kung nakadikit sa iyo ang isang Bombang Malagkit, tumalon at umikot ka. Baka\nmaialis mo ang bomba mula sa iyong sarili, dili kaya'y nakakaaliw Ang iyong huling sandali.", + "If you kill an enemy in one hit you get double points for it.": "Kapag pumatay ka ng isang kalaban sa isang tama lamang, makakakuha ka ng dobleng puntos.", + "If you pick up a curse, your only hope for survival is to\nfind a health powerup in the next few seconds.": "Kung 'di sinadyabg nakakuha ka ng Sumpa, ang tanging pag-asa mo para mabuhay ay\nmaghanap ng Medikal-Pakete sa susunod na 5 segundo.", + "If you stay in one place, you're toast. Run and dodge to survive..": "Kung manatili ka sa isang lugar, wala ka na. Tumakbo at umigtad para mabuhay..", + "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.": "Kung marami kang manlalaro na dumarating at pupunta, buksan ang 'Ipalayas ang manlalarong hindi gumagalaw’\nsa ilalim ng mga setting kung sakaling may makakalimutang umalis sa laro.", + "If your device gets too warm or you'd like to conserve battery power,\nturn down \"Visuals\" or \"Resolution\" in Settings->Graphics": "Kung masyadong mainit ang iyong device o gusto mong makatipid ng baterya,\nhanggahan ang \"Biswal” o \"Resolusyon\" sa Mga Setting->Grapika", + "If your framerate is choppy, try turning down resolution\nor visuals in the game's graphics settings.": "Kung hindi mabuti ang iyong framerate, subukang ibaba ang resolusyon\no mga biswal sa grapika, mga setting.", + "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.": "Sa Kunin-ang-Bandila, ang iyong sariling bandila ay dapat nasa iyong istasyon para makaiskor, Kung ang isa\nang team ay malapit nang makapuntos, ang pagnanakaw ng kanilang bandila ay maaaring maging isang paraan upang pigilan ang iyon.", "In hockey, you'll maintain more speed if you turn gradually.": "Sa hockey, mapapanatili mo ang higit na bilis kung unti-unti kang lumiko.", "It's easier to win with a friend or two helping.": "mas madaling manalo sa tulong ng isa o dalawang kaibigan.", - "Jump just as you're throwing to get bombs up to the highest levels.": "Tumalon habang humagis upang makakuha ng mga bomba hanggang sa pinakamataas na antas.", - "Land-mines are a good way to stop speedy enemies.": "Ang mga mina ay isang mahusay na paraan upang pigilan ang mabilis na mga kalaban.", - "Many things can be picked up and thrown, including other players. Tossing\nyour enemies off cliffs can be an effective and emotionally fulfilling strategy.": "Maraming bagay ang maaaring kunin at ihagis, kabilang ang iba pang mga manlalaro. Paghahagis\nang iyong mga kalaban mula sa mga gilid ay maaaring maging isang epektibo at emosyonal na diskarte.", - "No, you can't get up on the ledge. You have to throw bombs.": "Hindi, hindi ka makakabangon sa pag-ungos. Kailangan mong maghagis ng bomba.", - "Players can join and leave in the middle of most games,\nand you can also plug and unplug controllers on the fly.": "Maaaring sumali at umalis ang mga manlalaro sa gitna ng karamihan ng mga laro,\nat maaari mo ring isaksak at i-unplug ang mga controller nang mabilis.", - "Practice using your momentum to throw bombs more accurately.": "Magsanay gamit ang iyong momentum para maghagis ng mga bomba nang mas tumpak.", - "Punches do more damage the faster your fists are moving,\nso try running, jumping, and spinning like crazy.": "Ang mga suntok ay mas nagdudulot ng saktan sa mas mabilis na paggalaw ng iyong mga kamay,\nkaya subukang tumakbo, tumalon, at umiikot na parang baliw.", + "Jump just as you're throwing to get bombs up to the highest levels.": "Tumalon habang humagis ng mga bomba upang makarating ito sa pinakamataas na antas.", + "Land-mines are a good way to stop speedy enemies.": "Ang mga mina ay isang mahusay na paraan upang pigilan ang mabilis na mga kalaban.", + "Many things can be picked up and thrown, including other players. Tossing\nyour enemies off cliffs can be an effective and emotionally fulfilling strategy.": "Maraming bagay ang maaaring kunin at ihagis, kabilang ang iba pang mga manlalaro. Paghahagis\nang iyong mga kalaban mula sa talampas ay maaaring maging isang epektibo at emosyonal na pagdiskarte.", + "No, you can't get up on the ledge. You have to throw bombs.": "Hindi ka makakahawak sa ungos. Kailangan mong maghagis ng bomba.", + "Players can join and leave in the middle of most games,\nand you can also plug and unplug controllers on the fly.": "Maaaring sumali at umalis ang mga manlalaro sa gitna ng karamihan ng mga laro,\nat maaari mo ring isaksak at magpatanggal ang mga controller nang mabilis.", + "Practice using your momentum to throw bombs more accurately.": "Magsanay gamit ang iyong pag-udyok para maghagis ng mga bomba nang mas wasto.", + "Punches do more damage the faster your fists are moving,\nso try running, jumping, and spinning like crazy.": "Ang mga suntok ay mas nagdudulot ng pagsaktan sa mas mabilis na paggalaw ng iyong mga kamao,\nkaya subukang tumakbo, tumalon, at umiikot na parang baliw.", "Run back and forth before throwing a bomb\nto 'whiplash' it and throw it farther.": "Patakbong pabalik-balik bago maghagis ng bomba\nupang ‘ma-ikot’ ito at ihagis ito nang mas malayo.", - "Take out a group of enemies by\nsetting off a bomb near a TNT box.": "Ilabas ang isang grupo ng mga kalaban sa pamamagitan ng\nnaglalagay ng bomba malapit sa isang TNT box.", - "The head is the most vulnerable area, so a sticky-bomb\nto the noggin usually means game-over.": "Ang ulo ay ang pinaka-mahina na lugar, kaya isang malagkit-bomba \nna lumapag sa ulo mo ay game-over na.", - "This level never ends, but a high score here\nwill earn you eternal respect throughout the world.": "Ang level na ito ay hindi kailanman nagtatapos, ngunit isang mataas na iskor dito\nbibigyan ka ng walang hanggang paggalang sa buong mundo.", - "Throw strength is based on the direction you are holding.\nTo toss something gently in front of you, don't hold any direction.": "Ang lakas ng paghagis ay batay sa direksyon na iyong hinahawakan.\nUpang ihagis ang isang bagay nang malumanay sa harap mo, huwag humawak sa anumang direksyon.", - "Tired of the soundtrack? Replace it with your own!\nSee Settings->Audio->Soundtrack": "Pagod na sa mga soundtrack? Palitan nito ng iyong sarili!\nTingnan ang Mga Setting->Audio->Soundtrack", - "Try 'Cooking off' bombs for a second or two before throwing them.": "Try mo ‘I-timing” ang mga bomba sa isang segundo o dalawa bag mo ihagis.", - "Try tricking enemies into killing eachother or running off cliffs.": "Subukang linlangin ang mga kalaban sa pagpatay sa isa't isa o pahulog sa mga gilid.", - "Use the pick-up button to grab the flag < ${PICKUP} >": "Gamitin ang pick-up button para kunin ang bandera < ${PICKUP} >", - "Whip back and forth to get more distance on your throws..": "Paikut-ikot upang makakuha ng higit na distansya sa iyong mga paghagis..", - "You can 'aim' your punches by spinning left or right.\nThis is useful for knocking bad guys off edges or scoring in hockey.": "Maaari mong 'itutok' ang iyong mga suntok sa pamamagitan ng pag-ikot pakaliwa o pakanan.\nIto ay kapaki-pakinabang para sa pagtanggal ng mga kalaban sa mga gilid o pag-iskor sa hockey.", - "You can judge when a bomb is going to explode based on the\ncolor of sparks from its fuse: yellow..orange..red..BOOM.": "Maaari mong hatulan kung kailan sasabog ang isang bomba batay sa\nkulay ng sparks mula sa fuse nito: Dilaw..Kahel..Pula..SABOG.", - "You can throw bombs higher if you jump just before throwing.": "Maaari kang maghagis ng mga bomba nang mas mataas kung tumalon ka bago ihagis.", - "You take damage when you whack your head on things,\nso try to not whack your head on things.": "Pwede kang masaktan kapag natamaan mo ang iyong ulo sa mga bagay,\nkaya't subukang huwag ipilit ang iyong ulo sa iyan.", - "Your punches do much more damage if you are running or spinning.": "Ang iyong mga suntok ay nagdudulot ng higit na damage kung ikaw ay tumatakbo o umiikot." + "Take out a group of enemies by\nsetting off a bomb near a TNT box.": "Magpatay ang isang grupo ng mga kalaban sa pamamagitan ng\npagsabog ng bomba malapit sa isang kahong TNT.", + "The head is the most vulnerable area, so a sticky-bomb\nto the noggin usually means game-over.": "Ang ulo ay ang pinakamatalban na parte, kaya isang Bombang Malagkit \nna lumapag sa ulo mo ay dedma ka na.", + "This level never ends, but a high score here\nwill earn you eternal respect throughout the world.": "Ang anyas na ito ay hindi kailanman nagtatapos, ngunit isang mataas na iskor dito\nbibigyan ka ng walang hanggang paggalang mula sa buong mundo.", + "Throw strength is based on the direction you are holding.\nTo toss something gently in front of you, don't hold any direction.": "Ang lakas ng paghagis ay batay sa direksyon na iyong hinahawakan.\nUpang ihagis ang isang bagay nang malumanay sa harap mo, huwag maitungo sa anumang direksyon.", + "Tired of the soundtrack? Replace it with your own!\nSee Settings->Audio->Soundtrack": "Pagod na sa mga soundtrack? Palitan nito ng iyong sarili!\nTingnan ang Mga Setting->Tutugan->Mga Soundtrack", + "Try 'Cooking off' bombs for a second or two before throwing them.": "Try mo \"Itiisin” ang mga bomba sa isang segundo o dalawa bago mo ihagis.", + "Try tricking enemies into killing eachother or running off cliffs.": "Subukang linlangin ang mga kalaban sa pagsuntok sa isa't isa o pahulog sa mga gilid.", + "Use the pick-up button to grab the flag < ${PICKUP} >": "Gamitin ang pindutang \"Pulutin\" para makunin ang bandera < ${PICKUP} >", + "Whip back and forth to get more distance on your throws..": "Paikut-ikot upang makakuha ng higit pang distansya sa iyong mga paghagis..", + "You can 'aim' your punches by spinning left or right.\nThis is useful for knocking bad guys off edges or scoring in hockey.": "Maaari mong 'itutok' ang iyong mga suntok sa pamamagitan ng pagtungo pakaliwa o pakanan.\nIto ay kapaki-pakinabang para sa pagtama sa mga kalaban sa mga gilid o pag-iskor sa hockey.", + "You can judge when a bomb is going to explode based on the\ncolor of sparks from its fuse: yellow..orange..red..BOOM.": "Maaari mong hatulan kung kailan sasabog ang isang bomba batay sa\nkulay ng tilamsik mula sa bungad nito: Dilaw..Kahel..Pula..BOOM.", + "You can throw bombs higher if you jump just before throwing.": "Maaari kang maihagis ng mga bomba nang mas mataas kung tumalon ka bago ihagis.", + "You take damage when you whack your head on things,\nso try to not whack your head on things.": "Pwede kang masaktan kapag natamaan mo ang iyong ulo sa mga bagay,\nkaya't subukang huwag matama ang iyong ulo sa iyan.", + "Your punches do much more damage if you are running or spinning.": "Ang iyong mga suntok ay nagdudulot ng higit pang saktan kung ikaw ay tumatakbo o umiikot." } }, "trophiesRequiredText": "Nangangailangan ito ng ${NUMBER} na trophies.", @@ -1787,32 +1800,32 @@ "trophiesThisSeasonText": "Mga Tropeo Ngayong Season", "tutorial": { "cpuBenchmarkText": "Pagpapatakbo ng tutorial sa nakakatawang bilis (pangunahing test sa bilis ng CPU)", - "phrase01Text": "Kamusta!", + "phrase01Text": "Kumusta!", "phrase02Text": "Maligayang pagdating sa ${APP_NAME}!", - "phrase03Text": "Narito ang ilang mga tip para sa pagkontrol ng iyong karakter:", + "phrase03Text": "Narito ang ilang mga paunawa para sa pagkontrol ng iyong karakter:", "phrase04Text": "Maraming bagay sa ${APP_NAME} ang nakabatay sa PISIKA", "phrase05Text": "Halimbawa, kapag sumuntok ka,..", - "phrase06Text": "..binase ang damage sa bilis ng mga kamay mo.", + "phrase06Text": "..nakabatay ang sakit sa bilis ng mga kamay mo.", "phrase07Text": "Kita mo? Hindi kami gumagalaw, kaya halos hindi nasaktan si ${NAME}", "phrase08Text": "Ngayon, tumalon at umikot tayo upang makakuha ng higit na bilis.", - "phrase09Text": "Ayan, mas maganda", + "phrase09Text": "Ayan, mas maayos.", "phrase10Text": "Ang pagtakbo ay nakakatulong din.", "phrase11Text": "Pindutin nang matagal ang KAHIT ANONG pindutan para tumakbo.", - "phrase12Text": "Para sa sobrang kahanga-hangang mga suntok, subukang tumakbo AT umikot.", - "phrase13Text": "Oops; pasensya na ${NAME}.", - "phrase14Text": "Maaari mong kunin at ihagis ang mga bagay tulad ng mga bandera.. o si ${NAME}.", - "phrase15Text": "Sa huli, meron pampasabog", - "phrase16Text": "Ang paghagis ng bomba ay kailangan may practice", - "phrase17Text": "Aray! Hindi napakahusay na hagis.", - "phrase18Text": "Ang paggalaw ay nakakatulong sa iyo na ihagis ng mas malayo.", - "phrase19Text": "Ang pagtalon tumutulong para matapon mo na masmataas", - "phrase20Text": "Ikot at ihagis ang iyong mga bomba para sa mas mahabang paghagis.", - "phrase21Text": "Ang pagtiming sa bomba ay pwedeng tricky", + "phrase12Text": "Para sa sobrang kumikirot na mga suntok, subukang tumakbo AT umikot.", + "phrase13Text": "Pasensya na pala, ${NAME}.", + "phrase14Text": "Maaari mong kunin at ihagis ang mga bagay tulad ng mga bandila.. o si ${NAME}.", + "phrase15Text": "Sa huli, meron pampasabog o bomba.", + "phrase16Text": "Nangangailangang sanayin ang paghagis ng bomba.", + "phrase17Text": "Aray! Hindi maayos na hagis.", + "phrase18Text": "Ang paggalaw ay nakakatulong maihagis ng mas malayo.", + "phrase19Text": "Ang pagtalon ay tumutulong para maihagis mo na masmataas", + "phrase20Text": "Umikot at ihagis ang iyong mga bomba para sa mas mahabang paghagis.", + "phrase21Text": "Pwede kang malito sa pagtiyempo ng iyong bomba.", "phrase22Text": "Aba.", - "phrase23Text": "Subukang “i-timing” fuse para sa isang segundo o dalawa.", - "phrase24Text": "Yay! Magaling na gawa.", + "phrase23Text": "Subukang “ipagtiyempo” ang bomba sa isang segundo o dalawa.", + "phrase24Text": "Yay! Lutong-luto!", "phrase25Text": "Well, hanggang doon lang.", - "phrase26Text": "Ngayon kunin mo sila, tigre!", + "phrase26Text": "Ngayon,talunin mo na sila!", "phrase27Text": "Alalahanin ang iyong pagsasanay, at babalik KANG buhay!", "phrase28Text": "...siguro...", "phrase29Text": "Paalam!", @@ -1827,7 +1840,8 @@ "toSkipPressAnythingText": "(i-tap o pindutin ang anuman para laktawan ang tutorial)" }, "twoKillText": "DOBLENG PAGPATAY!!", - "unavailableText": "hindi available", + "uiScaleText": "UI Scale", + "unavailableText": "hindi pwede", "unconfiguredControllerDetectedText": "Naktuklas ang hindi naka-configure na controller:", "unlockThisInTheStoreText": "Ito ay dapat na naka-unlock sa tindahan.", "unlockThisProfilesText": "Upang lumikha ng higit sa ${NUM} na mga profile, kailangan mo:", @@ -1836,10 +1850,12 @@ "unsupportedHardwareText": "Pasensya na, ang hardware na ito ay hindi suportado ng build na ito ng laro.", "upFirstText": "Bumangon muna:", "upNextText": "Susunod sa larong ${COUNT}:", - "updatingAccountText": "Ina-update ang iyong account...", + "updatingAccountText": "Pinababago ang iyong account...", "upgradeText": "I-upgrade", "upgradeToPlayText": "I-unlock ang \"${PRO}\" sa in-game store upang i-play ito.", "useDefaultText": "Gamitin ang default", + "userSystemScriptsCreateText": "lumikha ng mga script ng system ng gumagamit", + "userSystemScriptsDeleteText": "tanggalin ang mga script ng system ng gumagamit", "usesExternalControllerText": "Gumagamit ang larong ito ng external na controller para sa input.", "usingItunesText": "Paggamit ng Music App para sa soundtrack...", "v2AccountLinkingInfoText": "Upang ma-link ang mga V2 account mo, dumeretso ka sa “I-Manage ang Account“.", @@ -1874,7 +1890,7 @@ "titleText": "Manood", "watchReplayButtonText": "Ipanood ang\nReplay" }, - "waveText": "Kaway", + "waveText": "Yugtong", "wellSureText": "Oo Syempre!", "whatIsThisText": "Ano ito?", "wiimoteLicenseWindow": { @@ -1897,9 +1913,9 @@ "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", + "worldScoresUnavailableText": "Ang mga score sa buong Mundo ay hindi makuha.", "worldsBestScoresText": "Pinakamahusay na Iskor ng Mundo", - "worldsBestTimesText": "Oras ng Pinakamahusay sa Mundo", + "worldsBestTimesText": "Oras ng Pinakamabilis sa Mundo", "xbox360ControllersWindow": { "getDriverText": "Kunin ang Driver", "macInstructions2Text": "Upang gumamit ng mga controller nang wireless, kakailanganin mo rin ang receiver na iyon\nay kasama ang 'Xbox 360 Wireless Controller para sa Windows'.\nPinapayagan ka ng isang receiver na kumonekta hanggang sa 4 na controllers.\n\nMahalaga: Ang mga 3rd-party na receiver ay hindi gagana sa driver na ito;\ntiyaking 'Microsoft' ang nakasulat dito sa iyong receiver, hindi 'XBOX 360'.\nHindi na ibinebenta ng Microsoft ang mga ito nang hiwalay, kaya kakailanganin mong makuha\nyung naka-bundle sa controller or else search ebay.\n\nKung sa tingin mo ay kapaki-pakinabang ito, mangyaring isaalang-alang ang isang donasyon sa\ndeveloper ng driver sa kanilang site.", diff --git a/dist/ba_data/data/languages/french.json b/dist/ba_data/data/languages/french.json index 18c4ce2..f27a008 100644 --- a/dist/ba_data/data/languages/french.json +++ b/dist/ba_data/data/languages/french.json @@ -621,6 +621,7 @@ "localProfileText": "(profil local)", "nameDescriptionText": "Nom du Joueur", "nameText": "Nom", + "profileAlreadyExistsText": "Un profile avec ce nom existe deja", "randomText": "aléatoire", "titleEditText": "Éditer ce Profil", "titleNewText": "Nouveau Profil", @@ -708,6 +709,8 @@ "editText": "Modifier\nLa Playlist", "gameListText": "Liste de jeu", "newText": "Nouvelle\nPlaylist", + "pointsToWinText": "Point pour gagner", + "seriesLengthText": "Longueur de la série", "showTutorialText": "Voir le Tutoriel", "shuffleGameOrderText": "Mélanger l'ordre des jeux", "titleText": "Personnaliser les Playlists ${TYPE}" @@ -1346,8 +1349,9 @@ "helpTranslateText": "Les traductions de ${APP_NAME} proviennent des efforts de \nla communauté. Si vous voulez contribuer ou corriger une \ntraduction, suivez le lien ci-dessous. Merci d'avance!", "kickIdlePlayersText": "Déconnecter les joueurs inactifs", "kidFriendlyModeText": "Mode Enfant-Gentil (moins de violence, etc)", - "languageText": "Langage", + "languageText": "Langue", "moddingGuideText": "Guide pour Modder", + "moddingToolsText": "Outils de mod", "mustRestartText": "Vous devez redémarrer le jeu pour que les changements prennent effet.", "netTestingText": "Tester Votre Réseau", "resetText": "Réinitialiser", @@ -1373,6 +1377,9 @@ "signInWithGameCenterText": "Pour l'utilisation d'un compte Game \nCenter, connectez-vous avec l'application Game Center.", "singleGamePlaylistNameText": "Seulement ${GAME}", "singlePlayerCountText": "1 joueur", + "sizeLargeText": "Large", + "sizeMediumText": "Moyen", + "sizeSmallText": "Petit", "soloNameFilterText": "${NAME} Solo", "soundtrackTypeNames": { "CharSelect": "Sélection du Personnage", @@ -1927,6 +1934,7 @@ "toSkipPressAnythingText": "(appuyez n'importe où pour passer le tutoriel)" }, "twoKillText": "DOUBLE MEURTRE!", + "uiScaleText": "Échelle UI", "unavailableText": "indisponible", "unconfiguredControllerDetectedText": "Contrôleur non-configuré détecté:", "unlockThisInTheStoreText": "Cela doit être débloqué dans le magasin.", diff --git a/dist/ba_data/data/languages/german.json b/dist/ba_data/data/languages/german.json index 80f3787..ae233de 100644 --- a/dist/ba_data/data/languages/german.json +++ b/dist/ba_data/data/languages/german.json @@ -1,6 +1,6 @@ { "accountSettingsWindow": { - "accountNameRules": "Der Konto Name darf kein Emoji oder andere spezielle Buchstaben enthalten.", + "accountNameRules": "Der Konto Name darf kein Emoji oder andere spezielle Zeichen enthalten", "accountProfileText": "Benutzerprofil", "accountsText": "Konten", "achievementProgressText": "Erfolge: ${COUNT} von ${TOTAL}", @@ -340,6 +340,8 @@ "titleText": "Spiel hinzufügen", "titleTextScale": 1.0 }, + "addToFavoritesText": "Zu Favoriten hinzufügen", + "addedToFavoritesText": "'${NAME}' zu Favoriten hinzugefügt.", "allText": "Alle", "allowText": "Erlauben", "alreadySignedInText": "Dein Account wird schon von einem anderen Gerät verwendet;\nbitte wechsle den Account oder schließe das Spiel auf\ndeinem anderen Gerät und versuche es nochmal.", @@ -589,6 +591,7 @@ "disableXInputDescriptionText": "Erlaubt mehr als 4 Controller aber kann schlechter funktionieren.", "disableXInputText": "XInput deaktivieren", "disabledText": "Deaktiviert", + "discordFriendsText": "Suchst du neue Leute mit denen du spielen kannst? Trete unserem Discord bei und finde neue Freunde!", "doneText": "Fertig", "drawText": "Unentschieden", "duplicateText": "dublizieren", @@ -623,6 +626,7 @@ "localProfileText": "Lokales Profil", "nameDescriptionText": "Spielername", "nameText": "Name", + "profileAlreadyExistsText": "Ein Profil mit diesem Namen existiert schon.", "randomText": "zufällig", "titleEditText": "Profil bearbeiten", "titleNewText": "Neues Profil", @@ -710,6 +714,7 @@ "editText": "Playlist\nbearbeiten", "gameListText": "Spieleliste", "newText": "Neue\nPlaylist", + "pointsToWinText": "Punkte um zu Gewinnen", "showTutorialText": "Tutorial anzeigen", "shuffleGameOrderText": "Zufällige Spielreihenfolge", "titleText": "${TYPE}-Playlists bearbeiten" @@ -867,6 +872,7 @@ "autoText": "Automatisch", "fullScreenCmdText": "Vollbildmodus (Cmd-F)", "fullScreenCtrlText": "Vollbildmodus (Strg-F)", + "fullScreenText": "Vollbild", "gammaText": "Gamma", "highText": "Hoch", "higherText": "Höher", @@ -1138,6 +1144,7 @@ "noJoinCoopMidwayText": "Koop-Spiele können nicht während des Spiels betreten werden.", "noProfilesErrorText": "Du hast kein Spielerprofil, deshalb ist dein Name \"${NAME}\".\nGehe zu Einstellungen->Spielerprofile um ein Profil anzulegen.", "noScoresYetText": "Noch kein Punktestand.", + "noServersFoundText": "Keine Server gefunden.", "noThanksText": "Nein Danke", "noTournamentsInTestBuildText": "WARNUNG: Turnierpunkte von dieser Testversion werden ignoriert.", "noValidMapsErrorText": "Keine gültigen Karten für diesen Spieltyp gefunden.", @@ -1383,6 +1390,9 @@ "signInWithGameCenterText": "Nutze die Game Center app,\num dich anzumelden.", "singleGamePlaylistNameText": "Nur ${GAME}", "singlePlayerCountText": "1 Spieler", + "sizeLargeText": "Groß", + "sizeMediumText": "Mittel", + "sizeSmallText": "Klein", "soloNameFilterText": "Solo ${NAME}", "soundtrackTypeNames": { "CharSelect": "Charakterauswahl", diff --git a/dist/ba_data/data/languages/gibberish.json b/dist/ba_data/data/languages/gibberish.json index afd5b6b..0e2b081 100644 --- a/dist/ba_data/data/languages/gibberish.json +++ b/dist/ba_data/data/languages/gibberish.json @@ -1,7 +1,7 @@ { "accountRejectedText": "You ac woefije obj acwoew. Aj cowier wore cs?", "accountSettingsWindow": { - "accountNameRules": "Acoief coej. \n woejf. cwoef ocoweofwjfj c wjefowfowef wocjowefffz", + "accountNameRules": "Acoief coej. woejf. cwoef ocoweofwjfj c wjefowfowef wocjoweffz", "accountProfileText": "(acczntl prfflzlf)", "accountsText": "Acctntzz", "achievementProgressText": "Achilfjasdflz: ${COUNT} ouzt of ${TOTAL}", @@ -28,6 +28,7 @@ "setAccountNameDesc": "Selcow f cjwo ot afoa fw jcpaoewj fwocowefj.\nYou wolf wc. weowyc oawe awoefm mcapowefi \ncoat aocjweo towirowmcowiefownr.", "signInInfoText": "Sgn inz tz strz yr prograrzz inz\nthz cld, rnzr tckrz, rnz", "signInText": "Sggnz Inz", + "signInWithAnEmailAddressText": "Sif c woe w oo wefwioje ofiewj dfjdf", "signInWithDeviceInfoText": "(an coiwjfow fcoa cowj efj woj cowij eofjwoj foijs aofij )", "signInWithDeviceText": "Sngi foicj oj de voa cwoiejfw", "signInWithGameCircleText": "Sgn in gh Gm Cirflc", @@ -585,6 +586,7 @@ "demoText": "Dmfwef", "denyText": "Dénziy", "deprecatedText": "Dowefowicjwer", + "descriptionText": "Djcowiejfwdf", "desktopResText": "Dzlflfjz Rzflz", "deviceAccountUpgradeText": "Wowierj:\nWofiwj c weojfw duo wdjfo weir are(${NAME}).\nDowjc weowje r ofjowei fjwoeodjfowdofijwodfjf.\nUpeworwe coweoijwV2 ocwoe roweijroidjjfj owejowejd.", "difficultyEasyText": "Ezrz", @@ -610,8 +612,10 @@ "gameListText": "Gámé Lzúst", "listNameText": "Plzljlfz Nmflf", "nameText": "Néáme", + "pointsToWinText": "JWP er OWejrd", "removeGameText": "Rzmóve\nGzmze", "saveText": "Sézve Lzést", + "seriesLengthText": "S oere cowejrd", "titleText": "Plzltls Edirtzlr" }, "editProfileWindow": { @@ -632,6 +636,7 @@ "localProfileText": "(lcllf proriocf)", "nameDescriptionText": "Pléyzr Náme", "nameText": "Núme", + "profileAlreadyExistsText": "We c weir wet fj c where jggafjw fweworer jdfdf.", "randomText": "rándzm", "titleEditText": "Edít Prófílze", "titleNewText": "Néw Prófílze", @@ -719,6 +724,8 @@ "editText": "Ezltjf\nPlaflfzlz", "gameListText": "Gzéme Lízt", "newText": "Núw\nPlzlrlrzl", + "pointsToWinText": "Peer c word fdjfw", + "seriesLengthText": "Soft cwojerr", "showTutorialText": "Shzwlf Trltlrjlzjf", "shuffleGameOrderText": "Shúfflze Gáme Oéder", "titleText": "Cmtlajrz ${TYPE} Plzlsfwz" @@ -764,7 +771,7 @@ "friendPromoCodeInstructionsText": "To Us ocj , ofijwe ${APP_NAME} oafn aco\"segoingaf- aocij weoifjwoe fowiejoijcowijcoiwjef.\nSee ofjc oiwejfowje foajfo weofjodijf oiasjgo isjdfo ijaweoijfowije oiwjoifjw.", "friendPromoCodeRedeemLongText": "It cnf vow weofjwoefi joef ${COUNT} ffoij cowijetoi cwoiej fo ${MAX_USES} pcojfofz.", "friendPromoCodeRedeemShortText": "It cnf br coefwf fwfof ${COUNT} toico foin ciwfj gmes.", - "friendPromoCodeWhereToEnterText": "(inz \"Settdfings->Adwefwced->Eftcer Codze\")", + "friendPromoCodeWhereToEnterText": "(inz \"Settdfings->Adwefwced->Eftcder Codze\")", "getFriendInviteCodeText": "Gz Frjor Infivo Cdz", "googlePlayDescriptionText": "Invlt Gglglz Plzlz plzlfer tz yzr prtaryz:", "googlePlayInviteText": "Invtlzz", @@ -1345,6 +1352,7 @@ }, "scoreWasText": "(wús ${COUNT})", "selectText": "Slézcz", + "sendInfoDescriptionText": "Snff ac c woof wc owei. jf;woef; a gwoiefj.\ncc c woe cw Rowe orijwg gwpofij wpeoifjwefd.", "seriesWinLine1PlayerText": "WZNNPLL THZ", "seriesWinLine1Scale": 0.65, "seriesWinLine1TeamText": "WNZTTTM THZ", @@ -1368,6 +1376,7 @@ "alwaysUseInternalKeyboardDescriptionText": "(a smpd a, condofia efjwefoajewofoiaj;s a sodifj a;osdfjoitest f)", "alwaysUseInternalKeyboardText": "Alzlt usl Intenralf kyBerz", "benchmarksText": "Bnchmkwr & Staf-Tsfsfz", + "devToolsText": "Dow Aocjwe", "disableCameraGyroscopeMotion": "Disa fe come won aweGowyeo CMosndf", "disableCameraGyroscopeMotionText": "Dislike og wefo cwei oweiaMowerdns", "disableCameraShakeText": "SDi Cmanan Blobofs", @@ -1381,11 +1390,14 @@ "kidFriendlyModeText": "Krz-Frjijglfz-Mzdz (rdfjifz voioifjf, fz)", "languageText": "Lnglfjslfd", "moddingGuideText": "Mddzng Gdufnzsd", + "moddingToolsText": "Meowed eFwefwf", "mustRestartText": "Yz msg restar thz gmm fof this tk ttk effectsz.", "netTestingText": "Ntwkrz Tsstcg", "resetText": "Rsttz", + "sendInfoText": "Show cowdf", "showBombTrajectoriesText": "Shzlz Bomf Tfwoejcwoefz", "showDemosWhenIdleText": "Sho cwoefj c wdofdfjdfwf", + "showDeprecatedLoginTypesText": "Show woe fjwoc woejoweo fwef", "showDevConsoleButtonText": "Sho c weroiw c wo cwoije fwois", "showInGamePingText": "Shoe o co fowl Png", "showPlayerNamesText": "SHzlfjl Plzlrr Nmzlzlls", @@ -1406,6 +1418,9 @@ "signInWithGameCenterText": "To loci wo gamg wofiw efoiwjef.\ncoaej goajb oaj Game foc etapp.", "singleGamePlaylistNameText": "Jzff ${GAME}", "singlePlayerCountText": "1 pláyzr", + "sizeLargeText": "Lwfefwdf", + "sizeMediumText": "MFwefwf", + "sizeSmallText": "WSDfjwf", "soloNameFilterText": "${NAME} Sólzo", "soundtrackTypeNames": { "CharSelect": "Czárazter Sélection", @@ -1961,6 +1976,7 @@ "toSkipPressAnythingText": "(tpz rz pzrzl anthfljzf tz skpfz tuzfjrlrrz)" }, "twoKillText": "DÓÚBLZ KÍLL!", + "uiScaleText": "SUFwfef", "unavailableText": "unavlfldsfjlbz", "unconfiguredControllerDetectedText": "Uncónfzgúred cúntrzllír dítzctíd:", "unlockThisInTheStoreText": "Thz mf voi eunlcoef owef joiefsfrwe.", @@ -1974,6 +1990,8 @@ "upgradeText": "Upgrorz", "upgradeToPlayText": "Upgglf tz \"${PRO}\" iz j wojcwoef paljf zs.", "useDefaultText": "Uzl Dflfjtzlz", + "userSystemScriptsCreateText": "Cwjof come rw dfoodwijfdf", + "userSystemScriptsDeleteText": "Down wc old. owiejf odfdf", "usesExternalControllerText": "Thz gjf coiwjef oif owicoiwefj owejf owejoojof", "usingItunesText": "Ufwefw Mfwoef co ef srnweoicjowe...", "usingItunesTurnRepeatAndShuffleOnText": "Plzelz mkdk srzlc shfflds isON anz andpreld is ALZ unz iTunes", diff --git a/dist/ba_data/data/languages/greek.json b/dist/ba_data/data/languages/greek.json index c3d1f54..98b9a95 100644 --- a/dist/ba_data/data/languages/greek.json +++ b/dist/ba_data/data/languages/greek.json @@ -30,6 +30,7 @@ "signInWithGooglePlayText": "Σύνδεση με Google Play", "signInWithTestAccountInfoText": "(προσωρινός λογαριασμός. Δημιουργήστε λογαριασμό συσκευής για συνέχιση προόδου)", "signInWithTestAccountText": "Σύνδεση με δοκιμαστικό λογαριασμό", + "signInWithText": "Σύνδεση μέσω ${SERVICE}", "signInWithV2InfoText": "ένας λογαριασμός που λειτουργεί σε όλες τις πλατφορμες", "signInWithV2Text": "Συνδεθείτε με ένα λογαριασμό BombSquad", "signOutText": "Αποσύνδεση", @@ -333,6 +334,9 @@ "getMoreGamesText": "Περισσότερα Παιχνίδια...", "titleText": "Προσθήκη Παιχνιδιού" }, + "addToFavoritesText": "Προσθήκη στα αγαπημένα", + "addedToFavoritesText": "Προστέθηκε το '${NAME}' στα αγαπημένα.", + "allText": "Όλα", "allowText": "Να Επιτρέπεται", "alreadySignedInText": "Ο λογαριασμός σας είναι συνδεδεμένος από άλλη συσκευή.\nΠαρακαλώ, άλλαξε το λογαριασμό σας ή απενεργοποίησε \nτο παιχνίδι από τις άλλες συσκευες σας και ξαναπροσπάθηστε.", "apiVersionErrorText": "Can't load module ${NAME}; it targets api-version ${VERSION_USED}; we require ${VERSION_REQUIRED}.", @@ -365,6 +369,7 @@ "chatMutedText": "Συζήτηση σε Σίγαση", "chatUnMuteText": "Απενεργοποίηση Σίγασης", "choosingPlayerText": "<επιλογή παίκτη>", + "codesExplainText": "Οι κωδικοί παρέχονται από τον δημιουργό για τη\nδιάγνωση και την επιδιόρθωση προβλημάτων λογαριασμού.", "completeThisLevelToProceedText": "Πρέπει να ολοκληρώσετε αυτό\nτο επίπεδο για να προχωρήσετε!", "completionBonusText": "Μπόνους Ολοκλήρωσης", "configControllersWindow": { @@ -445,6 +450,7 @@ "swipeText": "swipe", "titleText": "Οριστικοποίηση Οθόνης Αφής" }, + "configureDeviceInSystemSettingsText": "Η συσκευή ${DEVICE} μπορεί να προσαρμοστεί απο τις ρυθμίσεις της συσκευής σας.", "configureItNowText": "Οριστικοποίηση τώρα;", "configureText": "Οριστικοποίηση", "connectMobileDevicesWindow": { @@ -558,6 +564,9 @@ "disableRemoteAppConnectionsText": "Απενεργοποίηση Συνδέσεων Ασύρματης Εφαρμογής", "disableXInputDescriptionText": "Επιτρέπει περισσότερα από 4 χειριστήρια αλλά μπορεί να μη λειτουργήσει.", "disableXInputText": "Απενεργοποίηση XIinput", + "disabledText": "Απενεργοποιημένο", + "discordFriendsText": "Θέλετε να βρείτε νέους φίλους για να παίξετε;\nΜπείτε στο Discord μας και βρείτε νέους φίλους!", + "discordJoinText": "Μπείτε στο Discord", "doneText": "Έγινε", "drawText": "Ισοπαλία", "duplicateText": "Διπλοτυπία", @@ -590,6 +599,7 @@ "localProfileText": "(τοπικό προφίλ)", "nameDescriptionText": "Όνομα Παίκτη", "nameText": "Όνομα", + "profileAlreadyExistsText": "Υπάρχει ήδη προφίλ με αυτό το όνομα.", "randomText": "τυχαίο", "titleEditText": "Επεξεργασία Προφίλ", "titleNewText": "Νέο Προφίλ", @@ -625,6 +635,7 @@ "useMusicFolderText": "Φάκελος Αρχείων Μουσικής" }, "editText": "Επεξεργασία", + "enabledText": "Ενεργοποιημένο", "endText": "Τέλος", "enjoyText": "Απολαύστε!", "epicDescriptionFilterText": "${DESCRIPTION} Σε επικά αργή κίνηση.", @@ -670,6 +681,8 @@ "duplicateText": "Διπλοτυπία\nΛίστας", "editText": "Επεξεργασία\nΛίστας", "newText": "Νέα\nΛίστα", + "pointsToWinText": "Πόντοι Έως Νίκη", + "seriesLengthText": "Μήκος Σειράς", "showTutorialText": "Προβολή Εκπαιδευτικού Βίντεο", "shuffleGameOrderText": "Ανακάτεμα Ουράς Παιχνιδιών", "titleText": "Προσαρμογή Λίστων Παιχνιδιών ${TYPE}" @@ -742,6 +755,7 @@ "manualYourLocalAddressText": "Η τοπική σας διεύθυνση:", "nearbyText": "Κοντινά", "noConnectionText": "<εκτός σύνδεσης>", + "noPartiesAddedText": "Δεν Προστέθηκαν Πάρτι", "otherVersionsText": "(άλλες εκδόσεις)", "partyCodeText": "Κωδικός Party", "partyInviteAcceptText": "Αποδοχή", @@ -813,6 +827,7 @@ "alwaysText": "Πάντα", "fullScreenCmdText": "Πλήρης Οθόνη (Cmd-F)", "fullScreenCtrlText": "Πλήρης Οθόνη (Ctrl-F)", + "fullScreenText": "Πλήρης Οθόνη", "gammaText": "Gamma", "highText": "Υψηλό", "higherText": "Υψηλότερο", @@ -1059,7 +1074,9 @@ "noContinuesText": "(χωρίς \"Συνέχιση\")", "noExternalStorageErrorText": "Δεν βρέθηκε εξωτερικός αποθηκευτικός χώρος σε αυτή τη συσκευή", "noGameCircleText": "Σφάλμα: δεν έχετε συνδεθεί στο GameCircle", + "noPluginsInstalledText": "Καθόλου Πρόσθετα Εγκατεστημένα", "noScoresYetText": "Δεν υπάρχουν βαθμολογίες ακόμη.", + "noServersFoundText": "Δεν βρέθηκαν διακομηστές.", "noThanksText": "Όχι Ευχαριστώ", "noTournamentsInTestBuildText": "ΠΡΟΣΟΧΗ: Τα σκορ του τουρνουά από αυτή τη δοκιμαστική έκδοση θα αγνοηθούν.", "noValidMapsErrorText": "Δεν βρέθηκαν έγκυροι χάρτες γι' αυτόν τον τύπο παιχνιδιού.", @@ -1226,6 +1243,7 @@ "runText": "Τρέξιμο", "saveText": "Αποθήκευση", "scanScriptsErrorText": "Σφάλμα(α) κατά τη σάρωση σεναρίων. Δείτε το αρχείο καταγραφής για λεπτομέρειες.", + "scanScriptsMultipleModulesNeedUpdatesText": "${PATH} και ${NUM} άλλη(ες) λειτουργείες χρειάζεται(ονται) για το api ${API}.", "scanScriptsSingleModuleNeedsUpdatesText": "Το ${PATH} χρειάζεται να αναβαθμιστεί για το api ${API}", "scoreChallengesText": "Προκλήσεις Βαθμολογίας", "scoreListUnavailableText": "Λίστα βαθμολογίας μη διαθέσιμη.", @@ -1266,10 +1284,12 @@ "kidFriendlyModeText": "Λειτουργία για Παιδιά (μειωμένη βία, κτλ)", "languageText": "Γλώσσα", "moddingGuideText": "Οδηγός Τροποποίησης", + "moddingToolsText": "Εργαλεία Τροποποίησης", "mustRestartText": "Για να λειτουργήσει, πρέπει να επανεκκινήσετε το παιχνίδι.", "netTestingText": "Έλεγχος Δικτύου", "resetText": "Επαναφορά", "showBombTrajectoriesText": "Εμφάνιση Πορείας Βόμβας", + "showDemosWhenIdleText": "Προβολή Ντέμο Σε Αδράνεια", "showDevConsoleButtonText": "Εμφάνιση κονσόλας προγραμματιστών", "showInGamePingText": "Εμφάνιση Καθυστέρησης Εντός-Παιχνιδιού", "showPlayerNamesText": "Προβολή Ονομάτων Παικτών", @@ -1290,6 +1310,9 @@ "signInWithGameCenterText": "Για να χρησιμοποιήσετε έναν λογαριασμό Game Center,\nσυνδεθείτε σε αυτόν με την εφαρμογή Game Center.", "singleGamePlaylistNameText": "Μόνο ${GAME}", "singlePlayerCountText": "1 παίκτης", + "sizeLargeText": "Μεγάλο", + "sizeMediumText": "Μέτριο", + "sizeSmallText": "Μικρό", "soloNameFilterText": "Σόλο ${NAME}", "soundtrackTypeNames": { "CharSelect": "Επιλογή Χαρακτήρα", @@ -1362,6 +1385,8 @@ "storeText": "Κατάστημα", "submitText": "Υποβολή", "submittingPromoCodeText": "Υποβολή Κωδικού...", + "successText": "Επιτυχία!", + "supportEmailText": "Εάν αντιμετωπίζετε προβλήματα με την εφαρμογή, παρακαλώ να στείλετε e-mail \nστο ${EMAIL}", "teamNamesColorText": "Ονόματα/Χρώμματα Ομάδων...", "telnetAccessGrantedText": "Πρόσβαση telnet ενεργοποιημένη.", "telnetAccessText": "Πρόσβαση telnet εντοπίστηκε. Να επιτρέπεται;", @@ -1810,11 +1835,13 @@ "toSkipPressAnythingText": "(πατήστε ή πιέστε οτιδήποτε για να παραλείψετε το εκπαιδευτικό βίντεο)" }, "twoKillText": "ΔΙΠΛΟΣ ΦΟΝΟΣ!", + "uiScaleText": "Κλίμακα UI", "unavailableText": "μη διαθέσιμο", "unconfiguredControllerDetectedText": "Εντοπίστηκε μη διαμορφωμένο χειριστήριο:", "unlockThisInTheStoreText": "Αυτό πρέπει να ξεκλειδωθεί στο κατάστημα.", "unlockThisProfilesText": "Για να δημιουργήσετε περισσότερα από ${NUM} προφίλ, χρειάζεστε:", "unlockThisText": "Για να το ξεκλειδώσετε, χρειάζεστε:", + "unsupportedControllerText": "Συγνώμη, το χειρηστήριο \"${NAME}\" δεν υποστηρίζεται.", "unsupportedHardwareText": "Συγνώμη, αυτό το υλισμικό δεν υποστηρίζεται από αυτή την έκδοση του παιχνιδιού.", "upFirstText": "Για αρχή:", "upNextText": "Στη συνέχεια, παιχνίδι ${COUNT}:", @@ -1822,10 +1849,13 @@ "upgradeText": "Αναβάθμιση", "upgradeToPlayText": "Ξεκλειδώστε το \"${PRO}\" στο κατάστημα του παιχνιδιού για να μπορείτε να το παίξετε.", "useDefaultText": "Χρήση Προκαθορισμένων", + "userSystemScriptsCreateText": "Δημιουργία Κωδικών Συστήματος Χρήστη", + "userSystemScriptsDeleteText": "Διαγραφή Κωδικών Συστήματος Χρήστη", "usesExternalControllerText": "Αυτό το παιχνίδι χρησιμοποιεί ένα εξωτερικό χειριστήριο για είσοδο.", "usingItunesText": "Χρήση εφαρμογής μουσικής για ηχητική υπόκρουση...", "v2AccountLinkingInfoText": "Για να δεσμεύσετε λογαριασμούς V2, χρησιμοιποιήστε το κουμπί 'Διαχείριση Λογαριασμού'.", "validatingTestBuildText": "Επικύρωση Δοκιμαστικής Έκδοσης...", + "viaText": "μέσω", "victoryText": "Νίκη!", "voteDelayText": "Δεν μπορείτε να ξαναξεκινήσετε ψηφοφορία για ${NUMBER} δευτ.", "voteInProgressText": "Μια ψηφοφορία βρίσκεται ήδη σε εξέλιξη.", diff --git a/dist/ba_data/data/languages/hindi.json b/dist/ba_data/data/languages/hindi.json index be1cdc0..ea5a1bf 100644 --- a/dist/ba_data/data/languages/hindi.json +++ b/dist/ba_data/data/languages/hindi.json @@ -31,7 +31,7 @@ "signInWithGooglePlayText": "गूगल प्ले से साईन ईन करे", "signInWithTestAccountInfoText": "(पुराना खाते का प्ररूप; आगे के लिए यंत्र खाते का प्रयोग करें)", "signInWithTestAccountText": "परीक्षण के खाते से साइन इन करें", - "signInWithText": "${service} के साथ साइन इन करें", + "signInWithText": "${SERVICE} से साइन इन करे।", "signInWithV2InfoText": "(एक खाता जो सभी प्लेटफार्मों पर काम करता है)", "signInWithV2Text": "BombSquad खाते से साइन इन करें", "signOutText": "साइन आउट", @@ -47,7 +47,7 @@ "unlinkLegacyV1AccountsText": "लैगेसी (V1) खाते को एनलिंक करे", "v2LinkInstructionsText": "खाता बनाने या साइन इन करने के लिए इस लिंक का उपयोग करें।", "viaAccount": "(खाता ${NAME} के माध्यम से)", - "youAreSignedInAsText": "आप इस खाते से साइनड इन हो: " + "youAreSignedInAsText": "आप इस खाते से साइनड इन हो:" }, "achievementChallengesText": "उपलब्धि की चुनौतियां", "achievementText": "उपलब्धि", @@ -338,8 +338,8 @@ "getMoreGamesText": "और गेम्स कि जानकारी पायें", "titleText": "गेम जोड़ें" }, - "addToFavoritesText": "पसंदीदा में जोड़े", - "addedToFavoritesText": "\"${NAME}\"को पसंदीदा में जोड़ा गया।", + "addToFavoritesText": "पसंदीदा में जोड़े।", + "addedToFavoritesText": "'${NAME}' को पसंदीदा में जोड़ा गया हैं।", "allText": "सभी", "allowText": "अनुमति दें", "alreadySignedInText": "आपका खाता किसी अन्य डिवाइस से साइन किया गया है; \nकृपया खातों को स्विच करें या अपने गेम को अन्य डिवाइस \nपर बंद करें और फिर से प्रयास करें", @@ -373,7 +373,7 @@ "chatMutedText": "बातचीत मौन हो गई है", "chatUnMuteText": "बातचीत दोबारा शुरू करें", "choosingPlayerText": "<खिलाड़ी चुना जा रहा है>", - "codesExplainText": "डेवलपर द्वारा कोड प्रदान किए जाते हैं\nखाता समस्याओं का निदान और सुधार करें।", + "codesExplainText": "खाता समस्याओं के निदान और सुधार के लिए डेवलपर \nद्वारा कोड प्रदान किए जाते हैं ।", "completeThisLevelToProceedText": "आपको यह पड़ाव पार करना पड़ेगा आगे बढ़ने के लिए !", "completionBonusText": "पूर्णता पुरस्कार", "configControllersWindow": { @@ -454,7 +454,7 @@ "swipeText": "स्वाइप", "titleText": "टच स्क्रीन को कॉन्फ़िगर करें" }, - "configureDeviceInSystemSettingsText": "${DEVICE}को सिस्टम सेटिंग्स ऐप में कॉन्फ़िगर किया जा सकता है।", + "configureDeviceInSystemSettingsText": "${DEVICE} को सिस्टम सेटिंग्स एप में कंफीगर कर सकते हैं।", "configureItNowText": "अभी कॉन्फ़िगर करें ?", "configureText": "कॉन्फ़िगर", "connectMobileDevicesWindow": { @@ -466,7 +466,7 @@ "forIOSText": "आई-ओ-एस के लिए:", "getItForText": "${REMOTE_APP_NAME} पायें आई-ओ-एस के लिए एप्पल एप्लीकेशन भंडार से \nव एंड्राइड के लिए गूगल प्ले भंडार या अमेज़न एप्लीकेशन भंडार से |", "googlePlayText": "गूगल प्ले स्टोर", - "titleText": "मोबाइल यंत्र का नियंत्रक के रूप में प्रयोग करते हुए: " + "titleText": "मोबाइल यंत्र का नियंत्रक के रूप में प्रयोग करते हुए:" }, "continuePurchaseText": "${PRICE} के खर्चे पर जारी रखें ?", "continueText": "जारी रखें", @@ -557,6 +557,7 @@ "demoText": "डेमो", "denyText": "अस्वीकृत करें", "deprecatedText": "पदावनत", + "descriptionText": "डीसकरिपषण", "desktopResText": "डेस्कटॉप रेज़ोल्यूशन", "deviceAccountUpgradeText": "चेतावनी:\n आप डिवाइस खाते (${NAME}) से साइन इन हैं।\n डिवाइस खातों को भविष्य के अपडेट में हटा दिया जाएगा।\n यदि आप अपनी प्रगति को बनाए रखना चाहते हैं तो V2 खाते में अपग्रेड करें।", "difficultyEasyText": "आसन", @@ -568,8 +569,8 @@ "disableXInputDescriptionText": "4 नियंत्रकों से अधिक की अनुमति देता है लेकिन साथ ही साथ काम नहीं कर सकते", "disableXInputText": "Xinput अक्षम करें", "disabledText": "डिसेबल्ड", - "discordFriendsText": "क्या आप खेलने के लिए नए लोगों की तलाश करना चाहते हैं?\nहमारे डिस्कोर्ड में शामिल हों और नए दोस्त खोजें!", - "discordJoinText": "डिस्कोर्ड में शामिल हों।", + "discordFriendsText": "क्या आप खेलने के लिए नए लोगों की तलाश करना चाहते हैं? \n हमारे डिस्कोर्ड में शामिल हों और नए दोस्त खोजें!", + "discordJoinText": "डिस्कोर्ड में शामिल हों", "doneText": "हो गया", "drawText": "बराबर", "duplicateText": "प्रतिलिपि", @@ -603,6 +604,7 @@ "localProfileText": "(स्थानिक पार्श्वचित्र)", "nameDescriptionText": "खिलाड़ी का नाम", "nameText": "नाम", + "profileAlreadyExistsText": "खाता इस नाम से पहेले ही बन चूका है", "randomText": "यादृच्छिक", "titleEditText": "पार्श्वचित्र को संपादित करें", "titleNewText": "नया पार्श्वचित्र बनायें", @@ -671,7 +673,7 @@ "fiveKillText": "पांच हत्या !!!", "flawlessWaveText": "त्रुटिरहित लहर !", "fourKillText": "चार हत्या !!!", - "friendScoresUnavailableText": "दोस्तों के अंक उनुप्लाब्ध हैं ", + "friendScoresUnavailableText": "दोस्तों के अंक उनुप्लाब्ध हैं", "gameCenterText": "गेम-सेण्टर", "gameCircleText": "गेम-सर्किल", "gameLeadersText": "गेम ${COUNT} के सरदार", @@ -684,6 +686,7 @@ "duplicateText": "प्लेलिस्ट कि \nछवि बनायें", "editText": "प्लेलिस्ट को \nसंपादित करें", "newText": "नयी \nप्लेलिस्ट", + "pointsToWinText": "पौइनटस जीतने के लिए", "showTutorialText": "ट्युटोरियल दिखाएँ", "shuffleGameOrderText": "गेम के क्रमांक को मिलाएं", "titleText": "${TYPE} प्लेलिस्ट को अपने हिसाब से बदलें" @@ -694,7 +697,7 @@ "gamesToText": "${WINCOUNT} जीते और ${LOSECOUNT} हारे", "gatherWindow": { "aboutDescriptionLocalMultiplayerExtraText": "ध्यान रखें: किसी भी यंत्र पे एक से ज्यादा खिलाड़ी हो सकते हैं \nअगर आपके पास पर्याप्त नियंत्रक हैं", - "aboutDescriptionText": "पार्टी इकट्ठा करने के लिए इन टैब्स का प्रयोग करें | \n\nपार्टी में आप गेम व \nप्रतियोगिता अपने दोस्तों के साथ \nअलग यंत्रो पे भी खेल सकते हैं | \n\nऊपरी-दायें हाथ कोने में उपस्थित ${PARTY} बटन का प्रयोग करके \nआप अपनी पार्टी से बात कर सकते हैं | (नियंत्रक पे ${BUTTON} दबाएँ मेनू में) ", + "aboutDescriptionText": "पार्टी इकट्ठा करने के लिए इन टैब्स का प्रयोग करें | \n\nपार्टी में आप गेम व \nप्रतियोगिता अपने दोस्तों के साथ \nअलग यंत्रो पे भी खेल सकते हैं | \n\nऊपरी-दायें हाथ कोने में उपस्थित ${PARTY} बटन का प्रयोग करके \nआप अपनी पार्टी से बात कर सकते हैं | (नियंत्रक पे ${BUTTON} दबाएँ मेनू में)", "aboutText": "इसके बारे में", "addressFetchErrorText": "<पता पाने में त्रुटी>", "appInviteInfoText": "दोस्तों को बोम्ब-स्क्वाड खेलने के लिए आमंत्रित करें\nऔर आपको ${COUNT} टिकेट मुफ्त मिलेंगे | हर\nदोस्त के आपको ${YOU_COUNT} टिकेट मिलेंगे |", @@ -1075,7 +1078,7 @@ "noContinuesText": "(कोई जारी रखना नहीं)", "noExternalStorageErrorText": "कोई बाहरी संचयन करने कि जगह नहीं मिली", "noGameCircleText": "त्रुटी: गेम-सर्किल में लॉग-इन नहीं हैं |", - "noPluginsInstalledText": "कोई प्लगइन्स इंस्टॉल नहीं है", + "noPluginsInstalledText": "कोई प्लगइन इंस्टॉल नहीं है", "noProfilesErrorText": "आपकी कोई खिलाड़ी पार्श्वचित्र नहीं है, इसलिए आप '${NAME}' नाम के साथ फंसे हैं |\nसेटिंग -> खिलाड़ी पार्श्वचित्र में जाके अपने लिए पार्श्वचित्र बनायें |", "noScoresYetText": "अभी तक कोई स्कोर नहीं है |", "noServersFoundText": "कोई सरवर्स नहीं मिलें।", @@ -1185,7 +1188,7 @@ "purchasingText": "खरीद रहे हैं...", "quitGameText": "${APP_NAME} को बंद कर दें ?", "quittingIn5SecondsText": "५ सेकंड में बंद कर रहे हैं...", - "randomPlayerNamesText": "किशोर, यश, ख्याति, शालिनी, आदित्य, उमेस, निश्चिंत, भाविक, रिशव, तुषार, राहुल, अमोल, मिशेल, प्रियंका, कल्याण, रियान, दिविज, हरी, आदर्श, कौस्तुभ, ज़ोया, सीनू, प्रतीक, ज़ारा, रुक्सार, शकील, पूजा, शबनम, शेरा, चेतन, समीर, टोनी, अजय, आकाश, पंकज, आरती, शबाना, मुमताज़, शुभम, शिवम्, लकेव, सचिन, दीपक, अक्षय, अर्जुन, किशन, राधा, विश्वनाथ, शालू, विमल, शिवा, पप्पू, नरेंद्र, आज़म, अनमोल, काजल, संध्या, दिनेश, प्रिंस, आनंद, अज़हर, पवन, अभिषेक, विवेक", + "randomPlayerNamesText": "किशोर, यश, ख्याति, शालिनी, आदित्य, उमेस, निश्चिंत, भाविक, रिशव, तुषार, राहुल, अमोल, मिशेल, प्रियंका, कल्याण, रियान, दिविज, हरी, आदर्श, कौस्तुभ, ज़ोया, सीनू, प्रतीक, ज़ारा, रुक्सार, शकील, पूजा, शबनम, शेरा, चेतन, समीर, टोनी, अजय, आकाश, पंकज, आरती, शबाना, मुमताज़, शुभम, शिवम्, लकेव, सचिन, दीपक, अक्षय, अर्जुन, किशन, राधा, विश्वनाथ, शालू, विमल, शिवा, पप्पू, नरेंद्र, आज़म, अनमोल, काजल, संध्या, दिनेश, प्रिंस, आनंद, अज़हर, पवन, अभिषेक, विवेक, बेवन", "randomText": "अनियमित", "rankText": "पद", "ratingText": "मूल्यांकन", @@ -1256,6 +1259,7 @@ }, "scoreWasText": "(${COUNT} था)", "selectText": "चुनें", + "sendInfoDescriptionText": "अकाउंट और एप हालत की इनफो डेवलापर को बेजती है\nबेजने के अपना नाम और कारण बताईए", "seriesWinLine1PlayerText": "विजयी", "seriesWinLine1TeamText": "विजयी", "seriesWinLine1Text": "विजयी", @@ -1285,9 +1289,11 @@ "kidFriendlyModeText": "बच्चों के अनुकूल करें (कम हिंसा, आदि)", "languageText": "भाषा", "moddingGuideText": "परिवर्तन करने कि गाइड", + "moddingToolsText": "मोडी़ग टूलस", "mustRestartText": "इसके लागू होने के लिए आपको गेम को पुनः शुरू करना पड़ेगा |", "netTestingText": "नेटवर्क पर परीक्षण", "resetText": "रीसेट", + "sendInfoText": "खबर बेजिए", "showBombTrajectoriesText": "बोम्ब का पथ दिखाएँ", "showDemosWhenIdleText": "निष्क्रिय होने पर डेमो दिखाएं", "showDevConsoleButtonText": "डेव कंसोल बटन दिखाएँ", @@ -1310,6 +1316,9 @@ "signInWithGameCenterText": "गेम केंद्र खाते का उपयोग करने के लिए, \nगेम सेंटर एप के साथ साइन इन करें।", "singleGamePlaylistNameText": "केवल ${GAME}", "singlePlayerCountText": "1 खिलाड़ी", + "sizeLargeText": "बडा", + "sizeMediumText": "बराबर", + "sizeSmallText": "चोटा", "soloNameFilterText": "अकेले ${NAME}", "soundtrackTypeNames": { "CharSelect": "कैरेक्टर का चयन", @@ -1383,7 +1392,7 @@ "submitText": "जमा करें", "submittingPromoCodeText": "संहिता जमा कर रहा है ...", "successText": "सफल!", - "supportEmailText": "यदि आप किसी भी समस्या का सामना कर रहे हैं\nऐप, कृपया ${EMAIL} को ईमेल करें", + "supportEmailText": "यदि आप किसी भी समस्या का सामना कर रहे हैं ऐप, \nकृपया ईमेल करें ${EMAIL}।", "teamNamesColorText": "टीम के नाम / रंग ...", "telnetAccessGrantedText": "टेलनेट एक्सेस सक्षम है", "telnetAccessText": "टेलनेट पहुंच का पता चला; अनुमति देते हैं?", @@ -1832,6 +1841,7 @@ "toSkipPressAnythingText": "(शिक्षण छोड़ने के लिए कुछ भी स्पर्श करे या दबाएं)" }, "twoKillText": "दोहरी हत्या", + "uiScaleText": "यू आई सकेल", "unavailableText": "उपलब्ध नहीं", "unconfiguredControllerDetectedText": "बिना विन्यास वाले नियंत्रक का पता चला:", "unlockThisInTheStoreText": "यह स्टोर में अनलॉक होना चाहिए।", @@ -1845,6 +1855,8 @@ "upgradeText": "अभ्युत्थान", "upgradeToPlayText": "इन-गेम स्टोर में इसे चलाने के लिए \"${PRO}\" अनलॉक करें।", "useDefaultText": "पूर्व निर्धारित उपयोग करें", + "userSystemScriptsCreateText": "डसतमालक सिस्टम सृ‍‌‌इपट बनाईए", + "userSystemScriptsDeleteText": "डीलीइट यूजर्स सिस्टम सिकरीपट", "usesExternalControllerText": "यह गेम इनपुट के लिए बाहरी नियंत्रक का उपयोग करता है।", "usingItunesText": "गाने के लिए संगीत ऐप का उपयोग कर रहे है ...", "v2AccountLinkingInfoText": "V2 खातों को लिंक करने के लिए, 'खाता प्रबंधित करें' बटन का उपयोग करें।", diff --git a/dist/ba_data/data/languages/hungarian.json b/dist/ba_data/data/languages/hungarian.json index 949438d..af3416e 100644 --- a/dist/ba_data/data/languages/hungarian.json +++ b/dist/ba_data/data/languages/hungarian.json @@ -32,8 +32,9 @@ "signInWithGooglePlayText": "Belépés Google fiókkal", "signInWithTestAccountInfoText": "(Egy régi típusú felhasználó; Mostantól a készülék felhasználóját használd.)", "signInWithTestAccountText": "Bejelentkezés teszt felhasználóval", + "signInWithText": "Bejelentkezés ${SERVICE}-(v)al /-(v)el", "signInWithV2InfoText": "(egy fiók, amely minden platformon működik)", - "signInWithV2Text": "Jelentkezzen be BombSquad fiókkal", + "signInWithV2Text": "Jelentkezz be BombSquad fiókkal", "signOutText": "Kijelentkezés", "signingInText": "Bejelentkezés...", "signingOutText": "Kijelentkezés...", @@ -338,6 +339,8 @@ "getMoreGamesText": "Több Játékmód...", "titleText": "Játék Hozzáadása" }, + "addToFavoritesText": "Hozzáadás a kedvencekhez", + "addedToFavoritesText": "'${NAME}' hozzáadva a kedvencekhez.", "allText": "Minden", "allowText": "Engedélyezés", "alreadySignedInText": "A fiókoddal be vagy jelentkezve egy másik eszközről;\nkérlek cserélj fiókot vagy zárd be a játékot \na másik eszközön és próbáld újra", @@ -371,6 +374,7 @@ "chatMutedText": "Chat némítva", "chatUnMuteText": "Chat némítás feloldása", "choosingPlayerText": "", + "codesExplainText": "A kódokkal diagnosztizálni és javítani tudod az esetlegesen felmerülő,\n fiókoddal kapcsolatos problémákat.", "completeThisLevelToProceedText": "Teljesítened kell ezt\na szintet a folytatáshoz!", "completionBonusText": "Befejezési Bónusz", "configControllersWindow": { @@ -451,6 +455,7 @@ "swipeText": "nyilak", "titleText": "Érintőképernyő Konfigurálása" }, + "configureDeviceInSystemSettingsText": "${DEVICE} konfigurálását a rendszerbeállításokban tudod megtenni", "configureItNowText": "Most Konfigurálod?", "configureText": "Konfigurálás", "connectMobileDevicesWindow": { @@ -564,6 +569,8 @@ "disableXInputDescriptionText": "Engedélyezi ,hogy 4-nél több kontroller is csatlakozhasson ,viszont nem biztos a hibátlan működés.", "disableXInputText": "XInput kikapcsolása", "disabledText": "Kikapcsolva", + "discordFriendsText": "Szeretnél barátokat találni?\nCsatlakozz a Discord szerverünkre!", + "discordJoinText": "Csatlakozás a discord szerverre", "doneText": "Elvégezve", "drawText": "Döntetlen", "duplicateText": "Másolás", @@ -597,6 +604,7 @@ "localProfileText": "(alkalmi profil)", "nameDescriptionText": "Játékos Név", "nameText": "Név", + "profileAlreadyExistsText": "Egy ugyan ilyen nevű profil már létezik!", "randomText": "véletlen", "titleEditText": "Profil Szerkesztése", "titleNewText": "Új Profil", @@ -678,6 +686,8 @@ "duplicateText": "Lista\nMásolása", "editText": "Lista\nSzerkesztése", "newText": "Új\nLista", + "pointsToWinText": "Pont nyerésig", + "seriesLengthText": "Sorozat hossza", "showTutorialText": "Oktató végignézése", "shuffleGameOrderText": "Játék Rendelés Megkeverése", "titleText": "Szabd személyre a ${TYPE} Lejátszási listát" @@ -752,6 +762,7 @@ "manualYourLocalAddressText": "Lokális IP címed:", "nearbyText": "Közeli játékok", "noConnectionText": "", + "noPartiesAddedText": "Nincsenek hozzáadva játékok", "otherVersionsText": "(másik verziók)", "partyCodeText": "Party kód", "partyInviteAcceptText": "Elfogad", @@ -827,10 +838,12 @@ "alwaysText": "Mindig", "fullScreenCmdText": "Teljes képernyő (Cmd-F)", "fullScreenCtrlText": "Teljes képernyő (Ctrl-F)", + "fullScreenText": "Teljes képernyő", "gammaText": "Gamma", "highText": "Magas", "higherText": "Magasabb", "lowText": "Alacsony", + "maxFPSText": "Maximum FPS:", "mediumText": "Közepes", "neverText": "Soha", "resolutionText": "Felbontás", @@ -862,11 +875,11 @@ "powerupCurseNameText": "Átok", "powerupHealthDescriptionText": "Helyreállítja a teljes egészségedet.\nSose gondoltad volna.", "powerupHealthNameText": "Gyógyító-Csomag", - "powerupIceBombsDescriptionText": "Gyengébb mint a normál bombák,\nde hülten hagyja ellenfeleidet\nés különösen törékenyen.", + "powerupIceBombsDescriptionText": "Gyengébbek mint a normál bombák,\nde hülten hagyja ellenfeleidet.\nÉs különösen törékenyen!", "powerupIceBombsNameText": "Jég-Bombák", - "powerupImpactBombsDescriptionText": "Némileg gyengébb, mint az eredeti\nbombák, de becsapódásra robannak.", + "powerupImpactBombsDescriptionText": "Némileg gyengébbek, mint a normál\nbombák, de becsapódásra robannak.", "powerupImpactBombsNameText": "Ravasz-Bombák", - "powerupLandMinesDescriptionText": "Ezek 3-asával jönnek egy csomagba;\nHasznos a bázis védelemre vagy\na gyors ellenfelek megállítására.", + "powerupLandMinesDescriptionText": "Ezek 3-asával jönnek egy csomagban;\nHasznos a bázis védelemre vagy\na gyors ellenfelek megállítására.", "powerupLandMinesNameText": "Taposó-akna", "powerupPunchDescriptionText": "Felerősíti, gyorsítja, jobbá,\nés keményebbé teszi ütéseidet.", "powerupPunchNameText": "Box-Kesztyűk", @@ -1070,8 +1083,10 @@ "noContinuesText": "(újraéledés nélkül)", "noExternalStorageErrorText": "Nincs megtalálható külső tárhely ezen az eszközön", "noGameCircleText": "Hiba: nincs bejelentkezve egy JátékKörbe", + "noPluginsInstalledText": "Nincsenek hozzáadva pluginok", "noProfilesErrorText": "Nincs játékos profilod, tehát te most '${NAME}'-val nyomulsz.\nMenj a Beállítások->Játékos Profilok-ba hogy csinálj magadnak egy profilt-", "noScoresYetText": "Nincs még pontod.", + "noServersFoundText": "Nem találhatók szerverek", "noThanksText": "Nem Köszönöm", "noTournamentsInTestBuildText": "FIGYELEM: A tournament pontok ebből a teszt épitésből ki lesznek hagyva.", "noValidMapsErrorText": "Nem található pálya ehhez a játéktípushoz.", @@ -1279,10 +1294,13 @@ "kidFriendlyModeText": "Gyerekbarát Mód (erőszak csökkentése,stb.)", "languageText": "Nyelv", "moddingGuideText": "Modolási Útmutató", + "moddingToolsText": "Moddolási eszközök", "mustRestartText": "Újra kell indítanod a játékot a módosítások érvénybe lépéséhez.", "netTestingText": "Hálózat Tesztelése", "resetText": "Visszaállítás", "showBombTrajectoriesText": "Bomba Pályájának Mutatása", + "showDemosWhenIdleText": "Demók lejátszása tétlenség esetén", + "showDevConsoleButtonText": "A fejlesztői konzol gombjának megjelenítése", "showInGamePingText": "Ping mutatása játékban", "showPlayerNamesText": "Játékos Név Mutatása", "showUserModsText": "Mod Mappák Mutatása", @@ -1302,6 +1320,9 @@ "signInWithGameCenterText": "Hogy használd a Játék Központ felhasználódat\njelentkezz be a Játék Központban.", "singleGamePlaylistNameText": "Csak ${GAME}", "singlePlayerCountText": "1 játékos", + "sizeLargeText": "Óriás", + "sizeMediumText": "Közepes", + "sizeSmallText": "Kicsi", "soloNameFilterText": "Solo ${NAME}", "soundtrackTypeNames": { "CharSelect": "Karakter Választás", @@ -1377,6 +1398,8 @@ "storeText": "Bolt", "submitText": "Érvényesítsd", "submittingPromoCodeText": "Promóciós Kód Érvényesítése...", + "successText": "Siker!", + "supportEmailText": "Ha bármi problémát észlesz a játékkal, kérlek írd meg az alábbi e-mailen:\n${EMAIL}", "teamNamesColorText": "Csapatnevek/csapatszínek...", "telnetAccessGrantedText": "Telnet hozzáférés engedélyezve.", "telnetAccessText": "Telnet hozzáférés észlelve. Engedélyezed?", @@ -1826,11 +1849,13 @@ "toSkipPressAnythingText": "(tapints vagy nyomj meg akármit az oktató átlépéséhez)" }, "twoKillText": "DUPLA ÖLÉS!", + "uiScaleText": "UI skála", "unavailableText": "elérhetetlen", "unconfiguredControllerDetectedText": "Konfigurált vezérlő felismerve:", "unlockThisInTheStoreText": "Ennek feloldva kell lennie az áruházban.", "unlockThisProfilesText": "Hogy készíts több mint ${NUM} profilt, kelleni fog:", "unlockThisText": "Hogy ezt felold, szükséged van ezekre:", + "unsupportedControllerText": "Bocsi, de a(z) \"${NAME}\" nevű kontroller nem támogatott", "unsupportedHardwareText": "Bocs, ez a hárdver nem támogatott a játék ezen szerkezete által.", "upFirstText": "Elsőként:", "upNextText": "A következő a ${COUNT}. játékban:", @@ -1838,11 +1863,14 @@ "upgradeText": "Frissítés", "upgradeToPlayText": "Old fel a \"${PRO}\"-t az áruházba, hogy játszhass ezzel.", "useDefaultText": "Alapértelmezett Használata", + "userSystemScriptsCreateText": "Felhasználói rendszer-scriptek létrehozása", + "userSystemScriptsDeleteText": "Felhasználói rendszer-scriptek törlése", "usesExternalControllerText": "Ez a játék egy külső vezérlőt használ bemenet gyanánt.", "usingItunesText": "Itunes használása a zenéhez...", "usingItunesTurnRepeatAndShuffleOnText": "Kérlek bizonyosodj meg arról, hogy a keverés BE van kapcsolva és az ismétlés MINDEN-re van állítva az iTunes-on.", "v2AccountLinkingInfoText": "Hogy V2-es fiókot kapcsolj, menj a 'fiók kezelése' gombra.", "validatingTestBuildText": "Teszt Szerkezet Érvényesítése...", + "viaText": "via", "victoryText": "Győzelem!", "voteDelayText": "Még nem indíthatsz szavazást újabb ${NUMBER} másodpercig", "voteInProgressText": "Már egy szavazás folyamatban van...", @@ -1895,7 +1923,7 @@ "winsText": "${NAME} nyert!", "workspaceSyncErrorText": "Nem sikerült szinkronizálni a következő munkaterületet: ${WORKSPACE}. Nézd a naplót további adatokért.", "workspaceSyncReuseText": "Nem lehet szinkronizálni a következő munkaterületet: ${WORKSPACE}. Előző munkaterület lesz használatban.", - "worldScoresUnavailableText": "Világ eredmények elérhetetlenek", + "worldScoresUnavailableText": "A globális eredmények elérhetetlenek", "worldsBestScoresText": "Világ legjobb eredményei", "worldsBestTimesText": "Világ legjobb idejei", "xbox360ControllersWindow": { @@ -1906,6 +1934,6 @@ "titleText": "Xbox 360 kontroller használata a ${APP_NAME}-ban:" }, "yesAllowText": "Igen, Engedélyez!", - "yourBestScoresText": "Te legjobb pontjaid", - "yourBestTimesText": "Te legjobb időid" + "yourBestScoresText": "A legjobb pontjaid", + "yourBestTimesText": "A legjobb időid" } \ No newline at end of file diff --git a/dist/ba_data/data/languages/italian.json b/dist/ba_data/data/languages/italian.json index 4fc6d17..e765904 100644 --- a/dist/ba_data/data/languages/italian.json +++ b/dist/ba_data/data/languages/italian.json @@ -27,6 +27,7 @@ "setAccountNameDesc": "Seleziona quale nome visualizzare per il tuo account.\nPuoi usare quel nome per uno dei tuoi account collegati,\noppure creare un unico nome personalizzato.", "signInInfoText": "Accedi per raccogliere biglietti, competere online,\ne condividere i progressi tra i vari dispositivi.", "signInText": "Accedi", + "signInWithAnEmailAddressText": "Accedi con un indirizzo email", "signInWithDeviceInfoText": "(Un solo account automatico è disponibile per questo dispositivo)", "signInWithDeviceText": "Accedi con l'account del dispositivo", "signInWithGameCircleText": "Accedi con Game Circle", @@ -574,6 +575,7 @@ "demoText": "Demo", "denyText": "Nega", "deprecatedText": "Deprecato", + "descriptionText": "Descrizione", "desktopResText": "Risoluzione Nativa", "deviceAccountUpgradeText": "Attenzione:\nHai effettuato l'accesso con un profilo dispositivo (${NAME}$).\nI profili dispositivi verranno rimossi in un aggiornamento futuro.\nAggiornati ad un profilo V2 se desideri mantenere i tuoi progressi.", "difficultyEasyText": "Facile", @@ -594,7 +596,7 @@ "addGameText": "Aggiungi\nGioco", "cantOverwriteDefaultText": "Non puoi sovrascrivere la scaletta predefinita!", "cantSaveAlreadyExistsText": "Una scaletta con quel nome esiste già!", - "cantSaveEmptyListText": "Non puoi salvare una scaletta vuota!", + "cantSaveEmptyListText": "Non puoi salvare una playlist vuota!", "editGameText": "Modifica\nGioco", "gameListText": "Serie di Partite", "listNameText": "Nome Scaletta", @@ -621,6 +623,7 @@ "localProfileText": "(profilo locale)", "nameDescriptionText": "Nome del giocatore", "nameText": "Nome", + "profileAlreadyExistsText": "Esiste già un profilo con quel nome.", "randomText": "casuale", "titleEditText": "Modifica Profilo", "titleNewText": "Nuovo Profilo", @@ -708,6 +711,8 @@ "editText": "Modifica\nScaletta", "gameListText": "Lista delle serie di partite", "newText": "Nuova\nScaletta", + "pointsToWinText": "Punti Per Vincere", + "seriesLengthText": "Durata Serie", "showTutorialText": "Mostra Guida", "shuffleGameOrderText": "Ordine Partite Casuale", "titleText": "Personalizza Scalette ${TYPE}" @@ -748,10 +753,10 @@ "friendHasSentPromoCodeText": "${COUNT} biglietti di ${APP_NAME} da ${NAME}", "friendPromoCodeAwardText": "Riceverai ${COUNT} biglietti ogni volta che viene usato.", "friendPromoCodeExpireText": "Questo codice scadrà in ${EXPIRE_HOURS} ore e funziona soltanto per nuovi giocatori.", - "friendPromoCodeInstructionsText": "Per usarlo, apri ${APP_NAME} e vai su \"Impostazioni->Avanzate->Inserisci Codice\".\nVisita bombsquadgame.com per i link di download per tutte le versioni supportate.", + "friendPromoCodeInstructionsText": "Per usarlo, apri ${APP_NAME} e vai su \"Impostazioni->Avanzate->Invia Informazioni\".\nVisita bombsquadgame.com per i link di download per tutte le versioni supportate.", "friendPromoCodeRedeemLongText": "Puó essere utilizzato per ${COUNT} biglietti gratis per un massimo di ${MAX_USES} persone.", "friendPromoCodeRedeemShortText": "Puó essere utilizzato per ${COUNT} biglietti nel gioco.", - "friendPromoCodeWhereToEnterText": "(in \"Impostazioni->Avanzate->Inserisci Codice\")", + "friendPromoCodeWhereToEnterText": "(in \"Impostazioni->Avanzate->Invia Informazioni\")", "getFriendInviteCodeText": "Ottieni un Codice di Invito", "googlePlayDescriptionText": "Invita giocatori Google Play nel tuo gruppo:", "googlePlayInviteText": "Invita", @@ -1305,6 +1310,7 @@ }, "scoreWasText": "(era ${COUNT})", "selectText": "Seleziona", + "sendInfoDescriptionText": "Invia le informazioni sullo stato dell'account e dell'applicazione allo sviluppatore.\nPrego di includere il proprio nome o il motivo dell'invio.", "seriesWinLine1PlayerText": "VINCE LA", "seriesWinLine1TeamText": "VINCE LA", "seriesWinLine1Text": "VINCE LA", @@ -1321,8 +1327,9 @@ }, "settingsWindowAdvanced": { "alwaysUseInternalKeyboardDescriptionText": "(una semplice tastiera sullo schermo per editare testi)", - "alwaysUseInternalKeyboardText": "Usa Sempre La Tastiera Interna", + "alwaysUseInternalKeyboardText": "Usa sempre la tastiera interna", "benchmarksText": "Benchmarks & Prove Di Stress", + "devToolsText": "Strumenti di Sviluppo", "disableCameraGyroscopeMotionText": "Disabilita il movimento della visuale tramite giroscopio", "disableCameraShakeText": "Disabilita il tremolio della visuale", "disableThisNotice": "(puoi disattivare questo messaggio su Impostazioni > Avanzate)", @@ -1335,13 +1342,16 @@ "kidFriendlyModeText": "Modalità per bambini (violenza ridotta e altro)", "languageText": "Lingua", "moddingGuideText": "Manuale di personalizzazione", + "moddingToolsText": "Strumenti di Modding", "mustRestartText": "Devi riavviare il gioco per apportare questa modifica.", "netTestingText": "Collaudo Rete", "resetText": "Reset", + "sendInfoText": "Invia Informazioni", "showBombTrajectoriesText": "Mostra le traiettorie delle bombe", - "showDemosWhenIdleText": "Riproduci Demo All'Inattività", - "showDevConsoleButtonText": "mostra tasto console sviluppatore", - "showInGamePingText": "Mostra il Ping in gioco", + "showDemosWhenIdleText": "Mostra demo quando inattivo", + "showDeprecatedLoginTypesText": "Mostra metodi di login obsoleti", + "showDevConsoleButtonText": "Mostra tasto console sviluppatore", + "showInGamePingText": "Mostra il ping in gioco", "showPlayerNamesText": "Mostra i nomi dei giocatori", "showUserModsText": "Apri cartella personalizzazioni", "titleText": "Avanzato", @@ -1349,8 +1359,8 @@ "translationFetchErrorText": "stato traduzione non disponibile", "translationFetchingStatusText": "controllo stato traduzione...", "translationInformMe": "Informami quando la mia lingua ha bisogno di aggiornamenti", - "translationNoUpdateNeededText": "la traduzione italiana è completa; woohoo!", - "translationUpdateNeededText": "** ci sono testi da tradurre!! **", + "translationNoUpdateNeededText": "La traduzione italiana è completa; woohoo!", + "translationUpdateNeededText": "** Ci sono testi da tradurre!! **", "vrTestingText": "Collaudo VR" }, "shareText": "Condividi", @@ -1360,6 +1370,9 @@ "signInWithGameCenterText": "Per utilizzare un account Game Center,\naccedi utilizzando l'app Game Center.", "singleGamePlaylistNameText": "Solo ${GAME}", "singlePlayerCountText": "Un giocatore", + "sizeLargeText": "Grande", + "sizeMediumText": "Medio", + "sizeSmallText": "Piccolo", "soloNameFilterText": "${NAME} Testa a Testa", "soundtrackTypeNames": { "CharSelect": "Selezione del personaggio", @@ -1914,6 +1927,7 @@ "toSkipPressAnythingText": "(tocca o premi qualsiasi pulsante per saltare la guida)" }, "twoKillText": "DOPPIA UCCISIONE!", + "uiScaleText": "Dimensione Interfaccia", "unavailableText": "non disponibile", "unconfiguredControllerDetectedText": "Rilevato controller non configurato:", "unlockThisInTheStoreText": "Deve essere sbloccato nel negozio", @@ -1927,6 +1941,8 @@ "upgradeText": "Aggiorna", "upgradeToPlayText": "Sblocca \"${PRO}\" nel negozio in-game per poter giocare a questo.", "useDefaultText": "Usa Predefinito", + "userSystemScriptsCreateText": "Crea Script di Sistema Utente", + "userSystemScriptsDeleteText": "Elimina Script di Sistema Utente", "usesExternalControllerText": "Questo gioco utilizza un controller esterno come input.", "usingItunesText": "Sto usando una app musicale per la colonna sonora...", "usingItunesTurnRepeatAndShuffleOnText": "Per favore, assicurati che la riproduzione casuale sia ATTIVA e che la ripetizione sia su TUTTO su iTunes.", diff --git a/dist/ba_data/data/languages/malay.json b/dist/ba_data/data/languages/malay.json index ded9cbc..1eee755 100644 --- a/dist/ba_data/data/languages/malay.json +++ b/dist/ba_data/data/languages/malay.json @@ -29,6 +29,7 @@ "signInWithGooglePlayText": "Daftar masuk dengan Google Play", "signInWithTestAccountInfoText": "(jenis akaun warisan; gunakan akaun peranti untuk ke hadapan)", "signInWithTestAccountText": "Daftar masuk dengan akaun ujian", + "signInWithText": "Daftar masuk dengan ${SERVICE}", "signInWithV2InfoText": "(akaun yang berfungsi untuk semua platform)", "signInWithV2Text": "Log masuk dengan akaun BombSquad", "signOutText": "Daftar Keluar", @@ -332,6 +333,9 @@ "getMoreGamesText": "Dapatkan Lebih Banyak Permainan...", "titleText": "Tambahkan Permainan" }, + "addToFavoritesText": "Tambah pada Kegemaran", + "addedToFavoritesText": "'${NAME}' ditambah pada Kegemaran", + "allText": "Semua", "allowText": "Benarkan", "alreadySignedInText": "Akaun anda didaftar masuk dari peranti lain;\nsila tukar akaun atau tutupkan permainan pada\nperanti itu dan cuba lagi.", "apiVersionErrorText": "Tidak dapat memuatkan modul ${NAME}; ia menyasarkan versi api ${VERSION_USED}; kami memerlukan ${VERSION_REQUIRED}.", @@ -364,6 +368,7 @@ "chatMutedText": "Perbualan Disenyapkan", "chatUnMuteText": "Nyahsenyapkan Perbualan", "choosingPlayerText": "", + "codesExplainText": "Kod disediakan oleh pembangun untuk\nmendiagnosis dan membaiki masalah akaun.", "completeThisLevelToProceedText": "Anda mesti menyelasaikan\nperingkat ini untuk teruskan!", "completionBonusText": "Bonus Penyelesaian", "configControllersWindow": { @@ -444,6 +449,7 @@ "swipeText": "sapu", "titleText": "Atur Skrin Sentuhan" }, + "configureDeviceInSystemSettingsText": "${DEVICE} boleh diatur di apl Tetapan Sistem", "configureItNowText": "Aturnya sekarang?", "configureText": "Atur", "connectMobileDevicesWindow": { @@ -556,6 +562,9 @@ "disableRemoteAppConnectionsText": "Lumpuhkan Sambungan Apl Jauh", "disableXInputDescriptionText": "Membenarkan lebih daripada 4 pengawal tetapi mungkin tidak berfungsi juga.", "disableXInputText": "Lumpuhkan XInput", + "disabledText": "Dimatikan", + "discordFriendsText": "Mahu seseorang untuk bermain bersama?\nSertai Discord kami dan cari rakan baharu!", + "discordJoinText": "Sertai Discord", "doneText": "Selesai", "drawText": "Seri", "duplicateText": "Pendua", @@ -623,6 +632,7 @@ "useMusicFolderText": "Folder Fail Muzik" }, "editText": "Sunting", + "enabledText": "Dihidupkan", "endText": "Tamatkan", "enjoyText": "Nikmatilah!", "epicDescriptionFilterText": "${DESCRIPTION} dalam gerakan perlahan epik", @@ -668,6 +678,8 @@ "duplicateText": "Pendua\nSenarai main", "editText": "Sunting\nSenarai main", "newText": "Senarai Main\nBaru", + "pointsToWinText": "Mata Untuk Menang", + "seriesLengthText": "Panjang Siri", "showTutorialText": "Tunjukkan Cara-cara", "shuffleGameOrderText": "Pesanan Permainan Rombak", "titleText": "Ubahsuai Senarai Main ${TYPE}" @@ -740,6 +752,7 @@ "manualYourLocalAddressText": "Alamat tempatan anda", "nearbyText": "Berdekatan", "noConnectionText": "", + "noPartiesAddedText": "Tiada Parti Yang Ditambah", "otherVersionsText": "(versi lain)", "partyCodeText": "Kod Permainan", "partyInviteAcceptText": "Terima", @@ -811,10 +824,12 @@ "alwaysText": "Sentiasa", "fullScreenCmdText": "Skrin penuh (Cmd-F)", "fullScreenCtrlText": "Skrin penuh (Ctrl-F)", + "fullScreenText": "Skrin Penuh", "gammaText": "Gamma", "highText": "Tinggi", "higherText": "Lebih tinggi", "lowText": "Rendah", + "maxFPSText": "Maksimum FPS", "mediumText": "Sederhana", "neverText": "Tidak pernah", "resolutionText": "Resolusi", @@ -1052,7 +1067,9 @@ "noContinuesText": "(tidak boleh bersambung)", "noExternalStorageErrorText": "Tiada storan luaran ditemui pada peranti ini", "noGameCircleText": "Ralat: tidak log masuk ke GameCircle", + "noPluginsInstalledText": "Tiada Pemalam Yang Dipasang", "noScoresYetText": "Tiada markah lagi.", + "noServersFoundText": "Tiada pelayan yang ditemui.", "noThanksText": "Tak Terimo Kasih, Yo", "noTournamentsInTestBuildText": "AMARAN: Markah kejohanan daripada binaan ujian ini akan diabaikan.", "noValidMapsErrorText": "Tiada peta yang sah ditemui untuk jenis permainan ini.", @@ -1217,6 +1234,8 @@ "runText": "Lari", "saveText": "Simpan", "scanScriptsErrorText": "Ralat(s) mengimbas skrip; Lihat log untuk butiran.", + "scanScriptsMultipleModulesNeedUpdatesText": "${PATH} dan ${NUM} modul yang lain perlu dikemaskinikan untuk API ${API}.", + "scanScriptsSingleModuleNeedsUpdatesText": "${PATH} perlu dikemaskinikan untuk API ${API}.", "scoreChallengesText": "Cabaran Skor", "scoreListUnavailableText": "Senarai skor tidak tersedia.", "scoreText": "Markah", @@ -1260,6 +1279,8 @@ "netTestingText": "Ujian Rangkaian", "resetText": "Tetap Semula", "showBombTrajectoriesText": "Tunjukkan Trajektori Bom", + "showDemosWhenIdleText": "Tunjukkan Demo Apabila Melahu", + "showDevConsoleButtonText": "Tunjukkan Butang Konsol Pembangun", "showInGamePingText": "Tunjukkan Ping Dalam Permainan", "showPlayerNamesText": "Tunjukkan Nama Pemain", "showUserModsText": "Tunjukkan Pelipat Mod", @@ -1351,6 +1372,8 @@ "storeText": "Kedai", "submitText": "Hantar", "submittingPromoCodeText": "Mengemukakan Kod...", + "successText": "Berjaya!", + "supportEmailText": "Jika anda mengalami masalah dengan apl ini, sila\nhantar e-mel kepada ${EMAIL}.", "teamNamesColorText": "Nama/Warna Pasukan...", "telnetAccessGrantedText": "Akses telnet diaktifkan.", "telnetAccessText": "Akses telnet dikesan; benarkan?", @@ -1804,6 +1827,7 @@ "unlockThisInTheStoreText": "Ini mesti dibuka kunci di kedai.", "unlockThisProfilesText": "Untuk membuat lebih daripada ${NUM} profil, anda memerlukan:", "unlockThisText": "Untuk membuka kunci ini, anda memerlukan:", + "unsupportedControllerText": "Maaf, alat kawalan \"${NAME}\" tidak disokong.", "unsupportedHardwareText": "Maaf, perkakasan ini tidak disokong oleh binaan permainan ini.", "upFirstText": "Bangun dahulu:", "upNextText": "Seterusnya dalam permainan ${COUNT}:", @@ -1815,6 +1839,7 @@ "usingItunesText": "Menggunakan Apl Muzik untuk runut bunyi...", "v2AccountLinkingInfoText": "Untuk memautkan akaun V2, gunakan butang 'Urus Akaun'.", "validatingTestBuildText": "Mengesahkan Binaan Ujian...", + "viaText": "melalui", "victoryText": "Kemenangan!", "voteDelayText": "Anda tidak boleh memulakan undian lain selama ${NUMBER} saat", "voteInProgressText": "Undian sedang dijalankan.", diff --git a/dist/ba_data/data/languages/persian.json b/dist/ba_data/data/languages/persian.json index 545f4b6..1f5ce11 100644 --- a/dist/ba_data/data/languages/persian.json +++ b/dist/ba_data/data/languages/persian.json @@ -1,14 +1,14 @@ { "accountSettingsWindow": { - "accountNameRules": "نام حساب نمی‌تونه ایموجی (شکلک) یا نویسه‌ های ویژه داشته باشه", + "accountNameRules": "نام حساب کاربری نمیتونه شکلک و نویسه های ویژه داشته باشه جیگر", "accountProfileText": "(مشخصات حساب)", "accountsText": "حساب های کاربری", "achievementProgressText": "${TOTAL}/${COUNT} :دستاوردها", "campaignProgressText": "${PROGRESS} :[سخت]‎ پیشروی در بازی اصلی", "changeOncePerSeason": ".فقط یه‌ بار در هر فصل می‌تونی این مورد رو تغییر بدی", - "changeOncePerSeasonError": "(روز تا فصل بعد‎ ${NUM}) برای تغییر این گزینه باید تا فصل بعد صبر کنی", + "changeOncePerSeasonError": "(روز تا فصل بعد‎ ${NUM}) برای تغییر مجدد این گزینه باید تا فصل بعد صبر کنی", "customName": "نام سفارشی", - "googlePlayGamesAccountSwitchText": "،اگه میخوای از اکانت گوگل متفاوتی استفاده کنی\nبرای تعویض از گوگل پلی گیمز استفاده کن", + "googlePlayGamesAccountSwitchText": "اگه میخوای از حساب های مختلف گوگل استفاده کنی از بازی های گوگل برای عوض کردن استفاده کن", "linkAccountsEnterCodeText": "کد را وارد کنید", "linkAccountsGenerateCodeText": "ایجاد کد", "linkAccountsInfoText": "(به اشتراک گذاری پیشروی بین دستگاه‌های مختلف)", @@ -16,8 +16,8 @@ "linkAccountsInstructionsText": "برای اتصال دو حساب، در یکی از\nآن ها کدی ایجاد کرده \nو آن را در دیگری وارد کنید.\nپیشرفت ها و موجودی ترکیب خواهد شد.\nحساب را وصل کنید ${COUNT} شما می توانید.\n!توجه : فقط حساب هایی را وصل کنید که برای\n شماست\nاگر شما حساب دیگری را وصل کنید، شما توانایی این را ندارید که در یک زمان بازی کنید!\nاین عمل برگشت پذیر نیست، پس \nدقت کنید!", "linkAccountsText": "متصل کردن حساب ها", "linkedAccountsText": ":حساب های متصل شده", - "manageAccountText": "تنظیمات حساب", - "nameChangeConfirm": "تغییر کند؟‎ ${NAME} آیا نام شما به", + "manageAccountText": "مدیریت حساب", + "nameChangeConfirm": "تغییر کند؟‎ ${NAME} نام شما به", "resetProgressConfirmNoAchievementsText": "همهٔ پیشروی‌های شما در بخش همکاری و امتیازات\nشما پاک خواهد شد. (به استثنای بلیت‌های شما)\nاین کار برگشت‌پذیر نیست. آیا مطمئنید؟", "resetProgressConfirmText": "همهٔ پیشروی‌ها در بخش همکاری، دستاوردها\n.و امتیازات بالای شما پاک خواهد شد\n(به استثنای بلیت‌های شما)\nاین کار برگشت‌ پذیر نیست. آیا مطمئنید؟", "resetProgressText": "بازنشانی پیشروی", @@ -25,6 +25,7 @@ "setAccountNameDesc": "نامی که می‌خواهید برای این حساب نمایش داده شود را انتخاب کنید\nمی‌توانید از یکی از نام‌های حساب‌های وصل‌شده استفاده کنید\nیا یک نام جدید بسازید", "signInInfoText": "به حسابتان وصل شوید تا بلیت جمع کنید، آنلاین رقابت کنید \n.و پیشرفت خود را در دستگاه ها به اشتراک بگذارید", "signInText": "ورود به حساب", + "signInWithAnEmailAddressText": "ورود با آدرس ایمیل", "signInWithDeviceInfoText": "(یک حساب خودکار فقط از این دستگاه در دسترس می‌باشد)", "signInWithDeviceText": "وصل شدن با اکانت دستگاه", "signInWithGameCircleText": "وصل شدن از طریق حوزهٔ بازی", @@ -32,13 +33,13 @@ "signInWithTestAccountInfoText": "(حساب میراثی؛ از حساب‌های دستگاه برای پیشروی استفاده می‌کند)", "signInWithTestAccountText": "ورود با حساب آزمایشی", "signInWithText": "با ${SERVICE} وارد شوید", - "signInWithV2InfoText": "یک اکانت که روی همه سیستم عامل ها کار میکنه", + "signInWithV2InfoText": "یک حساب که روی همه سیستم عامل ها کار میکنه", "signInWithV2Text": "ورود به سیستم با حساب بمب اسکواد", "signOutText": "خروج از حساب", "signingInText": "در حال اتصال…", "signingOutText": "در حال خروج…", "ticketsText": "${COUNT} :بلیت‌ها", - "titleText": "حساب", + "titleText": "حساب کاربری", "unlinkAccountsInstructionsText": "یک حساب را برای جداسازی انتخاب کنید", "unlinkAccountsText": "جداسازی حساب‌ها", "unlinkLegacyV1AccountsText": "لغو پیوند حساب‌های قدیمی (V1)", @@ -81,8 +82,8 @@ "name": "بارگذار رایگان" }, "Gold Miner": { - "description": "شش حریف را با مین زمینی نابود کن", - "descriptionComplete": "شش حریف را با مین زمینی نابود کردی", + "description": "شش بچه بد را با مین زمینی نابود کن", + "descriptionComplete": "شش بچه بد را با مین زمینی نابود کردی", "descriptionFull": "با مین زمینی از بین ببر ${LEVEL} شش حریف را در مرحلهٔ", "descriptionFullComplete": "با مین زمینی نابود کردی ${LEVEL} شش حریف را در مرحلهٔ", "name": "مین‌گذار طلایی" @@ -92,7 +93,7 @@ "descriptionComplete": "بدون استفاده از هیچ مشت یا بمبی برنده شدی", "descriptionFull": "را بدون مشت یا بمب برنده شو ${LEVEL} بازی", "descriptionFullComplete": "را بدون مشت یا بمب برنده شدی ${LEVEL} بازی", - "name": "عجب حرکتی" + "name": "عجب حرکاتی بابا ورزشکار!" }, "In Control": { "descriptionFull": "(یک دستهٔ به بازی وصل کن (سخت‌افزاری یا نرم‌افزاری", @@ -163,8 +164,8 @@ "name": "${LEVEL} جادوگر" }, "Precision Bombing": { - "description": "بدون گرفتن هیچ قدرتی برنده شو", - "descriptionComplete": "بدون گرفتن هیچ قدرتی برنده شدی", + "description": "بدون گرفتن هیچ جعبه ی کمکی ای برنده شو", + "descriptionComplete": "بدون گرفتن هیچ جعبه ی کمکی ای برنده شدی", "descriptionFull": "رو بدون گرفتن قدرتی برنده شو ${LEVEL} مرحله", "descriptionFullComplete": "رو بدون گرفتن قدرتی برنده شدی ${LEVEL} مرحله", "name": "بمب باران دقیق" @@ -556,6 +557,7 @@ "demoText": "نسخه آزمایشی", "denyText": "نپذیرفتن", "deprecatedText": "ناراحت شد", + "descriptionText": "شرح", "desktopResText": "رزولوشن دسکتاپ", "deviceAccountUpgradeText": "هشدار:\nشما با حساب کاربری دستگاه ثبت نام کرده اید (${NAME}).\nدر بروزرسانی های آینده حساب کاربری دستگاه حذف خواهد شد.\nبه حساب کاربری نسخه 2 بروزرسانی کنید اگر می خواهید روند را ادامه دهید.", "difficultyEasyText": "آسان", @@ -601,6 +603,7 @@ "localProfileText": "(نمایهٔ محلی)", "nameDescriptionText": "نام بازیکن", "nameText": "نام", + "profileAlreadyExistsText": "نمایه ای با این نام از قبل وجود دارد.", "randomText": "تصادفی", "titleEditText": "ویرایش نمایه", "titleNewText": "نمایهٔ جدید", @@ -682,6 +685,8 @@ "duplicateText": "کپی کردن\nلیست بازی", "editText": "ویرایش\nلیست بازی", "newText": "لیست بازی\nجدید", + "pointsToWinText": "امتیاز برای برنده شدن", + "seriesLengthText": "طول سریال", "showTutorialText": "نمایش آموزش", "shuffleGameOrderText": "ترتیب تصادفی بازی ها", "titleText": "${TYPE} تنظیم لیست های" @@ -719,10 +724,10 @@ "friendHasSentPromoCodeText": "${NAME}از طرف ${APP_NAME}بلیطِ بازی ${COUNT}", "friendPromoCodeAwardText": ".بلیط خواهید گرفت هر بار که استفاده شود${COUNT}شما", "friendPromoCodeExpireText": ".ساعت منقضی میشود و تنها بر روی بازیکنان جدید کار میکند${EXPIRE_HOURS}این کد در", - "friendPromoCodeInstructionsText": ".را باز کنید و به قسمت تنظیمات>پیشرفته>وارد کردن کد بروید${APP_NAME}برای استفاده از کد، برنامه\n.سر بزنید تا لینک دانلود بازی برای سیستم عامل های مختلف بازی را بگیرید BombSquadgame.com به سایت", + "friendPromoCodeInstructionsText": ".را باز کنید و به قسمت تنظیمات>پیشرفته>فرستادن اطلاعات بروید${APP_NAME}برای استفاده از کد، برنامه\n.سر بزنید تا لینک دانلود بازی برای سیستم عامل های مختلف بازی را بگیرید BombSquadgame.com به سایت", "friendPromoCodeRedeemLongText": ".بلیط رایگان به دست آورند${COUNT}نفر میتوانند از این کد استفاده کنند تا${MAX_USES}", "friendPromoCodeRedeemShortText": ".بلیط در بازی بگیرید${COUNT}با این کد میتوانید", - "friendPromoCodeWhereToEnterText": "(در بخش تنظیمات>پیشرفته>وارد کردن کد)", + "friendPromoCodeWhereToEnterText": "(در بخش تنظیمات>پیشرفته>فرستادن اطلاعات)", "getFriendInviteCodeText": "گرفتن کد برای دعوت دوستان", "googlePlayDescriptionText": ":دعوت از بازیکنان گوگل پلی برای ملحق شدن به گروه شما", "googlePlayInviteText": "دعوت", @@ -1186,12 +1191,12 @@ "recentText": "اخیر", "remoteAppInfoShortText": ".خیلی جذاب تره وقتی با دوستانتون بازی می کنید ${APP_NAME} بازی\nکافیه چند تا دسته ی بازی به دستگاهتون وصل کنید یا\nرا روی گوشی و تبلت های دیگر نصب کنید تا ${REMOTE_APP_NAME} برنامه ی\n.به عنوان دسته برای بازی استفاده شوند", "remote_app": { - "app_name": "BombSquad کنترولر مخصوص", + "app_name": "کنترولر مخصوص بمب‌اسکواد", "app_name_short": "BSRemote", "button_position": "مکان دکمه", "button_size": "اندازه دکمه", "cant_resolve_host": "میزبان را نمی‌توان یافت.", - "capturing": "گرفتن…", + "capturing": "...در حال گرفتن", "connected": ".متصل شد", "description": "از گوشی یا تبلتتان به‌عنوان دستهٔ بازی برای بمب‌اسکواد استفاده کنید.\nتا ۸ دستگاه می‌توانند به‌صورت هم‌زمان برای جنون حماسهٔ محلی چندنفره روی یک تی‌وی یا تبلت متصل شوند.", "disconnected": "قطع اتصال از سرور", @@ -1212,7 +1217,7 @@ "searching": "جستجو برای بازی‌های فعال", "searching_caption": "بر روی نام یک بازی برای پیوستن به آن ضربه بزنید.\nاطمینان حاصل کنید که شما در شبکه وای فای همان بازی هستید.", "start": "شروع", - "version_mismatch": "عدم تطابق نسخه‌ها\nاطمینان حاصل کنید که بمب‌اسکوار و کنترولر\nآخرین ورژن باشن و دوباره امتحان کنید" + "version_mismatch": "عدم تطابق نسخه‌ها\nاطمینان حاصل کنید که بمب‌اسکواد و کنترولر\nآخرین ورژن باشن و دوباره امتحان کنید" }, "removeInGameAdsText": "بازی را در فروشگاه بخرید تا تبلیغات حذف شوند «${PRO}» نسخهٔ", "renameText": "تغییر نام", @@ -1248,6 +1253,7 @@ }, "scoreWasText": "(بود ${COUNT})", "selectText": "انتخاب", + "sendInfoDescriptionText": "اطلاعات اکانت و حالت برنامه را به سازنده میفرستد\nلطفاً اسم خود یا دلیل فرستادن را بیان کنید", "seriesWinLine1PlayerText": "برنده", "seriesWinLine1TeamText": "برنده", "seriesWinLine1Text": "برنده", @@ -1265,6 +1271,7 @@ "alwaysUseInternalKeyboardDescriptionText": "(یه صفحه‌کلید ساده و خوش‌دست بر روی صفحهٔ نمایش برای ویرایش متنی)", "alwaysUseInternalKeyboardText": "همیشه از صفحه‌کلید داخلی استفاده شود", "benchmarksText": "معیار و تست استرس", + "devToolsText": "ابزار های توسعه", "disableCameraGyroscopeMotionText": "غیرفعال کردن حرکت ژیروسکوپ دوربین", "disableCameraShakeText": "غیرفعال کردن لرزش دوربین", "disableThisNotice": "(شما میتوانید این اخطار را در تنظیمات یشرفته خاموش کنید)", @@ -1277,13 +1284,16 @@ "kidFriendlyModeText": "حالت دوستانه برای کودکان. خشونت کم", "languageText": "زبان", "moddingGuideText": "راهنمای برنامه‌نویسان", + "moddingToolsText": "اصلاح کردن ابزار", "mustRestartText": "برای اعمال تغییرات باید بازی را دوباره راه اندازی کنید", "netTestingText": "تست شبکه", "resetText": "باز گرداندن", + "sendInfoText": "فرستادن اطلاعات", "showBombTrajectoriesText": "نمایش خط سیر بمب", - "showDemosWhenIdleText": "نشان دادن دمو ها در وقت بیکاری", + "showDemosWhenIdleText": "نشان دادن دمو ها هنگام غیرفعال بودن", + "showDeprecatedLoginTypesText": "نمایش انواع ورود های منسوخ شده", "showDevConsoleButtonText": "نشان دادن دکمه توسعه دهنده کنسول", - "showInGamePingText": "نمایش پینگ در بازی", + "showInGamePingText": "نمایش پینگ درون بازی", "showPlayerNamesText": "نمایش نام بازیکنان", "showUserModsText": "نمایش پوشهٔ سبک بازی‌ها", "titleText": "پیشرفته", @@ -1291,8 +1301,8 @@ "translationFetchErrorText": "وضعیت ترجمه در دسترس نیست", "translationFetchingStatusText": "چک کردن وضعیت ترجمه ...", "translationInformMe": "!وقتی زبان من به‌روزرسانی نیاز داشت، خبرم کن", - "translationNoUpdateNeededText": "!زبان کنونی به‌روز است؛ ووهو", - "translationUpdateNeededText": "! زبان فعلی نیاز به به روز رسانی دارد", + "translationNoUpdateNeededText": "! زبان کنونی به‌روز است؛ ووهوو", + "translationUpdateNeededText": "! زبان فعلی نیاز به بروزرسانی دارد", "vrTestingText": "VR تست" }, "shareText": "اشتراک‌گذاری", @@ -1302,6 +1312,9 @@ "signInWithGameCenterText": "برای استفاده از حسابی که در یک گیم سنتر دارید,\nبا برنامه گیم سنتر خود وارد شوید.", "singleGamePlaylistNameText": "${GAME} فقط", "singlePlayerCountText": "1 بازیکن", + "sizeLargeText": "بزرگ", + "sizeMediumText": "متوسط", + "sizeSmallText": "کوچک", "soloNameFilterText": "رو در رو ${NAME}", "soundtrackTypeNames": { "CharSelect": "انتخاب کارکتر", @@ -1395,7 +1408,7 @@ "timeSuffixSecondsText": "ثانیه ${COUNT}", "tipText": "نکته", "titleText": "بمب‌اسکواد", - "titleVRText": "BombSquad VR", + "titleVRText": "VR بمب‌اسکواد نسخه", "topFriendsText": "بالاترین امتیاز دوستان", "tournamentCheckingStateText": "چک کردن وضعیت مسابقات؛ لطفا صبر کنید", "tournamentEndedText": "این دوره از مسابقات به پایان رسیده است دوره جدیدی بزودی آغاز خواهد شد", @@ -1827,6 +1840,7 @@ "toSkipPressAnythingText": "(هر کلیدی را بزنید تا از آموزش خارج شوید)" }, "twoKillText": "کشتن همزمان دونفر", + "uiScaleText": "UI مقیاس", "unavailableText": "در دسترس نیست", "unconfiguredControllerDetectedText": ":کنترول پیکربندی نشده شناسایی شد", "unlockThisInTheStoreText": ". این مورد باید در فروشگاه باز شود", @@ -1840,6 +1854,8 @@ "upgradeText": "ارتقا", "upgradeToPlayText": "بازی را از فروشگاه خریداری کنید تا این قابلیت فعال شود. ${PRO} نسخه ی", "useDefaultText": "استفاده از پیش فرض", + "userSystemScriptsCreateText": "اسناد ساخت حساب کاربری", + "userSystemScriptsDeleteText": "حذف اسناد کاربری سیستم", "usesExternalControllerText": "این بازی از یک کنترلر خارجی برای ورودی استفاده می کند.", "usingItunesText": "استفاده از برنامه ی موسیقی برای موسیقی متن...", "usingItunesTurnRepeatAndShuffleOnText": "مطمین شید که شافل روشن است و تکرار کنید همه رو در آیتونز", diff --git a/dist/ba_data/data/languages/polish.json b/dist/ba_data/data/languages/polish.json index 700aa81..78623aa 100644 --- a/dist/ba_data/data/languages/polish.json +++ b/dist/ba_data/data/languages/polish.json @@ -1,6 +1,6 @@ { "accountSettingsWindow": { - "accountNameRules": "Nazwy kont nie mogą zawierać emotikonów ani innych znaków specjalnych", + "accountNameRules": "Nazwy kont nie mogą zawierać emotikonów, ani innych znaków specjalnych", "accountProfileText": "(profil konta)", "accountsText": "Konta", "achievementProgressText": "Osiągnięcia: ${COUNT} z ${TOTAL}", @@ -46,7 +46,7 @@ "unlinkAccountsInstructionsText": "Wybierz konto do rozłączenia", "unlinkAccountsText": "Rozłącz konta", "unlinkLegacyV1AccountsText": "Rozłącz stare konta (V1)", - "v2LinkInstructionsText": "Użyj tego linku, aby stworzyć konto lub zaloguj się.", + "v2LinkInstructionsText": "Użyj tego linku, aby stworzyć konto lub zalogować się.", "viaAccount": "(przez konto ${NAME})", "youAreLoggedInAsText": "Jesteś zalogowany jako:", "youAreSignedInAsText": "Jesteś zalogowany jako:" @@ -74,15 +74,15 @@ "name": "Podwójne dzierżenie" }, "Flawless Victory": { - "description": "Wygraj nie dając się uderzyć", - "descriptionComplete": "Wygrałeś nie dając się uderzyć", - "descriptionFull": "Wygraj w trybie ${LEVEL} nie dając się uderzyć", - "descriptionFullComplete": "Wygrałeś w trybie ${LEVEL} nie dając się uderzyć", + "description": "Wygraj, nie dając się uderzyć", + "descriptionComplete": "Wygrałeś, nie dając się uderzyć", + "descriptionFull": "Wygraj w trybie ${LEVEL}, nie dając się uderzyć", + "descriptionFullComplete": "Wygrałeś w trybie ${LEVEL}, nie dając się uderzyć", "name": "Zwycięstwo bez skazy" }, "Free Loader": { "descriptionFull": "Zacznij grę Free-For-All z dwoma graczami lub więcej", - "descriptionFullComplete": "Zaczęto grę Free-For-All z dwoma, lub większą ilością graczy", + "descriptionFullComplete": "Zaczęto grę Free-For-All z dwoma lub większą ilością graczy", "name": "Łącznik graczy" }, "Gold Miner": { @@ -182,10 +182,10 @@ "name": "Zawodowy Bokser" }, "Pro Football Shutout": { - "description": "Wygraj nie pozwalając zapunktować złym gościom", - "descriptionComplete": "Wygrałeś nie pozwalając zapunktować złym gościom", - "descriptionFull": "Wygraj w trybie ${LEVEL} nie pozwalając zapunktować złym gościom", - "descriptionFullComplete": "Wygrałeś w trybie ${LEVEL} nie pozwalając zapunktować złym gościom", + "description": "Wygraj, nie pozwalając zapunktować złym gościom", + "descriptionComplete": "Wygrałeś, nie pozwalając zapunktować złym gościom", + "descriptionFull": "Wygraj w trybie ${LEVEL}, nie pozwalając zapunktować złym gościom", + "descriptionFullComplete": "Wygrałeś w trybie ${LEVEL}, nie pozwalając zapunktować złym gościom", "name": "Zamurowanie bramki w trybie ${LEVEL}" }, "Pro Football Victory": { @@ -304,10 +304,10 @@ "name": "Ściana" }, "Uber Football Shutout": { - "description": "Wygraj nie pozwalając zapunktować wrogom", - "descriptionComplete": "Wygrałeś nie pozwalając zapunktować wrogom", - "descriptionFull": "Wygraj w trybie ${LEVEL} nie pozwalając zapunktować wrogom", - "descriptionFullComplete": "Wygrałeś w trybie ${LEVEL} nie pozwalając zapunktować wrogom", + "description": "Wygraj, nie pozwalając zapunktować wrogom", + "descriptionComplete": "Wygrałeś, nie pozwalając zapunktować wrogom", + "descriptionFull": "Wygraj w trybie ${LEVEL}, nie pozwalając zapunktować wrogom", + "descriptionFullComplete": "Wygrałeś w trybie ${LEVEL}, nie pozwalając zapunktować wrogom", "name": "Zamurowanie bramki w trybie ${LEVEL}" }, "Uber Football Victory": { @@ -573,6 +573,7 @@ "demoText": "Demo", "denyText": "Odmów", "deprecatedText": "Przestarzałe", + "descriptionText": "Opis", "desktopResText": "Rozdzielczość ekranu", "deviceAccountUpgradeText": "Uwaga:\nLogujesz się kontem urządzenia (${NAME}).\nKonta urządzenia zostaną usunięte w przyszłej aktualizacji.\nUlepsz do konta V2, jeżeli chcesz zachować swój postęp.", "difficultyEasyText": "Łatwy", @@ -620,6 +621,7 @@ "localProfileText": "(lokalny profil)", "nameDescriptionText": "Nazwa gracza", "nameText": "Nazwa", + "profileAlreadyExistsText": "Profil z taką nazwą już istnieje.", "randomText": "losuj", "titleEditText": "Edytuj profil", "titleNewText": "Nowy profil", @@ -706,6 +708,8 @@ "editText": "Edytuj\nlistę", "gameListText": "Lista rozgrywek", "newText": "Nowa\nlista", + "pointsToWinText": "Punkty do zwycięstwa", + "seriesLengthText": "Długość serii", "showTutorialText": "Pokaż samouczek po grze", "shuffleGameOrderText": "Losowa kolejność rozgrywek", "titleText": "Własne listy rozgrywek trybu ${TYPE}" @@ -747,10 +751,10 @@ "friendPromoCodeAwardText": "Dostaniesz ${COUNT} kuponów zawsze gdy tego użyjesz.", "friendPromoCodeExpireText": "Ten kod wygaśnie po ${EXPIRE_HOURS} godzinach i działa tylko dla nowych graczy.", "friendPromoCodeInfoText": "Może zostać wykupione do ${COUNT} kuponów.\n\nIdź do \"Ustawienia->Zaawansowane->Wpisz Kod Promocyjny\" w grze aby go użyć. Idź do bombsquadgame.com by pobrać\nlinki do wszystkich wspieranych platform. Ten kod\nwygaśnie w ${EXPIRE_HOURS} godzin i jest prawidłowy tylko dla nowych graczy.", - "friendPromoCodeInstructionsText": "Aby użyć, otwórz ${APP_NAME} i idź do \"Ustawienia->Zaawansowane-> Wpisz kod\".\nWejdź na bombsquadgame.com by zobaczyć linki dla wszystkich dostępnych platform (Android itp.)", + "friendPromoCodeInstructionsText": "Aby go użyć, otwórz ${APP_NAME} i przejdź do \"Ustawienia->Zaawansowane->Wyślij informacje\".\nLinki do pobrania gry dla wszystkich obsługiwanych platform znajdziesz na stronie bombsquadgame.com.", "friendPromoCodeRedeemLongText": "Wykorzystanie go daje ${COUNT} darmowych kuponów dla najwięcej ${MAX_USES} ludzi.", "friendPromoCodeRedeemShortText": "Może być żądane do ${COUNT} kuponów w grze.", - "friendPromoCodeWhereToEnterText": "(W \"Ustawienia->Zaawansowane->Wpisz kod\")", + "friendPromoCodeWhereToEnterText": "(w \"Ustawienia->Zaawansowane->Wyślij informacje\")", "getFriendInviteCodeText": "Zdobądź kod promocyjny kumpla", "googlePlayDescriptionText": "Zaproś użytkowników Google Play na imprezę:", "googlePlayInviteText": "Zaproś", @@ -1307,6 +1311,7 @@ }, "scoreWasText": "(było ${COUNT})", "selectText": "Wybierz", + "sendInfoDescriptionText": "Przesyła stan konta i aplikacji do dewelopera.\nProszę o podanie imienia lub powodu wysłania.", "seriesWinLine1PlayerText": "WYGRAŁ", "seriesWinLine1TeamText": "WYGRALI", "seriesWinLine1Text": "WYGRAŁ", @@ -1325,7 +1330,8 @@ "alwaysUseInternalKeyboardDescriptionText": "(prosta klawiatura na ekranie do edycji tekstu - przyjazna kontrolerom)", "alwaysUseInternalKeyboardText": "Zawsze używaj wewn. klawiatury", "benchmarksText": "Benchmarki & Testy Wydajności", - "disableCameraGyroscopeMotionText": "Wyłącz Kontrolę Kamery Żyroskopem", + "devToolsText": "Narzędzia deweloperskie", + "disableCameraGyroscopeMotionText": "Wyłącz kontrolę kamery żyroskopem", "disableCameraShakeText": "Wyłącz trzęsienie kamery", "disableThisNotice": "(możesz wyłączyć to powiadomienie w ustawieniach zaawansowanych)", "enablePackageModsDescriptionText": "(aktywuje dodatkowe możliwości modowania ale wyłącza grę sieciową)", @@ -1337,10 +1343,14 @@ "kidFriendlyModeText": "Tryb dla dzieciaków (zredukowana przemoc itd.)", "languageText": "Język", "moddingGuideText": "Przewodnik modowania gry", + "moddingToolsText": "Narzędzia do modyfikacji", "mustRestartText": "Musisz uruchomić ponownie grę aby zastosować zmiany.", "netTestingText": "Testowanie sieci", "resetText": "Reset", + "sendInfoText": "Wyślij Info", "showBombTrajectoriesText": "Pokaż trajektorię bomb", + "showDemosWhenIdleText": "Pokazuj wersje demonstracyjne w stanie bezczynności", + "showDeprecatedLoginTypesText": "Pokaż przestarzałe typy logowania", "showDevConsoleButtonText": "Pokaż przycisk konsoli deweloperskiej", "showInGamePingText": "Pokaż ping w grze", "showPlayerNamesText": "Pokazuj nazwy graczy", @@ -1351,7 +1361,7 @@ "translationFetchingStatusText": "sprawdzanie statusu tłumaczenia...", "translationInformMe": "Powiadom mnie, gdy mój język będzie potrzebował uaktualnienia", "translationNoUpdateNeededText": "Obecnie używany język jest aktualny; ekstra!", - "translationUpdateNeededText": "** obecnie używany język wymaga jego zaktualizowania! **", + "translationUpdateNeededText": "** Obecnie używany język wymaga jego zaktualizowania! **", "vrTestingText": "Testowanie VR" }, "shareText": "Udostępnij", @@ -1361,6 +1371,9 @@ "signInWithGameCenterText": "By użyć konta Game Center\nzapisz się aplikacją Game Center.", "singleGamePlaylistNameText": "Tylko ${GAME}", "singlePlayerCountText": "1 gracz", + "sizeLargeText": "Duży", + "sizeMediumText": "Średni", + "sizeSmallText": "Mały", "soloNameFilterText": "Solówka ${NAME}", "soundtrackTypeNames": { "CharSelect": "Wybór postaci", @@ -1914,6 +1927,7 @@ "toSkipPressAnythingText": "(stuknij lub naciśnij cokolwiek, aby pominąć samouczek)" }, "twoKillText": "PODWÓJNE ZABÓJSTWO!", + "uiScaleText": "Skala interfejsu", "unavailableText": "niedostępne", "unconfiguredControllerDetectedText": "Wykryto nieskonfigurowany kontroler:", "unlockThisInTheStoreText": "To musi zostać odblokowane w sklepie.", @@ -1927,6 +1941,8 @@ "upgradeText": "Ulepsz", "upgradeToPlayText": "Aby zagrać, odblokuj grę w wersji \"${PRO}\".", "useDefaultText": "Użyj domyślnych", + "userSystemScriptsCreateText": "Utwórz skrypty systemowe użytkownika", + "userSystemScriptsDeleteText": "Usuń skrypty systemowe użytkownika", "usesExternalControllerText": "Ta gra wykorzystuje zewnętrzny kontroler jako wejście.", "usingItunesText": "Korzystanie z aplikacji muzycznej jako ścieżki dźwiękowej...", "usingItunesTurnRepeatAndShuffleOnText": "Upewnij się, że w ustawieniach iTunes tasowanie utworów i powtarzanie całości jest włączone.", @@ -1944,7 +1960,7 @@ "waitingForLocalPlayersText": "Oczekiwanie na lokalnych graczy...", "waitingForPlayersText": "oczekiwanie na dołączenie graczy...", "waitingInLineText": "Czekanie w kolejce (impreza pełna)...", - "watchAVideoText": "Zobacz Filmik", + "watchAVideoText": "Obejrzyj filmik", "watchAnAdText": "Obejrzyj reklamę", "watchWindow": { "deleteConfirmText": "Usunąć \"${REPLAY}\"?", diff --git a/dist/ba_data/data/languages/portuguese.json b/dist/ba_data/data/languages/portuguese.json index 9168085..56eaf26 100644 --- a/dist/ba_data/data/languages/portuguese.json +++ b/dist/ba_data/data/languages/portuguese.json @@ -26,6 +26,7 @@ "setAccountNameDesc": "Escolha o nome que será exibido na sua conta.\nVocê pode usar o nome de uma de suas contas\nou criar um nome personalizado exclusivo.", "signInInfoText": "Inicie sessão para ganhar bilhetes, compita online e compartilhe\no seu progresso entre vários dispositivos.", "signInText": "Iniciar sessão", + "signInWithAnEmailAddressText": "Faça login com um endereço de e-mail", "signInWithDeviceInfoText": "(uma conta automática disponível apenas neste aparelho)", "signInWithDeviceText": "Iniciar sessão com conta do dispositivo", "signInWithGameCircleText": "Iniciar sessão com Game Circle", @@ -428,11 +429,11 @@ "pressAnyButtonText": "Aperte qualquer botão...", "pressLeftRightText": "Aperte esquerda ou direita...", "pressUpDownText": "Aperte cima ou baixo...", - "runButton1Text": "Executar botão 1", - "runButton2Text": "Executar botão 2", - "runTrigger1Text": "Executar gatilho 1", - "runTrigger2Text": "Executar gatilho 2", - "runTriggerDescriptionText": "(os gatilhos o permitem correr em diferentes velocidades)", + "runButton1Text": "Correr Botão 1", + "runButton2Text": "Correr Botão 2", + "runTrigger1Text": "Correr Gatilho 1", + "runTrigger2Text": "Correr Gatilho 2", + "runTriggerDescriptionText": "(os gatilhos analogicos o permitem correr em diferentes velocidades)", "secondHalfText": "Use isto para configurar a segunda metade\nde um controle que funciona\ncomo 2-em-1.", "secondaryEnableText": "Ativar", "secondaryText": "Controle secundário", @@ -582,6 +583,7 @@ "demoText": "Teste", "denyText": "Recusar", "deprecatedText": "Descontinuado", + "descriptionText": "Descrição", "desktopResText": "Resolução da área de trabalho", "deviceAccountUpgradeText": "Aviso:\nVocê está logado com a conta do seu dispositivo\n(${NAME}).\nContas de dispositivo serão removidas em uma atualização futura.", "difficultyEasyText": "Fácil", @@ -629,6 +631,7 @@ "localProfileText": "(perfil local)", "nameDescriptionText": "Nome do jogador", "nameText": "Nome", + "profileAlreadyExistsText": "Um perfil com este nome já existe.", "randomText": "aleatório", "titleEditText": "Editar perfil", "titleNewText": "Novo perfil", @@ -716,6 +719,8 @@ "editText": "Editar\nPlaylist", "gameListText": "Lista de Games", "newText": "Nova\nPlaylist", + "pointsToWinText": "Pontos para ganhar", + "seriesLengthText": "Tamanho da série", "showTutorialText": "Mostrar Tutorial", "shuffleGameOrderText": "Ordem de partida aleatória", "titleText": "Personalizar playlists de ${TYPE}" @@ -755,10 +760,10 @@ "friendHasSentPromoCodeText": "${COUNT} bilhetes do ${APP_NAME} mandados por ${NAME}", "friendPromoCodeAwardText": "Você ganhará ${COUNT} bilhetes toda vez que for usado.", "friendPromoCodeExpireText": "O código expira em ${EXPIRE_HOURS} horas e só funcionará para novos jogadores.", - "friendPromoCodeInstructionsText": "Para usar, abra ${APP_NAME} e vá até \"Configurações-> Avançado-> Inserir código\".\nVeja bombsquadgame.com para os links de download para todas as plataformas disponíveis.", + "friendPromoCodeInstructionsText": "Para usar, abra ${APP_NAME} e vá até \"Configurações-> Avançaodo-> Inserir código\". \nVeja bombsquadgame.com para os download de links para todas as plataformas disponíveis.", "friendPromoCodeRedeemLongText": "Pode ser resgatado por ${COUNT} bilhetes gratuitos por até ${MAX_USES} pessoas.", "friendPromoCodeRedeemShortText": "Pode ser resgatado por ${COUNT} bilhetes no jogo.", - "friendPromoCodeWhereToEnterText": "(em \"Configurações-> Avançado-> Inserir código\")", + "friendPromoCodeWhereToEnterText": "(em \"Configuração -> Avançado -> Inserir código\")", "getFriendInviteCodeText": "Obter código de convite", "googlePlayDescriptionText": "Convidar jogadores do Google Play para a sua sala:", "googlePlayInviteText": "Convidar", @@ -1328,6 +1333,7 @@ }, "scoreWasText": "(foi ${COUNT})", "selectText": "Selecionar", + "sendInfoDescriptionText": "Envia a informação sua conta e estado de aplicativo para o desenvolvedor.\nPor favor incluir seu nome ou razão para mandar.", "seriesWinLine1PlayerText": "VENCEU A", "seriesWinLine1Scale": 0.65, "seriesWinLine1TeamText": "VENCEU A", @@ -1349,27 +1355,31 @@ }, "settingsWindowAdvanced": { "alwaysUseInternalKeyboardDescriptionText": "(um teclado simples para edição de texto)", - "alwaysUseInternalKeyboardText": "Sempre usar o teclado interno", + "alwaysUseInternalKeyboardText": "Sempre Usar O Teclado Interno", "benchmarksText": "Testes de desempenho", - "disableCameraGyroscopeMotionText": "Desativar o movimento do giroscópio da câmera", - "disableCameraShakeText": "Desabilitar o shake da câmera", + "devToolsText": "Ferramentas de Desenvolvimento", + "disableCameraGyroscopeMotionText": "Desativar Movimento Giroscópio da Câmera", + "disableCameraShakeText": "Desativar Tremida da câmera", "disableThisNotice": "(você pode desativar este aviso em configurações avançadas)", "enablePackageModsDescriptionText": "(ativa habilidades de modificação do jogo mas desabilita jogos em LAN)", "enablePackageModsText": "Ativar modificações locais do jogo", "enterPromoCodeText": "Inserir código", "forTestingText": "Nota: esses valores são somente para teste e serão perdidos assim que o aplicativo for fechado.", "helpTranslateText": "As traduções do ${APP_NAME} são sustentadas pelo\nesforço público. Se você gostaria de ajudar ou corrigir\numa tradução, siga o link a seguir. Agradeço desde já!", - "kickIdlePlayersText": "Expulsar jogadores ausentes", + "kickIdlePlayersText": "Expulsar Jogadores Ausentes", "kidFriendlyModeText": "Modo para crianças (violência reduzida, etc)", "languageText": "Idioma", "moddingGuideText": "Guia de modificação", + "moddingToolsText": "Ferramentas Para Mods", "mustRestartText": "Você deve reiniciar o jogo para a modificação ter efeito.", "netTestingText": "Teste de conexão", "resetText": "Redefinir", + "sendInfoText": "Enviar Informação", "showBombTrajectoriesText": "Mostrar trajetórias da bomba", - "showDemosWhenIdleText": "Mostrar demonstrações quando ocioso", + "showDemosWhenIdleText": "Mostrar Demonstrações Quando ocioso", + "showDeprecatedLoginTypesText": "Mostrar tipos de logins ultrapassados", "showDevConsoleButtonText": "Mostrar console de desenvolvedor", - "showInGamePingText": "Mostrar latência no jogo", + "showInGamePingText": "Mostrar Latência no jogo", "showPlayerNamesText": "Mostrar nomes dos jogadores", "showUserModsText": "Mostrar Pasta de Modificações", "titleText": "Avançado", @@ -1378,7 +1388,7 @@ "translationFetchingStatusText": "verificando estado da tradução...", "translationInformMe": "Informe quando meu idioma precisar de atualizações", "translationNoUpdateNeededText": "o idioma atual está atualizado; woohoo!", - "translationUpdateNeededText": "** o idioma atual precisa de atualizações!! **", + "translationUpdateNeededText": "** o idioma atual necessita de atualizações!! **", "vrTestingText": "Teste de RV" }, "shareText": "Compartilhar", @@ -1388,6 +1398,9 @@ "signInWithGameCenterText": "Para usar uma conta Game Center,\ninicie a sessão com o aplicativo Game Center.", "singleGamePlaylistNameText": "Somente ${GAME}", "singlePlayerCountText": "1 jogador", + "sizeLargeText": "Grande", + "sizeMediumText": "Médio", + "sizeSmallText": "Pequeno", "soloNameFilterText": "Solo ${NAME}", "soundtrackTypeNames": { "CharSelect": "Seleção do personagem", @@ -1942,6 +1955,7 @@ "toSkipPressAnythingText": "(pressione qualquer coisa para pular o tutorial)" }, "twoKillText": "MATOU DOIS!", + "uiScaleText": "Tamanho da Interface", "unavailableText": "indisponível", "unconfiguredControllerDetectedText": "Controle não configurado detectado:", "unlockThisInTheStoreText": "Isto deve ser desbloqueado na loja.", @@ -1955,6 +1969,8 @@ "upgradeText": "Melhorar", "upgradeToPlayText": "Atualize para \"${PRO}\" na loja para jogar.", "useDefaultText": "Usar padrão", + "userSystemScriptsCreateText": "Criar Scripts do Sistema do Usuário", + "userSystemScriptsDeleteText": "Deletar Scripts do Sistema do Usuário", "usesExternalControllerText": "Este jogo usa um controle externo para entrada.", "usingItunesText": "Usando o app de música para a trilha sonora", "usingItunesTurnRepeatAndShuffleOnText": "Por favor, certifique-se de que o aleatório esteja ligado e que a repetição seja TODAS AS MÚSICAS no iTunes.", diff --git a/dist/ba_data/data/languages/romanian.json b/dist/ba_data/data/languages/romanian.json index 0b00739..5c758e8 100644 --- a/dist/ba_data/data/languages/romanian.json +++ b/dist/ba_data/data/languages/romanian.json @@ -1878,7 +1878,7 @@ "xbox360ControllersWindow": { "getDriverText": "Instalează Driver-ul.", "macInstructions2Text": "Pentru a folosi controllerele fără fir, vei avea nevoie de un receptor\ncare vine cu pachetul 'Xbox 360 Wireless Controller for Windows'.\nFiecare receptor te lasă să conectezi până la 4 controllere.\n\nImportant: Receptoare de tip 3rd-party nu vor funcționa cu acest driver;\nAsigură-te că pe receptor scrie 'Microsoft', nu 'XBOX 360'.\nMicrosoft nu mai le vinde separat, deci va trebui să iei unul\nîmpreună cu controllerul sau de pe un site, cum ar fi Ebay.\n\nDacă ai găsit acest mesaj folositor, consideră faptul să-i donezi programatorului\ndriverului pe site-ul lui.", - "macInstructionsText": "Pentru a folosi controllere de Xbox 360, va trebui să instalezi \ndriver-ul pentru Mac disponibil din link-ul de mai jos. \nFuncționează pentru controalele cu/fără fir.", + "macInstructionsText": "Pentru a folosi controllere de Xbox 360, va trebui să instalezi \ndriver-ul pentru Mac disponibil din link-ul de mai jos. \nFuncționează pentru controalele cu/fără fir. ", "ouyaInstructionsText": "Pentru a folosi controllere de Xbox 360 cu fir, pur și simplu\nconectează-le la un port USB. Poți folosi un USB-hub pentru a\nconecta mai multe controllere.\n\nPentru a folosi controllere fără fir vei avea nevoie de un receptor\nwireless, valabil ca parte a pachetului \"Xbox 360 wireless controller\nfor Windows\" sau vândut separat. Fiecare receptor intră într-un port\nUSB și te lasă să conectezi până la 4 controllere.", "titleText": "Se folosesc controllere de Xbox 360 cu ${APP_NAME}:" }, diff --git a/dist/ba_data/data/languages/russian.json b/dist/ba_data/data/languages/russian.json index ab91cc6..de12288 100644 --- a/dist/ba_data/data/languages/russian.json +++ b/dist/ba_data/data/languages/russian.json @@ -7,9 +7,9 @@ "campaignProgressText": "Прогресс кампании [Сложный режим]: ${PROGRESS}", "changeOncePerSeason": "Вы можете изменить это только раз в сезон.", "changeOncePerSeasonError": "Вы должны подождать до следующего сезона, чтобы изменить это снова (${NUM} дней)", - "customName": "имя пользователя", + "customName": "Имя пользователя", "deviceSpecificAccountText": "Сейчас используется аккаунт имениустройства: ${NAME}", - "googlePlayGamesAccountSwitchText": "Если хотите сменить внутриигровой аккаунт Google, используйте приложение Google Play.", + "googlePlayGamesAccountSwitchText": "Если хотите сменить внутриигровой аккаунт Google,\nиспользуйте приложение Google Play.", "linkAccountsEnterCodeText": "Введите код", "linkAccountsGenerateCodeText": "Сгенерировать код", "linkAccountsInfoText": "(поделитесь достижениями с другими платформами)", @@ -27,6 +27,7 @@ "setAccountNameDesc": "Выберите имя для отображения своего аккаунта.\nВы можете использовать имя одного из ваших связанных аккаунтов или создать уникальное имя аккаунта.", "signInInfoText": "Войдите в аккаунт, чтобы собирать билеты, \nсоревноваться онлайн и делиться успехами.", "signInText": "Войти", + "signInWithAnEmailAddressText": "Войти через электронную почту", "signInWithDeviceInfoText": "(стандартный аккаунт только для этого устройства)", "signInWithDeviceText": "Войти через аккаунт устройства", "signInWithGameCircleText": "Войти через Game Circle", @@ -128,7 +129,7 @@ }, "Mine Games": { "description": "Убейте 3 плохих парней с помощью мин", - "descriptionComplete": "С помощью мин убито 3 плохих парней", + "descriptionComplete": "С помощью мин убита тройка плохих парней", "descriptionFull": "Убейте 3 негодяев с помощью мин на уровне ${LEVEL}", "descriptionFullComplete": "С помощью мин убито 3 негодяя на уровне ${LEVEL}", "name": "Игры с минами" @@ -138,7 +139,7 @@ "descriptionComplete": "С карты сброшено 3 плохих парней", "descriptionFull": "Сбросьте 3 плохих парней с карты на уровне ${LEVEL}", "descriptionFullComplete": "3 плохих парней сброшено с карты на уровне ${LEVEL}", - "name": "Пора учится летать" + "name": "Пора учится летать!" }, "Onslaught God": { "description": "Наберите 5000 очков", @@ -187,7 +188,7 @@ "descriptionComplete": "Уровень был пройден всухую", "descriptionFull": "Выиграйте матч ${LEVEL} всухую", "descriptionFullComplete": "Победа в матче ${LEVEL} всухую", - "name": "${LEVEL} всухую" + "name": "${LEVEL} Всухую" }, "Pro Football Victory": { "description": "Выиграйте матч", @@ -198,7 +199,7 @@ }, "Pro Onslaught Victory": { "description": "Победите все волны", - "descriptionComplete": "Пройдены все волны", + "descriptionComplete": "Все волны пройдены!", "descriptionFull": "Победите все волны на уровне ${LEVEL}", "descriptionFullComplete": "Пройдены все волны на ${LEVEL}", "name": "Победа на уровне ${LEVEL}" @@ -219,7 +220,7 @@ }, "Rookie Football Victory": { "description": "Выиграйте матч", - "descriptionComplete": "Матч пройден в вашу пользу", + "descriptionComplete": "Вы одержали победу в матче!", "descriptionFull": "Выиграйте матч ${LEVEL}", "descriptionFullComplete": "Выигран матч ${LEVEL}", "name": "Победа в матче ${LEVEL}" @@ -305,7 +306,7 @@ "name": "Стена" }, "Uber Football Shutout": { - "description": "Выиграйте в сухую", + "description": "Выиграйте всухую", "descriptionComplete": "Победа в сухую", "descriptionFull": "Выиграйте матч ${LEVEL} в сухую", "descriptionFullComplete": "Победа в матче ${LEVEL} в сухую", @@ -523,7 +524,7 @@ }, "copyConfirmText": "Скопировано в буфер обмена", "copyOfText": "Копия ${NAME}", - "copyText": "Копировать", + "copyText": "Скопировать", "copyrightText": "© 2013 Eric Froemling", "createAPlayerProfileText": "Создать профиль игрока?", "createEditPlayerText": "<Создание / редактирование игрока>", @@ -576,6 +577,7 @@ "demoText": "Демонстрация", "denyText": "Отклонить", "deprecatedText": "Устарело>=", + "descriptionText": "Описание", "desktopResText": "Разреш. экрана", "deviceAccountUpgradeText": "Внимание!\nВы заригестрированы как (${NAME})!\nДанный аккаунт будет удален в следующем обновлении!\nОбновите его до аккаунта Google Play, если не хотите потерять прогресс!", "difficultyEasyText": "Легкий", @@ -623,6 +625,7 @@ "localProfileText": "(локальный профиль)", "nameDescriptionText": "Имя игрока", "nameText": "Имя", + "profileAlreadyExistsText": "Профиль с таким именем уже существует", "randomText": "случайное", "titleEditText": "Изменение профиля", "titleNewText": "Новый профиль", @@ -710,6 +713,8 @@ "editText": "Изменить\nплей-лист", "gameListText": "Список игр", "newText": "Новый\nплей-лист", + "pointsToWinText": "Очки до победы", + "seriesLengthText": "Продолжительность сессии", "showTutorialText": "Показать обучение", "shuffleGameOrderText": "Смешать порядок игр", "titleText": "Настроить плей-листы '${TYPE}'" @@ -750,10 +755,10 @@ "friendHasSentPromoCodeText": "${COUNT} ${APP_NAME} билетов от ${NAME}", "friendPromoCodeAwardText": "Вы получите ${COUNT} билетов за каждую активацию.", "friendPromoCodeExpireText": "Код действителен в течении ${EXPIRE_HOURS} часов и только для новых игроков.", - "friendPromoCodeInstructionsText": "Для активации в ${APP_NAME} зайдите в\n\"Настройки->Дополнительно->Ввести промо-код\". Перейди на сайт bombsquadgame.com, чтобы скачать версию игры для своей платформы.", + "friendPromoCodeInstructionsText": "Для активации в ${APP_NAME} зайдите в\n\"Настройки->Дополнительно->Прислать информацию\". Перейдите на сайт bombsquadgame.com, чтобы скачать версию игры для своей платформы.", "friendPromoCodeRedeemLongText": "Каждая активация принесет ${COUNT} билетов до ${MAX_USES} игрокам.", "friendPromoCodeRedeemShortText": "Он принесет ${COUNT} билетов в игре.", - "friendPromoCodeWhereToEnterText": "(в \"Настройках->Дополнительно->Ввести код\")", + "friendPromoCodeWhereToEnterText": "(в \"Настройках->Дополнительно->Прислать информацию\")", "getFriendInviteCodeText": "Получить промо-код", "googlePlayDescriptionText": "Пригласить игроков Google Play в ваше лобби:", "googlePlayInviteText": "Пригласить", @@ -1309,6 +1314,7 @@ }, "scoreWasText": "(было ${COUNT})", "selectText": "Выбрать", + "sendInfoDescriptionText": "Отправьте информацию об вашей учётной записи и состоянии приложения непосредственно разработчику.\nПожалуйста, не забудь указать ваше имя и причину отправки.", "seriesWinLine1PlayerText": "ПОБЕДИЛ В", "seriesWinLine1TeamText": "ПОБЕДИЛИ В", "seriesWinLine1Text": "ПОБЕДИЛ В", @@ -1325,27 +1331,31 @@ }, "settingsWindowAdvanced": { "alwaysUseInternalKeyboardDescriptionText": "(простая, удобная для контроллера виртуальная клавиатура для ввода текста)", - "alwaysUseInternalKeyboardText": "Всегда использовать встроенную клавиатуру", + "alwaysUseInternalKeyboardText": "Всегда использовать ТОЛЬКО встроенную (внутреннюю) клавиатуру. Удобно если клавиатура др. приложений лагает.", "benchmarksText": "Тест производительности и тест-нагрузка", - "disableCameraGyroscopeMotionText": "Отключить движение камеры с помощью гироскопа", - "disableCameraShakeText": "Отключить тряску камеры", + "devToolsText": "Инструменты разработчиков", + "disableCameraGyroscopeMotionText": "Убрать тряску и движение камеры при помощи гироскопа", + "disableCameraShakeText": "Отключить эффект трясущейся камеры.", "disableThisNotice": "(вы можете отключить это уведомление в настройках)", "enablePackageModsDescriptionText": "(позволяет дополнительные возможности для моддинга, но отключает сетевую игру)", "enablePackageModsText": "Включить моды локального пакета", "enterPromoCodeText": "Введите промо-код", "forTestingText": "Примечание: эти значения используются только для тестирования и будут потеряны, когда приложение завершит работу.", "helpTranslateText": "Переводы игры ${APP_NAME} с английского совершён общественными\nусилиями. Если вы хотите предложить или исправить\nперевод, следуйте по ссылке ниже. Заранее спасибо!", - "kickIdlePlayersText": "Выкидывать бездействующих игроков", + "kickIdlePlayersText": "Включите, чтобы выкидывать игроков ушедших в статус \"AFK\".", "kidFriendlyModeText": "Семейный режим (меньше насилия, и т.д.)", "languageText": "Язык", "moddingGuideText": "Руководство по моддингу", + "moddingToolsText": "Настройки модов", "mustRestartText": "Необходимо перезапустить игру, чтобы изменения вступили в силу.", "netTestingText": "Тестирование сети", "resetText": "Сбросить", + "sendInfoText": "Отослать информацию (обратная связь с разработчиком).", "showBombTrajectoriesText": "Показывать траекторию бомбы", - "showDemosWhenIdleText": "Показывать демоверсии в режиме ожидания", - "showDevConsoleButtonText": "Показать кнопку консоли", - "showInGamePingText": "Показать Ping", + "showDemosWhenIdleText": "Показать Демо-Версию в режиме ожидания.", + "showDeprecatedLoginTypesText": "Показывать устаревшие типы входа в систему", + "showDevConsoleButtonText": "Показать кнопку консоли (для разработчика).", + "showInGamePingText": "Показать пинг (Задержку действий на сервере).", "showPlayerNamesText": "Показывать имена игроков", "showUserModsText": "Показать папку модов", "titleText": "Дополнительно", @@ -1353,8 +1363,8 @@ "translationFetchErrorText": "статус перевода недоступен", "translationFetchingStatusText": "проверка статуса перевода...", "translationInformMe": "Сообщите мне, если мой язык нуждается в обновлениях", - "translationNoUpdateNeededText": "данный язык полностью обновлен, ура!", - "translationUpdateNeededText": "** данный язык нуждается в обновлениях!! **", + "translationNoUpdateNeededText": "Данный язык полностью обновлен, Ееее-Юху!)", + "translationUpdateNeededText": "** Установленный язык требует обновлений!!!**", "vrTestingText": "Тестирование VR" }, "shareText": "Поделиться", @@ -1364,6 +1374,9 @@ "signInWithGameCenterText": "Чтобы использовать аккаунт GameCenter,\nвойдите через GameCenter.", "singleGamePlaylistNameText": "Просто ${GAME}", "singlePlayerCountText": "1 игрок", + "sizeLargeText": "Гигантский", + "sizeMediumText": "Средний", + "sizeSmallText": "Крошечный", "soloNameFilterText": "${NAME} соло", "soundtrackTypeNames": { "CharSelect": "Выбор персонажа", @@ -1918,6 +1931,7 @@ "toSkipPressAnythingText": "(коснитесь или нажмите что-нибудь чтобы пропустить тьюториал)" }, "twoKillText": "ДВОИХ ЗА РАЗ!", + "uiScaleText": "Размер UI", "unavailableText": "недоступно", "unconfiguredControllerDetectedText": "Обнаружен ненастроенный геймпад:", "unlockThisInTheStoreText": "Это должно быть разблокировано в магазине.", @@ -1931,6 +1945,8 @@ "upgradeText": "Обновление", "upgradeToPlayText": "Разблокируйте \"${PRO}\" в магазине что-бы играть в это.", "useDefaultText": "Использовать стандартные", + "userSystemScriptsCreateText": "Создать Свои Скрипты", + "userSystemScriptsDeleteText": "Стереть Свои Скрипты", "usesExternalControllerText": "Эта игра может использовать внешний контроллер для управления.", "usingItunesText": "Использование музыкального приложения для саундтрека...", "usingItunesTurnRepeatAndShuffleOnText": "Убедитесь, что в iTunes включен случайный порядок, и повтор установлен на 'все'.", diff --git a/dist/ba_data/data/languages/slovak.json b/dist/ba_data/data/languages/slovak.json index 1460245..30410ab 100644 --- a/dist/ba_data/data/languages/slovak.json +++ b/dist/ba_data/data/languages/slovak.json @@ -32,6 +32,7 @@ "signInWithGooglePlayText": "Príhlásit sa s Google Play", "signInWithTestAccountInfoText": "(starý typ účtu; v budúcnosti používajte účty zariadení)", "signInWithTestAccountText": "Prihlásit sa s testovacím účtom", + "signInWithText": "Prihlás sa s ${SERVICE}", "signInWithV2InfoText": "(účet, ktorý funguje na všetkých platformách)", "signInWithV2Text": "Prihláste sa pomocou účtu BombSquad", "signOutText": "Odhlasujem", @@ -338,6 +339,8 @@ "getMoreGamesText": "Viac Hier...", "titleText": "Pridať Hru" }, + "addToFavoritesText": "Pridať do Obľúbených", + "addedToFavoritesText": "'${NAME}' pridané do Obľúbených.", "allText": "Všetko", "allowText": "Povol", "alreadySignedInText": "Tvoj účet je prihlásený z iného zariadenia;\nprosím prepni si účty alebo ukonči hru na\ntvojich ostatných zariadeniach a skús to znovu.", @@ -371,6 +374,7 @@ "chatMutedText": "Čet Zablokovaný", "chatUnMuteText": "Odblokovať Čet", "choosingPlayerText": "", + "codesExplainText": "Kódy sú poskytované vývojárovi \nk diagnostike a oprave problémov s účtom.", "completeThisLevelToProceedText": "Musíš dokončiť\ntento level pre postup!", "completionBonusText": "Bonus za Dokončenie", "configControllersWindow": { @@ -451,6 +455,7 @@ "swipeText": "posúvanie", "titleText": "Konfigurovať Obrazovku" }, + "configureDeviceInSystemSettingsText": "${DEVICE} možno nakonfigurovať v systémových nastaveniach aplikácie.", "configureItNowText": "Konfigurovať teraz?", "configureText": "Konfigurovať", "connectMobileDevicesWindow": { @@ -563,6 +568,9 @@ "disableRemoteAppConnectionsText": "Zakázať Remote-App Pripojenia", "disableXInputDescriptionText": "Povolí viac ako 4 ovládače ale nemusí fungovať dobre.", "disableXInputText": "Zakázať XInput", + "disabledText": "Vypnuté", + "discordFriendsText": "Chcete nájsť nových ľudí na hranie?\nPripojte sa na náš Discord a nájdite nových priateľov!", + "discordJoinText": "Pripojiť na Diskord", "doneText": "Hotovo", "drawText": "Remíza", "duplicateText": "Duplikovať", @@ -595,6 +603,7 @@ "localProfileText": "(lokálny profil)", "nameDescriptionText": "Meno Hráča", "nameText": "Meno", + "profileAlreadyExistsText": "Profil s týmto menom už existuje.", "randomText": "náhodne", "titleEditText": "Upraviť Profil", "titleNewText": "Nový Profil", @@ -630,6 +639,7 @@ "useMusicFolderText": "Zložka Súborov Hudby" }, "editText": "Upraviť", + "enabledText": "Povolené", "endText": "Ukončiť", "enjoyText": "Užite si to!", "epicDescriptionFilterText": "${DESCRIPTION} Spomalene.", @@ -675,6 +685,8 @@ "duplicateText": "Duplikovať\nPlaylist", "editText": "Upraviť\nPlaylist", "newText": "Nový\nPlaylist", + "pointsToWinText": "Body ku výhre", + "seriesLengthText": "Dĺžka Série", "showTutorialText": "Ukázať Tutoriál", "shuffleGameOrderText": "Náhodné Poradie Hier", "titleText": "Upraviť ${TYPE} Playlisty" @@ -747,6 +759,7 @@ "manualYourLocalAddressText": "Tvoja lokálna adresa:", "nearbyText": "Neďaleko", "noConnectionText": "<žiadne pripojenie>", + "noPartiesAddedText": "Žiadne Párty nie sú pridané", "otherVersionsText": "(ostatné verzie)", "partyCodeText": "Párty kód", "partyInviteAcceptText": "Potvrdiť", @@ -818,10 +831,12 @@ "alwaysText": "Stále", "fullScreenCmdText": "Celá Obrazovka (Cmd+F)", "fullScreenCtrlText": "Celá Obrazovka (Ctrl+F)", + "fullScreenText": "Na celú obrazovku", "gammaText": "Gamma", "highText": "Vysoko", "higherText": "Vyššie", "lowText": "Nízko", + "maxFPSText": "Maximálny počet FPS", "mediumText": "Stredne", "neverText": "Nikdy", "resolutionText": "Rozlíšenie", @@ -1059,7 +1074,9 @@ "noContinuesText": "(žiadne pokračovania)", "noExternalStorageErrorText": "Žiadne úložisko sa v tomto zariadení nenašlo", "noGameCircleText": "Error: nie si prihlásený do GameCircle", + "noPluginsInstalledText": "Žiadne nainštalované doplnky", "noScoresYetText": "Zatiaľ žiadne skóre.", + "noServersFoundText": "Nenašiel sa žiadny server.", "noThanksText": "Nie Vďaka", "noTournamentsInTestBuildText": "UPOZORNENIE: Výsledky turnajov z tejto testovacej zostavy budú ignorované.", "noValidMapsErrorText": "Žiadne platné mapy sa pre tento typ hry nenašli.", @@ -1224,6 +1241,8 @@ "runText": "Bežať", "saveText": "Uložiť", "scanScriptsErrorText": "Chyba pri skenovaní skriptov. Pozri zápis pre detaily.", + "scanScriptsMultipleModulesNeedUpdatesText": "${PATH} a ${NUM} ďalšie moduly je potrebné aktualizovať pre api ${API}.", + "scanScriptsSingleModuleNeedsUpdatesText": "${PATH} je potrebné aktualizovať pre api ${API}.", "scoreChallengesText": "Challenge pre Skóre", "scoreListUnavailableText": "List pre skóre je nedostupné.", "scoreText": "Skóre", @@ -1267,6 +1286,8 @@ "netTestingText": "Testovanie Internetu", "resetText": "Resetovať", "showBombTrajectoriesText": "Ukázovať Trajektóriu Bomby", + "showDemosWhenIdleText": "Zobraziť Ukážky Pri Nečinnosti", + "showDevConsoleButtonText": "Zobraziť Tlačidlo Konzoly Zariadenia", "showInGamePingText": "Ukázať Ping v Hre", "showPlayerNamesText": "Ukazovať Mená Hráčov", "showUserModsText": "Ukázať Zložku pre Módy", @@ -1286,6 +1307,9 @@ "signInWithGameCenterText": "Ak chceš použiť GameCircle účet,\nprihlás sa s Game Center aplikáciou.", "singleGamePlaylistNameText": "Len ${GAME}", "singlePlayerCountText": "1 hráč", + "sizeLargeText": "Velké", + "sizeMediumText": "Stredné", + "sizeSmallText": "Malé", "soloNameFilterText": "Sólo ${NAME}", "soundtrackTypeNames": { "CharSelect": "Výber Charakteru", @@ -1358,6 +1382,8 @@ "storeText": "Obchod", "submitText": "Poslať", "submittingPromoCodeText": "Overujem Kód...", + "successText": "Úspech!", + "supportEmailText": "Ak sa vyskytujú nejaké problémy s aplikáciou,\npošlite e-mail na adresu ${EMAIL}.", "teamNamesColorText": "Mená/Farby tímov", "telnetAccessGrantedText": "Telnet povolené.", "telnetAccessText": "Telnet detekovaný; povoliť?", @@ -1811,6 +1837,7 @@ "unlockThisInTheStoreText": "Toto musí byť odomknuté v obchode.", "unlockThisProfilesText": "Ak chceš vytvoriť viac ako ${NUM} profilov, potrebuješ:", "unlockThisText": "Ak chceš toto odomknúť, potrebuješ:", + "unsupportedControllerText": "Je nám ľúto, ovládač \"${NAME}\" nie je podporovaný.", "unsupportedHardwareText": "Prepáč, tento hardware nie je podporovaný pre túto verziu hry.", "upFirstText": "Ako prvé ide:", "upNextText": "Ďalej v hre ${COUNT}:", @@ -1822,6 +1849,7 @@ "usingItunesText": "Používam Music App ako soundtrack...", "v2AccountLinkingInfoText": "Na prepojenie V2 účtov, použi tlačidlo 'Spravovať Účet'.", "validatingTestBuildText": "Overujem Testovaciu Verziu...", + "viaText": "ako", "victoryText": "Výhra!", "voteDelayText": "Nemôžeš začať ďalšie hlasovanie v podobe ${NUMBER} sekúnd", "voteInProgressText": "Hlasovanie už prebieha.", diff --git a/dist/ba_data/data/languages/spanish.json b/dist/ba_data/data/languages/spanish.json index 08f850a..7d8c601 100644 --- a/dist/ba_data/data/languages/spanish.json +++ b/dist/ba_data/data/languages/spanish.json @@ -1,6 +1,6 @@ { "accountSettingsWindow": { - "accountNameRules": "Los nombres de cuentas no pueden contener emojis u otros caracteres especiales", + "accountNameRules": "Los nombres de cuentas no pueden contener emojis o otros caracteres especiales", "accountProfileText": "(Perfil de la cuenta)", "accountsText": "Cuentas", "achievementProgressText": "Logros: ${COUNT} de ${TOTAL}", @@ -27,6 +27,7 @@ "setAccountNameDesc": "Selecciona el nombre a mostrar para tu cuenta. \nPuedes usar el nombre de tu cuenta asociada\no crear un nombre personalizado.", "signInInfoText": "Inicia sesión para obtener boletos, competir en línea\ny compartir tu progreso a través de plataformas.", "signInText": "Iniciar Sesión", + "signInWithAnEmailAddressText": "Iniciar sesión con una dirección de correo electrónico", "signInWithDeviceInfoText": "(una cuenta automática disponible únicamente en este dispositivo)", "signInWithDeviceText": "Iniciar sesión con cuenta del dispositivo", "signInWithGameCircleText": "Iniciar sesión con Game Circle", @@ -473,7 +474,7 @@ "amazonText": "Appstore de Amazon", "appStoreText": "App Store", "bestResultsScale": 0.65, - "bestResultsText": "Para mejores resultados necesitas una red inalámbrica rápida. Puedes\nreducir el retraso wifi apagando otros dispositivos inalámbricos,\njugando cerca de tu router inalámbrico, y conectando el\njuego anfitrión directamente a la red a través del cable Ethernet.", + "bestResultsText": "Para mejores resultados necesitas una red inalámbrica rápida. Puedes\nreducir el retraso wifi apagando otros dispositivos inalámbricos,\njugando cerca de tu router inalámbrico y conectando el\njuego anfitrión directamente a la red a través del cable Ethernet.", "explanationScale": 0.8, "explanationText": "Para utilizar tus dispositivos móviles como controles, \ninstala la app \"${REMOTE_APP_NAME}\" en ellos. Conecta todos\ntus dispositivos con ${APP_NAME} a través de tu red Wi-fi, ¡es gratis!.", "forAndroidText": "para Android:", @@ -520,8 +521,8 @@ "toRankedText": "Para Clasificar", "totalText": "total", "tournamentInfoText": "Compite por puntajes altos con\notros jugadores en tu liga.\n\nLos premios los obtienen los jugadores\ncon mejor puntaje al acabarse el torneo.", - "welcome1Text": "Bienvenido/a a ${LEAGUE}. Puedes mejorar tu\nposición en la liga ganando estrellas, completando\nlogros, y ganando trofeos en desafíos.", - "welcome2Text": "También puedes ganar boletos desde varias actividades similares.\nLos boletos pueden ser usados para desbloquear nuevos personajes,\nmapas y mini juegos, entrar a torneos, y más.", + "welcome1Text": "Bienvenido/a a ${LEAGUE}. Puedes mejorar tu\nposición en la liga ganando estrellas, completando\nlogros y ganando trofeos en desafíos.", + "welcome2Text": "También puedes ganar boletos desde varias actividades similares.\nLos boletos pueden ser usados para desbloquear nuevos personajes,\nmapas y mini juegos, entrar a torneos y más.", "yourPowerRankingText": "Tu Clasificación de Poder:" }, "copyConfirmText": "Copiado al portapapeles.", @@ -578,6 +579,7 @@ "demoText": "Demo", "denyText": "Denegar", "deprecatedText": "Obsoleto", + "descriptionText": "Descripción", "desktopResText": "Resolución De Escritorio", "deviceAccountUpgradeText": "Advertencia:\nIniciaste sesión con una cuenta de dispositivo (${NAME}).\nLas cuentas de dispositivo serán removidas en una futura actualización.\nActualiza a una cuenta V2 si quieres conservar tu progreso.", "difficultyEasyText": "Fácil", @@ -625,6 +627,7 @@ "localProfileText": "(perfil local)", "nameDescriptionText": "Nombre Del Jugador", "nameText": "Nombre", + "profileAlreadyExistsText": "Ya existe un perfil con ese nombre", "randomText": "aleatorio", "titleEditText": "Editar Perfil", "titleNewText": "Nuevo Perfil", @@ -711,6 +714,8 @@ "editText": "Editar\nPlaylist", "gameListText": "Lista de juego", "newText": "Nueva\nPlaylist", + "pointsToWinText": "Puntos para Ganar", + "seriesLengthText": "Duración de la Serie", "showTutorialText": "Ver Tutorial", "shuffleGameOrderText": "Orden de Juego Aleatorio", "titleText": "Personalizar Playlists de ${TYPE}" @@ -751,10 +756,10 @@ "friendHasSentPromoCodeText": "Recibiste ${COUNT} boletos de ${APP_NAME} de ${NAME}", "friendPromoCodeAwardText": "Recibirás ${COUNT} boletos cada vez que lo uses.", "friendPromoCodeExpireText": "El código expirará en ${EXPIRE_HOURS} horas y solo funciona para jugadores nuevos.", - "friendPromoCodeInstructionsText": "Para usarlo, abre ${APP_NAME} y ve a \"Ajustes->Avanzado->Ingresar Código\"\nVisita bombsquadgame.com para enlaces de descarga de todas las plataformas disponibles.", + "friendPromoCodeInstructionsText": "Para usarlo, abre ${APP_NAME} y ve a \"Ajustes->Avanzado->Ingresar Código\"\nVisita bombsquadgame.com para enlaces de descarga de todas las plataformas disponibles", "friendPromoCodeRedeemLongText": "Se puede canjear por ${COUNT} boletos gratis hasta ${MAX_USES} personas.", "friendPromoCodeRedeemShortText": "Puede ser canjeado por ${COUNT} boletos en el juego.", - "friendPromoCodeWhereToEnterText": "(en \"Ajustes->Avanzado->Ingresar Código\")", + "friendPromoCodeWhereToEnterText": "39739117", "getFriendInviteCodeText": "Obtener Código De Invitación De Amigo", "googlePlayDescriptionText": "Invitar jugadores de Google Play a tu partida:", "googlePlayInviteText": "Invitar", @@ -883,31 +888,31 @@ "visualsText": "Visuales" }, "helpWindow": { - "bombInfoText": "- Bomba -\nMas fuerte que los golpes, pero puede\nresultar en auto-lesiones bastante graves.\nPara mejores resultados, tírala hacia tus\noponentes antes de que explote.", + "bombInfoText": "- Bomba -\nMás fuerte que los golpes, pero puede\nresultar en autolesiones bastante graves.\nPara mejores resultados, tírala hacia tus\noponentes antes de que explote.", "bombInfoTextScale": 0.57, "canHelpText": "${APP_NAME} te puede ayudar.", - "controllersInfoText": "Puedes jugar ${APP_NAME} con tus amigos en una red, o todos pueden\njugar en el mismo dispositivo si se tienen varios controles.\n${APP_NAME} soporta una gran variedad de ellos; incluso puedes utilizar\ntus dispositivos móviles como controles instalando la app '${REMOTE_APP_NAME}'.\nVe a 'Ajustes-> Controles' para más información.", + "controllersInfoText": "Puedes jugar ${APP_NAME} con tus amigos en una red o todos pueden\njugar en el mismo dispositivo si se tienen varios controles.\n${APP_NAME} soporta una gran variedad de ellos; incluso puedes utilizar\ntus dispositivos móviles como controles instalando la app '${REMOTE_APP_NAME}'.\nVe a 'Ajustes-> Controles' para más información.", "controllersInfoTextFantasia": "Un jugador puede usar el mando a distancia para jugar, pero\nse recomienda usar gamepads. También puedes usar dispositivos\niOS o Android como controles con la app \"BombSquad Remote\".\nRevisa la sección \"Controles\" en los Ajustes para más información.", "controllersInfoTextMac": "Uno o dos jugadores pueden usar el teclado, pero BombSquad es mejor\ncon controles. BombSquad puede usar controles USB, controles de PS3,\ncontroles de Xbox 360, controles Wii y dispositivos iOS/Android para\ncontrolar los personajes. Espero que tengas algunos de ellos a la mano. \nConsulta 'Controles' bajo 'Ajustes' para más información.", "controllersInfoTextOuya": "Puedes usar controles OUYA, controles de PS3, controles de Xbox\n360, y muchos de otros controles USB y Bluetooth con BombSquad.\nPuedes usar también dispositivos iOS/Android como controles con la aplicación \n'BombSquad Remote'. Consulta 'Controles' bajo 'Ajustes' para más información.", - "controllersInfoTextRemoteOnly": "Tu puedes jugar ${APP_NAME} con tus amigos en la red, o pueden\njugar todos en el mismo dispositivo usando los teléfonos\ncomo controles libres con la aplicación '${REMOTE_APP_NAME}'", + "controllersInfoTextRemoteOnly": "Tu puedes jugar ${APP_NAME} con tus amigos en la red o pueden\njugar todos en el mismo dispositivo usando los teléfonos\ncomo controles libres con la aplicación '${REMOTE_APP_NAME}'", "controllersText": "Controles", "controllersTextScale": 0.67, "controlsSubtitleText": "Tu amistoso personaje de ${APP_NAME} tiene algunas acciones básicas:", "controlsSubtitleTextScale": 0.7, "controlsText": "Controles", "controlsTextScale": 1.4, - "devicesInfoText": "La version VR de ${APP_NAME} se puede jugar en red con la versión \nregular, así que saca tus celulares, tablets y computadoras extras y que \nempieze el juego. Es muy útil conectar un dispositivo con la versión\nregular a uno con la versión VR para que todos los que esten fuera\npuedan ver la acción.", + "devicesInfoText": "La versión VR de ${APP_NAME} se puede jugar en red con la versión \nregular, así que saca tus celulares, tablets y computadoras extras y que \nempieze el juego. Es muy útil conectar un dispositivo con la versión\nregular a uno con la versión VR para que todos los que estén fuera\npuedan ver la acción.", "devicesText": "Dispositivos", "friendsGoodText": "Cuantos más, mejor. ${APP_NAME} es más divertido con muchos \namigos y soporta hasta 8 a la vez, lo que nos lleva a:", "friendsGoodTextScale": 0.62, "friendsText": "Amigos", "friendsTextScale": 0.67, - "jumpInfoText": "- Saltar -\nSalta para cruzar pequeños espacios, \npara tirar cosas más lejos, y \npara expresar sentimientos de alegría.", + "jumpInfoText": "- Saltar -\nSalta para cruzar pequeños espacios, \npara tirar cosas más lejos y \npara expresar sentimientos de alegría.", "jumpInfoTextScale": 0.6, - "orPunchingSomethingText": "O golpear algo, tirarlo por un precipicio, y explotarlo en plena caída con una bomba pegajosa.", + "orPunchingSomethingText": "O golpear algo, tirarlo por un precipicio y explotarlo en plena caída con una bomba pegajosa.", "orPunchingSomethingTextScale": 0.51, - "pickUpInfoText": "- Recoger -\nAgarra banderas, enemigos, o cualquier\notra cosa no atornillada al suelo.\nPresiona de nuevo para lanzar.", + "pickUpInfoText": "- Recoger -\nAgarra banderas, enemigos o cualquier\notra cosa no atornillada al suelo.\nPresiona de nuevo para lanzar.", "pickUpInfoTextScale": 0.6, "powerupBombDescriptionText": "Te permite sacar tres bombas\nen una fila en lugar de solo una.", "powerupBombNameText": "Bombas Triples", @@ -921,7 +926,7 @@ "powerupImpactBombsNameText": "Insta-Bombas", "powerupLandMinesDescriptionText": "Estas vienen en grupos de 3;\nSon buenas para defensa territorial\no para detener enemigos veloces.", "powerupLandMinesNameText": "Minas Terrestres", - "powerupPunchDescriptionText": "Hacen que tus golpes sean más duros,\nmás rápidos, mejores, y más fuertes.", + "powerupPunchDescriptionText": "Hacen que tus golpes sean más duros,\nmás rápidos, mejores y más fuertes.", "powerupPunchNameText": "Guantes De Boxeo", "powerupShieldDescriptionText": "Absorbe un poco de daño\npara que tú no tengas que hacerlo.", "powerupShieldNameText": "Electro-Escudo", @@ -945,7 +950,7 @@ "holdAnyButtonText": "", "holdAnyKeyText": "", "hostIsNavigatingMenusText": "- ${HOST} está navegando los menús como todo un pro -", - "importPlaylistCodeInstructionsText": "Usa el siguiente código para importar esta lista de reproducción en otra parte", + "importPlaylistCodeInstructionsText": "Usa el siguiente código para importar esta lista de reproducción en otra parte:", "importPlaylistSuccessText": "Lista de reproducción '${NAME}' importada ${TYPE}", "importText": "Importar", "importingText": "Importando...", @@ -955,7 +960,7 @@ "arrowsToExitListText": "pulsa ${LEFT} o ${RIGHT} para salir de la lista", "buttonText": "botón", "cantKickHostError": "No puedes expulsar al anfitrión.", - "chatBlockedText": "${NAME} esta bloqueado del chat por ${TIME} segundos.", + "chatBlockedText": "${NAME} está bloqueado del chat por ${TIME} segundos.", "connectedToGameText": "Unido a '${NAME}'", "connectedToPartyText": "¡Unido a la partida de ${NAME}!", "connectingToPartyText": "Conectando...", @@ -973,7 +978,7 @@ "controllersConnectedText": "${COUNT} controles conectados.", "controllersDetectedText": "${COUNT} controles detectados.", "controllersDisconnectedText": "${COUNT} controles desconectados.", - "corruptFileText": "Archivo(s) corrupto(s) detectado(s). Por favor intenta re-instalar, o contactate con ${EMAIL}", + "corruptFileText": "Archivo(s) corrupto(s) detectado(s). Por favor intenta reinstalar o contáctate con ${EMAIL}", "errorPlayingMusicText": "Error reproduciendo música: ${MUSIC}", "errorResettingAchievementsText": "Incapaz de reiniciar los logros; por favor inténtalo de nuevo más tarde.", "hasMenuControlText": "${NAME} tiene el control del menú.", @@ -995,7 +1000,7 @@ "noMusicFilesInFolderText": "La carpeta no contiene archivos de audio.", "playerJoinedPartyText": "¡${NAME} se unió a la partida!", "playerLeftPartyText": "${NAME} abandonó la partida.", - "rejectingInviteAlreadyInPartyText": "Rechazando invitación (Ya estas en una partida).", + "rejectingInviteAlreadyInPartyText": "Rechazando invitación (Ya estás en una partida).", "serverRestartingText": "El servidor se está reiniciando. Por favor vuelva a unirse en un momento...", "serverShuttingDownText": "El servidor está fuera de servicio...", "signInErrorText": "Error iniciando sesión.", @@ -1107,7 +1112,7 @@ "modeClassicText": "Modo Clásico", "modeDemoText": "Modo Demo", "mostValuablePlayerText": "Jugador Más Valioso", - "mostViolatedPlayerText": "Jugador Más Agredido", + "mostViolatedPlayerText": "Jugador Más Violado", "mostViolentPlayerText": "Jugador Más Violento", "moveText": "Mover", "multiKillText": "¡¡¡COMBO DE ${COUNT}!!!", @@ -1212,7 +1217,7 @@ "pluginInitErrorText": "Error iniciando complemento '${PLUGIN}': ${ERROR}", "pluginSettingsText": "Ajustes De Complementos", "pluginsAutoEnableNewText": "Auto Habilitar Nuevos Complementos", - "pluginsDetectedText": "Complemento detectado(s). Reinicia el juego, o ve a ajustes para configurarlo.", + "pluginsDetectedText": "Complemento detectado(s). Reinicia el juego o ve a ajustes para configurarlo.", "pluginsDisableAllText": "Deshabilitar Todos Los Complementos", "pluginsEnableAllText": "Habilitar Todos Los Complementos", "pluginsRemovedText": "${NUM} complemento(s) ya no se encuentra(n).", @@ -1324,6 +1329,7 @@ }, "scoreWasText": "(era ${COUNT})", "selectText": "Elegir", + "sendInfoDescriptionText": "Envía información del estado de la cuenta y de la aplicación al desarrollador.\nPor favor incluya su nombre o el motivo del envío.", "seriesWinLine1PlayerText": "GANA LA", "seriesWinLine1TeamText": "GANA LA", "seriesWinLine1Text": "¡GANA LA", @@ -1340,10 +1346,11 @@ }, "settingsWindowAdvanced": { "alwaysUseInternalKeyboardDescriptionText": "(un teclado simple y amigable con controles para editar textos)", - "alwaysUseInternalKeyboardText": "Siempre Usar El Teclado Interno", + "alwaysUseInternalKeyboardText": "Siempre usar el teclado interno", "benchmarksText": "Puntos De Referencia & Pruebas De Estrés", - "disableCameraGyroscopeMotionText": "Deshabilitar El Movimiento Del Giroscopio De La Cámara", - "disableCameraShakeText": "Deshabilitar El Temblor De La Cámara", + "devToolsText": "Herramientas de Desarrollo", + "disableCameraGyroscopeMotionText": "Deshabilitar el movimiento del giroscopio de la cámara", + "disableCameraShakeText": "Deshabilitar el temblor de la cámara", "disableThisNotice": "(puedes deshabilitar este aviso en ajustes avanzados)", "enablePackageModsDescriptionText": "(enciende modificaciones pero apaga juegos vía red)", "enablePackageModsText": "Encender Modificaciones Locales", @@ -1351,18 +1358,21 @@ "forTestingText": "Notas: estos datos son solo para pruebas y se reiniciarán cuando se abra la app de nuevo.", "helpTranslateText": "Las traducciones de ${APP_NAME} son un esfuerzo\napoyado por la comunidad. Si deseas aportar o corregir una\ntraducción, utiliza el siguiente enlace. ¡Gracias de antemano!", "helpTranslateTextScale": 1.0, - "kickIdlePlayersText": "Expulsar Jugadores Inactivos", + "kickIdlePlayersText": "Expulsar a jugadores inactivos", "kidFriendlyModeText": "Modo Niños (reduce violencia, etc)", "languageText": "Idioma", "languageTextScale": 1.0, "moddingGuideText": "Guía De Modificación", + "moddingToolsText": "Herramientas de modificación", "mustRestartText": "Tienes que reiniciar el juego para que tome efecto.", "netTestingText": "Prueba De Red", "resetText": "Reiniciar", + "sendInfoText": "Enviar Información", "showBombTrajectoriesText": "Mostrar Trayectorias De Las Bombas", "showDemosWhenIdleText": "Mostrar demostraciones cuando esté inactivo", - "showDevConsoleButtonText": "Mostrar Botón de Consola de Desarrollador", - "showInGamePingText": "Mostrar Ping En El Juego", + "showDeprecatedLoginTypesText": "Mostrar tipos de inicio de sesión obsoletos", + "showDevConsoleButtonText": "Mostrar botón de consola de desarrollador", + "showInGamePingText": "Mostrar ping en el juego", "showPlayerNamesText": "Mostrar Nombres De Los Jugadores", "showUserModsText": "Mostrar Carpeta De Mods", "titleText": "Avanzado", @@ -1370,8 +1380,8 @@ "translationFetchErrorText": "estado de traducción no disponible", "translationFetchingStatusText": "viendo estado de traducción...", "translationInformMe": "Informarme cuando mi idioma necesite actualizaciones", - "translationNoUpdateNeededText": "el idioma actual está actualizado; ¡wuju!", - "translationUpdateNeededText": "** ¡¡el idioma actual necesita actualizarse!! **", + "translationNoUpdateNeededText": "El idioma actual está actualizado; ¡wuju!", + "translationUpdateNeededText": "** ¡¡El idioma actual necesita actualizarse!! **", "vrTestingText": "Prueba VR" }, "shareText": "Compartir", @@ -1381,6 +1391,9 @@ "signInWithGameCenterText": "Para usar una cuenta de Game Center,\ningresa con la aplicación Game Center.", "singleGamePlaylistNameText": "Solo ${GAME}", "singlePlayerCountText": "1 jugador", + "sizeLargeText": "Grande", + "sizeMediumText": "Medium", + "sizeSmallText": "pequeñ@", "soloNameFilterText": "Solo ${NAME}", "soundtrackTypeNames": { "CharSelect": "Selección De Personajes", @@ -1417,7 +1430,7 @@ "charactersText": "Personajes", "comingSoonText": "Próximamente...", "extrasText": "Extras", - "freeBombSquadProText": "BombSquad es gratis, pero ya que compraste el juego recibirás\nla mejora de BombSquad Pro y ${COUNT} boletos como agradecimiento.\n¡Disfruta de las nuevas funciones, y gracias por tu apoyo!\n-Eric", + "freeBombSquadProText": "BombSquad es gratis, pero ya que compraste el juego recibirás\nla mejora de BombSquad Pro y ${COUNT} boletos como agradecimiento.\n¡Disfruta de las nuevas funciones y gracias por tu apoyo!\n-Eric", "gameUpgradesText": "Mejoras", "getCoinsText": "Obtén monedas", "holidaySpecialText": "¡Especial De Temporada!", @@ -1445,7 +1458,7 @@ "winterSpecialText": "Especial De Invierno", "youOwnThisText": "- ya posees esto -" }, - "storeDescriptionText": "¡Juego en grupo de 8 jugadores!\n\n¡Revienta a tus amigos (o a la computadora) en un torneo de minijuegos explosivos como Captura la Bandera, Hockey-Bomba, y Combate Mortal Épico en cámara lenta!\n\nControles sencillos y un amplio apoyo de controles hacen que sea fácil que 8 personas entren en la acción; ¡incluso puedes usar tus dispositivos móviles como controles a través de la aplicación gratuita 'Control Remoto BombSquad'!\n\n¡Bombas Fuera!\n\nEcha un vistazo en www.froemling.net/bombsquad para más información.", + "storeDescriptionText": "¡Juego en grupo de 8 jugadores!\n\n¡Revienta a tus amigos (o a la computadora) en un torneo de minijuegos explosivos como Captura la Bandera, Hockey-Bomba y Combate Mortal Épico en cámara lenta!\n\nControles sencillos y un amplio apoyo de controles hacen que sea fácil que 8 personas entren en la acción; ¡incluso puedes usar tus dispositivos móviles como controles a través de la aplicación gratuita 'Control Remoto BombSquad'!\n\n¡Bombas Fuera!\n\nEcha un vistazo en www.froemling.net/bombsquad para más información.", "storeDescriptions": { "blowUpYourFriendsText": "Revienta a tus amigos.", "competeInMiniGamesText": "Compite en juegos de carreras, vuelo y mucho más.", @@ -1458,7 +1471,7 @@ "submitText": "Enviar", "submittingPromoCodeText": "Enviando Código...", "successText": "¡Éxito!", - "supportEmailText": "Si estas teniendo algun problema con la\napp, porfavor manda un email a ${EMAIL}.", + "supportEmailText": "Si estás teniendo algún problema con la\napp, por favor, manda un email a ${EMAIL}.", "teamNamesColorText": "Nombres De Equipos/Colores...", "teamsText": "Equipos", "telnetAccessGrantedText": "Acceso a Telnet activado.", @@ -1851,7 +1864,7 @@ "If someone picks you up, punch them and they'll let go.\nThis works in real life too.": "Si alguien te recoge, dale un golpe y te soltará.\nTambién funciona en la vida real.", "If you are short on controllers, install the '${REMOTE_APP_NAME}' app\non your mobile devices to use them as controllers.": "Si no tienes suficientes controles, instala la aplicación '${REMOTE_APP_NAME}'\nen tus dispositivos móviles para utilizarlos como controles.", "If you are short on controllers, install the 'BombSquad Remote' app\non your iOS or Android devices to use them as controllers.": "Te faltan controles? Instala la aplicación 'BombSquad Remote'\nen tu dispositivo iOS o Android para usarlo como control.", - "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.": "Si te adhieres a una bomba-pegajosa, salta y da muchas vueltas. Es posible que sacudas\nla bomba pegada, o sin nada más tus últimos momentos serán entretenidos.", + "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.": "Si te adhieres a una bomba pegajosa, salta y da muchas vueltas. Es posible que sacudas\nla bomba pegada o sin nada más tus últimos momentos serán entretenidos.", "If you kill an enemy in one hit you get double points for it.": "Si matas a un enemigo de un solo golpe obtendrás puntos dobles.", "If you pick up a curse, your only hope for survival is to\nfind a health powerup in the next few seconds.": "Si tomaste una maldición, tu única esperanza es\nencontrar un botiquín en tus últimos segundos.", "If you stay in one place, you're toast. Run and dodge to survive..": "Si te quedas quieto, estás frito. Corre y esquiva para sobrevivir...", @@ -1934,6 +1947,7 @@ "toSkipPressAnythingText": "(pulsa cualquier botón para omitir el tutorial)" }, "twoKillText": "¡COMBO DOBLE!", + "uiScaleText": "Escala de interfaz de usuario", "unavailableText": "no disponible", "unconfiguredControllerDetectedText": "Control desconfigurado detectado:", "unlockThisInTheStoreText": "Esto debe ser desbloqueado en la tienda.", @@ -1947,6 +1961,8 @@ "upgradeText": "Mejorar", "upgradeToPlayText": "Desbloquea \"${PRO}\" en la tienda para jugar esto.", "useDefaultText": "Usar Por Defecto", + "userSystemScriptsCreateText": "Crear scripts del sistema de usuario", + "userSystemScriptsDeleteText": "Eliminar scripts del sistema de usuario", "usesExternalControllerText": "Este juego usa un control externo como entrada.", "usingItunesText": "Usando Aplicación De Música para la banda sonora...", "usingItunesTurnRepeatAndShuffleOnText": "Asegúrate de que mezclar esté ENCENDIDO y repetir TODOS esté activado en iTunes.", @@ -2001,7 +2017,7 @@ "copyrightText": "Marca registrada DarwiinRemote", "copyrightTextScale": 0.6, "listenText": "Escuchar", - "macInstructionsText": "Asegúrate de que tu Wii esté apagado y el Bluetooth esté activado en\ntu Mac, a continuación, pulsa 'Escuchar'. El Soporte para controles\nWii puede ser un poco extraño, así que puede que tengas que\nprobar un par de veces antes de obtener una conexión.\n\nEl Bluetooth puede procesar hasta 7 dispositivos conectados,\naunque tu kilometraje puede variar.\n\nBombSquad es compatible con los Controles Wii originales,\nNunchuks, y el Control Clásico.\nEl nuevo Control Wii Plus también funciona\npero sin accesorios.", + "macInstructionsText": "Asegúrate de que tu Wii esté apagado y el Bluetooth esté activado en\ntu Mac, a continuación, pulsa 'Escuchar'. El Soporte para controles\nWii puede ser un poco extraño, así que puede que tengas que\nprobar un par de veces antes de obtener una conexión.\n\nEl Bluetooth puede procesar hasta 7 dispositivos conectados,\naunque tu kilometraje puede variar.\n\nBombSquad es compatible con los Controles Wii originales,\nNunchuks y el Control Clásico.\nEl nuevo Control Wii Plus también funciona\npero sin accesorios.", "macInstructionsTextScale": 0.7, "thanksText": "Gracias al equipo DarwiinRemote\nPor hacer esto posible.", "thanksTextScale": 0.8, diff --git a/dist/ba_data/data/languages/tamil.json b/dist/ba_data/data/languages/tamil.json index bd0cd58..d106c39 100644 --- a/dist/ba_data/data/languages/tamil.json +++ b/dist/ba_data/data/languages/tamil.json @@ -1888,7 +1888,7 @@ "worldsBestScoresText": "உலகின் சிறந்த மதிப்பெண்கள்", "worldsBestTimesText": "உலகின் சிறந்த நேரங்கள்", "xbox360ControllersWindow": { - "getDriverText": "Driver ஐ பெரு", + "getDriverText": "Driver ஐ பெரு ", "macInstructions2Text": "கட்டுப்படுத்திகளை கம்பியில்லாமல் பயன்படுத்த, உங்களுக்கு ரிசீவரும் தேவை\nவிண்டோஸிற்கான எக்ஸ்பாக்ஸ் 360 வயர்லெஸ் கன்ட்ரோலருடன் வருகிறது.\nஒரு ரிசீவர் உங்களை 4 கட்டுப்படுத்திகளை இணைக்க அனுமதிக்கிறது.\n\nமுக்கியமானது: 3 வது தரப்பு பெறுநர்கள் இந்த டிரைவருடன் வேலை செய்ய மாட்டார்கள்;\nஉங்கள் ரிசீவர் அதில் 'மைக்ரோசாப்ட்' என்று கூறுவதை உறுதி செய்து கொள்ளுங்கள், 'எக்ஸ்பாக்ஸ் 360' அல்ல.\nமைக்ரோசாப்ட் இனி தனித்தனியாக விற்காது, எனவே நீங்கள் பெற வேண்டும்\nகட்டுப்பாட்டாளருடன் தொகுக்கப்பட்ட ஒன்று அல்லது வேறு ஈபேயைத் தேடுங்கள்.\n\nஇது உங்களுக்கு பயனுள்ளதாக இருந்தால், தயவுசெய்து ஒரு நன்கொடையைக் கருத்தில் கொள்ளவும்\nஅவரது தளத்தில் டிரைவர் டெவலப்பர்.", "macInstructionsText": "எக்ஸ்பாக்ஸ் 360 கட்டுப்படுத்திகளைப் பயன்படுத்த, நீங்கள் நிறுவ வேண்டும்\nமேக் டிரைவர் கீழே உள்ள இணைப்பில் கிடைக்கிறது.\nஇது கம்பி மற்றும் வயர்லெஸ் கட்டுப்படுத்திகளுடன் வேலை செய்கிறது.", "ouyaInstructionsText": "BombSquad உடன் கம்பி எக்ஸ்பாக்ஸ் 360 கட்டுப்படுத்திகளைப் பயன்படுத்த, வெறுமனே\nஅவற்றை உங்கள் சாதனத்தின் USB போர்ட்டில் செருகவும். நீங்கள் ஒரு USB ஹப் பயன்படுத்தலாம்\nபல கட்டுப்படுத்திகளை இணைக்க.\n\nவயர்லெஸ் கன்ட்ரோலர்களைப் பயன்படுத்த உங்களுக்கு வயர்லெஸ் ரிசீவர் தேவை,\n\"விண்டோஸிற்கான எக்ஸ்பாக்ஸ் 360 வயர்லெஸ் கன்ட்ரோலர்\" இன் ஒரு பகுதியாக கிடைக்கிறது\nதொகுப்பு அல்லது தனித்தனியாக விற்கப்படுகிறது. ஒவ்வொரு ரிசீவரும் USB போர்ட்டில் செருகப்படுகிறது மற்றும்\n4 வயர்லெஸ் கட்டுப்படுத்திகளை இணைக்க உங்களை அனுமதிக்கிறது.", diff --git a/dist/ba_data/data/languages/thai.json b/dist/ba_data/data/languages/thai.json index 0566599..130f807 100644 --- a/dist/ba_data/data/languages/thai.json +++ b/dist/ba_data/data/languages/thai.json @@ -551,7 +551,7 @@ "deleteText": "ลบ", "demoText": "ทดลอง", "denyText": "ยกเลิก", - "deprecatedText": "คัดค้าน", + "deprecatedText": "ไม่ถูกใช้งาน", "desktopResText": "เดสก์ท็อป Res", "deviceAccountUpgradeText": "คำเตือน:\nคุณลงชื่อเข้าใช้ด้วยบัญชีอุปกรณ์ (${NAME})\nบัญชีอุปกรณ์จะถูกลบออกในการอัปเดตในอนาคต\nอัปเกรดเป็นบัญชี V2 หากคุณต้องการติดตามความคืบหน้า", "difficultyEasyText": "ง่าย", @@ -562,6 +562,9 @@ "disableRemoteAppConnectionsText": "ปิดการใช้การเชื่อมต่อแอพรีโมต", "disableXInputDescriptionText": "อนุญาตให้ใช้คอนโทรลเลอร์มากกว่า 4 ตัวแต่จะทำงานได้ไม่ค่อยดีเท่าไหร่", "disableXInputText": "ปิด XInput", + "disabledText": "ปิด", + "discordFriendsText": "ต้องการหาเพื่อนที่จะเล่นด้วยใช่ไหม\nเข้า Discord ของพวกเราและหาเพื่อนใหม่ๆ!", + "discordJoinText": "เข้าร่วม Discord", "doneText": "เสร็จสิ้น", "drawText": "เสมอ", "duplicateText": "ทำซ้ำ", @@ -629,6 +632,7 @@ "useMusicFolderText": "โฟลเดอร์ของไฟล์เพลง" }, "editText": "แก้ไข", + "enabledText": "เปิด", "endText": "จบ", "enjoyText": "ขอให้สนุก!", "epicDescriptionFilterText": "${DESCRIPTION} ในการเคลื่อนไหวที่ช้ามากๆ", @@ -746,6 +750,7 @@ "manualYourLocalAddressText": "ที่อยู่ในท้องถิ่นของคุณ:", "nearbyText": "ใกล้เคียง", "noConnectionText": "<ไม่มีการเชื่อมต่อ>", + "noPartiesAddedText": "ไม่มีปาร์ตี้เพื่มอยู่", "otherVersionsText": "(เวอร์ชั่นอื่นๆ)", "partyCodeText": "รหัสปาร์ตี้", "partyInviteAcceptText": "ยอมรับ", @@ -817,10 +822,12 @@ "alwaysText": "ตลอด", "fullScreenCmdText": "เต็มหน้าจอ (Cmd-F)", "fullScreenCtrlText": "เต็มหน้าจอ (Ctrl-F)", + "fullScreenText": "แสดงเต็มจอ", "gammaText": "ส่องสว่าง", "highText": "สูง", "higherText": "สูงกว่า", "lowText": "ต่ำ", + "maxFPSText": "อัตราเฟรมสูงสุด", "mediumText": "ปานกลาง", "neverText": "ไม่เคย", "resolutionText": "ความละเอียด", @@ -1058,6 +1065,7 @@ "noContinuesText": "(ไม่มีต่อ)", "noExternalStorageErrorText": "ไม่พบที่จัดเก็บข้อมูลภายนอกในอุปกรณ์นี้", "noGameCircleText": "ข้อผิดพลาด: ไม่ได้ลงชื่อเข้าใช้ GameCircle", + "noPluginsInstalledText": "ไม่มีปลั๊กอินติดตั้งอยู่", "noScoresYetText": "ไม่มีคะแนน", "noServersFoundText": "ไม่พบเซิร์ฟเวอร์", "noThanksText": "ไม่เป็นไรขอบคุณ", @@ -1224,6 +1232,8 @@ "runText": "วิ่ง", "saveText": "บันทึก", "scanScriptsErrorText": "เกิดข้อผิดพลาดในการสแกนสคริปต์ ดูบันทึกสำหรับรายละเอียด", + "scanScriptsMultipleModulesNeedUpdatesText": "${PATH} และ ${NUM} โมดูลอื่นๆ (s) จำเป็นต้องมีการอัพเดตสำหรับ api นี้ ${API}.", + "scanScriptsSingleModuleNeedsUpdatesText": "${PATH} จำเป็นต้องมีการอัพเดตสำหรับ api นี้ ${API}.", "scoreChallengesText": "คะแนนความท้าทาย", "scoreListUnavailableText": "ไม่มีรายการคะแนน", "scoreText": "คะแนน", diff --git a/dist/ba_data/data/languages/turkish.json b/dist/ba_data/data/languages/turkish.json index c1f255f..66fae40 100644 --- a/dist/ba_data/data/languages/turkish.json +++ b/dist/ba_data/data/languages/turkish.json @@ -25,6 +25,7 @@ "setAccountNameDesc": "Hesabın için gösterilecek bir isim seç. \nBağladığın hesaplardan birinin adını kullanabilir\nya da yeni bir tane oluşturabilirsin.", "signInInfoText": "Bilet toplamak Çevrimiçi olmak ve ilerlemeyi \ndiğer cihazlarla paylaşmak için Giriş yap.", "signInText": "Giriş Yap", + "signInWithAnEmailAddressText": "Bir e-posta adresiyle giriş yap", "signInWithDeviceInfoText": "Otomatik bir hesap sadece bu cihaz için kullanılabilir", "signInWithDeviceText": "Cihaz hesabı ile Giriş yap", "signInWithGameCircleText": "Game Circle ile Giriş yap", @@ -555,6 +556,7 @@ "demoText": "Tanıtım", "denyText": "Reddet", "deprecatedText": "Kullanımdan kaldırıldı", + "descriptionText": "Açıklama", "desktopResText": "PC Çözünürlüğü", "deviceAccountUpgradeText": "Uyarı:\n(${NAME}) isimli bir cihaz hesabıyla giriş yaptın.\nCihaz hesapları gelecek güncellemelerde kaldırılacak.\nİlerlemeni tutmak istiyorsan V2 hesabına geçiş yap.", "difficultyEasyText": "Kolay", @@ -566,7 +568,7 @@ "disableXInputDescriptionText": "4 kontrolcüden fazla kullanılabilir fakat iyi çalışmayabilir.", "disableXInputText": "XInput etkisizleştir", "disabledText": "Kullanılmıyor", - "discordFriendsText": "Yeni oyuncularmı arıyorsun?\nDiscord sunucumuza katılarak yeni arkadaşlar edinebilirsin!", + "discordFriendsText": "Yeni oyuncular mı arıyorsun?\nDiscord sunucumuza katılarak yeni arkadaşlar edinebilirsin!", "discordJoinText": "Discord sunucumuza katıl", "doneText": "Tamam", "drawText": "Beraberlik", @@ -600,6 +602,7 @@ "localProfileText": "(yerel profil)", "nameDescriptionText": "Oyuncu Adı", "nameText": "Ad", + "profileAlreadyExistsText": "Bu ad ile bir profil zaten var.", "randomText": "rastgele", "titleEditText": "Profil Düzenle", "titleNewText": "Yeni Profil", @@ -681,6 +684,8 @@ "duplicateText": "ÇalmaListesi\nKopyala", "editText": "ÇalmaListesi\nDüzenle", "newText": "ÇalmaListesi\nYarat", + "pointsToWinText": "Kazanmak İçin Gereken Puan", + "seriesLengthText": "Seri Uzunluğu", "showTutorialText": "Öğreticiyi Göster", "shuffleGameOrderText": "Oyun Sırasını Karıştır", "titleText": "Özel ${TYPE} ÇalmaListeleri" @@ -718,10 +723,10 @@ "friendHasSentPromoCodeText": "${NAME} tarafından ${COUNT} ${APP_NAME} bileti", "friendPromoCodeAwardText": "Kullanıldığı her zaman ${COUNT} bilet alacaksın.", "friendPromoCodeExpireText": "Bu kodun ${EXPIRE_HOURS} saat içinde süresi dolacak ve sadece yeni oyuncularda çalışır.", - "friendPromoCodeInstructionsText": "Kullanmak için ${APP_NAME} uygulamasını açın ve \"Ayarlar->Gelişmiş->Promo Kodu\"\nna gidin. Tüm platformlar arası İndirme bağlantıları için bombsquadgame.com'a gidin.", + "friendPromoCodeInstructionsText": "Kullanmak için ${APP_NAME} uygulamasını açın ve \"Ayarlar->Gelişmiş->Kodu gir\" \ngidin. Tüm platformlar arası İndirme bağlantıları için bombsquadgame.com'a gidin.", "friendPromoCodeRedeemLongText": "Maksimum ${MAX_USES} kişi olmak üzere ${COUNT} bedava bilet alabilirsin.", "friendPromoCodeRedeemShortText": "Oyun içi ${COUNT} bilet alabilirsin", - "friendPromoCodeWhereToEnterText": "(Ayarlar>Gelişmiş>Promo kodu gir içinde)", + "friendPromoCodeWhereToEnterText": "(\"Ayarlar>Gelişmiş>Kodu gir\" içinde)", "getFriendInviteCodeText": "Arkadaş Davet Kodu Al", "googlePlayDescriptionText": "Partine Google Play oyuncularını davet et:", "googlePlayInviteText": "Davet", @@ -1247,6 +1252,7 @@ }, "scoreWasText": "(${COUNT} idi)", "selectText": "Seç", + "sendInfoDescriptionText": "Geliştiriciye hesap ve uygulama durumu bilgilerini gönderir.\nGöndermek için lütfen ad ve gönderme sebebini de ekleyin.", "seriesWinLine1PlayerText": "SERİLERİ", "seriesWinLine1TeamText": "SERİLERİ", "seriesWinLine1Text": "SERİLERİ", @@ -1262,27 +1268,31 @@ }, "settingsWindowAdvanced": { "alwaysUseInternalKeyboardDescriptionText": "(Basitçe; yazı yazmak için daha kullanışlı ekran klavyesi)", - "alwaysUseInternalKeyboardText": "Her zaman Dahili Klavye Kullan", + "alwaysUseInternalKeyboardText": "Her zaman dahili klavye kullan", "benchmarksText": "Benchmark & Stres-Testleri", - "disableCameraGyroscopeMotionText": "Kamera Jiroskop Hareketini Devre Dışı Bırak", - "disableCameraShakeText": "Kamera Titremesini Devre Dışı Bırak", + "devToolsText": "Geliştirici Ayarları", + "disableCameraGyroscopeMotionText": "Kamera jiroskop hareketini devre dışı bırak", + "disableCameraShakeText": "Kamera titremesini devre dışı bırak", "disableThisNotice": "(bu bildiriyi gelişmiş ayarlardan devre dışı bırakabilirsiniz)", "enablePackageModsDescriptionText": "(ekstra modlama özelliği ekler fakat ağ-oyununu etkisiz kılar)", "enablePackageModsText": "Yerel Paket Modlarını Etkinleştir", "enterPromoCodeText": "Kodu gir", "forTestingText": "Not: Bu değerler sadece test için ve uygulamadan çıkıldığında sıfırlanacak.", "helpTranslateText": "${APP_NAME}'ın İngilizce olmayan çevirileri topluluk yardımı ile\nyapılabilir. Katkı veya düzgün çeviri yapmak istiyorsan aşağıdaki\nbağlantıya tıkla. Şimdiden teşekkürler!", - "kickIdlePlayersText": "Boştaki Oyuncuları Çıkar", + "kickIdlePlayersText": "Boştaki oyuncuları çıkar", "kidFriendlyModeText": "Çoçuk Modu (Azaltılmış şiddet, vb)", "languageText": "Dil", "moddingGuideText": "Modlama Rehberi", + "moddingToolsText": "Modlama Araçları", "mustRestartText": "Etkisini göstermesi için oyunu yeniden başlatmalısınız.", "netTestingText": "Ağ Testi", "resetText": "Sıfırla", + "sendInfoText": "Bilgi Gönder", "showBombTrajectoriesText": "Bomba Gidişatını Göster", - "showDemosWhenIdleText": "Boşta İken Demolar Göster", + "showDemosWhenIdleText": "Boşta iken demolar göster", + "showDeprecatedLoginTypesText": "Kullanımdan kaldırılmış oturum açma türlerini göster", "showDevConsoleButtonText": "Geliştirici panelini aç", - "showInGamePingText": "Oyun İçinde Gecikmeyi Göster", + "showInGamePingText": "Oyun içinde gecikmeyi göster", "showPlayerNamesText": "Oyuncu Adlarını Göster", "showUserModsText": "Mod Klasörünü Göster", "titleText": "Gelişmiş", @@ -1290,7 +1300,7 @@ "translationFetchErrorText": "çeviri durumu mevcut değil", "translationFetchingStatusText": "Çeviri durumu kontrol ediliyor...", "translationInformMe": "Benim dilimin güncellenmesi gerektiğinde bana haber ver", - "translationNoUpdateNeededText": "Şu anki dil güncel; wohooo!", + "translationNoUpdateNeededText": "Şu anki dil güncel; wohoo!", "translationUpdateNeededText": "** Şu anki dilin güncellenmeye ihtiyacı var!! **", "vrTestingText": "VR Testi" }, @@ -1301,6 +1311,9 @@ "signInWithGameCenterText": "Game Center hesabı kullanmak için\nGame Center uygulaması ile giriş yap.", "singleGamePlaylistNameText": "Sadece ${GAME}", "singlePlayerCountText": "1 oyuncu", + "sizeLargeText": "Büyük", + "sizeMediumText": "Orta", + "sizeSmallText": "Küçük", "soloNameFilterText": "Solo ${NAME}", "soundtrackTypeNames": { "CharSelect": "Karakter Seçimi", @@ -1824,6 +1837,7 @@ "toSkipPressAnythingText": "(öğreticiyi atlamak için herhangi bir tuşa bas)" }, "twoKillText": "ÇİFTE ÖLÜM!", + "uiScaleText": "UI Boyutu", "unavailableText": "mevcut değil", "unconfiguredControllerDetectedText": "Yapılandırılmamış kontrolcü tespit edildi:", "unlockThisInTheStoreText": "Bunun mağazada kilidi açık olmalı.", @@ -1837,12 +1851,14 @@ "upgradeText": "Yükselt", "upgradeToPlayText": "Bunu oynamak için \"${PRO}\" kilidini oyun-içi mağazadan aç.", "useDefaultText": "Varsayılanı Kullan", + "userSystemScriptsCreateText": "Kullanıcı Sistem Kodlarını Oluştur", + "userSystemScriptsDeleteText": "Kullanıcı Sistem Kodlarını Sil", "usesExternalControllerText": "Bu oyun girdi olarak harici kontrolcü kullanıyor.", "usingItunesText": "Müzikler için iTunes kullanılıyor...", "usingItunesTurnRepeatAndShuffleOnText": "Lütfen iTunes da karıştırmanın KAPALI oldugundan ve Yinelemenin TÜM oldugundan emin olun. ", "v2AccountLinkingInfoText": "V2 hesapları bağlamak için, 'Hesabı yönet' butonuna tıklayın.", "validatingTestBuildText": "Test Yapısı Onaylanıyor...", - "viaText": "Böylede bilinir", + "viaText": "Böyle de bilinir", "victoryText": "Galibiyet!", "voteDelayText": "${NUMBER} saniye boyunca başka oylama başlatamazsın.", "voteInProgressText": "Oylama zaten işlemde.", diff --git a/dist/ba_data/data/languages/ukrainian.json b/dist/ba_data/data/languages/ukrainian.json index 8bdc698..2061469 100644 --- a/dist/ba_data/data/languages/ukrainian.json +++ b/dist/ba_data/data/languages/ukrainian.json @@ -556,6 +556,7 @@ "demoText": "Демо", "denyText": "Відхилити", "deprecatedText": "Застаріла", + "descriptionText": "Опис", "desktopResText": "Розширення екрану", "deviceAccountUpgradeText": "Увага!\nТи ввійшов за допомогою акаунта пристрою (${NAME}).\nАкаунти пристроїв будуть видалені у майбутньому оновленні.\nОновись до V2-акаунта, якщо ти хочеш зберегти свій прогрес.", "difficultyEasyText": "Легкий", @@ -601,6 +602,7 @@ "localProfileText": "(локальний профіль)", "nameDescriptionText": "Ім'я гравця", "nameText": "Ім'я", + "profileAlreadyExistsText": "Профіль з таким ім'ям вже існує.", "randomText": "випадкове", "titleEditText": "Змінити профіль", "titleNewText": "Новий профіль", @@ -682,6 +684,8 @@ "duplicateText": "Дублювати\nплейлист", "editText": "Змінити\nплейлист", "newText": "Новий\nплейлист", + "pointsToWinText": "Очок до виграшу", + "seriesLengthText": "Довжина серії", "showTutorialText": "Показати туторіал", "shuffleGameOrderText": "Змішати порядок ігор", "titleText": "Налаштувати плейлисти '${TYPE}'" @@ -1248,6 +1252,7 @@ }, "scoreWasText": "(було ${COUNT})", "selectText": "Вибрати", + "sendInfoDescriptionText": "Надсилає інформацію про обліковий запис і стан програми розробнику. Будь ласка, вкажіть своє ім'я або причину надсилання\nِ", "seriesWinLine1PlayerText": "ПЕРЕМІГ У", "seriesWinLine1TeamText": "ПЕРЕМОГЛИ У", "seriesWinLine1Text": "ПЕРЕМІГ У", @@ -1263,25 +1268,28 @@ }, "settingsWindowAdvanced": { "alwaysUseInternalKeyboardDescriptionText": "(проста, зручна для контролера віртуальна клавіатура для введення тексту)", - "alwaysUseInternalKeyboardText": "Завжди використовувати вбудовану клавіатуру", + "alwaysUseInternalKeyboardText": "Завжди використовуйте внутрішню клавіатуру", "benchmarksText": "Тест продуктивності і навантаження", - "disableCameraGyroscopeMotionText": "Вимкнути Рух Гіроскопу Камери", - "disableCameraShakeText": "Вимкнути Тремтіння Камери", + "disableCameraGyroscopeMotionText": "Вимкнути рух гіроскопа камери", + "disableCameraShakeText": "Вимкнути тремтіння камери", "disableThisNotice": "(ви можете відключити це повідомлення в налаштуваннях)", "enablePackageModsDescriptionText": "(включає додаткові можливості для модінгу, але відключає мережеву гру)", "enablePackageModsText": "Включити моди локального пакета", "enterPromoCodeText": "Ввести код", "forTestingText": "Примітка: ці значення використовуються тільки для тестування і будуть втрачені, коли додаток завершить роботу.", "helpTranslateText": "Переклади гри ${APP_NAME} з англійської зроблені спільнотою. \nЯкщо ви хочете запропонувати або виправити переклад, \nПерейдіть за посиланню нижче. Заздалегідь дякую!", - "kickIdlePlayersText": "Викидати неактивних гравців", + "kickIdlePlayersText": "Викидати непрацюючих гравців", "kidFriendlyModeText": "Сімейний режим (менше насильства, і т.д.)", "languageText": "Мова", "moddingGuideText": "Інструкція по модінгу", + "moddingToolsText": "Інструменти для моддингу", "mustRestartText": "Необхідно перезапустити гру, щоб зміни вступили в силу.", "netTestingText": "Тестування мережі", "resetText": "Скинути", + "sendInfoText": "Надіслати інформацію", "showBombTrajectoriesText": "Показувати траєкторію бомби", - "showDevConsoleButtonText": "Показати кнопку консолі адміністратора", + "showDemosWhenIdleText": "Показувати демонстрації в режимі очікування", + "showDevConsoleButtonText": "Показати кнопку консолі розробника", "showInGamePingText": "Показувати пінг у грі", "showPlayerNamesText": "Показувати імена гравців", "showUserModsText": "Показати теку модів", @@ -1290,8 +1298,8 @@ "translationFetchErrorText": "статус перекладу недоступний", "translationFetchingStatusText": "перевірка статусу перекладу...", "translationInformMe": "Повідомляйте мене, коли моя мова потребує оновлення", - "translationNoUpdateNeededText": "дана мова повністю оновлена, ура!", - "translationUpdateNeededText": "** дана мова потребує оновлення!! **", + "translationNoUpdateNeededText": "Поточна мова оновлена; ураа!", + "translationUpdateNeededText": "** Поточна мова потребує оновлення!! **", "vrTestingText": "Тестування VR" }, "shareText": "Поділитися", @@ -1301,6 +1309,9 @@ "signInWithGameCenterText": "Щоб використовувати аккаунт GameCenter,\nувійдіть через GameCenter.", "singleGamePlaylistNameText": "Просто ${GAME}", "singlePlayerCountText": "1 гравець", + "sizeLargeText": "Великий", + "sizeMediumText": "Середній", + "sizeSmallText": "Малий", "soloNameFilterText": "${NAME} соло", "soundtrackTypeNames": { "CharSelect": "Вибір персонажа", @@ -1823,6 +1834,7 @@ "toSkipPressAnythingText": "(торкніться або натисніть що-небудь щоб пропустити туторіал)" }, "twoKillText": "ДВОХ ЗА РАЗ!", + "uiScaleText": "Зміна розміру інтерфейсу", "unavailableText": "недоступно", "unconfiguredControllerDetectedText": "Виявлено налаштований контролер:", "unlockThisInTheStoreText": "Це повинно бути розблоковано в магазині.", @@ -1836,6 +1848,8 @@ "upgradeText": "Оновлення", "upgradeToPlayText": "Розблокуйте \"${PRO}\" в магазині щоб грати в це.", "useDefaultText": "Використовувати стандартні", + "userSystemScriptsCreateText": "Створення системних сценаріїв користувача", + "userSystemScriptsDeleteText": "Видалити системні сценарії користувача", "usesExternalControllerText": "Ця гра може використовувати зовнішній контролер для управління.", "usingItunesText": "Використання музикального додатку для саундтрека...", "usingItunesTurnRepeatAndShuffleOnText": "Будь ласка, переконайтеся, що випадковий порядок і повтор усіх пісень включений в Itunes.", diff --git a/dist/ba_data/data/languages/venetian.json b/dist/ba_data/data/languages/venetian.json index a135d13..36b9ab5 100644 --- a/dist/ba_data/data/languages/venetian.json +++ b/dist/ba_data/data/languages/venetian.json @@ -247,7 +247,7 @@ "Sharing is Caring": { "descriptionFull": "Sparpagna el zugo co un amigo co suceso", "descriptionFullComplete": "Zugo sparpagnà co suceso co un amigo", - "name": "Sparpagnar A vołe dir tegnerghe" + "name": "Sparpagnar vołe dir tegnerghe" }, "Stayin' Alive": { "description": "Vinsi sensa mai crepar", @@ -445,7 +445,7 @@ "movementText": "Movimento a", "resetText": "Reinposta", "swipeControlsHiddenText": "Scondi i comandi a zlizo", - "swipeInfoText": "Par bituarse co i controłi a \"zlizo\" A serve un fià de pì tenpo.\nMa sensa dover vardar i controłi, el zugo el deventarà pì senpio.", + "swipeInfoText": "Par bituarse co i controłi a \"zlizo\" serve un fià de tenpo.\nMa sensa dover vardar i controłi, el zugo el deventarà pì senpio.", "swipeText": "zlizo", "titleText": "Configura schermo tàtiłe" }, @@ -477,7 +477,7 @@ "customText": "E in pì...", "entryFeeText": "Costo", "forfeitConfirmText": "Vutu dabon dàrgheła vinta?", - "forfeitNotAllowedYetText": "Deso A no te połi miga dàrgheła vinta suito.", + "forfeitNotAllowedYetText": "No te połi miga dàrgheła vinta suito.", "forfeitText": "Dàgheła vinta", "multipliersText": "Moltiplegadori", "nextChallengeText": "Sfida pròsema", @@ -532,7 +532,7 @@ "deathsText": "Crepà", "debugText": "dezbao", "debugWindow": { - "reloadBenchmarkBestResultsText": "Nota: par 'sta proa A sarìa mejo inpostar ła Defenision so 'Alta' so Inpostasion > Gràfega > Defenision.", + "reloadBenchmarkBestResultsText": "Nota: par 'sta proa sarìa mejo inpostar ła Defenision so 'Alta' so Inpostasion > Gràfega > Defenision.", "runCPUBenchmarkText": "Saja prestasion CPU", "runGPUBenchmarkText": "Saja prestasion GPU", "runMediaReloadBenchmarkText": "Saja prestasion recargamento gràfega", @@ -552,6 +552,7 @@ "demoText": "Demo", "denyText": "Refuda", "deprecatedText": "Roba vecia", + "descriptionText": "Descrision", "desktopResText": "Resołusion PC", "deviceAccountUpgradeText": "Ocio:\nte ghè fazesto l'aceso co l'account łogałe ${NAME}.\nCo un pròsemo ajornamento i account łogałi i vegnarà cavài via.\nSe te vołi tegnerte i to progresi ajorna el to account a ła varsion V2.", "difficultyEasyText": "Fàsiłe", @@ -589,7 +590,7 @@ "colorText": "cołor", "getMoreCharactersText": "Otien pì parsonaji...", "getMoreIconsText": "Otien pì icone...", - "globalProfileInfoText": "I profiłi zugador globałi i gà nomi ùgnołi garantìi.\nIn pì A se połe zontarghe anca icone parsonałizàe.", + "globalProfileInfoText": "I profiłi zugador globałi i gà nomi ùgnołi garantìi.\nIn pì se połe zontarghe anca icone parsonałizàe.", "globalProfileText": "(profiło globałe)", "highlightText": "detaji", "iconText": "icona", @@ -597,6 +598,7 @@ "localProfileText": "(profiło łogałe)", "nameDescriptionText": "Nome zugador", "nameText": "Nome", + "profileAlreadyExistsText": "Eziste dezà un profiło co 'sto nome.", "randomText": "", "titleEditText": "Muda profiło", "titleNewText": "Profiło novo", @@ -678,6 +680,8 @@ "duplicateText": "Zdopia\nłista de zugo", "editText": "Muda\nłista de zugo", "newText": "Nova\nłista de zugo", + "pointsToWinText": "Punti par vìnsar", + "seriesLengthText": "Łonghesa serie", "showTutorialText": "Demostrasion", "shuffleGameOrderText": "Zmisia òrdene łevełi", "titleText": "Parsonałiza łiste de zugo \"${TYPE}\"" @@ -855,7 +859,7 @@ "pickUpInfoText": "- Łeva sù -\nBrinca sù bandiere, nemighi o calsìase\naltra roba miga inciodada par tera.\nStruca danovo par trarla in jiro.", "powerupBombDescriptionText": "El te parmete de trar in jiro fin\na 3 bonbe drioman invese che 1.", "powerupBombNameText": "Bonbe triple", - "powerupCurseDescriptionText": "Probabilmente A te vorè schivarla\n'sta chive. ...o dìzitu de nò?", + "powerupCurseDescriptionText": "Probabilmente te vorè schivarla\n'sta cuà. ...o dìzitu de nò?", "powerupCurseNameText": "Małedision", "powerupHealthDescriptionText": "El te recarga de'l tuto ła vida.\nA no te ło gavarisi mai pensà, ahn?", "powerupHealthNameText": "Parecio mèdego", @@ -940,10 +944,10 @@ "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'.", + "touchScreenJoinWarningText": "Te te si zontà co'l touchscreen.\nSe ła ze stà na capeła, struca 'Menù > Moła łeveło'.", "touchScreenText": "TouchScreen", - "unableToResolveHostText": "Eror: A no ze mìa posìbiłe conétarse co l'ospitador.", - "unavailableNoConnectionText": "Par deso mìa disponìbiłe (gnauna conesion a internet?)", + "unableToResolveHostText": "Eror: no ze miga posìbiłe conétarse co l'ospitador.", + "unavailableNoConnectionText": "Par deso miga disponìbiłe (gnauna conesion a internet?)", "vrOrientationResetCardboardText": "Dopàreło par reinpostar l'orientasion de'l VR.\nA te servirà un controłador esterno par zugar.", "vrOrientationResetText": "Reinposta orientasion VR.", "willTimeOutText": "(ma se in sonera el ghe vegnarà cavà)." @@ -958,8 +962,8 @@ "kickOccurredText": "${NAME} ze stà parà fora.", "kickQuestionText": "Vutu parar vìa ${NAME}?", "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.", + "kickVoteCantKickAdminsText": "I aministradori no i połe miga èsar parài vìa.", + "kickVoteCantKickSelfText": "No te połi miga pararte vìa da soło.", "kickVoteFailedNotEnoughVotersText": "A ghe ze masa pochi zugadori par na votasion.", "kickVoteFailedText": "Votasion de zlontanamento fałìa.", "kickVoteStartedText": "A se gà tacà na votasion par parar vìa ${NAME}.", @@ -1128,8 +1132,8 @@ "titleText": "Profiłi zugador" }, "playerText": "Zugador", - "playlistNoValidGamesErrorText": "'Sta łista de zugo ła gà rento łevełi dezblocài mìa vàłidi.", - "playlistNotFoundText": "łista de zugo mìa catada", + "playlistNoValidGamesErrorText": "'Sta łista de zugo ła gà rento łevełi dezblocài miga vàłidi.", + "playlistNotFoundText": "łista de zugo miga catada", "playlistText": "Łista de zugo", "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", @@ -1186,7 +1190,7 @@ "app_name_short": "BSRemote", "button_position": "Pozision botoni", "button_size": "Grandesa botoni", - "cant_resolve_host": "A no ze mìa posìbiłe catar fora l'ospitador.", + "cant_resolve_host": "No ze miga 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.\nPołe conétarse so un schermo ùgnoło fin a 8 dispozidivi insenbre, par un feston bueło multizugador!", @@ -1216,7 +1220,7 @@ "replayNameDefaultText": "Revardo partìa ùltema", "replayReadErrorText": "Eror de łedura de'l file de revardo.", "replayRenameWarningText": "Par salvar na partìa fenìa, renòmena \"${REPLAY}\". Senò te sevitarè sorascrìvarle.", - "replayVersionErrorText": "Ne despiaze, 'sto revardo el ze stà fato co na varsion\nde'l zugo defarente e no'l połe mìa èsar doparà.", + "replayVersionErrorText": "'Sto revardo el ze stà fato co na varsion de'l\nzugo defarente e no'l połe miga èsar doparà.", "replayWatchText": "Varda revardo", "replayWriteErrorText": "Eror de salvatajo de'l revardo.", "replaysText": "Revardi", @@ -1235,7 +1239,7 @@ "scanScriptsMultipleModulesNeedUpdatesText": "${PATH} e ${NUM} n'antro/i mòduło/i el/i gà èsar ajornà/i par l'api ${API}.", "scanScriptsSingleModuleNeedsUpdatesText": "${PATH} el gà èsar ajornà par l'api ${API}.", "scoreChallengesText": "Puntejo sfide", - "scoreListUnavailableText": "Łista de puntejo mìa disponìbiłe.", + "scoreListUnavailableText": "Łista de puntejo miga disponìbiłe.", "scoreText": "Puntejo", "scoreUnits": { "millisecondsText": "Miłesegondi", @@ -1244,6 +1248,7 @@ }, "scoreWasText": "(prima ${COUNT})", "selectText": "Sełesiona", + "sendInfoDescriptionText": "Par mandarghe informasion so'l stado de account e apl a'l dezviłupador.\nZóntaghe el tó nome e ła rajon de'l parché te ghe scrivi.", "seriesWinLine1PlayerText": "GÀ VINTO ŁA", "seriesWinLine1TeamText": "GÀ VINTO ŁA", "seriesWinLine1Text": "GÀ VINTO ŁA", @@ -1261,8 +1266,8 @@ "alwaysUseInternalKeyboardDescriptionText": "(na botonera so schermo, senpia e còmoda, par scrìvar testi)", "alwaysUseInternalKeyboardText": "Dòpara botonera integrada", "benchmarksText": "Prestasion & Proe soto sforso", - "disableCameraGyroscopeMotionText": "Dezativa movimento de ła prospetiva łigada a'l jiroscopio", - "disableCameraShakeText": "Dezativa i zgorlamenti de ła videocàmara", + "disableCameraGyroscopeMotionText": "Dezativa movimento de ła prospetiva co'l jiroscopio", + "disableCameraShakeText": "Dezativa zgorlamenti de ła videocàmara", "disableThisNotice": "(Te połi dezativar 'sta notìfega inte łe inpostasion avansàe)", "enablePackageModsDescriptionText": "(l'ativasion de ła capasidà de modifegasion ła dezativa el zugo in rede)", "enablePackageModsText": "Ativa pacheti mod łogałi", @@ -1273,21 +1278,24 @@ "kidFriendlyModeText": "Modałidà bocia (viołensa reduzesta, evc)", "languageText": "Łengua", "moddingGuideText": "Guida par modifegasion", - "mustRestartText": "Par rèndar efetive łe modìfeghe, A te ghè da retacar el zugo.", + "moddingToolsText": "Strumenti de modifegasion", + "mustRestartText": "Par rèndar efetive łe modìfeghe, te ghè da retacar el zugo.", "netTestingText": "Proa de rede", "resetText": "Reinposta", + "sendInfoText": "Manda informasion", "showBombTrajectoriesText": "Mostra trajetore bonbe", - "showDevConsoleButtonText": "Mostra boton cuadro comandi dezviłupador", + "showDemosWhenIdleText": "Mostra demostrasion in sonera", + "showDevConsoleButtonText": "Mostra boton de i comandi dezviłupador", "showInGamePingText": "Mostra łatensa de'l zugo", "showPlayerNamesText": "Mostra nomi zugadori", "showUserModsText": "Mostra carteła modifegasion", "titleText": "Avansàe", "translationEditorButtonText": "Piataforma de tradusion de ${APP_NAME}", - "translationFetchErrorText": "stado de ła tradusion mìa disponìbiłe", + "translationFetchErrorText": "stado de ła tradusion miga disponìbiłe", "translationFetchingStatusText": "controło stado de ła tradusion...", - "translationInformMe": "Infòrmame co A ghe ze tochi novi da tradùzar", - "translationNoUpdateNeededText": "ła tradusion in veneto ła ze aposto: un aereo!", - "translationUpdateNeededText": "** A ghe ze testi novi da tradùzar!! **", + "translationInformMe": "Infòrmame co ghe ze tochi novi da tradùzar", + "translationNoUpdateNeededText": "Ła tradusion in veneto ła ze a posto: un aèreo!", + "translationUpdateNeededText": "** Ghe ze testi novi da tradùzar!! **", "vrTestingText": "Proa VR" }, "shareText": "Sparpagna", @@ -1297,6 +1305,9 @@ "signInWithGameCenterText": "Par doparar un account Game Center,\nconétate da rento l'apl Game Center.", "singleGamePlaylistNameText": "Tuti \"${GAME}\"", "singlePlayerCountText": "1 zugador", + "sizeLargeText": "Granda", + "sizeMediumText": "Mezana", + "sizeSmallText": "Ceła", "soloNameFilterText": "\"${NAME}\" 1 VS 1", "soundtrackTypeNames": { "CharSelect": "Sełesion parsonajo", @@ -1337,14 +1348,14 @@ "howToUseIconsText": "(par dopararle, crea un profiło zugador globałe inte ła sesion account)", "howToUseMapsText": "(dòpara 'sti łevełi inte łe to łiste de zugo: scuadre/tuti contro tuti)", "iconsText": "Icone", - "loadErrorText": "A no ze mìa posìbiłe cargar ła pàjina.\nDaghe na ociada a ła to conesion internet.", + "loadErrorText": "No ze miga posìbiłe cargar ła pàjina.\nDaghe na ociada a ła tó conesion internet.", "loadingText": "cargamento", "mapsText": "Łevełi", "miniGamesText": "Minizughi", "oneTimeOnlyText": "(ocazion ùgnoła)", "purchaseAlreadyInProgressText": "Cronpa de 'sto ojeto dezà drio conpirse.", "purchaseConfirmText": "Vutu cronpar ${ITEM}?", - "purchaseNotValidError": "Cronpa mìa vàłida.\nSe'l ze un eror, contata ${EMAIL}.", + "purchaseNotValidError": "Cronpa miga vàłida.\nSe'l ze un eror, contata ${EMAIL}.", "purchaseText": "Cronpa", "saleBundleText": "Pacheto in oferta!", "saleExclaimText": "Oferta!", @@ -1376,7 +1387,7 @@ "telnetAccessText": "Rełevà aceso a telnet: vutu autorizarlo?", "testBuildErrorText": "'Sta varsion de proa no ła ze miga pì ativa. Controła se A ghin ze una pì resente.", "testBuildText": "Varsion de proa", - "testBuildValidateErrorText": "A no ze mìa posìbiłe varifegar ła varsion de proa. (gnauna conesion a internet?)", + "testBuildValidateErrorText": "No ze miga posìbiłe varifegar ła varsion de proa. (gnauna conesion a internet?)", "testBuildValidatedText": "Varsion de proa aposto. Gòdateła!", "thankYouText": "Grasie par el to suporto! Gùstate el zugo!", "threeKillText": "NO GHE N'È 2 SENSA 3!!", @@ -1618,18 +1629,18 @@ "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 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.", - "Can't link; would surpass maximum of ${COUNT} linked accounts.": "A no se połe mìa cołegarlo: A ze dezà stà cołegài un màsemo de ${COUNT} profiłi.", + "Can't link 2 accounts of this type.": "No se połe miga cołegar 2 profiłi de 'sto tipo.", + "Can't link 2 diamond league accounts.": "No se połe miga cołegar 2 profiłi de ła łega de damante.", + "Can't link; would surpass maximum of ${COUNT} linked accounts.": "No se połe miga cołegarlo: ze dezà stà cołegài un màsemo de ${COUNT} profiłi.", "Cheating detected; scores and prizes suspended for ${COUNT} days.": "Rełevà un inbrojo: punteji e premi sospendesti par ${COUNT} dì.", - "Could not establish a secure connection.": "A no ze mìa posìbiłe stabiłir na conesion segura.", + "Could not establish a secure connection.": "No ze miga posìbiłe stabiłir na conesion segura.", "Daily maximum reached.": "Màsemo jornałiero pasà.", "Entering tournament...": "Entrada inte'l tornèo...", - "Invalid code.": "Còdaze mìa vàłido.", + "Invalid code.": "Còdaze miga vàłido.", "Invalid payment; purchase canceled.": "Pagamento miga vàłido: cronpa anułada.", - "Invalid promo code.": "Còdaze promosionałe mìa vàłido.", - "Invalid purchase.": "Cronpa mìa vàłida.", - "Invalid tournament entry; score will be ignored.": "Entrada inte'l tornèo mìa vàłida: el puntejo el vegnarà ignorà.", + "Invalid promo code.": "Còdaze promosionałe miga vàłido.", + "Invalid purchase.": "Cronpa miga vàłida.", + "Invalid tournament entry; score will be ignored.": "Entrada inte'l tornèo miga 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 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 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?", @@ -1639,10 +1650,10 @@ "Message is too long.": "Mesajo masa łongo.", "No servers are available. Please try again soon.": "Gnaun server disponìbiłe. Proa pì tardi.", "Profile \"${NAME}\" upgraded successfully.": "Profiło \"${NAME}\" mejorà co suceso.", - "Profile could not be upgraded.": "El profiło no'l połe mìa èsar mejorà.", + "Profile could not be upgraded.": "El profiło no'l połe miga èsar mejorà.", "Purchase successful!": "Cronpà co suceso!", "Received ${COUNT} tickets for signing in.\nCome back tomorrow to receive ${TOMORROW_COUNT}.": "A te ghè resevesto ${COUNT} biłieti par ver verto el zugo.\nTorna anca doman par brincàrghine ${TOMORROW_COUNT}.", - "Server functionality is no longer supported in this version of the game;\nPlease update to a newer version.": "Ła funsionałidà de'l server no ła ze mìa pì suportada inte 'sta varsion de'l zugo.\nAjòrneło a ła varsion nova.", + "Server functionality is no longer supported in this version of the game;\nPlease update to a newer version.": "So 'sta varsion de'l zugo ła funsionałidà de'l server no ła ze miga pì suportada.\nAjòrneło a ła varsion nova.", "Sorry, there are no uses remaining on this code.": "Ne despiaze, 'sto còdaze el ze dezà stà doparà a'l màsemo.", "Sorry, this code has already been used.": "Ne despiaze, 'sto còdaze el ze dezà stà doparà.", "Sorry, this code has expired.": "Ne despiaze, ła vałidità de 'sto còdaze ła ze terminada.", @@ -1650,15 +1661,15 @@ "Still searching for nearby servers; please try again soon.": "Reserca de server visini in corso: proa danovo pì tardi.", "Temporarily unavailable; please try again later.": "Par deso miga disponìbiłe: proa danovo pì tardi.", "The tournament ended before you finished.": "El tornèo el ze terminà prima che te ghesi fenìo.", - "This account cannot be unlinked for ${NUM} days.": "'Sto account no'l połe mìa èsar descołegà prima de ${NUM} dì.", - "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 account cannot be unlinked for ${NUM} days.": "'Sto account no'l połe miga èsar descołegà prima de ${NUM} dì.", + "This code cannot be used on the account that created it.": "'Sto còdaze no'l połe miga è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 dispozidivo co'l root.", - "Tournaments require ${VERSION} or newer": "Par i tornèi A serve ła varsion ${VERSION} o una pì resente", + "Tournaments require ${VERSION} or newer": "Par i tornèi 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 el to account łogałe co 'sto cuà?\n\nEl to account łogałe el ze ${ACCOUNT1}\n'Sto account el ze ${ACCOUNT2}\n\n'Sta oparasion ła te parmetarà de mantegner i to progresi ezistenti.\nOcio: 'sta asion no ła połe pì è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 el tó account łogałe co 'sto cuà?\n\nEl tó account łogałe el ze ${ACCOUNT1}\n'Sto account el ze ${ACCOUNT2}\n\n'Sta oparasion ła te parmetarà de mantegner i tó progresi ezistenti.\nOcio: 'sta asion no ła połe pì èsar anułada!", "You already own this!": "Dezà cronpà!", "You can join in ${COUNT} seconds.": "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!", @@ -1753,7 +1764,7 @@ "Jump just as you're throwing to get bombs up to the highest levels.": "Co A te si drio tirar na bonba, salta par farla rivar pì alta posìbiłe.", "Land-mines are a good way to stop speedy enemies.": "Łe mine łe ze fantàsteghe par fermar i nemighi pì ràpidi.", "Many things can be picked up and thrown, including other players. Tossing\nyour enemies off cliffs can be an effective and emotionally fulfilling strategy.": "Te połi łevar sù e tirar racuante robe, anca cheł'altri zugadori. Trar zó da na croda\ni tó nemighi ła połe èsar na stratejìa efisente che ła połe cavarte anca calche spisa.", - "No, you can't get up on the ledge. You have to throw bombs.": "Nò, A no te połi mìa montar so'l bordo. A te ghè da tirar bonbe.", + "No, you can't get up on the ledge. You have to throw bombs.": "Nò, no te połi miga montar so'l bordo. Te ghè da tirar bonbe.", "Players can join and leave in the middle of most games,\nand you can also plug and unplug controllers on the fly.": "I zugadori i połe zontarse e ndar vìa inte'l medo de ła majornasa de łe\npartìe. Ti te połi anca tacar e destacar un controłador a'l voło.", "Practice using your momentum to throw bombs more accurately.": "Fà pràtega tirardo bonbe corendo par far crèsar ła to presizion.", "Punches do more damage the faster your fists are moving,\nso try running, jumping, and spinning like crazy.": "Pì vełosi che se move łe to man, pì dano i farà i to\ncrogni! Donca proa córar, saltar e ndar torno cofà un mato.", @@ -1819,19 +1830,22 @@ "toSkipPressAnythingText": "(toca o struca un boton par saltar ła demostrasion)" }, "twoKillText": "E 2 DE COPÀI!", + "uiScaleText": "Grandesa interfasa", "unavailableText": "miga disponìbiłe", "unconfiguredControllerDetectedText": "Rełevà controłador miga configurà:", "unlockThisInTheStoreText": "Da dezblocar inte ła botega.", "unlockThisProfilesText": "Par crear pì de ${NUM} profiłi, A te serve:", "unlockThisText": "Par dezblocar 'sta funsion A te serve:", "unsupportedControllerText": "El controłador \"${NAME}\" no'l ze miga suportà.", - "unsupportedHardwareText": "Ne despiaze, 'sto hardware no'l ze mìa conpatìbiłe co 'sta varsion de'l zugo.", + "unsupportedHardwareText": "'Sto hardware no'l ze miga conpatìbiłe co 'sta varsion de'l zugo.", "upFirstText": "Par tacar:", "upNextText": "Inte'l łeveło n°${COUNT}:", "updatingAccountText": "Ajornamento de'l to account...", "upgradeText": "Mejora", "upgradeToPlayText": "Par zugarghe, dezbloca \"${PRO}\" inte ła botega.", "useDefaultText": "Reinposta", + "userSystemScriptsCreateText": "Crea script de sistema de l'utente", + "userSystemScriptsDeleteText": "Ełìmena script de sistema de l'utente", "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...", "v2AccountLinkingInfoText": "Par łigar un account V2 dòpara el boton 'Jestisi account'.", @@ -1860,8 +1874,8 @@ "replayDeleteErrorText": "A se gà verifegà un eror ełimenando el revardo.", "replayNameText": "Nome de'l revardo", "replayRenameErrorAlreadyExistsText": "A eziste dezà un revardo co 'sto nome.", - "replayRenameErrorInvalidName": "A no ze mìa posìbiłe renomenar el revardo: nome mìa vàłido.", - "replayRenameErrorText": "A se gà verifegà un eror renomenando el revardo.", + "replayRenameErrorInvalidName": "No ze miga posìbiłe renomenar el revardo: nome miga vàłido.", + "replayRenameErrorText": "Se gà verifegà un eror renomenando el revardo.", "sharedReplaysText": "Revardi sparpagnài", "titleText": "Varda", "watchReplayButtonText": "Varda\nrevardo" diff --git a/dist/ba_data/data/languages/vietnamese.json b/dist/ba_data/data/languages/vietnamese.json index 2cd5f39..94d2a21 100644 --- a/dist/ba_data/data/languages/vietnamese.json +++ b/dist/ba_data/data/languages/vietnamese.json @@ -31,6 +31,7 @@ "signInWithGooglePlayText": "đăng nhập bằng google chơi trò chơi", "signInWithTestAccountInfoText": "(loại tài khoản đặc biệt; chỉ đăng nhập trên máy này)", "signInWithTestAccountText": "Đăng nhập bằng tài khoản máy tính", + "signInWithText": "Đăng nhập bằng ${SERVICE}", "signInWithV2InfoText": "(một tài khoản hoạt động trên tất cả các nền tảng)", "signInWithV2Text": "Đăng nhập bằng tài khoản BombSquad", "signOutText": "Đăng Xuất", @@ -337,6 +338,8 @@ "getMoreGamesText": "Thêm các thể loại chơi", "titleText": "Thêm trận đấu" }, + "addToFavoritesText": "Thêm vào mục yêu thích", + "addedToFavoritesText": "Đã thêm '${NAME}' vào Mục yêu thích.", "allText": "Tất cả", "allowText": "Cho phép", "alreadySignedInText": "Tài khoản của bạn đã được đăng nhập trên thiết bị khác;\nhãy đổi tài khoản hoặc đăng xuất khỏi thiết bị khác\nvà thử lại.", @@ -370,6 +373,7 @@ "chatMutedText": "Đã ẩn trò chuyện", "chatUnMuteText": "Khôi phục trò chuyện", "choosingPlayerText": "<đang chọn nhân vật>", + "codesExplainText": "Mã được nhà phát triển cung cấp để\nchẩn đoán và khắc phục các vấn đề về tài khoản.", "completeThisLevelToProceedText": "Bạn phải hoàn thành \ncấp độ này để tiếp tục!", "completionBonusText": "Hoàn thành phần thưởng", "configControllersWindow": { @@ -450,6 +454,7 @@ "swipeText": "Trượt", "titleText": "Điều chinh màn hình cảm ứng" }, + "configureDeviceInSystemSettingsText": "${DEVICE} có thể được định cấu hình trong ứng dụng Cài đặt hệ thống.", "configureItNowText": "Điều chỉnh nó ngay ?", "configureText": "Điều chỉnh", "connectMobileDevicesWindow": { @@ -563,6 +568,8 @@ "disableXInputDescriptionText": "Cho phép trên 4 thiết bị điều khiển nhưng nó có thể hoạt động không tốt", "disableXInputText": "Ngắt Kết NỐi XInput", "disabledText": "Vô hiệu hóa", + "discordFriendsText": "Bạn muốn tìm người mới để chơi cùng?\nTham gia Discord của chúng tôi và tìm những người bạn mới!", + "discordJoinText": "Tham gia Disscord", "doneText": "Xong", "drawText": "Hòa", "duplicateText": "Nhân Đôi", @@ -596,6 +603,7 @@ "localProfileText": "(tài khoản cố định)", "nameDescriptionText": "Tên nhân vật", "nameText": "Tên", + "profileAlreadyExistsText": "Hồ sơ có tên đó đã tồn tại.", "randomText": "Ngẫu nhiên", "titleEditText": "Chỉnh sửa thông tin", "titleNewText": "Tạo mới", @@ -677,6 +685,8 @@ "duplicateText": "Nhân đôi \nDanh sách", "editText": "Đặt lại \nDanh sách", "newText": "Danh sách\nMới", + "pointsToWinText": "Điểm để thắng", + "seriesLengthText": "Độ dài loạt phim", "showTutorialText": "Hiện Hướng dẫn", "shuffleGameOrderText": "Xáo trộn thứ tự trò chơi", "titleText": "Tùy chỉnh ${TYPE} Danh sách phát" @@ -749,6 +759,7 @@ "manualYourLocalAddressText": "Địa chỉ nội bộ:", "nearbyText": "Gần nhất", "noConnectionText": "(không có kết nối)", + "noPartiesAddedText": "Không có bên nào được thêm vào", "otherVersionsText": "(phiên bản khác)", "partyCodeText": "Mã phòng", "partyInviteAcceptText": "Chấp nhận", @@ -820,10 +831,12 @@ "alwaysText": "Luôn Luôn", "fullScreenCmdText": "Toàn màn hình (Cmd-F)", "fullScreenCtrlText": "Toàn màn hình (Ctrl-F)", + "fullScreenText": "Toàn màn hình", "gammaText": "Gam-ma", "highText": "Cao", "higherText": "Cao hơn", "lowText": "Thấp", + "maxFPSText": "FPS tối đa", "mediumText": "Vừa", "neverText": "Không bao giờ", "resolutionText": "Độ phân giải", @@ -1061,7 +1074,9 @@ "noContinuesText": "(không tiếp tục)", "noExternalStorageErrorText": "Không tìm thấy bộ nhớ ngoài trên thiết bị này", "noGameCircleText": "Lỗi: không đăng nhập vào GameCircle", + "noPluginsInstalledText": "Không có plugin nào được cài đặt", "noScoresYetText": "Chưa có điểm nào.", + "noServersFoundText": "Không tìm thấy máy chủ nào.", "noThanksText": "Không cảm ơn", "noTournamentsInTestBuildText": "CẢNH BÁO: Điểm thi đấu từ bản dựng thử nghiệm này sẽ bị bỏ qua.", "noValidMapsErrorText": "Không tìm thấy bản đồ hợp lệ cho loại trò chơi này.", @@ -1267,10 +1282,13 @@ "kidFriendlyModeText": "Chế độ thân thiện với trẻ em (giảm bạo lực , vân vân)", "languageText": "Ngôn ngữ", "moddingGuideText": "Hướng dẫn mod", + "moddingToolsText": "Công cụ sửa đổi", "mustRestartText": "Bạn phải khởi động lại trò chơi để áp dụng cài đặt", "netTestingText": "Kiểm tra mạng", "resetText": "Cài lại", "showBombTrajectoriesText": "Hiển thị quỹ đạo của bom", + "showDemosWhenIdleText": "Hiện bản demo khi không hoạt động", + "showDevConsoleButtonText": "Hiện nút Bảng điều khiển cho nhà phát triển", "showInGamePingText": "Hiển thị Ping trong Game", "showPlayerNamesText": "Hiển thị tên người chơi", "showUserModsText": "Hiển thị Thư mục Mod", @@ -1290,6 +1308,9 @@ "signInWithGameCenterText": "Để sử dụng một tài khoản Trung tâm trò chơi,\n đăng nhập với ứng dụng Trung Tâm Trò Chơi", "singleGamePlaylistNameText": "Chỉ ${GAME}", "singlePlayerCountText": "1 người chơi", + "sizeLargeText": "Lớn", + "sizeMediumText": "Trung bình", + "sizeSmallText": "Nhỏ", "soloNameFilterText": "Solo ${NAME}", "soundtrackTypeNames": { "CharSelect": "Chọn nhân vật", @@ -1362,6 +1383,8 @@ "storeText": "Cửa hàng", "submitText": "Gửi", "submittingPromoCodeText": "Đang xác nhận mã code...", + "successText": "Thành công!", + "supportEmailText": "Nếu bạn đang gặp bất kỳ vấn đề nào với\nứng dụng, vui lòng gửi email tới ${EMAIL}.", "teamNamesColorText": "Tên/Màu sắc đội...", "telnetAccessGrantedText": "Đã kích hoạt truy cập giao thức.", "telnetAccessText": "Phát hiện truy cập giao thức; cho phép?", @@ -1810,11 +1833,13 @@ "toSkipPressAnythingText": "(chạm hoặc nhấn bất kì đâu để bỏ qua hướng dẩn)" }, "twoKillText": "Hai Mạng!", + "uiScaleText": "Quy mô giao diện", "unavailableText": "Không Khả Dụng", "unconfiguredControllerDetectedText": "Tay cầm không cấu hình được phát hiện:", "unlockThisInTheStoreText": "Nó phải được mở khóa trong cửa hàng.", "unlockThisProfilesText": "Để tạo nhiều hơn ${NUM} hồ sơ,bạn cần:", "unlockThisText": "Để mở khóa bạn cần", + "unsupportedControllerText": "Rất tiếc, bộ điều khiển \"${NAME}\" không được hỗ trợ.", "unsupportedHardwareText": "Xin lỗi, phần cứng này không được hỗ trợ bởi bản dựng này của trò chơi.", "upFirstText": "Game đầu:", "upNextText": "Trò chơi tiếp theo ${COUNT}:", @@ -1822,10 +1847,13 @@ "upgradeText": "Nâng cấp", "upgradeToPlayText": "Mở khóa \"${PRO}\" trong cửa hàng để chơi.", "useDefaultText": "Sử dụng mặc định", + "userSystemScriptsCreateText": "Tạo tập lệnh hệ thống người dùng", + "userSystemScriptsDeleteText": "Xóa tập lệnh hệ thống người dùng", "usesExternalControllerText": "Trò chơi này sử dụng tay cầm bên ngoài cho đầu vào.", "usingItunesText": "Sử dụng Ứng dụng âm nhạc cho nhạc nền", "v2AccountLinkingInfoText": "Để đến tài khoản V2, sử dụng nút 'Thiết đặt tài khoản'.", "validatingTestBuildText": "Kiểm tra xác thực Xây dựng ...", + "viaText": "Thông qua", "victoryText": "Chiến thắng!", "voteDelayText": "Bạn không thể bắt đầu cuộc bầu chọn khác trong ${NUMBER} giây", "voteInProgressText": "Một cuộc bầu chọn đang tiến hành.", diff --git a/dist/ba_data/python-site-packages/certifi/__init__.py b/dist/ba_data/python-site-packages/certifi/__init__.py index b93bb49..1c91f3e 100644 --- a/dist/ba_data/python-site-packages/certifi/__init__.py +++ b/dist/ba_data/python-site-packages/certifi/__init__.py @@ -1,4 +1,4 @@ from .core import contents, where __all__ = ["contents", "where"] -__version__ = "2023.11.17" +__version__ = "2024.02.02" diff --git a/dist/ba_data/python-site-packages/certifi/cacert.pem b/dist/ba_data/python-site-packages/certifi/cacert.pem index f309400..fac3c31 100644 --- a/dist/ba_data/python-site-packages/certifi/cacert.pem +++ b/dist/ba_data/python-site-packages/certifi/cacert.pem @@ -245,34 +245,6 @@ mJlglFwjz1onl14LBQaTNx47aTbrqZ5hHY8y2o4M1nQ+ewkk2gF3R8Q7zTSMmfXK 4SVhM7JZG+Ju1zdXtg2pEto= -----END CERTIFICATE----- -# Issuer: O=SECOM Trust.net OU=Security Communication RootCA1 -# Subject: O=SECOM Trust.net OU=Security Communication RootCA1 -# Label: "Security Communication Root CA" -# Serial: 0 -# MD5 Fingerprint: f1:bc:63:6a:54:e0:b5:27:f5:cd:e7:1a:e3:4d:6e:4a -# SHA1 Fingerprint: 36:b1:2b:49:f9:81:9e:d7:4c:9e:bc:38:0f:c6:56:8f:5d:ac:b2:f7 -# SHA256 Fingerprint: e7:5e:72:ed:9f:56:0e:ec:6e:b4:80:00:73:a4:3f:c3:ad:19:19:5a:39:22:82:01:78:95:97:4a:99:02:6b:6c ------BEGIN CERTIFICATE----- -MIIDWjCCAkKgAwIBAgIBADANBgkqhkiG9w0BAQUFADBQMQswCQYDVQQGEwJKUDEY -MBYGA1UEChMPU0VDT00gVHJ1c3QubmV0MScwJQYDVQQLEx5TZWN1cml0eSBDb21t -dW5pY2F0aW9uIFJvb3RDQTEwHhcNMDMwOTMwMDQyMDQ5WhcNMjMwOTMwMDQyMDQ5 -WjBQMQswCQYDVQQGEwJKUDEYMBYGA1UEChMPU0VDT00gVHJ1c3QubmV0MScwJQYD -VQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTEwggEiMA0GCSqGSIb3 -DQEBAQUAA4IBDwAwggEKAoIBAQCzs/5/022x7xZ8V6UMbXaKL0u/ZPtM7orw8yl8 -9f/uKuDp6bpbZCKamm8sOiZpUQWZJtzVHGpxxpp9Hp3dfGzGjGdnSj74cbAZJ6kJ -DKaVv0uMDPpVmDvY6CKhS3E4eayXkmmziX7qIWgGmBSWh9JhNrxtJ1aeV+7AwFb9 -Ms+k2Y7CI9eNqPPYJayX5HA49LY6tJ07lyZDo6G8SVlyTCMwhwFY9k6+HGhWZq/N -QV3Is00qVUarH9oe4kA92819uZKAnDfdDJZkndwi92SL32HeFZRSFaB9UslLqCHJ -xrHty8OVYNEP8Ktw+N/LTX7s1vqr2b1/VPKl6Xn62dZ2JChzAgMBAAGjPzA9MB0G -A1UdDgQWBBSgc0mZaNyFW2XjmygvV5+9M7wHSDALBgNVHQ8EBAMCAQYwDwYDVR0T -AQH/BAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAaECpqLvkT115swW1F7NgE+vG -kl3g0dNq/vu+m22/xwVtWSDEHPC32oRYAmP6SBbvT6UL90qY8j+eG61Ha2POCEfr -Uj94nK9NrvjVT8+amCoQQTlSxN3Zmw7vkwGusi7KaEIkQmywszo+zenaSMQVy+n5 -Bw+SUEmK3TGXX8npN6o7WWWXlDLJs58+OmJYxUmtYg5xpTKqL8aJdkNAExNnPaJU -JRDL8Try2frbSVa7pv6nQTXD4IhhyYjH3zYQIphZ6rBK+1YWc26sTfcioU+tHXot -RSflMMFe8toTyyVCUZVHA4xsIcx0Qu1T/zOLjw9XARYvz6buyXAiFL39vmwLAw== ------END CERTIFICATE----- - # Issuer: CN=XRamp Global Certification Authority O=XRamp Security Services Inc OU=www.xrampsecurity.com # Subject: CN=XRamp Global Certification Authority O=XRamp Security Services Inc OU=www.xrampsecurity.com # Label: "XRamp Global CA Root" @@ -4776,3 +4748,67 @@ lklyALKrdVwPaFsdZcJfMw8eD/A7hvWwTruc9+olBdytoptLFwG+Qt81IR2tq670 v64fG9PiO/yzcnMcmyiQiRM9HcEARwmWmjgb3bHPDcK0RPOWlc4yOo80nOAXx17O rg3bhzjlP1v9mxnhMUF6cKojawHhRUzNlM47ni3niAIi9G7oyOzWPPO5std3eqx7 -----END CERTIFICATE----- + +# Issuer: CN=Telekom Security TLS ECC Root 2020 O=Deutsche Telekom Security GmbH +# Subject: CN=Telekom Security TLS ECC Root 2020 O=Deutsche Telekom Security GmbH +# Label: "Telekom Security TLS ECC Root 2020" +# Serial: 72082518505882327255703894282316633856 +# MD5 Fingerprint: c1:ab:fe:6a:10:2c:03:8d:bc:1c:22:32:c0:85:a7:fd +# SHA1 Fingerprint: c0:f8:96:c5:a9:3b:01:06:21:07:da:18:42:48:bc:e9:9d:88:d5:ec +# SHA256 Fingerprint: 57:8a:f4:de:d0:85:3f:4e:59:98:db:4a:ea:f9:cb:ea:8d:94:5f:60:b6:20:a3:8d:1a:3c:13:b2:bc:7b:a8:e1 +-----BEGIN CERTIFICATE----- +MIICQjCCAcmgAwIBAgIQNjqWjMlcsljN0AFdxeVXADAKBggqhkjOPQQDAzBjMQsw +CQYDVQQGEwJERTEnMCUGA1UECgweRGV1dHNjaGUgVGVsZWtvbSBTZWN1cml0eSBH +bWJIMSswKQYDVQQDDCJUZWxla29tIFNlY3VyaXR5IFRMUyBFQ0MgUm9vdCAyMDIw +MB4XDTIwMDgyNTA3NDgyMFoXDTQ1MDgyNTIzNTk1OVowYzELMAkGA1UEBhMCREUx +JzAlBgNVBAoMHkRldXRzY2hlIFRlbGVrb20gU2VjdXJpdHkgR21iSDErMCkGA1UE +AwwiVGVsZWtvbSBTZWN1cml0eSBUTFMgRUNDIFJvb3QgMjAyMDB2MBAGByqGSM49 +AgEGBSuBBAAiA2IABM6//leov9Wq9xCazbzREaK9Z0LMkOsVGJDZos0MKiXrPk/O +tdKPD/M12kOLAoC+b1EkHQ9rK8qfwm9QMuU3ILYg/4gND21Ju9sGpIeQkpT0CdDP +f8iAC8GXs7s1J8nCG6NCMEAwHQYDVR0OBBYEFONyzG6VmUex5rNhTNHLq+O6zd6f +MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMAoGCCqGSM49BAMDA2cA +MGQCMHVSi7ekEE+uShCLsoRbQuHmKjYC2qBuGT8lv9pZMo7k+5Dck2TOrbRBR2Di +z6fLHgIwN0GMZt9Ba9aDAEH9L1r3ULRn0SyocddDypwnJJGDSA3PzfdUga/sf+Rn +27iQ7t0l +-----END CERTIFICATE----- + +# Issuer: CN=Telekom Security TLS RSA Root 2023 O=Deutsche Telekom Security GmbH +# Subject: CN=Telekom Security TLS RSA Root 2023 O=Deutsche Telekom Security GmbH +# Label: "Telekom Security TLS RSA Root 2023" +# Serial: 44676229530606711399881795178081572759 +# MD5 Fingerprint: bf:5b:eb:54:40:cd:48:71:c4:20:8d:7d:de:0a:42:f2 +# SHA1 Fingerprint: 54:d3:ac:b3:bd:57:56:f6:85:9d:ce:e5:c3:21:e2:d4:ad:83:d0:93 +# SHA256 Fingerprint: ef:c6:5c:ad:bb:59:ad:b6:ef:e8:4d:a2:23:11:b3:56:24:b7:1b:3b:1e:a0:da:8b:66:55:17:4e:c8:97:86:46 +-----BEGIN CERTIFICATE----- +MIIFszCCA5ugAwIBAgIQIZxULej27HF3+k7ow3BXlzANBgkqhkiG9w0BAQwFADBj +MQswCQYDVQQGEwJERTEnMCUGA1UECgweRGV1dHNjaGUgVGVsZWtvbSBTZWN1cml0 +eSBHbWJIMSswKQYDVQQDDCJUZWxla29tIFNlY3VyaXR5IFRMUyBSU0EgUm9vdCAy +MDIzMB4XDTIzMDMyODEyMTY0NVoXDTQ4MDMyNzIzNTk1OVowYzELMAkGA1UEBhMC +REUxJzAlBgNVBAoMHkRldXRzY2hlIFRlbGVrb20gU2VjdXJpdHkgR21iSDErMCkG +A1UEAwwiVGVsZWtvbSBTZWN1cml0eSBUTFMgUlNBIFJvb3QgMjAyMzCCAiIwDQYJ +KoZIhvcNAQEBBQADggIPADCCAgoCggIBAO01oYGA88tKaVvC+1GDrib94W7zgRJ9 +cUD/h3VCKSHtgVIs3xLBGYSJwb3FKNXVS2xE1kzbB5ZKVXrKNoIENqil/Cf2SfHV +cp6R+SPWcHu79ZvB7JPPGeplfohwoHP89v+1VmLhc2o0mD6CuKyVU/QBoCcHcqMA +U6DksquDOFczJZSfvkgdmOGjup5czQRxUX11eKvzWarE4GC+j4NSuHUaQTXtvPM6 +Y+mpFEXX5lLRbtLevOP1Czvm4MS9Q2QTps70mDdsipWol8hHD/BeEIvnHRz+sTug +BTNoBUGCwQMrAcjnj02r6LX2zWtEtefdi+zqJbQAIldNsLGyMcEWzv/9FIS3R/qy +8XDe24tsNlikfLMR0cN3f1+2JeANxdKz+bi4d9s3cXFH42AYTyS2dTd4uaNir73J +co4vzLuu2+QVUhkHM/tqty1LkCiCc/4YizWN26cEar7qwU02OxY2kTLvtkCJkUPg +8qKrBC7m8kwOFjQgrIfBLX7JZkcXFBGk8/ehJImr2BrIoVyxo/eMbcgByU/J7MT8 +rFEz0ciD0cmfHdRHNCk+y7AO+oMLKFjlKdw/fKifybYKu6boRhYPluV75Gp6SG12 +mAWl3G0eQh5C2hrgUve1g8Aae3g1LDj1H/1Joy7SWWO/gLCMk3PLNaaZlSJhZQNg ++y+TS/qanIA7AgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUtqeX +gj10hZv3PJ+TmpV5dVKMbUcwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBS2 +p5eCPXSFm/c8n5OalXl1UoxtRzANBgkqhkiG9w0BAQwFAAOCAgEAqMxhpr51nhVQ +pGv7qHBFfLp+sVr8WyP6Cnf4mHGCDG3gXkaqk/QeoMPhk9tLrbKmXauw1GLLXrtm +9S3ul0A8Yute1hTWjOKWi0FpkzXmuZlrYrShF2Y0pmtjxrlO8iLpWA1WQdH6DErw +M807u20hOq6OcrXDSvvpfeWxm4bu4uB9tPcy/SKE8YXJN3nptT+/XOR0so8RYgDd +GGah2XsjX/GO1WfoVNpbOms2b/mBsTNHM3dA+VKq3dSDz4V4mZqTuXNnQkYRIer+ +CqkbGmVps4+uFrb2S1ayLfmlyOw7YqPta9BO1UAJpB+Y1zqlklkg5LB9zVtzaL1t +xKITDmcZuI1CfmwMmm6gJC3VRRvcxAIU/oVbZZfKTpBQCHpCNfnqwmbU+AGuHrS+ +w6jv/naaoqYfRvaE7fzbzsQCzndILIyy7MMAo+wsVRjBfhnu4S/yrYObnqsZ38aK +L4x35bcF7DvB7L6Gs4a8wPfc5+pbrrLMtTWGS9DiP7bY+A4A7l3j941Y/8+LN+lj +X273CXE2whJdV/LItM3z7gLfEdxquVeEHVlNjM7IDiPCtyaaEBRx/pOyiriA8A4Q +ntOoUAw3gi/q4Iqd4Sw5/7W0cwDk90imc6y/st53BIe0o82bNSQ3+pCTE4FCxpgm +dTdmQRCsu/WU48IxK63nI1bMNSWSs1A= +-----END CERTIFICATE----- diff --git a/dist/ba_data/python-site-packages/certifi/core.py b/dist/ba_data/python-site-packages/certifi/core.py index de02898..91f538b 100644 --- a/dist/ba_data/python-site-packages/certifi/core.py +++ b/dist/ba_data/python-site-packages/certifi/core.py @@ -5,6 +5,10 @@ certifi.py This module returns the installation location of cacert.pem or its contents. """ import sys +import atexit + +def exit_cacert_ctx() -> None: + _CACERT_CTX.__exit__(None, None, None) # type: ignore[union-attr] if sys.version_info >= (3, 11): @@ -35,6 +39,7 @@ if sys.version_info >= (3, 11): # we will also store that at the global level as well. _CACERT_CTX = as_file(files("certifi").joinpath("cacert.pem")) _CACERT_PATH = str(_CACERT_CTX.__enter__()) + atexit.register(exit_cacert_ctx) return _CACERT_PATH @@ -70,6 +75,7 @@ elif sys.version_info >= (3, 7): # we will also store that at the global level as well. _CACERT_CTX = get_path("certifi", "cacert.pem") _CACERT_PATH = str(_CACERT_CTX.__enter__()) + atexit.register(exit_cacert_ctx) return _CACERT_PATH diff --git a/dist/ba_data/python-site-packages/filelock.py b/dist/ba_data/python-site-packages/filelock.py new file mode 100644 index 0000000..16e2d07 --- /dev/null +++ b/dist/ba_data/python-site-packages/filelock.py @@ -0,0 +1,101 @@ +# Copyright (c) 2009, Evan Fosmark +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +# The views and conclusions contained in the software and documentation are those +# of the authors and should not be interpreted as representing official policies, +# either expressed or implied, of the FreeBSD Project. + +import os +import time +import errno + + +class FileLockException(Exception): + pass + + +class FileLock(object): + """A file locking mechanism that has context-manager support so + you can use it in a with statement. This should be relatively cross + compatible as it doesn't rely on msvcrt or fcntl for the locking. + """ + + def __init__(self, file_name, timeout=10, delay=0.05): + """Prepare the file locker. Specify the file to lock and optionally + the maximum timeout and the delay between each attempt to lock. + """ + self.is_locked = False + self.lockfile = os.path.join(os.getcwd(), "%s.lock" % file_name) + self.file_name = file_name + self.timeout = timeout + self.delay = delay + + def acquire(self): + """Acquire the lock, if possible. If the lock is in use, it check again + every `wait` seconds. It does this until it either gets the lock or + exceeds `timeout` number of seconds, in which case it throws + an exception. + """ + start_time = time.time() + while True: + try: + self.fd = os.open(self.lockfile, os.O_CREAT | os.O_EXCL | os.O_RDWR) + break + except OSError as e: + if e.errno != errno.EEXIST: + raise + if (time.time() - start_time) >= self.timeout: + raise FileLockException("Timeout occured.") + time.sleep(self.delay) + self.is_locked = True + + def release(self): + """Get rid of the lock by deleting the lockfile. + When working in a `with` statement, this gets automatically + called at the end. + """ + if self.is_locked: + os.close(self.fd) + os.unlink(self.lockfile) + self.is_locked = False + + def __enter__(self): + """Activated when used in the with statement. + Should automatically acquire a lock to be used in the with block. + """ + if not self.is_locked: + self.acquire() + return self + + def __exit__(self, type, value, traceback): + """Activated at the end of the with statement. + It automatically releases the lock if it isn't locked. + """ + if self.is_locked: + self.release() + + def __del__(self): + """Make sure that the FileLock instance doesn't leave a lockfile + lying around. + """ + self.release() diff --git a/dist/ba_data/python-site-packages/typing_extensions.py b/dist/ba_data/python-site-packages/typing_extensions.py index 1666e96..9ccd519 100644 --- a/dist/ba_data/python-site-packages/typing_extensions.py +++ b/dist/ba_data/python-site-packages/typing_extensions.py @@ -83,6 +83,7 @@ __all__ = [ 'TypeAlias', 'TypeAliasType', 'TypeGuard', + 'TypeIs', 'TYPE_CHECKING', 'Never', 'NoReturn', @@ -146,27 +147,6 @@ class _Sentinel: _marker = _Sentinel() -def _check_generic(cls, parameters, elen=_marker): - """Check correct count for parameters of a generic cls (internal helper). - This gives a nice error message in case of count mismatch. - """ - if not elen: - raise TypeError(f"{cls} is not a generic class") - if elen is _marker: - if not hasattr(cls, "__parameters__") or not cls.__parameters__: - raise TypeError(f"{cls} is not a generic class") - elen = len(cls.__parameters__) - alen = len(parameters) - if alen != elen: - if hasattr(cls, "__parameters__"): - parameters = [p for p in cls.__parameters__ if not _is_unpack(p)] - num_tv_tuples = sum(isinstance(p, TypeVarTuple) for p in parameters) - if (num_tv_tuples > 0) and (alen >= elen - num_tv_tuples): - return - raise TypeError(f"Too {'many' if alen > elen else 'few'} parameters for {cls};" - f" actual {alen}, expected {elen}") - - if sys.version_info >= (3, 10): def _should_collect_from_parameters(t): return isinstance( @@ -180,27 +160,6 @@ else: return isinstance(t, typing._GenericAlias) and not t._special -def _collect_type_vars(types, typevar_types=None): - """Collect all type variable contained in types in order of - first appearance (lexicographic order). For example:: - - _collect_type_vars((T, List[S, T])) == (T, S) - """ - if typevar_types is None: - typevar_types = typing.TypeVar - tvars = [] - for t in types: - if ( - isinstance(t, typevar_types) and - t not in tvars and - not _is_unpack(t) - ): - tvars.append(t) - if _should_collect_from_parameters(t): - tvars.extend([t for t in t.__parameters__ if t not in tvars]) - return tuple(tvars) - - NoReturn = typing.NoReturn # Some unconstrained type variables. These are used by the container types. @@ -473,7 +432,7 @@ _EXCLUDED_ATTRS = { "_is_runtime_protocol", "__dict__", "__slots__", "__parameters__", "__orig_bases__", "__module__", "_MutableMapping__marker", "__doc__", "__subclasshook__", "__orig_class__", "__init__", "__new__", - "__protocol_attrs__", "__callable_proto_members_only__", + "__protocol_attrs__", "__non_callable_proto_members__", "__match_args__", } @@ -521,6 +480,22 @@ else: if type(self)._is_protocol: raise TypeError('Protocols cannot be instantiated') + def _type_check_issubclass_arg_1(arg): + """Raise TypeError if `arg` is not an instance of `type` + in `issubclass(arg, )`. + + In most cases, this is verified by type.__subclasscheck__. + Checking it again unnecessarily would slow down issubclass() checks, + so, we don't perform this check unless we absolutely have to. + + For various error paths, however, + we want to ensure that *this* error message is shown to the user + where relevant, rather than a typing.py-specific error message. + """ + if not isinstance(arg, type): + # Same error message as for issubclass(1, int). + raise TypeError('issubclass() arg 1 must be a class') + # Inheriting from typing._ProtocolMeta isn't actually desirable, # but is necessary to allow typing.Protocol and typing_extensions.Protocol # to mix without getting TypeErrors about "metaclass conflict" @@ -551,11 +526,6 @@ else: abc.ABCMeta.__init__(cls, *args, **kwargs) if getattr(cls, "_is_protocol", False): cls.__protocol_attrs__ = _get_protocol_attrs(cls) - # PEP 544 prohibits using issubclass() - # with protocols that have non-method members. - cls.__callable_proto_members_only__ = all( - callable(getattr(cls, attr, None)) for attr in cls.__protocol_attrs__ - ) def __subclasscheck__(cls, other): if cls is Protocol: @@ -564,26 +534,23 @@ else: getattr(cls, '_is_protocol', False) and not _allow_reckless_class_checks() ): - if not isinstance(other, type): - # Same error message as for issubclass(1, int). - raise TypeError('issubclass() arg 1 must be a class') - if ( - not cls.__callable_proto_members_only__ - and cls.__dict__.get("__subclasshook__") is _proto_hook - ): - non_method_attrs = sorted( - attr for attr in cls.__protocol_attrs__ - if not callable(getattr(cls, attr, None)) - ) - raise TypeError( - "Protocols with non-method members don't support issubclass()." - f" Non-method members: {str(non_method_attrs)[1:-1]}." - ) if not getattr(cls, '_is_runtime_protocol', False): + _type_check_issubclass_arg_1(other) raise TypeError( "Instance and class checks can only be used with " "@runtime_checkable protocols" ) + if ( + # this attribute is set by @runtime_checkable: + cls.__non_callable_proto_members__ + and cls.__dict__.get("__subclasshook__") is _proto_hook + ): + _type_check_issubclass_arg_1(other) + non_method_attrs = sorted(cls.__non_callable_proto_members__) + raise TypeError( + "Protocols with non-method members don't support issubclass()." + f" Non-method members: {str(non_method_attrs)[1:-1]}." + ) return abc.ABCMeta.__subclasscheck__(cls, other) def __instancecheck__(cls, instance): @@ -610,7 +577,8 @@ else: val = inspect.getattr_static(instance, attr) except AttributeError: break - if val is None and callable(getattr(cls, attr, None)): + # this attribute is set by @runtime_checkable: + if val is None and attr not in cls.__non_callable_proto_members__: break else: return True @@ -678,8 +646,58 @@ else: cls.__init__ = _no_init +if sys.version_info >= (3, 13): + runtime_checkable = typing.runtime_checkable +else: + def runtime_checkable(cls): + """Mark a protocol class as a runtime protocol. + + Such protocol can be used with isinstance() and issubclass(). + Raise TypeError if applied to a non-protocol class. + This allows a simple-minded structural check very similar to + one trick ponies in collections.abc such as Iterable. + + For example:: + + @runtime_checkable + class Closable(Protocol): + def close(self): ... + + assert isinstance(open('/some/file'), Closable) + + Warning: this will check only the presence of the required methods, + not their type signatures! + """ + if not issubclass(cls, typing.Generic) or not getattr(cls, '_is_protocol', False): + raise TypeError('@runtime_checkable can be only applied to protocol classes,' + ' got %r' % cls) + cls._is_runtime_protocol = True + + # Only execute the following block if it's a typing_extensions.Protocol class. + # typing.Protocol classes don't need it. + if isinstance(cls, _ProtocolMeta): + # PEP 544 prohibits using issubclass() + # with protocols that have non-method members. + # See gh-113320 for why we compute this attribute here, + # rather than in `_ProtocolMeta.__init__` + cls.__non_callable_proto_members__ = set() + for attr in cls.__protocol_attrs__: + try: + is_callable = callable(getattr(cls, attr, None)) + except Exception as e: + raise TypeError( + f"Failed to determine whether protocol member {attr!r} " + "is a method member" + ) from e + else: + if not is_callable: + cls.__non_callable_proto_members__.add(attr) + + return cls + + # The "runtime" alias exists for backwards compatibility. -runtime = runtime_checkable = typing.runtime_checkable +runtime = runtime_checkable # Our version of runtime-checkable protocols is faster on Python 3.8-3.11 @@ -774,7 +792,11 @@ def _ensure_subclassable(mro_entries): return inner -if hasattr(typing, "ReadOnly"): +# Update this to something like >=3.13.0b1 if and when +# PEP 728 is implemented in CPython +_PEP_728_IMPLEMENTED = False + +if _PEP_728_IMPLEMENTED: # The standard library TypedDict in Python 3.8 does not store runtime information # about which (if any) keys are optional. See https://bugs.python.org/issue38834 # The standard library TypedDict in Python 3.9.0/1 does not honour the "total" @@ -785,7 +807,8 @@ if hasattr(typing, "ReadOnly"): # Aaaand on 3.12 we add __orig_bases__ to TypedDict # to enable better runtime introspection. # On 3.13 we deprecate some odd ways of creating TypedDicts. - # PEP 705 proposes adding the ReadOnly[] qualifier. + # Also on 3.13, PEP 705 adds the ReadOnly[] qualifier. + # PEP 728 (still pending) makes more changes. TypedDict = typing.TypedDict _TypedDictMeta = typing._TypedDictMeta is_typeddict = typing.is_typeddict @@ -815,7 +838,7 @@ else: break class _TypedDictMeta(type): - def __new__(cls, name, bases, ns, *, total=True): + def __new__(cls, name, bases, ns, *, total=True, closed=False): """Create new typed dict class object. This method is called when TypedDict is subclassed, @@ -860,6 +883,7 @@ else: optional_keys = set() readonly_keys = set() mutable_keys = set() + extra_items_type = None for base in bases: base_dict = base.__dict__ @@ -869,6 +893,26 @@ else: optional_keys.update(base_dict.get('__optional_keys__', ())) readonly_keys.update(base_dict.get('__readonly_keys__', ())) mutable_keys.update(base_dict.get('__mutable_keys__', ())) + base_extra_items_type = base_dict.get('__extra_items__', None) + if base_extra_items_type is not None: + extra_items_type = base_extra_items_type + + if closed and extra_items_type is None: + extra_items_type = Never + if closed and "__extra_items__" in own_annotations: + annotation_type = own_annotations.pop("__extra_items__") + qualifiers = set(_get_typeddict_qualifiers(annotation_type)) + if Required in qualifiers: + raise TypeError( + "Special key __extra_items__ does not support " + "Required" + ) + if NotRequired in qualifiers: + raise TypeError( + "Special key __extra_items__ does not support " + "NotRequired" + ) + extra_items_type = annotation_type annotations.update(own_annotations) for annotation_key, annotation_type in own_annotations.items(): @@ -883,11 +927,7 @@ else: else: optional_keys.add(annotation_key) if ReadOnly in qualifiers: - if annotation_key in mutable_keys: - raise TypeError( - f"Cannot override mutable key {annotation_key!r}" - " with read-only key" - ) + mutable_keys.discard(annotation_key) readonly_keys.add(annotation_key) else: mutable_keys.add(annotation_key) @@ -900,6 +940,8 @@ else: tp_dict.__mutable_keys__ = frozenset(mutable_keys) if not hasattr(tp_dict, '__total__'): tp_dict.__total__ = total + tp_dict.__closed__ = closed + tp_dict.__extra_items__ = extra_items_type return tp_dict __call__ = dict # static method @@ -913,7 +955,7 @@ else: _TypedDict = type.__new__(_TypedDictMeta, 'TypedDict', (), {}) @_ensure_subclassable(lambda bases: (_TypedDict,)) - def TypedDict(typename, fields=_marker, /, *, total=True, **kwargs): + def TypedDict(typename, fields=_marker, /, *, total=True, closed=False, **kwargs): """A simple typed namespace. At runtime it is equivalent to a plain dict. TypedDict creates a dictionary type such that a type checker will expect all @@ -973,6 +1015,9 @@ else: "using the functional syntax, pass an empty dictionary, e.g. " ) + example + "." warnings.warn(deprecation_msg, DeprecationWarning, stacklevel=2) + if closed is not False and closed is not True: + kwargs["closed"] = closed + closed = False fields = kwargs elif kwargs: raise TypeError("TypedDict takes either a dict or keyword arguments," @@ -994,7 +1039,7 @@ else: # Setting correct module is necessary to make typed dict classes pickleable. ns['__module__'] = module - td = _TypedDictMeta(typename, (), ns, total=total) + td = _TypedDictMeta(typename, (), ns, total=total, closed=closed) td.__orig_bases__ = (TypedDict,) return td @@ -1040,15 +1085,15 @@ else: return val -if hasattr(typing, "Required"): # 3.11+ +if hasattr(typing, "ReadOnly"): # 3.13+ get_type_hints = typing.get_type_hints -else: # <=3.10 +else: # <=3.13 # replaces _strip_annotations() def _strip_extras(t): """Strips Annotated, Required and NotRequired from a given type.""" if isinstance(t, _AnnotatedAlias): return _strip_extras(t.__origin__) - if hasattr(t, "__origin__") and t.__origin__ in (Required, NotRequired): + if hasattr(t, "__origin__") and t.__origin__ in (Required, NotRequired, ReadOnly): return _strip_extras(t.__args__[0]) if isinstance(t, typing._GenericAlias): stripped_args = tuple(_strip_extras(a) for a in t.__args__) @@ -1768,6 +1813,98 @@ else: PEP 647 (User-Defined Type Guards). """) +# 3.13+ +if hasattr(typing, 'TypeIs'): + TypeIs = typing.TypeIs +# 3.9 +elif sys.version_info[:2] >= (3, 9): + @_ExtensionsSpecialForm + def TypeIs(self, parameters): + """Special typing form used to annotate the return type of a user-defined + type narrower function. ``TypeIs`` only accepts a single type argument. + At runtime, functions marked this way should return a boolean. + + ``TypeIs`` aims to benefit *type narrowing* -- a technique used by static + type checkers to determine a more precise type of an expression within a + program's code flow. Usually type narrowing is done by analyzing + conditional code flow and applying the narrowing to a block of code. The + conditional expression here is sometimes referred to as a "type guard". + + Sometimes it would be convenient to use a user-defined boolean function + as a type guard. Such a function should use ``TypeIs[...]`` as its + return type to alert static type checkers to this intention. + + Using ``-> TypeIs`` tells the static type checker that for a given + function: + + 1. The return value is a boolean. + 2. If the return value is ``True``, the type of its argument + is the intersection of the type inside ``TypeGuard`` and the argument's + previously known type. + + For example:: + + def is_awaitable(val: object) -> TypeIs[Awaitable[Any]]: + return hasattr(val, '__await__') + + def f(val: Union[int, Awaitable[int]]) -> int: + if is_awaitable(val): + assert_type(val, Awaitable[int]) + else: + assert_type(val, int) + + ``TypeIs`` also works with type variables. For more information, see + PEP 742 (Narrowing types with TypeIs). + """ + item = typing._type_check(parameters, f'{self} accepts only a single type.') + return typing._GenericAlias(self, (item,)) +# 3.8 +else: + class _TypeIsForm(_ExtensionsSpecialForm, _root=True): + def __getitem__(self, parameters): + item = typing._type_check(parameters, + f'{self._name} accepts only a single type') + return typing._GenericAlias(self, (item,)) + + TypeIs = _TypeIsForm( + 'TypeIs', + doc="""Special typing form used to annotate the return type of a user-defined + type narrower function. ``TypeIs`` only accepts a single type argument. + At runtime, functions marked this way should return a boolean. + + ``TypeIs`` aims to benefit *type narrowing* -- a technique used by static + type checkers to determine a more precise type of an expression within a + program's code flow. Usually type narrowing is done by analyzing + conditional code flow and applying the narrowing to a block of code. The + conditional expression here is sometimes referred to as a "type guard". + + Sometimes it would be convenient to use a user-defined boolean function + as a type guard. Such a function should use ``TypeIs[...]`` as its + return type to alert static type checkers to this intention. + + Using ``-> TypeIs`` tells the static type checker that for a given + function: + + 1. The return value is a boolean. + 2. If the return value is ``True``, the type of its argument + is the intersection of the type inside ``TypeGuard`` and the argument's + previously known type. + + For example:: + + def is_awaitable(val: object) -> TypeIs[Awaitable[Any]]: + return hasattr(val, '__await__') + + def f(val: Union[int, Awaitable[int]]) -> int: + if is_awaitable(val): + assert_type(val, Awaitable[int]) + else: + assert_type(val, int) + + ``TypeIs`` also works with type variables. For more information, see + PEP 742 (Narrowing types with TypeIs). + """) + # Vendored from cpython typing._SpecialFrom class _SpecialForm(typing._Final, _root=True): @@ -2515,9 +2652,151 @@ else: # counting generic parameters, so that when we subscript a generic, # the runtime doesn't try to substitute the Unpack with the subscripted type. if not hasattr(typing, "TypeVarTuple"): - typing._collect_type_vars = _collect_type_vars - typing._check_generic = _check_generic + def _check_generic(cls, parameters, elen=_marker): + """Check correct count for parameters of a generic cls (internal helper). + This gives a nice error message in case of count mismatch. + """ + if not elen: + raise TypeError(f"{cls} is not a generic class") + if elen is _marker: + if not hasattr(cls, "__parameters__") or not cls.__parameters__: + raise TypeError(f"{cls} is not a generic class") + elen = len(cls.__parameters__) + alen = len(parameters) + if alen != elen: + expect_val = elen + if hasattr(cls, "__parameters__"): + parameters = [p for p in cls.__parameters__ if not _is_unpack(p)] + num_tv_tuples = sum(isinstance(p, TypeVarTuple) for p in parameters) + if (num_tv_tuples > 0) and (alen >= elen - num_tv_tuples): + return + + # deal with TypeVarLike defaults + # required TypeVarLikes cannot appear after a defaulted one. + if alen < elen: + # since we validate TypeVarLike default in _collect_type_vars + # or _collect_parameters we can safely check parameters[alen] + if getattr(parameters[alen], '__default__', None) is not None: + return + + num_default_tv = sum(getattr(p, '__default__', None) + is not None for p in parameters) + + elen -= num_default_tv + + expect_val = f"at least {elen}" + + things = "arguments" if sys.version_info >= (3, 10) else "parameters" + raise TypeError(f"Too {'many' if alen > elen else 'few'} {things}" + f" for {cls}; actual {alen}, expected {expect_val}") +else: + # Python 3.11+ + + def _check_generic(cls, parameters, elen): + """Check correct count for parameters of a generic cls (internal helper). + + This gives a nice error message in case of count mismatch. + """ + if not elen: + raise TypeError(f"{cls} is not a generic class") + alen = len(parameters) + if alen != elen: + expect_val = elen + if hasattr(cls, "__parameters__"): + parameters = [p for p in cls.__parameters__ if not _is_unpack(p)] + + # deal with TypeVarLike defaults + # required TypeVarLikes cannot appear after a defaulted one. + if alen < elen: + # since we validate TypeVarLike default in _collect_type_vars + # or _collect_parameters we can safely check parameters[alen] + if getattr(parameters[alen], '__default__', None) is not None: + return + + num_default_tv = sum(getattr(p, '__default__', None) + is not None for p in parameters) + + elen -= num_default_tv + + expect_val = f"at least {elen}" + + raise TypeError(f"Too {'many' if alen > elen else 'few'} arguments" + f" for {cls}; actual {alen}, expected {expect_val}") + +typing._check_generic = _check_generic + +# Python 3.11+ _collect_type_vars was renamed to _collect_parameters +if hasattr(typing, '_collect_type_vars'): + def _collect_type_vars(types, typevar_types=None): + """Collect all type variable contained in types in order of + first appearance (lexicographic order). For example:: + + _collect_type_vars((T, List[S, T])) == (T, S) + """ + if typevar_types is None: + typevar_types = typing.TypeVar + tvars = [] + # required TypeVarLike cannot appear after TypeVarLike with default + default_encountered = False + for t in types: + if ( + isinstance(t, typevar_types) and + t not in tvars and + not _is_unpack(t) + ): + if getattr(t, '__default__', None) is not None: + default_encountered = True + elif default_encountered: + raise TypeError(f'Type parameter {t!r} without a default' + ' follows type parameter with a default') + + tvars.append(t) + if _should_collect_from_parameters(t): + tvars.extend([t for t in t.__parameters__ if t not in tvars]) + return tuple(tvars) + + typing._collect_type_vars = _collect_type_vars +else: + def _collect_parameters(args): + """Collect all type variables and parameter specifications in args + in order of first appearance (lexicographic order). + + For example:: + + assert _collect_parameters((T, Callable[P, T])) == (T, P) + """ + parameters = [] + # required TypeVarLike cannot appear after TypeVarLike with default + default_encountered = False + for t in args: + if isinstance(t, type): + # We don't want __parameters__ descriptor of a bare Python class. + pass + elif isinstance(t, tuple): + # `t` might be a tuple, when `ParamSpec` is substituted with + # `[T, int]`, or `[int, *Ts]`, etc. + for x in t: + for collected in _collect_parameters([x]): + if collected not in parameters: + parameters.append(collected) + elif hasattr(t, '__typing_subst__'): + if t not in parameters: + if getattr(t, '__default__', None) is not None: + default_encountered = True + elif default_encountered: + raise TypeError(f'Type parameter {t!r} without a default' + ' follows type parameter with a default') + + parameters.append(t) + else: + for x in getattr(t, '__parameters__', ()): + if x not in parameters: + parameters.append(x) + + return tuple(parameters) + + typing._collect_parameters = _collect_parameters # Backport typing.NamedTuple as it exists in Python 3.13. # In 3.11, the ability to define generic `NamedTuple`s was supported. diff --git a/dist/ba_data/python/babase/__init__.py b/dist/ba_data/python/babase/__init__.py index e3f115a..8b4cce3 100644 --- a/dist/ba_data/python/babase/__init__.py +++ b/dist/ba_data/python/babase/__init__.py @@ -79,6 +79,11 @@ from _babase import ( native_review_request_supported, native_stack_trace, open_file_externally, + open_url, + overlay_web_browser_close, + overlay_web_browser_is_open, + overlay_web_browser_is_supported, + overlay_web_browser_open_url, print_load_info, pushcall, quit, @@ -285,6 +290,11 @@ __all__ = [ 'normalized_color', 'NotFoundError', 'open_file_externally', + 'open_url', + 'overlay_web_browser_close', + 'overlay_web_browser_is_open', + 'overlay_web_browser_is_supported', + 'overlay_web_browser_open_url', 'Permission', 'PlayerNotFoundError', 'Plugin', diff --git a/dist/ba_data/python/babase/_app.py b/dist/ba_data/python/babase/_app.py index f7fb907..bc2e5de 100644 --- a/dist/ba_data/python/babase/_app.py +++ b/dist/ba_data/python/babase/_app.py @@ -7,11 +7,11 @@ from __future__ import annotations import os import logging from enum import Enum -from typing import TYPE_CHECKING, TypeVar +from typing import TYPE_CHECKING, TypeVar, override from concurrent.futures import ThreadPoolExecutor -from functools import cached_property +from threading import RLock + -from typing_extensions import override from efro.call import tpartial import _babase @@ -220,6 +220,13 @@ class App: ] self._pool_thread_count = 0 + # We hold a lock while lazy-loading our subsystem properties so + # we don't spin up any subsystem more than once, but the lock is + # recursive so that the subsystems can instantiate other + # subsystems. + self._subsystem_property_lock = RLock() + self._subsystem_property_data: dict[str, AppSubsystem | bool] = {} + def postinit(self) -> None: """Called after we've been inited and assigned to babase.app. @@ -227,8 +234,9 @@ class App: must go here instead of __init__. """ - # Hack for docs-generation: we can be imported with dummy modules - # instead of our actual binary ones, but we don't function. + # Hack for docs-generation: We can be imported with dummy + # modules instead of our actual binary ones, but we don't + # function. if os.environ.get('BA_RUNNING_WITH_DUMMY_MODULES') == '1': return @@ -272,10 +280,7 @@ class App: return self._asyncio_loop def create_async_task( - self, - coro: Generator[Any, Any, T] | Coroutine[Any, Any, T], - *, - name: str | None = None, + self, coro: Coroutine[Any, Any, T], *, name: str | None = None ) -> None: """Create a fully managed async task. @@ -285,6 +290,7 @@ class App: App.asyncio_loop. """ assert _babase.in_logic_thread() + # Hold a strong reference to the task until it is done. # Otherwise it is possible for it to be garbage collected and # disappear midway if the caller does not hold on to the @@ -293,7 +299,6 @@ class App: task = self.asyncio_loop.create_task(coro, name=name) self._asyncio_tasks.add(task) task.add_done_callback(self._on_task_done) - # return task def _on_task_done(self, task: asyncio.Task) -> None: # Report any errors that occurred. @@ -333,14 +338,66 @@ class App: def mode_selector(self, selector: babase.AppModeSelector) -> None: self._mode_selector = selector + def _get_subsystem_property( + self, ssname: str, create_call: Callable[[], AppSubsystem | None] + ) -> AppSubsystem | None: + + # Quick-out: if a subsystem is present, just return it; no + # locking necessary. + val = self._subsystem_property_data.get(ssname) + if val is not None: + if val is False: + # False means subsystem is confirmed as unavailable. + return None + if val is not True: + # A subsystem has been set. Return it. + return val + + # Anything else (no val present or val True) requires locking. + with self._subsystem_property_lock: + val = self._subsystem_property_data.get(ssname) + if val is not None: + if val is False: + # False means confirmed as not present. + return None + if val is True: + # True means this property is already being loaded, + # and the fact that we're holding the lock means + # we're doing the loading, so this is a dependency + # loop. Not good. + raise RuntimeError( + f'Subsystem dependency loop detected for {ssname}' + ) + # Must be an instantiated subsystem. Noice. + return val + + # Ok, there's nothing here for it. Instantiate and set it + # while we hold the lock. Set a placeholder value of True + # while we load so we can error if something we're loading + # tries to recursively load us. + self._subsystem_property_data[ssname] = True + + # Do our one attempt to create the singleton. + val = create_call() + self._subsystem_property_data[ssname] = ( + False if val is None else val + ) + + return val + # __FEATURESET_APP_SUBSYSTEM_PROPERTIES_BEGIN__ # This section generated by batools.appmodule; do not edit. - @cached_property + @property def classic(self) -> ClassicSubsystem | None: """Our classic subsystem (if available).""" - # pylint: disable=cyclic-import + return self._get_subsystem_property( + 'classic', self._create_classic_subsystem + ) # type: ignore + @staticmethod + def _create_classic_subsystem() -> ClassicSubsystem | None: + # pylint: disable=cyclic-import try: from baclassic import ClassicSubsystem @@ -351,11 +408,16 @@ class App: logging.exception('Error importing baclassic.') return None - @cached_property + @property def plus(self) -> PlusSubsystem | None: """Our plus subsystem (if available).""" - # pylint: disable=cyclic-import + return self._get_subsystem_property( + 'plus', self._create_plus_subsystem + ) # type: ignore + @staticmethod + def _create_plus_subsystem() -> PlusSubsystem | None: + # pylint: disable=cyclic-import try: from baplus import PlusSubsystem @@ -366,9 +428,15 @@ class App: logging.exception('Error importing baplus.') return None - @cached_property + @property def ui_v1(self) -> UIV1Subsystem: """Our ui_v1 subsystem (always available).""" + return self._get_subsystem_property( + 'ui_v1', self._create_ui_v1_subsystem + ) # type: ignore + + @staticmethod + def _create_ui_v1_subsystem() -> UIV1Subsystem: # pylint: disable=cyclic-import from bauiv1 import UIV1Subsystem @@ -384,6 +452,7 @@ class App: # reached the 'running' state. This ensures that all subsystems # receive a consistent set of callbacks starting with # on_app_running(). + if self._subsystem_registration_ended: raise RuntimeError( 'Subsystems can no longer be registered at this point.' diff --git a/dist/ba_data/python/babase/_apputils.py b/dist/ba_data/python/babase/_apputils.py index ce911c9..5550755 100644 --- a/dist/ba_data/python/babase/_apputils.py +++ b/dist/ba_data/python/babase/_apputils.py @@ -8,9 +8,8 @@ import os import logging from threading import Thread from dataclasses import dataclass -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override -from typing_extensions import override from efro.call import tpartial from efro.log import LogLevel from efro.dataclassio import ioprepped, dataclass_to_json, dataclass_from_json @@ -107,8 +106,8 @@ def handle_v1_cloud_log() -> None: info = { 'log': _babase.get_v1_cloud_log(), - 'version': app.env.version, - 'build': app.env.build_number, + 'version': app.env.engine_version, + 'build': app.env.engine_build_number, 'userAgentString': classic.legacy_user_agent_string, 'session': sessionname, 'activity': activityname, diff --git a/dist/ba_data/python/babase/_devconsole.py b/dist/ba_data/python/babase/_devconsole.py index 47580b8..d4e7099 100644 --- a/dist/ba_data/python/babase/_devconsole.py +++ b/dist/ba_data/python/babase/_devconsole.py @@ -4,12 +4,10 @@ from __future__ import annotations import os -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override from dataclasses import dataclass import logging -from typing_extensions import override - import _babase if TYPE_CHECKING: diff --git a/dist/ba_data/python/babase/_emptyappmode.py b/dist/ba_data/python/babase/_emptyappmode.py index 3573769..6745d21 100644 --- a/dist/ba_data/python/babase/_emptyappmode.py +++ b/dist/ba_data/python/babase/_emptyappmode.py @@ -3,9 +3,8 @@ """Provides AppMode functionality.""" from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override -from typing_extensions import override from bacommon.app import AppExperience import _babase diff --git a/dist/ba_data/python/babase/_env.py b/dist/ba_data/python/babase/_env.py index 124caa2..5efc253 100644 --- a/dist/ba_data/python/babase/_env.py +++ b/dist/ba_data/python/babase/_env.py @@ -7,9 +7,8 @@ import sys import signal import logging import warnings -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override -from typing_extensions import override from efro.log import LogLevel if TYPE_CHECKING: diff --git a/dist/ba_data/python/babase/_general.py b/dist/ba_data/python/babase/_general.py index 2d2738d..d1914d5 100644 --- a/dist/ba_data/python/babase/_general.py +++ b/dist/ba_data/python/babase/_general.py @@ -8,9 +8,8 @@ import weakref import random import logging import inspect -from typing import TYPE_CHECKING, TypeVar, Protocol, NewType +from typing import TYPE_CHECKING, TypeVar, Protocol, NewType, override -from typing_extensions import override from efro.terminal import Clr import _babase diff --git a/dist/ba_data/python/babase/_hooks.py b/dist/ba_data/python/babase/_hooks.py index 535d308..c344d26 100644 --- a/dist/ba_data/python/babase/_hooks.py +++ b/dist/ba_data/python/babase/_hooks.py @@ -85,6 +85,7 @@ def open_url_with_webbrowser_module(url: str) -> None: import webbrowser from babase._language import Lstr + assert _babase.in_logic_thread() try: webbrowser.open(url) except Exception: @@ -384,7 +385,7 @@ def show_client_too_old_error() -> None: # a newer build. if ( _babase.app.config.get('SuppressClientTooOldErrorForBuild') - == _babase.app.env.build_number + == _babase.app.env.engine_build_number ): return diff --git a/dist/ba_data/python/babase/_language.py b/dist/ba_data/python/babase/_language.py index 3983a7a..f36e9ac 100644 --- a/dist/ba_data/python/babase/_language.py +++ b/dist/ba_data/python/babase/_language.py @@ -6,9 +6,7 @@ from __future__ import annotations import os import json import logging -from typing import TYPE_CHECKING, overload - -from typing_extensions import override +from typing import TYPE_CHECKING, overload, override import _babase from babase._appsubsystem import AppSubsystem diff --git a/dist/ba_data/python/babase/_login.py b/dist/ba_data/python/babase/_login.py index 6796844..81c0791 100644 --- a/dist/ba_data/python/babase/_login.py +++ b/dist/ba_data/python/babase/_login.py @@ -7,9 +7,8 @@ from __future__ import annotations import time import logging from dataclasses import dataclass -from typing import TYPE_CHECKING, final +from typing import TYPE_CHECKING, final, override -from typing_extensions import override from bacommon.login import LoginType import _babase diff --git a/dist/ba_data/python/babase/_mgen/enums.py b/dist/ba_data/python/babase/_mgen/enums.py index 9766e64..25a67be 100644 --- a/dist/ba_data/python/babase/_mgen/enums.py +++ b/dist/ba_data/python/babase/_mgen/enums.py @@ -1,5 +1,5 @@ # Released under the MIT License. See LICENSE for details. -"""Enum vals generated by batools.pythonenumsmodule; do not edit by hand.""" +"""Enum vals generated by batools.enumspython; do not edit by hand.""" from enum import Enum @@ -85,39 +85,6 @@ class UIScale(Enum): SMALL = 2 -class TimeType(Enum): - """Specifies the type of time for various operations to target/use. - - Category: Enums - - 'sim' time is the local simulation time for an activity or session. - It can proceed at different rates depending on game speed, stops - for pauses, etc. - - 'base' is the baseline time for an activity or session. It proceeds - consistently regardless of game speed or pausing, but may stop during - occurrences such as network outages. - - 'real' time is mostly based on clock time, with a few exceptions. It may - not advance while the app is backgrounded for instance. (the engine - attempts to prevent single large time jumps from occurring) - """ - - SIM = 0 - BASE = 1 - REAL = 2 - - -class TimeFormat(Enum): - """Specifies the format time values are provided in. - - Category: Enums - """ - - SECONDS = 0 - MILLISECONDS = 1 - - class Permission(Enum): """Permissions that can be requested from the OS. diff --git a/dist/ba_data/python/babase/_net.py b/dist/ba_data/python/babase/_net.py index a02e3c3..226fcb9 100644 --- a/dist/ba_data/python/babase/_net.py +++ b/dist/ba_data/python/babase/_net.py @@ -32,6 +32,8 @@ class NetworkSubsystem: # For debugging. self.v1_test_log: str = '' self.v1_ctest_results: dict[int, str] = {} + self.connectivity_state = 'uninited' + self.transport_state = 'uninited' self.server_time_offset_hours: float | None = None @property diff --git a/dist/ba_data/python/babase/_plugin.py b/dist/ba_data/python/babase/_plugin.py index d83050b..2ddab07 100644 --- a/dist/ba_data/python/babase/_plugin.py +++ b/dist/ba_data/python/babase/_plugin.py @@ -6,9 +6,7 @@ from __future__ import annotations import logging import importlib.util -from typing import TYPE_CHECKING - -from typing_extensions import override +from typing import TYPE_CHECKING, override import _babase from babase._appsubsystem import AppSubsystem diff --git a/dist/ba_data/python/babase/_ui.py b/dist/ba_data/python/babase/_ui.py index 6457ead..55c895b 100644 --- a/dist/ba_data/python/babase/_ui.py +++ b/dist/ba_data/python/babase/_ui.py @@ -3,9 +3,7 @@ """UI related bits of babase.""" from __future__ import annotations -from typing import TYPE_CHECKING - -from typing_extensions import override +from typing import TYPE_CHECKING, override from babase._stringedit import StringEditAdapter import _babase diff --git a/dist/ba_data/python/babase/modutils.py b/dist/ba_data/python/babase/modutils.py index 7780d1f..716a2d2 100644 --- a/dist/ba_data/python/babase/modutils.py +++ b/dist/ba_data/python/babase/modutils.py @@ -133,10 +133,15 @@ def create_user_system_scripts() -> None: if env.python_directory_app is None: raise RuntimeError('app python dir unset') - path = f'{env.python_directory_user}/sys/{env.version}' + path = f'{env.python_directory_user}/sys/{env.engine_version}' pathtmp = path + '_tmp' if os.path.exists(path): - shutil.rmtree(path) + print('Delete Existing User Scripts first!') + _babase.screenmessage( + 'Delete Existing User Scripts first!', + color=(1, 0, 0), + ) + return if os.path.exists(pathtmp): shutil.rmtree(pathtmp) @@ -159,6 +164,7 @@ def create_user_system_scripts() -> None: f"'\nRestart {_babase.appname()} to use them." f' (use babase.quit() to exit the game)' ) + _babase.screenmessage('Created User System Scripts', color=(0, 1, 0)) if app.classic is not None and app.classic.platform == 'android': print( 'Note: the new files may not be visible via ' @@ -175,16 +181,18 @@ def delete_user_system_scripts() -> None: if env.python_directory_user is None: raise RuntimeError('user python dir unset') - path = f'{env.python_directory_user}/sys/{env.version}' + path = f'{env.python_directory_user}/sys/{env.engine_version}' if os.path.exists(path): shutil.rmtree(path) - print( - f'User system scripts deleted.\n' - f'Restart {_babase.appname()} to use internal' - f' scripts. (use babase.quit() to exit the game)' + print('User system scripts deleted.') + _babase.screenmessage('Deleted User System Scripts', color=(0, 1, 0)) + _babase.screenmessage( + f'Closing {_babase.appname()} to make changes.', color=(0, 1, 0) ) + _babase.apptimer(2.0, _babase.quit) else: print(f"User system scripts not found at '{path}'.") + _babase.screenmessage('User Scripts Not Found', color=(1, 0, 0)) # If the sys path is empty, kill it. dpath = env.python_directory_user + '/sys' diff --git a/dist/ba_data/python/baclassic/_accountv1.py b/dist/ba_data/python/baclassic/_accountv1.py index e732f13..d0df1f6 100644 --- a/dist/ba_data/python/baclassic/_accountv1.py +++ b/dist/ba_data/python/baclassic/_accountv1.py @@ -152,9 +152,9 @@ class AccountV1Subsystem: """(internal)""" for entry in info: - cache_entry = self.tournament_info[ - entry['tournamentID'] - ] = copy.deepcopy(entry) + cache_entry = self.tournament_info[entry['tournamentID']] = ( + copy.deepcopy(entry) + ) # Also store the time we received this, so we can adjust # time-remaining values/etc. diff --git a/dist/ba_data/python/baclassic/_achievement.py b/dist/ba_data/python/baclassic/_achievement.py index 38f6f5b..3ef3bed 100644 --- a/dist/ba_data/python/baclassic/_achievement.py +++ b/dist/ba_data/python/baclassic/_achievement.py @@ -75,9 +75,9 @@ class AchievementSubsystem: def __init__(self) -> None: self.achievements: list[Achievement] = [] - self.achievements_to_display: ( - list[tuple[baclassic.Achievement, bool]] - ) = [] + self.achievements_to_display: list[ + tuple[baclassic.Achievement, bool] + ] = [] self.achievement_display_timer: bascenev1.BaseTimer | None = None self.last_achievement_display_time: float = 0.0 self.achievement_completion_banner_slots: set[int] = set() diff --git a/dist/ba_data/python/baclassic/_benchmark.py b/dist/ba_data/python/baclassic/_benchmark.py index 23033ee..0c25f59 100644 --- a/dist/ba_data/python/baclassic/_benchmark.py +++ b/dist/ba_data/python/baclassic/_benchmark.py @@ -5,9 +5,8 @@ from __future__ import annotations import random from dataclasses import dataclass -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override -from typing_extensions import override import babase import bascenev1 import _baclassic diff --git a/dist/ba_data/python/baclassic/_input.py b/dist/ba_data/python/baclassic/_input.py index 5c934c3..0effcb5 100644 --- a/dist/ba_data/python/baclassic/_input.py +++ b/dist/ba_data/python/baclassic/_input.py @@ -13,7 +13,10 @@ if TYPE_CHECKING: def get_input_device_mapped_value( - devicename: str, unique_id: str, name: str + devicename: str, + unique_id: str, + name: str, + default: bool = False, ) -> Any: """Returns a mapped value for an input device. @@ -30,8 +33,9 @@ def get_input_device_mapped_value( subplatform = app.classic.subplatform appconfig = babase.app.config - # If there's an entry in our config for this controller, use it. - if 'Controllers' in appconfig: + # If there's an entry in our config for this controller and + # we're not looking for our default mappings, use it. + if 'Controllers' in appconfig and not default: ccfgs = appconfig['Controllers'] if devicename in ccfgs: mapping = None diff --git a/dist/ba_data/python/baclassic/_net.py b/dist/ba_data/python/baclassic/_net.py index 04d3fb6..98fbc9a 100644 --- a/dist/ba_data/python/baclassic/_net.py +++ b/dist/ba_data/python/baclassic/_net.py @@ -7,9 +7,8 @@ import copy import weakref import threading from enum import Enum -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override -from typing_extensions import override import babase import bascenev1 diff --git a/dist/ba_data/python/baclassic/_servermode.py b/dist/ba_data/python/baclassic/_servermode.py index f719f5d..a9ce949 100644 --- a/dist/ba_data/python/baclassic/_servermode.py +++ b/dist/ba_data/python/baclassic/_servermode.py @@ -102,8 +102,8 @@ class ServerController: self._shutdown_reason: ShutdownReason | None = None self._executing_shutdown = False - # Make note if they want us to import a playlist; - # we'll need to do that first if so. + # Make note if they want us to import a playlist; we'll need to + # do that first if so. self._playlist_fetch_running = self._config.playlist_code is not None self._playlist_fetch_sent_request = False self._playlist_fetch_got_response = False @@ -216,7 +216,7 @@ class ServerController: 'bsAccessCheck', { 'port': bascenev1.get_game_port(), - 'b': babase.app.env.build_number, + 'b': babase.app.env.engine_build_number, }, callback=self._access_check_response, ) @@ -304,7 +304,7 @@ class ServerController: ) -> None: if result is None: print('Error fetching playlist; aborting.') - print('Falling back to use default playlist.') #BCS + print('Falling back to use default playlist.') self._config.session_type = "teams" self._prep_timer = None babase.pushcall(self._launch_server_session) @@ -314,9 +314,7 @@ class ServerController: typename = ( 'teams' if result['playlistType'] == 'Team Tournament' - else 'ffa' - if result['playlistType'] == 'Free-for-All' - else '??' + else 'ffa' if result['playlistType'] == 'Free-for-All' else '??' ) plistname = result['playlistName'] print(f'{Clr.SBLU}Got playlist: "{plistname}" ({typename}).{Clr.RST}') @@ -372,7 +370,8 @@ class ServerController: raise RuntimeError(f'Unknown session type {sessiontype}') # Need to add this in a transaction instead of just setting - # it directly or it will get overwritten by the master-server. + # it directly or it will get overwritten by the + # master-server. plus.add_v1_account_transaction( { 'type': 'ADD_PLAYLIST', @@ -386,22 +385,23 @@ class ServerController: if self._first_run: curtimestr = time.strftime('%c') startupmsg = ( - f'{Clr.BLD}{Clr.BLU}{babase.appnameupper()} {app.env.version}' - f' ({app.env.build_number})' + f'{Clr.BLD}{Clr.BLU}{babase.appnameupper()}' + f' {app.env.engine_version}' + f' ({app.env.engine_build_number})' f' entering server-mode {curtimestr}{Clr.RST}' ) logging.info(startupmsg) if sessiontype is bascenev1.FreeForAllSession: appcfg['Free-for-All Playlist Selection'] = self._playlist_name - appcfg[ - 'Free-for-All Playlist Randomize' - ] = self._config.playlist_shuffle + appcfg['Free-for-All Playlist Randomize'] = ( + self._config.playlist_shuffle + ) elif sessiontype is bascenev1.DualTeamSession: appcfg['Team Tournament Playlist Selection'] = self._playlist_name - appcfg[ - 'Team Tournament Playlist Randomize' - ] = self._config.playlist_shuffle + appcfg['Team Tournament Playlist Randomize'] = ( + self._config.playlist_shuffle + ) elif sessiontype is bascenev1.CoopSession: classic.coop_session_args = { 'campaign': self._config.coop_campaign, @@ -410,6 +410,10 @@ class ServerController: else: raise RuntimeError(f'Unknown session type {sessiontype}') + appcfg['Teams Series Length'] = self._config.teams_series_length + appcfg['FFA Series Length'] = self._config.ffa_series_length + + # Deprecated; left here in order to not break mods. classic.teams_series_length = self._config.teams_series_length classic.ffa_series_length = self._config.ffa_series_length @@ -425,12 +429,23 @@ class ServerController: bascenev1.set_public_party_queue_enabled(self._config.enable_queue) bascenev1.set_public_party_name(self._config.party_name) bascenev1.set_public_party_stats_url(self._config.stats_url) + bascenev1.set_public_party_public_address_ipv4( + self._config.public_ipv4_address + ) + bascenev1.set_public_party_public_address_ipv6( + self._config.public_ipv6_address + ) + bascenev1.set_public_party_enabled(self._config.party_is_public) bascenev1.set_player_rejoin_cooldown( self._config.player_rejoin_cooldown ) + bascenev1.set_max_players_override( + self._config.session_max_players_override + ) + # And here.. we.. go. if self._config.stress_test_players is not None: # Special case: run a stress test. diff --git a/dist/ba_data/python/baclassic/_store.py b/dist/ba_data/python/baclassic/_store.py index e239adf..f469012 100644 --- a/dist/ba_data/python/baclassic/_store.py +++ b/dist/ba_data/python/baclassic/_store.py @@ -7,6 +7,8 @@ from __future__ import annotations import logging from typing import TYPE_CHECKING +from efro.util import utc_now + import babase import bascenev1 @@ -522,10 +524,10 @@ class StoreSubsystem: if item in sales_raw: if not plus.get_purchased(item): to_end = ( - datetime.datetime.utcfromtimestamp( - sales_raw[item]['e'] + datetime.datetime.fromtimestamp( + sales_raw[item]['e'], datetime.UTC ) - - datetime.datetime.utcnow() + - utc_now() ).total_seconds() if to_end > 0: sale_times.append(int(to_end * 1000)) diff --git a/dist/ba_data/python/baclassic/_subsystem.py b/dist/ba_data/python/baclassic/_subsystem.py index bb9bbad..16611ec 100644 --- a/dist/ba_data/python/baclassic/_subsystem.py +++ b/dist/ba_data/python/baclassic/_subsystem.py @@ -3,12 +3,11 @@ """Provides classic app subsystem.""" from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override import random import logging import weakref -from typing_extensions import override from efro.dataclassio import dataclass_from_dict import babase import bauiv1 @@ -103,8 +102,8 @@ class ClassicSubsystem(babase.AppSubsystem): self.maps: dict[str, type[bascenev1.Map]] = {} # Gameplay. - self.teams_series_length = 7 - self.ffa_series_length = 24 + self.teams_series_length = 7 # deprecated, left for old mods + self.ffa_series_length = 24 # deprecated, left for old mods self.coop_session_args: dict = {} # UI. @@ -575,15 +574,18 @@ class ClassicSubsystem(babase.AppSubsystem): ) def get_input_device_mapped_value( - self, device: bascenev1.InputDevice, name: str + self, + device: bascenev1.InputDevice, + name: str, + default: bool = False, ) -> Any: - """Returns a mapped value for an input device. + """Return a mapped value for an input device. This checks the user config and falls back to default values where available. """ return _input.get_input_device_mapped_value( - device.name, device.unique_identifier, name + device.name, device.unique_identifier, name, default ) def get_input_device_map_hash( diff --git a/dist/ba_data/python/baclassic/_tournament.py b/dist/ba_data/python/baclassic/_tournament.py index 552dca0..242ba52 100644 --- a/dist/ba_data/python/baclassic/_tournament.py +++ b/dist/ba_data/python/baclassic/_tournament.py @@ -35,9 +35,11 @@ def get_tournament_prize_strings(entry: dict[str, Any]) -> list[str]: prval = ( '' if rng is None - else ('#' + str(rng[0])) - if (rng[0] == rng[1]) - else ('#' + str(rng[0]) + '-' + str(rng[1])) + else ( + ('#' + str(rng[0])) + if (rng[0] == rng[1]) + else ('#' + str(rng[0]) + '-' + str(rng[1])) + ) ) pvval = '' if trophy_type is not None: diff --git a/dist/ba_data/python/baclassic/macmusicapp.py b/dist/ba_data/python/baclassic/macmusicapp.py index 12fe684..6b4b9b4 100644 --- a/dist/ba_data/python/baclassic/macmusicapp.py +++ b/dist/ba_data/python/baclassic/macmusicapp.py @@ -6,9 +6,8 @@ from __future__ import annotations import logging import threading from collections import deque -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override -from typing_extensions import override import babase from baclassic._music import MusicPlayer diff --git a/dist/ba_data/python/baclassic/osmusic.py b/dist/ba_data/python/baclassic/osmusic.py index f2198ce..919df49 100644 --- a/dist/ba_data/python/baclassic/osmusic.py +++ b/dist/ba_data/python/baclassic/osmusic.py @@ -7,9 +7,8 @@ import os import random import logging import threading -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override -from typing_extensions import override import babase from baclassic._music import MusicPlayer diff --git a/dist/ba_data/python/bacommon/app.py b/dist/ba_data/python/bacommon/app.py index 2b8abdc..5f42c89 100644 --- a/dist/ba_data/python/bacommon/app.py +++ b/dist/ba_data/python/bacommon/app.py @@ -15,7 +15,7 @@ if TYPE_CHECKING: class AppInterfaceIdiom(Enum): - """A general form-factor or way of experiencing a Ballistica app. + """A general form-factor or method of experiencing a Ballistica app. Note that it is possible for a running app to switch idioms (for instance if a mobile device or computer is connected to a TV). @@ -32,11 +32,11 @@ class AppExperience(Enum): """A particular experience that can be provided by a Ballistica app. This is one metric used to isolate different playerbases from - eachother where there might be no technical barriers doing so. - For example, a casual one-hand-playable phone game and an augmented + each other where there might be no technical barriers doing so. For + example, a casual one-hand-playable phone game and an augmented reality tabletop game may both use the same scene-versions and networking-protocols and whatnot, but it would make no sense to - allow players of one join servers for the other. AppExperience can + allow players of one to join servers of the other. AppExperience can be used to keep these player bases separate. Generally a single Ballistica app targets a single AppExperience. @@ -47,13 +47,14 @@ class AppExperience(Enum): visible to client apps designed for that play style. """ - # An experience that is supported everywhere. Used - # for the default empty AppMode when starting the app, etc. + # An experience that is supported everywhere. Used for the default + # empty AppMode when starting the app, etc. EMPTY = 'empty' # The traditional BombSquad experience: multiple players using - # controllers in a single arena small enough for all action to be - # viewed on a single screen. + # traditional game controllers (or touch screen equivalents) in a + # single arena small enough for all action to be viewed on a single + # screen. MELEE = 'melee' # The traditional BombSquad Remote experience; buttons on a @@ -72,7 +73,7 @@ class AppArchitecture(Enum): class AppPlatform(Enum): - """Overall platform a Ballistica build can be targeting. + """Overall platform a Ballistica build is targeting. Each distinct flavor of an app has a unique combination of AppPlatform and AppVariant. Generally platform describes @@ -124,8 +125,9 @@ class AppInstanceInfo: """General info about an individual running app.""" name = Annotated[str, IOAttrs('n')] - version = Annotated[str, IOAttrs('v')] - build = Annotated[int, IOAttrs('b')] + + engine_version = Annotated[str, IOAttrs('ev')] + engine_build = Annotated[int, IOAttrs('eb')] platform = Annotated[AppPlatform, IOAttrs('p')] variant = Annotated[AppVariant, IOAttrs('va')] diff --git a/dist/ba_data/python/bacommon/cloud.py b/dist/ba_data/python/bacommon/cloud.py index dd14bb5..8376ef8 100644 --- a/dist/ba_data/python/bacommon/cloud.py +++ b/dist/ba_data/python/bacommon/cloud.py @@ -4,10 +4,9 @@ from __future__ import annotations from dataclasses import dataclass, field -from typing import TYPE_CHECKING, Annotated +from typing import TYPE_CHECKING, Annotated, override from enum import Enum -from typing_extensions import override from efro.message import Message, Response from efro.dataclassio import ioprepped, IOAttrs from bacommon.transfer import DirectoryManifest @@ -33,9 +32,12 @@ class LoginProxyRequestMessage(Message): class LoginProxyRequestResponse(Response): """Response to a request for a login proxy.""" - # URL to direct the user to for login. + # URL to direct the user to for sign in. url: Annotated[str, IOAttrs('u')] + # URL to use for overlay-web-browser sign ins. + url_overlay: Annotated[str, IOAttrs('uo')] + # Proxy-Login id for querying results. proxyid: Annotated[str, IOAttrs('p')] @@ -123,24 +125,25 @@ class TestResponse(Response): @ioprepped @dataclass -class PromoCodeMessage(Message): - """User is entering a promo code""" +class SendInfoMessage(Message): + """User is using the send-info function""" - code: Annotated[str, IOAttrs('c')] + description: Annotated[str, IOAttrs('c')] @override @classmethod def get_response_types(cls) -> list[type[Response] | None]: - return [PromoCodeResponse] + return [SendInfoResponse] @ioprepped @dataclass -class PromoCodeResponse(Response): - """Applied that promo code for ya, boss.""" +class SendInfoResponse(Response): + """Response to sending into the server.""" - valid: Annotated[bool, IOAttrs('v')] + handled: Annotated[bool, IOAttrs('v')] message: Annotated[str | None, IOAttrs('m', store_default=False)] = None + legacy_code: Annotated[str | None, IOAttrs('l', store_default=False)] = None @ioprepped diff --git a/dist/ba_data/python/bacommon/net.py b/dist/ba_data/python/bacommon/net.py index d07f774..0f14602 100644 --- a/dist/ba_data/python/bacommon/net.py +++ b/dist/ba_data/python/bacommon/net.py @@ -20,6 +20,11 @@ class ServerNodeEntry: """Information about a specific server.""" zone: Annotated[str, IOAttrs('r')] + + # TODO: Remove soft_default after all master-servers upgraded. + latlong: Annotated[ + tuple[float, float] | None, IOAttrs('ll', soft_default=None) + ] address: Annotated[str, IOAttrs('a')] port: Annotated[int, IOAttrs('p')] @@ -32,6 +37,16 @@ class ServerNodeQueryResponse: # The current utc time on the master server. time: Annotated[datetime.datetime, IOAttrs('t')] + # Where the master server sees the query as coming from. + latlong: Annotated[tuple[float, float] | None, IOAttrs('ll')] + + ping_per_dist: Annotated[float, IOAttrs('ppd')] + max_dist: Annotated[float, IOAttrs('md')] + + debug_log_seconds: Annotated[ + float | None, IOAttrs('d', store_default=False) + ] = None + # If present, something went wrong, and this describes it. error: Annotated[str | None, IOAttrs('e', store_default=False)] = None @@ -78,6 +93,7 @@ class PrivatePartyConnectResult: """Info about a server we get back when connecting.""" error: str | None = None - addr: str | None = None + address4: Annotated[str | None, IOAttrs('addr')] = None + address6: Annotated[str | None, IOAttrs('addr6')] = None port: int | None = None password: str | None = None diff --git a/dist/ba_data/python/bacommon/servermanager.py b/dist/ba_data/python/bacommon/servermanager.py index 0fe8e7d..31f235f 100644 --- a/dist/ba_data/python/bacommon/servermanager.py +++ b/dist/ba_data/python/bacommon/servermanager.py @@ -22,109 +22,138 @@ class ServerConfig: party_name: str = 'FFA' # If True, your party will show up in the global public party list - # Otherwise it will still be joinable via LAN or connecting by IP address. + # Otherwise it will still be joinable via LAN or connecting by IP + # address. party_is_public: bool = True - # If True, all connecting clients will be authenticated through the master - # server to screen for fake account info. Generally this should always - # be enabled unless you are hosting on a LAN with no internet connection. + # If True, all connecting clients will be authenticated through the + # master server to screen for fake account info. Generally this + # should always be enabled unless you are hosting on a LAN with no + # internet connection. authenticate_clients: bool = True - # IDs of server admins. Server admins are not kickable through the default - # kick vote system and they are able to kick players without a vote. To get - # your account id, enter 'getaccountid' in settings->advanced->enter-code. + # IDs of server admins. Server admins are not kickable through the + # default kick vote system and they are able to kick players without + # a vote. To get your account id, enter 'getaccountid' in + # settings->advanced->enter-code. admins: list[str] = field(default_factory=list) # Whether the default kick-voting system is enabled. enable_default_kick_voting: bool = True - # UDP port to host on. Change this to work around firewalls or run multiple - # servers on one machine. - # 43210 is the default and the only port that will show up in the LAN - # browser tab. + # To be included in the public server list, your server MUST be + # accessible via an ipv4 address. By default, the master server will + # try to use the address your server contacts it from, but this may + # be an ipv6 address these days so you may need to provide an ipv4 + # address explicitly. + public_ipv4_address: str | None = None + + # You can optionally provide an ipv6 address for your server for the + # public server list. Unlike ipv4, a server is not required to have + # an ipv6 address to appear in the list, but is still good to + # provide when available since more and more devices are using ipv6 + # these days. Your server's ipv6 address will be autodetected if + # your server uses ipv6 when communicating with the master server. You + # can pass an empty string here to explicitly disable the ipv6 + # address. + public_ipv6_address: str | None = None + + # UDP port to host on. Change this to work around firewalls or run + # multiple servers on one machine. + # + # 43210 is the default and the only port that will show up in the + # LAN browser tab. port: int = 43210 - # Max devices in the party. Note that this does *NOT* mean max players. - # Any device in the party can have more than one player on it if they have - # multiple controllers. Also, this number currently includes the server so - # generally make it 1 bigger than you need. Max-players is not currently - # exposed but I'll try to add that soon. + # Max devices in the party. Note that this does *NOT* mean max + # players. Any device in the party can have more than one player on + # it if they have multiple controllers. Also, this number currently + # includes the server so generally make it 1 bigger than you need. max_party_size: int = 6 - # Options here are 'ffa' (free-for-all), 'teams' and 'coop' (cooperative) - # This value is ignored if you supply a playlist_code (see below). + # Max players that can join a session. If present this will override + # the session's preferred max_players. if a value below 0 is given + # player limit will be removed. + session_max_players_override: int | None = None + + # Options here are 'ffa' (free-for-all), 'teams' and 'coop' + # (cooperative) This value is ignored if you supply a playlist_code + # (see below). session_type: str = 'ffa' - # Playlist-code for teams or free-for-all mode sessions. - # To host your own custom playlists, use the 'share' functionality in the - # playlist editor in the regular version of the game. - # This will give you a numeric code you can enter here to host that - # playlist. + # Playlist-code for teams or free-for-all mode sessions. To host + # your own custom playlists, use the 'share' functionality in the + # playlist editor in the regular version of the game. This will give + # you a numeric code you can enter here to host that playlist. playlist_code: int | None = None - # Alternately, you can embed playlist data here instead of using codes. - # Make sure to set session_type to the correct type for the data here. + # Alternately, you can embed playlist data here instead of using + # codes. Make sure to set session_type to the correct type for the + # data here. playlist_inline: list[dict[str, Any]] | None = None - # Whether to shuffle the playlist or play its games in designated order. + # Whether to shuffle the playlist or play its games in designated + # order. playlist_shuffle: bool = True - # If True, keeps team sizes equal by disallowing joining the largest team - # (teams mode only). + # If True, keeps team sizes equal by disallowing joining the largest + # team (teams mode only). auto_balance_teams: bool = True - # The campaign used when in co-op session mode. - # Do print(ba.app.campaigns) to see available campaign names. + # The campaign used when in co-op session mode. Do + # print(ba.app.campaigns) to see available campaign names. coop_campaign: str = 'Easy' - # The level name within the campaign used in co-op session mode. - # For campaign name FOO, do print(ba.app.campaigns['FOO'].levels) to see + # The level name within the campaign used in co-op session mode. For + # campaign name FOO, do print(ba.app.campaigns['FOO'].levels) to see # available level names. coop_level: str = 'Onslaught Training' # Whether to enable telnet access. - # IMPORTANT: This option is no longer available, as it was being used - # for exploits. Live access to the running server is still possible through - # the mgr.cmd() function in the server script. Run your server through - # tools such as 'screen' or 'tmux' and you can reconnect to it remotely - # over a secure ssh connection. + # + # IMPORTANT: This option is no longer available, as it was being + # used for exploits. Live access to the running server is still + # possible through the mgr.cmd() function in the server script. Run + # your server through tools such as 'screen' or 'tmux' and you can + # reconnect to it remotely over a secure ssh connection. enable_telnet: bool = False # Series length in teams mode (7 == 'best-of-7' series; a team must # get 4 wins) teams_series_length: int = 7 - # Points to win in free-for-all mode (Points are awarded per game based on - # performance) + # Points to win in free-for-all mode (Points are awarded per game + # based on performance) ffa_series_length: int = 24 - # If you have a custom stats webpage for your server, you can use this - # to provide a convenient in-game link to it in the server-browser - # alongside the server name. + # If you have a custom stats webpage for your server, you can use + # this to provide a convenient in-game link to it in the + # server-browser alongside the server name. + # # if ${ACCOUNT} is present in the string, it will be replaced by the # currently-signed-in account's id. To fetch info about an account, # your back-end server can use the following url: # https://legacy.ballistica.net/accountquery?id=ACCOUNT_ID_HERE stats_url: str | None = None - # If present, the server subprocess will attempt to gracefully exit after - # this amount of time. A graceful exit can occur at the end of a series - # or other opportune time. Server-managers set to auto-restart (the - # default) will then spin up a fresh subprocess. This mechanism can be - # useful to clear out any memory leaks or other accumulated bad state - # in the server subprocess. + # If present, the server subprocess will attempt to gracefully exit + # after this amount of time. A graceful exit can occur at the end of + # a series or other opportune time. Server-managers set to + # auto-restart (the default) will then spin up a fresh subprocess. + # This mechanism can be useful to clear out any memory leaks or + # other accumulated bad state in the server subprocess. clean_exit_minutes: float | None = None - # If present, the server subprocess will shut down immediately after this - # amount of time. This can be useful as a fallback for clean_exit_time. - # The server manager will then spin up a fresh server subprocess if - # auto-restart is enabled (the default). + # If present, the server subprocess will shut down immediately after + # this amount of time. This can be useful as a fallback for + # clean_exit_time. The server manager will then spin up a fresh + # server subprocess if auto-restart is enabled (the default). unclean_exit_minutes: float | None = None - # If present, the server subprocess will shut down immediately if this - # amount of time passes with no activity from any players. The server - # manager will then spin up a fresh server subprocess if auto-restart is - # enabled (the default). + # If present, the server subprocess will shut down immediately if + # this amount of time passes with no activity from any players. The + # server manager will then spin up a fresh server subprocess if + # auto-restart is enabled (the default). idle_exit_minutes: float | None = None # Should the tutorial be shown at the beginning of games? @@ -138,9 +167,9 @@ class ServerConfig: tuple[tuple[float, float, float], tuple[float, float, float]] | None ) = None - # Whether to enable the queue where players can line up before entering - # your server. Disabling this can be used as a workaround to deal with - # queue spamming attacks. + # Whether to enable the queue where players can line up before + # entering your server. Disabling this can be used as a workaround + # to deal with queue spamming attacks. enable_queue: bool = True # Protocol version we host with. Currently the default is 33 which @@ -158,9 +187,9 @@ class ServerConfig: player_rejoin_cooldown: float = 10.0 -# NOTE: as much as possible, communication from the server-manager to the -# child-process should go through these and not ad-hoc Python string commands -# since this way is type safe. +# NOTE: as much as possible, communication from the server-manager to +# the child-process should go through these and not ad-hoc Python string +# commands since this way is type safe. class ServerCommand: """Base class for commands that can be sent to the server.""" diff --git a/dist/ba_data/python/bacommon/transfer.py b/dist/ba_data/python/bacommon/transfer.py index 65221cb..aa8d38d 100644 --- a/dist/ba_data/python/bacommon/transfer.py +++ b/dist/ba_data/python/bacommon/transfer.py @@ -31,7 +31,9 @@ class DirectoryManifest: files: Annotated[dict[str, DirectoryManifestFile], IOAttrs('f')] - # _empty_hash: str | None = None + # Soft-default added April 2024; can remove eventually once this + # attr is widespread in client. + exists: Annotated[bool, IOAttrs('e', soft_default=True)] @classmethod def create_from_disk(cls, path: Path) -> DirectoryManifest: @@ -42,6 +44,8 @@ class DirectoryManifest: pathstr = str(path) paths: list[str] = [] + exists = path.exists() + if path.is_dir(): # Build the full list of relative paths. for basename, _dirnames, filenames in os.walk(path): @@ -51,7 +55,7 @@ class DirectoryManifest: # 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(): + elif exists: # Just return a single file entry if path is not a dir. paths.append(path.as_posix()) @@ -76,7 +80,9 @@ class DirectoryManifest: if cpus is None: cpus = 4 with ThreadPoolExecutor(max_workers=cpus) as executor: - return cls(files=dict(executor.map(_get_file_info, paths))) + return cls( + files=dict(executor.map(_get_file_info, paths)), exists=exists + ) def validate(self) -> None: """Log any odd data in the manifest; for debugging.""" diff --git a/dist/ba_data/python/baenv.py b/dist/ba_data/python/baenv.py index cdcf043..17953a4 100644 --- a/dist/ba_data/python/baenv.py +++ b/dist/ba_data/python/baenv.py @@ -52,8 +52,8 @@ if TYPE_CHECKING: # Build number and version of the ballistica binary we expect to be # using. -TARGET_BALLISTICA_BUILD = 21766 -TARGET_BALLISTICA_VERSION = '1.7.33' +TARGET_BALLISTICA_BUILD = 21879 +TARGET_BALLISTICA_VERSION = '1.7.35' @dataclass @@ -264,6 +264,10 @@ def _calc_data_dir(data_dir: str | None) -> str: def _setup_logging() -> LogHandler: from efro.log import setup_logging, LogLevel + # TODO: should set this up with individual loggers under a top level + # 'ba' logger, and at that point we can kill off the + # suppress_non_root_debug option since we'll only ever need to set + # 'ba' to DEBUG at most. log_handler = setup_logging( log_path=None, level=LogLevel.DEBUG, diff --git a/dist/ba_data/python/baplus/_cloud.py b/dist/ba_data/python/baplus/_cloud.py index d2e51eb..0a7b4af 100644 --- a/dist/ba_data/python/baplus/_cloud.py +++ b/dist/ba_data/python/baplus/_cloud.py @@ -57,8 +57,7 @@ class CloudSubsystem(babase.AppSubsystem): on_response: Callable[ [bacommon.cloud.LoginProxyRequestResponse | Exception], None ], - ) -> None: - ... + ) -> None: ... @overload def send_message_cb( @@ -67,24 +66,21 @@ class CloudSubsystem(babase.AppSubsystem): on_response: Callable[ [bacommon.cloud.LoginProxyStateQueryResponse | Exception], None ], - ) -> None: - ... + ) -> None: ... @overload def send_message_cb( self, msg: bacommon.cloud.LoginProxyCompleteMessage, on_response: Callable[[None | Exception], None], - ) -> None: - ... + ) -> None: ... @overload def send_message_cb( self, msg: bacommon.cloud.PingMessage, on_response: Callable[[bacommon.cloud.PingResponse | Exception], None], - ) -> None: - ... + ) -> None: ... @overload def send_message_cb( @@ -93,8 +89,7 @@ class CloudSubsystem(babase.AppSubsystem): on_response: Callable[ [bacommon.cloud.SignInResponse | Exception], None ], - ) -> None: - ... + ) -> None: ... @overload def send_message_cb( @@ -103,8 +98,7 @@ class CloudSubsystem(babase.AppSubsystem): on_response: Callable[ [bacommon.cloud.ManageAccountResponse | Exception], None ], - ) -> None: - ... + ) -> None: ... def send_message_cb( self, @@ -129,20 +123,17 @@ class CloudSubsystem(babase.AppSubsystem): @overload def send_message( self, msg: bacommon.cloud.WorkspaceFetchMessage - ) -> bacommon.cloud.WorkspaceFetchResponse: - ... + ) -> bacommon.cloud.WorkspaceFetchResponse: ... @overload def send_message( self, msg: bacommon.cloud.MerchAvailabilityMessage - ) -> bacommon.cloud.MerchAvailabilityResponse: - ... + ) -> bacommon.cloud.MerchAvailabilityResponse: ... @overload def send_message( self, msg: bacommon.cloud.TestMessage - ) -> bacommon.cloud.TestResponse: - ... + ) -> bacommon.cloud.TestResponse: ... def send_message(self, msg: Message) -> Response | None: """Synchronously send a message to the cloud. @@ -153,15 +144,13 @@ class CloudSubsystem(babase.AppSubsystem): @overload async def send_message_async( - self, msg: bacommon.cloud.PromoCodeMessage - ) -> bacommon.cloud.PromoCodeResponse: - ... + self, msg: bacommon.cloud.SendInfoMessage + ) -> bacommon.cloud.SendInfoResponse: ... @overload async def send_message_async( self, msg: bacommon.cloud.TestMessage - ) -> bacommon.cloud.TestResponse: - ... + ) -> bacommon.cloud.TestResponse: ... async def send_message_async(self, msg: Message) -> Response | None: """Synchronously send a message to the cloud. diff --git a/dist/ba_data/python/baplus/_subsystem.py b/dist/ba_data/python/baplus/_subsystem.py index 567f3cd..7268abc 100644 --- a/dist/ba_data/python/baplus/_subsystem.py +++ b/dist/ba_data/python/baplus/_subsystem.py @@ -3,9 +3,8 @@ """Provides plus app subsystem.""" from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override -from typing_extensions import override from babase import AppSubsystem import _baplus diff --git a/dist/ba_data/python/bascenev1/__init__.py b/dist/ba_data/python/bascenev1/__init__.py index 25c1f5e..b351348 100644 --- a/dist/ba_data/python/bascenev1/__init__.py +++ b/dist/ba_data/python/bascenev1/__init__.py @@ -120,6 +120,7 @@ from _bascenev1 import ( release_keyboard_input, reset_random_player_names, resume_replay, + seek_replay, broadcastmessage, SessionData, SessionPlayer, @@ -133,6 +134,8 @@ from _bascenev1 import ( set_public_party_enabled, set_public_party_max_size, set_public_party_name, + set_public_party_public_address_ipv4, + set_public_party_public_address_ipv6, set_public_party_queue_enabled, set_public_party_stats_url, set_replay_speed_exponent, @@ -231,7 +234,11 @@ from bascenev1._settings import ( IntSetting, Setting, ) -from bascenev1._session import Session, set_player_rejoin_cooldown +from bascenev1._session import ( + Session, + set_player_rejoin_cooldown, + set_max_players_override, +) from bascenev1._stats import PlayerScoredMessage, PlayerRecord, Stats from bascenev1._team import SessionTeam, Team, EmptyTeam from bascenev1._teamgame import TeamGameActivity @@ -400,6 +407,7 @@ __all__ = [ 'release_keyboard_input', 'reset_random_player_names', 'resume_replay', + 'seek_replay', 'safecolor', 'screenmessage', 'SceneV1AppMode', @@ -423,9 +431,12 @@ __all__ = [ 'set_public_party_enabled', 'set_public_party_max_size', 'set_public_party_name', + 'set_public_party_public_address_ipv4', + 'set_public_party_public_address_ipv6', 'set_public_party_queue_enabled', 'set_public_party_stats_url', 'set_player_rejoin_cooldown', + 'set_max_players_override', 'set_replay_speed_exponent', 'set_touchscreen_editing', 'setmusic', diff --git a/dist/ba_data/python/bascenev1/_activitytypes.py b/dist/ba_data/python/bascenev1/_activitytypes.py index 9b46b3e..a11d8c0 100644 --- a/dist/ba_data/python/bascenev1/_activitytypes.py +++ b/dist/ba_data/python/bascenev1/_activitytypes.py @@ -3,9 +3,8 @@ """Some handy base class and special purpose Activity types.""" from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override -from typing_extensions import override import babase import _bascenev1 @@ -203,9 +202,11 @@ class ScoreScreenActivity(Activity[EmptyPlayer, EmptyTeam]): sval = babase.Lstr(resource='pressAnyButtonText') Text( - self._custom_continue_message - if self._custom_continue_message is not None - else sval, + ( + self._custom_continue_message + if self._custom_continue_message is not None + else sval + ), v_attach=Text.VAttach.BOTTOM, h_align=Text.HAlign.CENTER, flash=True, diff --git a/dist/ba_data/python/bascenev1/_actor.py b/dist/ba_data/python/bascenev1/_actor.py index 178a901..11f4f07 100644 --- a/dist/ba_data/python/bascenev1/_actor.py +++ b/dist/ba_data/python/bascenev1/_actor.py @@ -198,12 +198,14 @@ class Actor: # Overloads to convey our exact return type depending on 'doraise' value. @overload - def getactivity(self, doraise: Literal[True] = True) -> bascenev1.Activity: - ... + def getactivity( + self, doraise: Literal[True] = True + ) -> bascenev1.Activity: ... @overload - def getactivity(self, doraise: Literal[False]) -> bascenev1.Activity | None: - ... + def getactivity( + self, doraise: Literal[False] + ) -> bascenev1.Activity | None: ... def getactivity(self, doraise: bool = True) -> bascenev1.Activity | None: """Return the bascenev1.Activity this Actor is associated with. diff --git a/dist/ba_data/python/bascenev1/_appmode.py b/dist/ba_data/python/bascenev1/_appmode.py index 481fb4e..72a77ae 100644 --- a/dist/ba_data/python/bascenev1/_appmode.py +++ b/dist/ba_data/python/bascenev1/_appmode.py @@ -3,9 +3,8 @@ """Provides AppMode functionality.""" from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override -from typing_extensions import override from bacommon.app import AppExperience from babase import ( app, diff --git a/dist/ba_data/python/bascenev1/_coopgame.py b/dist/ba_data/python/bascenev1/_coopgame.py index e0af515..e4f20e3 100644 --- a/dist/ba_data/python/bascenev1/_coopgame.py +++ b/dist/ba_data/python/bascenev1/_coopgame.py @@ -4,9 +4,8 @@ from __future__ import annotations import logging -from typing import TYPE_CHECKING, TypeVar +from typing import TYPE_CHECKING, TypeVar, override -from typing_extensions import override import babase import _bascenev1 diff --git a/dist/ba_data/python/bascenev1/_coopsession.py b/dist/ba_data/python/bascenev1/_coopsession.py index f1d59ae..d5040e0 100644 --- a/dist/ba_data/python/bascenev1/_coopsession.py +++ b/dist/ba_data/python/bascenev1/_coopsession.py @@ -3,9 +3,8 @@ """Functionality related to coop-mode sessions.""" from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override -from typing_extensions import override import babase import _bascenev1 @@ -61,6 +60,10 @@ class CoopSession(Session): max_players = classic.coop_session_args['max_players'] else: max_players = app.config.get('Coop Game Max Players', 4) + if 'submit_score' in classic.coop_session_args: + submit_score = classic.coop_session_args['submit_score'] + else: + submit_score = True # print('FIXME: COOP SESSION WOULD CALC DEPS.') depsets: Sequence[bascenev1.DependencySet] = [] @@ -71,6 +74,7 @@ class CoopSession(Session): team_colors=TEAM_COLORS, min_players=min_players, max_players=max_players, + submit_score=submit_score, ) # Tournament-ID if we correspond to a co-op tournament (otherwise None) @@ -346,7 +350,10 @@ class CoopSession(Session): self.setactivity(next_game) if not (env.demo or env.arcade): - if self.tournament_id is not None: + if ( + self.tournament_id is not None + and classic.coop_session_args['submit_score'] + ): self._custom_menu_ui = [ { 'label': babase.Lstr(resource='restartText'), diff --git a/dist/ba_data/python/bascenev1/_dependency.py b/dist/ba_data/python/bascenev1/_dependency.py index 0a2f49f..9741493 100644 --- a/dist/ba_data/python/bascenev1/_dependency.py +++ b/dist/ba_data/python/bascenev1/_dependency.py @@ -5,9 +5,8 @@ from __future__ import annotations import weakref -from typing import Generic, TypeVar, TYPE_CHECKING +from typing import Generic, TypeVar, TYPE_CHECKING, override -from typing_extensions import override import babase import _bascenev1 diff --git a/dist/ba_data/python/bascenev1/_dualteamsession.py b/dist/ba_data/python/bascenev1/_dualteamsession.py index 8280768..4e0eb43 100644 --- a/dist/ba_data/python/bascenev1/_dualteamsession.py +++ b/dist/ba_data/python/bascenev1/_dualteamsession.py @@ -3,9 +3,8 @@ """Functionality related to teams sessions.""" from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override -from typing_extensions import override import babase import _bascenev1 diff --git a/dist/ba_data/python/bascenev1/_freeforallsession.py b/dist/ba_data/python/bascenev1/_freeforallsession.py index 2718377..f32eb64 100644 --- a/dist/ba_data/python/bascenev1/_freeforallsession.py +++ b/dist/ba_data/python/bascenev1/_freeforallsession.py @@ -4,9 +4,8 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override -from typing_extensions import override import babase import _bascenev1 diff --git a/dist/ba_data/python/bascenev1/_gameactivity.py b/dist/ba_data/python/bascenev1/_gameactivity.py index 1557ffc..1517733 100644 --- a/dist/ba_data/python/bascenev1/_gameactivity.py +++ b/dist/ba_data/python/bascenev1/_gameactivity.py @@ -7,9 +7,8 @@ from __future__ import annotations import random import logging -from typing import TYPE_CHECKING, TypeVar +from typing import TYPE_CHECKING, TypeVar, override -from typing_extensions import override import babase import _bascenev1 diff --git a/dist/ba_data/python/bascenev1/_gameresults.py b/dist/ba_data/python/bascenev1/_gameresults.py index e493939..8735ff3 100644 --- a/dist/ba_data/python/bascenev1/_gameresults.py +++ b/dist/ba_data/python/bascenev1/_gameresults.py @@ -42,9 +42,9 @@ class GameResults: self._scores: dict[ int, tuple[weakref.ref[bascenev1.SessionTeam], int | None] ] = {} - self._sessionteams: list[ - weakref.ref[bascenev1.SessionTeam] - ] | None = None + self._sessionteams: list[weakref.ref[bascenev1.SessionTeam]] | None = ( + None + ) self._playerinfos: list[bascenev1.PlayerInfo] | None = None self._lower_is_better: bool | None = None self._score_label: str | None = None diff --git a/dist/ba_data/python/bascenev1/_level.py b/dist/ba_data/python/bascenev1/_level.py index 2f1906e..6da0a7e 100644 --- a/dist/ba_data/python/bascenev1/_level.py +++ b/dist/ba_data/python/bascenev1/_level.py @@ -5,9 +5,8 @@ from __future__ import annotations import copy import weakref -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override -from typing_extensions import override import babase if TYPE_CHECKING: @@ -73,9 +72,11 @@ class Level: return babase.Lstr( translate=( 'coopLevelNames', - self._displayname - if self._displayname is not None - else self._name, + ( + self._displayname + if self._displayname is not None + else self._name + ), ), subs=[ ('${GAME}', self._gametype.get_display_string(self._settings)) diff --git a/dist/ba_data/python/bascenev1/_map.py b/dist/ba_data/python/bascenev1/_map.py index 832632f..4b3b7a9 100644 --- a/dist/ba_data/python/bascenev1/_map.py +++ b/dist/ba_data/python/bascenev1/_map.py @@ -4,9 +4,8 @@ from __future__ import annotations import random -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override -from typing_extensions import override import babase import _bascenev1 @@ -256,9 +255,7 @@ class Map(Actor): return ( None if val is None - else babase.vec3validate(val) - if __debug__ - else val + else babase.vec3validate(val) if __debug__ else val ) def get_def_points(self, name: str) -> list[Sequence[float]]: @@ -334,8 +331,7 @@ class Map(Actor): closest_player_dist = 9999.0 for ppt in player_pts: dist = (ppt - testpt).length() - if dist < closest_player_dist: - closest_player_dist = dist + closest_player_dist = min(dist, closest_player_dist) if closest_player_dist > farthestpt_dist: farthestpt_dist = closest_player_dist farthestpt = testpt diff --git a/dist/ba_data/python/bascenev1/_multiteamsession.py b/dist/ba_data/python/bascenev1/_multiteamsession.py index a4d6c2d..6dad127 100644 --- a/dist/ba_data/python/bascenev1/_multiteamsession.py +++ b/dist/ba_data/python/bascenev1/_multiteamsession.py @@ -6,9 +6,8 @@ from __future__ import annotations import copy import random import logging -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override -from typing_extensions import override import babase import _bascenev1 @@ -67,8 +66,8 @@ class MultiTeamSession(Session): max_players=self.get_max_players(), ) - self._series_length: int = classic.teams_series_length - self._ffa_series_length: int = classic.ffa_series_length + self._series_length: int = int(cfg.get('Teams Series Length', 7)) + self._ffa_series_length: int = int(cfg.get('FFA Series Length', 24)) show_tutorial = cfg.get('Show Tutorial', True) diff --git a/dist/ba_data/python/bascenev1/_nodeactor.py b/dist/ba_data/python/bascenev1/_nodeactor.py index 7b3551e..965b596 100644 --- a/dist/ba_data/python/bascenev1/_nodeactor.py +++ b/dist/ba_data/python/bascenev1/_nodeactor.py @@ -4,9 +4,7 @@ from __future__ import annotations -from typing import TYPE_CHECKING - -from typing_extensions import override +from typing import TYPE_CHECKING, override from bascenev1._messages import DieMessage from bascenev1._actor import Actor diff --git a/dist/ba_data/python/bascenev1/_playlist.py b/dist/ba_data/python/bascenev1/_playlist.py index 35c902c..013f6e5 100644 --- a/dist/ba_data/python/bascenev1/_playlist.py +++ b/dist/ba_data/python/bascenev1/_playlist.py @@ -89,18 +89,18 @@ def filter_playlist( 'bs_king_of_the_hill.KingOfTheHillGame', 'bastd.game.kingofthehill.KingOfTheHillGame', ): - entry[ - 'type' - ] = 'bascenev1lib.game.kingofthehill.KingOfTheHillGame' + entry['type'] = ( + 'bascenev1lib.game.kingofthehill.KingOfTheHillGame' + ) if entry['type'] in ( 'Capture_the_Flag.CTFGame', 'bsCaptureTheFlag.CTFGame', 'bs_capture_the_flag.CTFGame', 'bastd.game.capturetheflag.CaptureTheFlagGame', ): - entry[ - 'type' - ] = 'bascenev1lib.game.capturetheflag.CaptureTheFlagGame' + entry['type'] = ( + 'bascenev1lib.game.capturetheflag.CaptureTheFlagGame' + ) if entry['type'] in ( 'Death_Match.DeathMatchGame', 'bsDeathMatch.DeathMatchGame', @@ -163,25 +163,25 @@ def filter_playlist( 'bs_easter_egg_hunt.EasterEggHuntGame', 'bastd.game.easteregghunt.EasterEggHuntGame', ): - entry[ - 'type' - ] = 'bascenev1lib.game.easteregghunt.EasterEggHuntGame' + entry['type'] = ( + 'bascenev1lib.game.easteregghunt.EasterEggHuntGame' + ) if entry['type'] in ( 'bsMeteorShower.MeteorShowerGame', 'bs_meteor_shower.MeteorShowerGame', 'bastd.game.meteorshower.MeteorShowerGame', ): - entry[ - 'type' - ] = 'bascenev1lib.game.meteorshower.MeteorShowerGame' + entry['type'] = ( + 'bascenev1lib.game.meteorshower.MeteorShowerGame' + ) if entry['type'] in ( 'bsTargetPractice.TargetPracticeGame', 'bs_target_practice.TargetPracticeGame', 'bastd.game.targetpractice.TargetPracticeGame', ): - entry[ - 'type' - ] = 'bascenev1lib.game.targetpractice.TargetPracticeGame' + entry['type'] = ( + 'bascenev1lib.game.targetpractice.TargetPracticeGame' + ) gameclass = babase.getclass(entry['type'], GameActivity) diff --git a/dist/ba_data/python/bascenev1/_session.py b/dist/ba_data/python/bascenev1/_session.py index 9ee5531..dd75734 100644 --- a/dist/ba_data/python/bascenev1/_session.py +++ b/dist/ba_data/python/bascenev1/_session.py @@ -23,6 +23,9 @@ if TYPE_CHECKING: # such as skipping respawn waits. _g_player_rejoin_cooldown: float = 0.0 +# overrides the session's decision of max_players +_max_players_override: int | None = None + def set_player_rejoin_cooldown(cooldown: float) -> None: """Set the cooldown for individual players rejoining after leaving.""" @@ -30,6 +33,12 @@ def set_player_rejoin_cooldown(cooldown: float) -> None: _g_player_rejoin_cooldown = max(0.0, cooldown) +def set_max_players_override(max_players: int | None) -> None: + """Set the override for how many players can join a session""" + global _max_players_override # pylint: disable=global-statement + _max_players_override = max_players + + class Session: """Defines a high level series of bascenev1.Activity-es. @@ -91,6 +100,7 @@ class Session: team_colors: Sequence[Sequence[float]] | None = None, min_players: int = 1, max_players: int = 8, + submit_score: bool = True, ): """Instantiate a session. @@ -161,7 +171,12 @@ class Session: self.sessionteams = [] self.sessionplayers = [] self.min_players = min_players - self.max_players = max_players + self.max_players = ( + max_players + if _max_players_override is None + else _max_players_override + ) + self.submit_score = submit_score self.customdata = {} self._in_set_activity = False @@ -255,7 +270,7 @@ class Session: babase.app.classic is not None and babase.app.classic.stress_test_update_timer is None ): - if len(self.sessionplayers) >= self.max_players: + if len(self.sessionplayers) >= self.max_players >= 0: # Print a rejection message *only* to the client trying to # join (prevents spamming everyone else in the game). _bascenev1.getsound('error').play() diff --git a/dist/ba_data/python/bascenev1/_teamgame.py b/dist/ba_data/python/bascenev1/_teamgame.py index e835bd0..29ecf7d 100644 --- a/dist/ba_data/python/bascenev1/_teamgame.py +++ b/dist/ba_data/python/bascenev1/_teamgame.py @@ -5,9 +5,8 @@ from __future__ import annotations import logging -from typing import TYPE_CHECKING, TypeVar +from typing import TYPE_CHECKING, TypeVar, override -from typing_extensions import override import babase import _bascenev1 diff --git a/dist/ba_data/python/bascenev1lib/activity/coopjoin.py b/dist/ba_data/python/bascenev1lib/activity/coopjoin.py index 55f3fd8..44dbd7e 100644 --- a/dist/ba_data/python/bascenev1lib/activity/coopjoin.py +++ b/dist/ba_data/python/bascenev1lib/activity/coopjoin.py @@ -4,7 +4,8 @@ from __future__ import annotations -from typing_extensions import override +from typing import override + import bascenev1 as bs diff --git a/dist/ba_data/python/bascenev1lib/activity/coopscore.py b/dist/ba_data/python/bascenev1lib/activity/coopscore.py index a88c418..4b92b69 100644 --- a/dist/ba_data/python/bascenev1lib/activity/coopscore.py +++ b/dist/ba_data/python/bascenev1lib/activity/coopscore.py @@ -7,9 +7,8 @@ from __future__ import annotations import random import logging -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override -from typing_extensions import override from bacommon.login import LoginType import bascenev1 as bs import bauiv1 as bui @@ -125,6 +124,7 @@ class CoopScoreScreen(bs.Activity[bs.Player, bs.Team]): self._tournament_time_remaining: float | None = None self._tournament_time_remaining_text: Text | None = None self._tournament_time_remaining_text_timer: bs.BaseTimer | None = None + self._submit_score = self.session.submit_score # Stuff for activity skip by pressing button self._birth_time = bs.time() @@ -395,11 +395,15 @@ class CoopScoreScreen(bs.Activity[bs.Player, bs.Team]): color=(0.45, 0.4, 0.5), position=(160, v_offs + 480), size=(350, 62), - label=bui.Lstr(resource='tournamentStandingsText') - if self.session.tournament_id is not None - else bui.Lstr(resource='worldsBestScoresText') - if self._score_type == 'points' - else bui.Lstr(resource='worldsBestTimesText'), + label=( + bui.Lstr(resource='tournamentStandingsText') + if self.session.tournament_id is not None + else ( + bui.Lstr(resource='worldsBestScoresText') + if self._score_type == 'points' + else bui.Lstr(resource='worldsBestTimesText') + ) + ), autoselect=True, on_activate_call=bui.WeakCall(self._ui_worlds_best), transition_delay=delay + 1.9, @@ -515,9 +519,11 @@ class CoopScoreScreen(bs.Activity[bs.Player, bs.Team]): bui.containerwidget( edit=rootc, - selected_child=next_button - if (self._newly_complete and self._victory and show_next_button) - else restart_button, + selected_child=( + next_button + if (self._newly_complete and self._victory and show_next_button) + else restart_button + ), on_cancel_call=menu_button.activate, ) @@ -644,14 +650,16 @@ class CoopScoreScreen(bs.Activity[bs.Player, bs.Team]): and not (env.demo or env.arcade) ): Text( - bs.Lstr( - value='${A}:\n', - subs=[('${A}', bs.Lstr(resource='levelUnlockedText'))], - ) - if self._newly_complete - else bs.Lstr( - value='${A}:\n', - subs=[('${A}', bs.Lstr(resource='nextLevelText'))], + ( + bs.Lstr( + value='${A}:\n', + subs=[('${A}', bs.Lstr(resource='levelUnlockedText'))], + ) + if self._newly_complete + else bs.Lstr( + value='${A}:\n', + subs=[('${A}', bs.Lstr(resource='nextLevelText'))], + ) ), transition=Text.Transition.IN_RIGHT, transition_delay=5.2, @@ -781,7 +789,7 @@ class CoopScoreScreen(bs.Activity[bs.Player, bs.Team]): transition_delay=2.0, ) - if self._score is not None: + if self._score is not None and self._submit_score: bs.timer(0.4, bs.WeakCall(self._play_drumroll)) # Add us to high scores, filter, and store. @@ -860,11 +868,15 @@ class CoopScoreScreen(bs.Activity[bs.Player, bs.Team]): ts_h_offs = 210 v_offs = 40 txt = Text( - bs.Lstr(resource='tournamentStandingsText') - if self.session.tournament_id is not None - else bs.Lstr(resource='worldsBestScoresText') - if self._score_type == 'points' - else bs.Lstr(resource='worldsBestTimesText'), + ( + bs.Lstr(resource='tournamentStandingsText') + if self.session.tournament_id is not None + else ( + bs.Lstr(resource='worldsBestScoresText') + if self._score_type == 'points' + else bs.Lstr(resource='worldsBestTimesText') + ) + ), maxwidth=210, position=(ts_h_offs - 10, ts_height / 2 + 25 + v_offs + 20), transition=Text.Transition.IN_LEFT, @@ -882,9 +894,11 @@ class CoopScoreScreen(bs.Activity[bs.Player, bs.Team]): ts_h_offs = -480 v_offs = 40 Text( - bs.Lstr(resource='yourBestScoresText') - if self._score_type == 'points' - else bs.Lstr(resource='yourBestTimesText'), + ( + bs.Lstr(resource='yourBestScoresText') + if self._score_type == 'points' + else bs.Lstr(resource='yourBestTimesText') + ), maxwidth=210, position=(ts_h_offs - 10, ts_height / 2 + 25 + v_offs + 20), transition=Text.Transition.IN_RIGHT, @@ -948,9 +962,11 @@ class CoopScoreScreen(bs.Activity[bs.Player, bs.Team]): tdelay1 = times[i][0] tdelay2 = times[i][1] Text( - str(display_scores[i][0]) - if self._score_type == 'points' - else bs.timestring((display_scores[i][0] * 10) / 1000.0), + ( + str(display_scores[i][0]) + if self._score_type == 'points' + else bs.timestring((display_scores[i][0] * 10) / 1000.0) + ), position=( ts_h_offs + 20 + h_offs_extra, v_offs_extra @@ -1127,9 +1143,11 @@ class CoopScoreScreen(bs.Activity[bs.Player, bs.Team]): tdelay2 = times[i][1] if name_str != '-': Text( - str(score) - if self._score_type == 'points' - else bs.timestring((score * 10) / 1000.0), + ( + str(score) + if self._score_type == 'points' + else bs.timestring((score * 10) / 1000.0) + ), position=( ts_h_offs + 20 + h_offs_extra, v_offs_extra @@ -1313,9 +1331,11 @@ class CoopScoreScreen(bs.Activity[bs.Player, bs.Team]): if name_str != '-': Text( - str(score) - if self._score_type == 'points' - else bs.timestring((score * 10) / 1000.0), + ( + str(score) + if self._score_type == 'points' + else bs.timestring((score * 10) / 1000.0) + ), position=( ts_h_offs + 20 + h_offs_extra, ts_height / 2 @@ -1376,7 +1396,7 @@ class CoopScoreScreen(bs.Activity[bs.Player, bs.Team]): assert self._show_info is not None available = self._show_info['results'] is not None - if available: + if available and self._submit_score: error = ( self._show_info['results']['error'] if 'error' in self._show_info['results'] @@ -1509,7 +1529,7 @@ class CoopScoreScreen(bs.Activity[bs.Player, bs.Team]): maxwidth=400, transition_delay=1.0, ).autoretain() - else: + elif self._submit_score: ZoomText( ( ('#' + str(player_rank)) @@ -1689,17 +1709,22 @@ class CoopScoreScreen(bs.Activity[bs.Player, bs.Team]): ) if not self._newly_complete: Text( - bs.Lstr( - value='${A}${B}', - subs=[ - ('${A}', bs.Lstr(resource='newPersonalBestText')), - ('${B}', was_string), - ], - ) - if new_best - else bs.Lstr( - resource='bestRatingText', - subs=[('${RATING}', str(best_rank))], + ( + bs.Lstr( + value='${A}${B}', + subs=[ + ( + '${A}', + bs.Lstr(resource='newPersonalBestText'), + ), + ('${B}', was_string), + ], + ) + if new_best + else bs.Lstr( + resource='bestRatingText', + subs=[('${RATING}', str(best_rank))], + ) ), position=(0, -165), color=(1, 1, 1, 0.7), @@ -1727,9 +1752,10 @@ class CoopScoreScreen(bs.Activity[bs.Player, bs.Team]): transition_delay=0, ).autoretain() - bs.timer(0.35, self._score_display_sound.play) - if not error: - bs.timer(0.35, self.cymbal_sound.play) + if self._submit_score: + bs.timer(0.35, self._score_display_sound.play) + if not error: + bs.timer(0.35, self.cymbal_sound.play) def _show_fail(self) -> None: ZoomText( @@ -1773,14 +1799,16 @@ class CoopScoreScreen(bs.Activity[bs.Player, bs.Team]): jitter=1.0, ).autoretain() Text( - bs.Lstr( - value='${A}:', - subs=[('${A}', bs.Lstr(resource='finalScoreText'))], - ) - if self._score_type == 'points' - else bs.Lstr( - value='${A}:', - subs=[('${A}', bs.Lstr(resource='finalTimeText'))], + ( + bs.Lstr( + value='${A}:', + subs=[('${A}', bs.Lstr(resource='finalScoreText'))], + ) + if self._score_type == 'points' + else bs.Lstr( + value='${A}:', + subs=[('${A}', bs.Lstr(resource='finalTimeText'))], + ) ), maxwidth=300, position=(0, 200), diff --git a/dist/ba_data/python/bascenev1lib/activity/drawscore.py b/dist/ba_data/python/bascenev1lib/activity/drawscore.py index d6e1b28..78def50 100644 --- a/dist/ba_data/python/bascenev1lib/activity/drawscore.py +++ b/dist/ba_data/python/bascenev1lib/activity/drawscore.py @@ -4,7 +4,8 @@ from __future__ import annotations -from typing_extensions import override +from typing import override + import bascenev1 as bs from bascenev1lib.activity.multiteamscore import MultiTeamScoreScreenActivity diff --git a/dist/ba_data/python/bascenev1lib/activity/dualteamscore.py b/dist/ba_data/python/bascenev1lib/activity/dualteamscore.py index 2df370b..dc01ac5 100644 --- a/dist/ba_data/python/bascenev1lib/activity/dualteamscore.py +++ b/dist/ba_data/python/bascenev1lib/activity/dualteamscore.py @@ -4,7 +4,8 @@ from __future__ import annotations -from typing_extensions import override +from typing import override + import bascenev1 as bs from bascenev1lib.activity.multiteamscore import MultiTeamScoreScreenActivity diff --git a/dist/ba_data/python/bascenev1lib/activity/freeforallvictory.py b/dist/ba_data/python/bascenev1lib/activity/freeforallvictory.py index cf82d4a..5f2189f 100644 --- a/dist/ba_data/python/bascenev1lib/activity/freeforallvictory.py +++ b/dist/ba_data/python/bascenev1lib/activity/freeforallvictory.py @@ -4,9 +4,8 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override -from typing_extensions import override import bascenev1 as bs from bascenev1lib.activity.multiteamscore import MultiTeamScoreScreenActivity diff --git a/dist/ba_data/python/bascenev1lib/activity/multiteamjoin.py b/dist/ba_data/python/bascenev1lib/activity/multiteamjoin.py index ff8f74b..3339871 100644 --- a/dist/ba_data/python/bascenev1lib/activity/multiteamjoin.py +++ b/dist/ba_data/python/bascenev1lib/activity/multiteamjoin.py @@ -4,7 +4,8 @@ from __future__ import annotations -from typing_extensions import override +from typing import override + import bascenev1 as bs from bascenev1lib.actor.text import Text diff --git a/dist/ba_data/python/bascenev1lib/activity/multiteamscore.py b/dist/ba_data/python/bascenev1lib/activity/multiteamscore.py index 7b75401..9feb385 100644 --- a/dist/ba_data/python/bascenev1lib/activity/multiteamscore.py +++ b/dist/ba_data/python/bascenev1lib/activity/multiteamscore.py @@ -3,7 +3,8 @@ """Functionality related to teams mode score screen.""" from __future__ import annotations -from typing_extensions import override +from typing import override + import bascenev1 as bs from bascenev1lib.actor.text import Text @@ -199,9 +200,9 @@ class MultiTeamScoreScreenActivity(bs.ScoreScreenActivity): ts_v_offset + (voffs + 15) * scale, ), scale=scale, - color=(1.0, 0.9, 0.5, 1.0) - if highlight - else (0.5, 0.5, 0.6, 0.5), + color=( + (1.0, 0.9, 0.5, 1.0) if highlight else (0.5, 0.5, 0.6, 0.5) + ), h_align=Text.HAlign.RIGHT, v_align=Text.VAlign.CENTER, maxwidth=maxwidth, diff --git a/dist/ba_data/python/bascenev1lib/activity/multiteamvictory.py b/dist/ba_data/python/bascenev1lib/activity/multiteamvictory.py index afc2a26..a8ecc28 100644 --- a/dist/ba_data/python/bascenev1lib/activity/multiteamvictory.py +++ b/dist/ba_data/python/bascenev1lib/activity/multiteamvictory.py @@ -4,7 +4,8 @@ from __future__ import annotations -from typing_extensions import override +from typing import override + import bascenev1 as bs from bascenev1lib.activity.multiteamscore import MultiTeamScoreScreenActivity @@ -374,9 +375,11 @@ class TeamSeriesVictoryScoreScreenActivity(MultiTeamScoreScreenActivity): tdelay -= 4 * t_incr v_offs -= 40 Text( - str(prec.team.customdata['score']) - if self._is_ffa - else str(prec.score), + ( + str(prec.team.customdata['score']) + if self._is_ffa + else str(prec.score) + ), color=(0.5, 0.5, 0.5, 1.0), position=(ts_h_offs + 230, ts_height / 2 + v_offs), h_align=Text.HAlign.RIGHT, diff --git a/dist/ba_data/python/bascenev1lib/actor/background.py b/dist/ba_data/python/bascenev1lib/actor/background.py index d5625a2..ff5d508 100644 --- a/dist/ba_data/python/bascenev1lib/actor/background.py +++ b/dist/ba_data/python/bascenev1lib/actor/background.py @@ -7,9 +7,8 @@ from __future__ import annotations import random import weakref import logging -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override -from typing_extensions import override import bascenev1 as bs if TYPE_CHECKING: diff --git a/dist/ba_data/python/bascenev1lib/actor/bomb.py b/dist/ba_data/python/bascenev1lib/actor/bomb.py index 058057d..c73eceb 100644 --- a/dist/ba_data/python/bascenev1lib/actor/bomb.py +++ b/dist/ba_data/python/bascenev1lib/actor/bomb.py @@ -8,9 +8,8 @@ from __future__ import annotations import random -from typing import TYPE_CHECKING, TypeVar +from typing import TYPE_CHECKING, TypeVar, override -from typing_extensions import override import bascenev1 as bs from bascenev1lib.gameutils import SharedObjects diff --git a/dist/ba_data/python/bascenev1lib/actor/controlsguide.py b/dist/ba_data/python/bascenev1lib/actor/controlsguide.py index f3068f0..a750dfd 100644 --- a/dist/ba_data/python/bascenev1lib/actor/controlsguide.py +++ b/dist/ba_data/python/bascenev1lib/actor/controlsguide.py @@ -4,9 +4,8 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override -from typing_extensions import override import bascenev1 as bs if TYPE_CHECKING: @@ -456,9 +455,11 @@ class ControlsGuide(bs.Actor): ( '${B}', bs.Lstr( - resource='holdAnyKeyText' - if all_keyboards - else 'holdAnyButtonText' + resource=( + 'holdAnyKeyText' + if all_keyboards + else 'holdAnyButtonText' + ) ), ), ], diff --git a/dist/ba_data/python/bascenev1lib/actor/flag.py b/dist/ba_data/python/bascenev1lib/actor/flag.py index cd24c9e..0347203 100644 --- a/dist/ba_data/python/bascenev1lib/actor/flag.py +++ b/dist/ba_data/python/bascenev1lib/actor/flag.py @@ -5,9 +5,8 @@ from __future__ import annotations from dataclasses import dataclass -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override -from typing_extensions import override import bascenev1 as bs from bascenev1lib.gameutils import SharedObjects diff --git a/dist/ba_data/python/bascenev1lib/actor/image.py b/dist/ba_data/python/bascenev1lib/actor/image.py index 00e71d0..daa648b 100644 --- a/dist/ba_data/python/bascenev1lib/actor/image.py +++ b/dist/ba_data/python/bascenev1lib/actor/image.py @@ -5,9 +5,8 @@ from __future__ import annotations from enum import Enum -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override -from typing_extensions import override import bascenev1 as bs if TYPE_CHECKING: diff --git a/dist/ba_data/python/bascenev1lib/actor/onscreencountdown.py b/dist/ba_data/python/bascenev1lib/actor/onscreencountdown.py index 68c477b..7c2364f 100644 --- a/dist/ba_data/python/bascenev1lib/actor/onscreencountdown.py +++ b/dist/ba_data/python/bascenev1lib/actor/onscreencountdown.py @@ -4,9 +4,8 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override -from typing_extensions import override import bascenev1 as bs if TYPE_CHECKING: diff --git a/dist/ba_data/python/bascenev1lib/actor/onscreentimer.py b/dist/ba_data/python/bascenev1lib/actor/onscreentimer.py index 192fecc..11050ee 100644 --- a/dist/ba_data/python/bascenev1lib/actor/onscreentimer.py +++ b/dist/ba_data/python/bascenev1lib/actor/onscreentimer.py @@ -3,10 +3,9 @@ """Defines Actor(s).""" from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override import logging -from typing_extensions import override import bascenev1 as bs if TYPE_CHECKING: diff --git a/dist/ba_data/python/bascenev1lib/actor/playerspaz.py b/dist/ba_data/python/bascenev1lib/actor/playerspaz.py index 2ea3ead..07caae5 100644 --- a/dist/ba_data/python/bascenev1lib/actor/playerspaz.py +++ b/dist/ba_data/python/bascenev1lib/actor/playerspaz.py @@ -4,9 +4,8 @@ from __future__ import annotations -from typing import TYPE_CHECKING, TypeVar, overload +from typing import TYPE_CHECKING, TypeVar, overload, override -from typing_extensions import override import bascenev1 as bs from bascenev1lib.actor.spaz import Spaz @@ -79,14 +78,12 @@ class PlayerSpaz(Spaz): @overload def getplayer( self, playertype: type[PlayerT], doraise: Literal[False] = False - ) -> PlayerT | None: - ... + ) -> PlayerT | None: ... @overload def getplayer( self, playertype: type[PlayerT], doraise: Literal[True] - ) -> PlayerT: - ... + ) -> PlayerT: ... def getplayer( self, playertype: type[PlayerT], doraise: bool = False @@ -225,10 +222,20 @@ class PlayerSpaz(Spaz): elif isinstance(msg, bs.DieMessage): # Report player deaths to the game. if not self._dead: - # Immediate-mode or left-game deaths don't count as 'kills'. - killed = ( - not msg.immediate and msg.how is not bs.DeathType.LEFT_GAME + # Was this player killed while being held? + was_held = self.held_count > 0 and self.last_player_held_by + # Was this player attacked before death? + was_attacked_recently = ( + self.last_player_attacked_by + and bs.time() - self.last_attacked_time < 4.0 ) + # Leaving the game doesn't count as a kill *unless* + # someone does it intentionally while being attacked. + left_game_cleanly = msg.how is bs.DeathType.LEFT_GAME and not ( + was_held or was_attacked_recently + ) + + killed = not (msg.immediate or left_game_cleanly) activity = self._activity() @@ -238,7 +245,7 @@ class PlayerSpaz(Spaz): else: # If this player was being held at the time of death, # the holder is the killer. - if self.held_count > 0 and self.last_player_held_by: + if was_held: killerplayer = self.last_player_held_by else: # Otherwise, if they were attacked by someone in the @@ -248,10 +255,7 @@ class PlayerSpaz(Spaz): # all bot kills would register as suicides; need to # change this from last_player_attacked_by to # something like last_actor_attacked_by to fix that. - if ( - self.last_player_attacked_by - and bs.time() - self.last_attacked_time < 4.0 - ): + if was_attacked_recently: killerplayer = self.last_player_attacked_by else: # ok, call it a suicide unless we're in co-op diff --git a/dist/ba_data/python/bascenev1lib/actor/popuptext.py b/dist/ba_data/python/bascenev1lib/actor/popuptext.py index 6fdd41d..ca375fc 100644 --- a/dist/ba_data/python/bascenev1lib/actor/popuptext.py +++ b/dist/ba_data/python/bascenev1lib/actor/popuptext.py @@ -5,9 +5,8 @@ from __future__ import annotations import random -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override -from typing_extensions import override import bascenev1 as bs if TYPE_CHECKING: diff --git a/dist/ba_data/python/bascenev1lib/actor/powerupbox.py b/dist/ba_data/python/bascenev1lib/actor/powerupbox.py index 1b95e4d..c6ebd56 100644 --- a/dist/ba_data/python/bascenev1lib/actor/powerupbox.py +++ b/dist/ba_data/python/bascenev1lib/actor/powerupbox.py @@ -5,9 +5,8 @@ from __future__ import annotations import random -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override -from typing_extensions import override import bascenev1 as bs from bascenev1lib.gameutils import SharedObjects diff --git a/dist/ba_data/python/bascenev1lib/actor/respawnicon.py b/dist/ba_data/python/bascenev1lib/actor/respawnicon.py index 72a14c9..59aa881 100644 --- a/dist/ba_data/python/bascenev1lib/actor/respawnicon.py +++ b/dist/ba_data/python/bascenev1lib/actor/respawnicon.py @@ -22,7 +22,9 @@ class RespawnIcon: def __init__(self, player: bs.Player, respawn_time: float): """Instantiate with a Player and respawn_time (in seconds).""" + # pylint: disable=too-many-locals self._visible = True + self._dots_epic_only = False on_right, offs_extra, respawn_icons = self._get_context(player) @@ -92,7 +94,7 @@ class RespawnIcon: assert self._name.node bs.animate(self._name.node, 'scale', {0: 0, 0.1: 0.5}) - tpos = (-60 - h_offs if on_right else 60 + h_offs, -192 + offs) + tpos = (-60 - h_offs if on_right else 60 + h_offs, -193 + offs) self._text: bs.NodeActor | None = bs.NodeActor( bs.newnode( 'text', @@ -109,11 +111,37 @@ class RespawnIcon: }, ) ) + dpos = [ipos[0] + (7 if on_right else -7), ipos[1] - 16] + self._dec_text: bs.NodeActor | None = None + if ( + self._dots_epic_only + and bs.getactivity().globalsnode.slow_motion + or not self._dots_epic_only + ): + self._dec_text = bs.NodeActor( + bs.newnode( + 'text', + attrs={ + 'position': dpos, + 'h_attach': 'right' if on_right else 'left', + 'h_align': 'right' if on_right else 'left', + 'scale': 0.65, + 'shadow': 0.5, + 'flatness': 0.5, + 'v_attach': 'top', + 'color': bs.safecolor(icon['tint_color']), + 'text': '', + }, + ) + ) assert self._text.node bs.animate(self._text.node, 'scale', {0: 0, 0.1: 0.9}) + if self._dec_text: + bs.animate(self._dec_text.node, 'scale', {0: 0, 0.1: 0.65}) self._respawn_time = bs.time() + respawn_time + self._dec_timer: bs.Timer | None = None self._update() self._timer: bs.Timer | None = bs.Timer( 1.0, bs.WeakCall(self._update), repeat=True @@ -128,7 +156,7 @@ class RespawnIcon: """Return info on where we should be shown and stored.""" activity = bs.getactivity() - if isinstance(bs.getsession(), bs.DualTeamSession): + if isinstance(activity.session, bs.DualTeamSession): on_right = player.team.id % 2 == 1 # Store a list of icons in the team. @@ -153,12 +181,43 @@ class RespawnIcon: offs_extra = -20 return on_right, offs_extra, icons + def _dec_step(self, display: list) -> None: + if not self._dec_text: + self._dec_timer = None + return + old_text: bs.Lstr | str = self._dec_text.node.text + iterate: int + # Get the following display text using our current one. + try: + iterate = display.index(old_text) + 1 + # If we don't match any in the display list, we + # can assume we've just started iterating. + except ValueError: + iterate = 0 + # Kill the timer if we're at the last iteration. + if iterate >= len(display): + self._dec_timer = None + return + self._dec_text.node.text = display[iterate] + def _update(self) -> None: remaining = int(round(self._respawn_time - bs.time())) + if remaining > 0: assert self._text is not None if self._text.node: self._text.node.text = str(remaining) + if self._dec_text: + # Display our decimal dots. + self._dec_text.node.text = '...' + # Start the timer to tick down. + self._dec_timer = bs.Timer( + 0.25, + bs.WeakCall(self._dec_step, ['..', '.', '']), + repeat=True, + ) else: self._visible = False - self._image = self._text = self._timer = self._name = None + self._image = self._text = self._dec_text = self._timer = ( + self._name + ) = None diff --git a/dist/ba_data/python/bascenev1lib/actor/spaz.py b/dist/ba_data/python/bascenev1lib/actor/spaz.py index ad914b8..f70383f 100644 --- a/dist/ba_data/python/bascenev1lib/actor/spaz.py +++ b/dist/ba_data/python/bascenev1lib/actor/spaz.py @@ -7,13 +7,12 @@ from __future__ import annotations import random import logging -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override -from typing_extensions import override import bascenev1 as bs from bascenev1lib.actor.bomb import Bomb, Blast -from bascenev1lib.actor.powerupbox import PowerupBoxFactory +from bascenev1lib.actor.powerupbox import PowerupBoxFactory, PowerupBox from bascenev1lib.actor.spazfactory import SpazFactory from bascenev1lib.gameutils import SharedObjects @@ -68,6 +67,7 @@ class Spaz(bs.Actor): default_bomb_type = 'normal' default_boxing_gloves = False default_shields = False + default_hitpoints = 1000 def __init__( self, @@ -174,8 +174,8 @@ class Spaz(bs.Actor): setattr(node, attr, val) bs.timer(1.0, bs.Call(_safesetattr, self.node, 'invincible', False)) - self.hitpoints = 1000 - self.hitpoints_max = 1000 + self.hitpoints = self.default_hitpoints + self.hitpoints_max = self.default_hitpoints self.shield_hitpoints: int | None = None self.shield_hitpoints_max = 650 self.shield_decay_rate = 0 @@ -629,7 +629,8 @@ class Spaz(bs.Actor): 1000.0 * (tval + self.curse_time) ) self._curse_timer = bs.Timer( - 5.0, bs.WeakCall(self.handlemessage, CurseExplodeMessage()) + self.curse_time, + bs.WeakCall(self.handlemessage, CurseExplodeMessage()), ) def equip_boxing_gloves(self) -> None: @@ -1227,6 +1228,10 @@ class Spaz(bs.Actor): return None node = bs.getcollision().opposingnode + # Don't want to physically affect powerups. + if node.getdelegate(PowerupBox): + return None + # Only allow one hit per node per punch. if node and (node not in self._punched_nodes): punch_momentum_angular = ( diff --git a/dist/ba_data/python/bascenev1lib/actor/spazbot.py b/dist/ba_data/python/bascenev1lib/actor/spazbot.py index bdd8c27..56118af 100644 --- a/dist/ba_data/python/bascenev1lib/actor/spazbot.py +++ b/dist/ba_data/python/bascenev1lib/actor/spazbot.py @@ -8,9 +8,8 @@ from __future__ import annotations import random import weakref import logging -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override -from typing_extensions import override import bascenev1 as bs from bascenev1lib.actor.spaz import Spaz @@ -776,7 +775,6 @@ class ChargerBotPro(ChargerBot): color = PRO_BOT_COLOR highlight = PRO_BOT_HIGHLIGHT - default_shields = True default_boxing_gloves = True points_mult = 3 diff --git a/dist/ba_data/python/bascenev1lib/actor/text.py b/dist/ba_data/python/bascenev1lib/actor/text.py index 1569851..cdea37d 100644 --- a/dist/ba_data/python/bascenev1lib/actor/text.py +++ b/dist/ba_data/python/bascenev1lib/actor/text.py @@ -5,9 +5,8 @@ from __future__ import annotations from enum import Enum -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override -from typing_extensions import override import bascenev1 as bs if TYPE_CHECKING: diff --git a/dist/ba_data/python/bascenev1lib/actor/tipstext.py b/dist/ba_data/python/bascenev1lib/actor/tipstext.py index 7a6335f..9c16168 100644 --- a/dist/ba_data/python/bascenev1lib/actor/tipstext.py +++ b/dist/ba_data/python/bascenev1lib/actor/tipstext.py @@ -4,9 +4,8 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override -from typing_extensions import override import bascenev1 as bs if TYPE_CHECKING: @@ -73,9 +72,11 @@ class TipsText(bs.Actor): next_tip = bs.Lstr( translate=( 'tips', - bs.app.classic.get_next_tip() - if bs.app.classic is not None - else '', + ( + bs.app.classic.get_next_tip() + if bs.app.classic is not None + else '' + ), ), subs=[('${REMOTE_APP_NAME}', get_remote_app_name())], ) diff --git a/dist/ba_data/python/bascenev1lib/actor/zoomtext.py b/dist/ba_data/python/bascenev1lib/actor/zoomtext.py index 12cdb75..46525aa 100644 --- a/dist/ba_data/python/bascenev1lib/actor/zoomtext.py +++ b/dist/ba_data/python/bascenev1lib/actor/zoomtext.py @@ -6,9 +6,8 @@ from __future__ import annotations import random import logging -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override -from typing_extensions import override import bascenev1 as bs if TYPE_CHECKING: diff --git a/dist/ba_data/python/bascenev1lib/game/assault.py b/dist/ba_data/python/bascenev1lib/game/assault.py index 12eb122..fa50f2c 100644 --- a/dist/ba_data/python/bascenev1lib/game/assault.py +++ b/dist/ba_data/python/bascenev1lib/game/assault.py @@ -8,9 +8,8 @@ from __future__ import annotations import random -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override -from typing_extensions import override import bascenev1 as bs from bascenev1lib.actor.playerspaz import PlayerSpaz diff --git a/dist/ba_data/python/bascenev1lib/game/capturetheflag.py b/dist/ba_data/python/bascenev1lib/game/capturetheflag.py index 837c3be..a5e65ed 100644 --- a/dist/ba_data/python/bascenev1lib/game/capturetheflag.py +++ b/dist/ba_data/python/bascenev1lib/game/capturetheflag.py @@ -8,9 +8,8 @@ from __future__ import annotations import logging -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override -from typing_extensions import override import bascenev1 as bs from bascenev1lib.actor.playerspaz import PlayerSpaz @@ -527,7 +526,38 @@ class CaptureTheFlagGame(bs.TeamGameActivity[Player, Team]): team.touch_return_timer = None team.touch_return_timer_ticking = None if team.flag_return_touches < 0: - logging.exception('CTF flag_return_touches < 0') + logging.error('CTF flag_return_touches < 0', stack_info=True) + + def _handle_death_flag_capture(self, player: Player) -> None: + """Handles flag values when a player dies or leaves the game.""" + # Don't do anything if the player hasn't touched the flag at all. + if not player.touching_own_flag: + return + + team = player.team + + # For each "point" our player has touched theflag (Could be + # multiple), deduct one from both our player and the flag's + # return touches variable. + for _ in range(player.touching_own_flag): + # Deduct + player.touching_own_flag -= 1 + + # (This was only incremented if we have non-zero + # return-times). + if float(self.flag_touch_return_time) > 0.0: + team.flag_return_touches -= 1 + # Update our flag's timer accordingly + # (Prevents immediate resets in case + # there might be more people touching it). + if team.flag_return_touches == 0: + team.touch_return_timer = None + team.touch_return_timer_ticking = None + # Safety check, just to be sure! + if team.flag_return_touches < 0: + logging.error( + 'CTF flag_return_touches < 0', stack_info=True + ) def _flash_base(self, team: Team, length: float = 2.0) -> None: light = bs.newnode( @@ -591,6 +621,7 @@ class CaptureTheFlagGame(bs.TeamGameActivity[Player, Team]): def handlemessage(self, msg: Any) -> Any: if isinstance(msg, bs.PlayerDiedMessage): super().handlemessage(msg) # Augment standard behavior. + self._handle_death_flag_capture(msg.getplayer(Player)) self.respawn_player(msg.getplayer(Player)) elif isinstance(msg, FlagDiedMessage): @@ -617,3 +648,8 @@ class CaptureTheFlagGame(bs.TeamGameActivity[Player, Team]): else: super().handlemessage(msg) + + @override + def on_player_leave(self, player: Player) -> None: + """Prevents leaving players from capturing their flag.""" + self._handle_death_flag_capture(player) diff --git a/dist/ba_data/python/bascenev1lib/game/chosenone.py b/dist/ba_data/python/bascenev1lib/game/chosenone.py index aba373d..1716010 100644 --- a/dist/ba_data/python/bascenev1lib/game/chosenone.py +++ b/dist/ba_data/python/bascenev1lib/game/chosenone.py @@ -8,9 +8,8 @@ from __future__ import annotations import logging -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override -from typing_extensions import override import bascenev1 as bs from bascenev1lib.actor.flag import Flag diff --git a/dist/ba_data/python/bascenev1lib/game/conquest.py b/dist/ba_data/python/bascenev1lib/game/conquest.py index d50041f..7c14011 100644 --- a/dist/ba_data/python/bascenev1lib/game/conquest.py +++ b/dist/ba_data/python/bascenev1lib/game/conquest.py @@ -8,9 +8,8 @@ from __future__ import annotations import random -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override -from typing_extensions import override import bascenev1 as bs from bascenev1lib.actor.flag import Flag diff --git a/dist/ba_data/python/bascenev1lib/game/deathmatch.py b/dist/ba_data/python/bascenev1lib/game/deathmatch.py index 7721b63..85ff142 100644 --- a/dist/ba_data/python/bascenev1lib/game/deathmatch.py +++ b/dist/ba_data/python/bascenev1lib/game/deathmatch.py @@ -7,9 +7,8 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override -from typing_extensions import override import bascenev1 as bs from bascenev1lib.actor.playerspaz import PlayerSpaz diff --git a/dist/ba_data/python/bascenev1lib/game/easteregghunt.py b/dist/ba_data/python/bascenev1lib/game/easteregghunt.py index e7159f8..260628a 100644 --- a/dist/ba_data/python/bascenev1lib/game/easteregghunt.py +++ b/dist/ba_data/python/bascenev1lib/game/easteregghunt.py @@ -8,9 +8,8 @@ from __future__ import annotations import random -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override -from typing_extensions import override import bascenev1 as bs from bascenev1lib.actor.bomb import Bomb diff --git a/dist/ba_data/python/bascenev1lib/game/elimination.py b/dist/ba_data/python/bascenev1lib/game/elimination.py index 5628fae..c0434fb 100644 --- a/dist/ba_data/python/bascenev1lib/game/elimination.py +++ b/dist/ba_data/python/bascenev1lib/game/elimination.py @@ -8,9 +8,8 @@ from __future__ import annotations import logging -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override -from typing_extensions import override import bascenev1 as bs from bascenev1lib.actor.spazfactory import SpazFactory @@ -478,7 +477,7 @@ class EliminationGame(bs.TeamGameActivity[Player, Team]): points.append( ((start_pos - player_pos).length(), start_pos) ) - # Hmm.. we need to sorting vectors too? + # Hmm.. we need to sort vectors too? points.sort(key=lambda x: x[0]) return points[-1][1] return None diff --git a/dist/ba_data/python/bascenev1lib/game/football.py b/dist/ba_data/python/bascenev1lib/game/football.py index 1c99555..3555794 100644 --- a/dist/ba_data/python/bascenev1lib/game/football.py +++ b/dist/ba_data/python/bascenev1lib/game/football.py @@ -11,9 +11,8 @@ from __future__ import annotations import math import random import logging -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override -from typing_extensions import override import bascenev1 as bs from bascenev1lib.actor.bomb import TNTSpawner diff --git a/dist/ba_data/python/bascenev1lib/game/hockey.py b/dist/ba_data/python/bascenev1lib/game/hockey.py index 64487e5..c8b8449 100644 --- a/dist/ba_data/python/bascenev1lib/game/hockey.py +++ b/dist/ba_data/python/bascenev1lib/game/hockey.py @@ -7,9 +7,8 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override -from typing_extensions import override import bascenev1 as bs from bascenev1lib.actor.playerspaz import PlayerSpaz diff --git a/dist/ba_data/python/bascenev1lib/game/keepaway.py b/dist/ba_data/python/bascenev1lib/game/keepaway.py index 416723e..874b03e 100644 --- a/dist/ba_data/python/bascenev1lib/game/keepaway.py +++ b/dist/ba_data/python/bascenev1lib/game/keepaway.py @@ -9,9 +9,8 @@ from __future__ import annotations import logging from enum import Enum -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override -from typing_extensions import override import bascenev1 as bs from bascenev1lib.actor.playerspaz import PlayerSpaz diff --git a/dist/ba_data/python/bascenev1lib/game/kingofthehill.py b/dist/ba_data/python/bascenev1lib/game/kingofthehill.py index 907b434..24244f2 100644 --- a/dist/ba_data/python/bascenev1lib/game/kingofthehill.py +++ b/dist/ba_data/python/bascenev1lib/game/kingofthehill.py @@ -9,9 +9,8 @@ from __future__ import annotations import weakref from enum import Enum -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override -from typing_extensions import override import bascenev1 as bs from bascenev1lib.actor.flag import Flag diff --git a/dist/ba_data/python/bascenev1lib/game/meteorshower.py b/dist/ba_data/python/bascenev1lib/game/meteorshower.py index a9a70ae..7596632 100644 --- a/dist/ba_data/python/bascenev1lib/game/meteorshower.py +++ b/dist/ba_data/python/bascenev1lib/game/meteorshower.py @@ -8,9 +8,8 @@ from __future__ import annotations import random -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override -from typing_extensions import override import bascenev1 as bs from bascenev1lib.actor.bomb import Bomb @@ -73,6 +72,7 @@ class MeteorShowerGame(bs.TeamGameActivity[Player, Team]): self._last_player_death_time: float | None = None self._meteor_time = 2.0 self._timer: OnScreenTimer | None = None + self._ended: bool = False # Some base class overrides: self.default_music = ( @@ -161,6 +161,10 @@ class MeteorShowerGame(bs.TeamGameActivity[Player, Team]): return None def _check_end_game(self) -> None: + # We don't want to end this activity more than once. + if self._ended: + return + living_team_count = 0 for team in self.teams: for player in team.players: @@ -270,4 +274,5 @@ class MeteorShowerGame(bs.TeamGameActivity[Player, Team]): # Submit the score value in milliseconds. results.set_team_score(team, int(1000.0 * longest_life)) + self._ended = True self.end(results=results) diff --git a/dist/ba_data/python/bascenev1lib/game/ninjafight.py b/dist/ba_data/python/bascenev1lib/game/ninjafight.py index 98ca8d0..67658cf 100644 --- a/dist/ba_data/python/bascenev1lib/game/ninjafight.py +++ b/dist/ba_data/python/bascenev1lib/game/ninjafight.py @@ -8,9 +8,8 @@ from __future__ import annotations import random -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override -from typing_extensions import override import bascenev1 as bs from bascenev1lib.actor.spazbot import ( diff --git a/dist/ba_data/python/bascenev1lib/game/onslaught.py b/dist/ba_data/python/bascenev1lib/game/onslaught.py index d464413..6933a0c 100644 --- a/dist/ba_data/python/bascenev1lib/game/onslaught.py +++ b/dist/ba_data/python/bascenev1lib/game/onslaught.py @@ -15,9 +15,8 @@ import random import logging from enum import Enum, unique from dataclasses import dataclass -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override -from typing_extensions import override import bascenev1 as bs from bascenev1lib.actor.popuptext import PopupText @@ -334,29 +333,37 @@ class OnslaughtGame(bs.CoopGameActivity[Player, Team]): Wave( base_angle=130, entries=[ - Spawn(BrawlerBotLite, spacing=5) - if player_count > 1 - else None, + ( + Spawn(BrawlerBotLite, spacing=5) + if player_count > 1 + else None + ), Spawn(BrawlerBotLite, spacing=5), Spacing(30), - Spawn(BomberBotLite, spacing=5) - if player_count > 3 - else None, + ( + Spawn(BomberBotLite, spacing=5) + if player_count > 3 + else None + ), Spawn(BomberBotLite, spacing=5), Spacing(30), Spawn(BrawlerBotLite, spacing=5), - Spawn(BrawlerBotLite, spacing=5) - if player_count > 2 - else None, + ( + Spawn(BrawlerBotLite, spacing=5) + if player_count > 2 + else None + ), ], ), Wave( base_angle=195, entries=[ Spawn(TriggerBot, spacing=90), - Spawn(TriggerBot, spacing=90) - if player_count > 1 - else None, + ( + Spawn(TriggerBot, spacing=90) + if player_count > 1 + else None + ), ], ), ] @@ -367,9 +374,11 @@ class OnslaughtGame(bs.CoopGameActivity[Player, Team]): self._waves = [ Wave( entries=[ - Spawn(ChargerBot, Point.LEFT_UPPER_MORE) - if player_count > 2 - else None, + ( + Spawn(ChargerBot, Point.LEFT_UPPER_MORE) + if player_count > 2 + else None + ), Spawn(ChargerBot, Point.LEFT_UPPER), ] ), @@ -377,36 +386,50 @@ class OnslaughtGame(bs.CoopGameActivity[Player, Team]): entries=[ Spawn(BomberBotStaticLite, Point.TURRET_TOP_RIGHT), Spawn(BrawlerBotLite, Point.RIGHT_UPPER), - Spawn(BrawlerBotLite, Point.RIGHT_LOWER) - if player_count > 1 - else None, - Spawn(BomberBotStaticLite, Point.TURRET_BOTTOM_RIGHT) - if player_count > 2 - else None, + ( + Spawn(BrawlerBotLite, Point.RIGHT_LOWER) + if player_count > 1 + else None + ), + ( + Spawn( + BomberBotStaticLite, Point.TURRET_BOTTOM_RIGHT + ) + if player_count > 2 + else None + ), ] ), Wave( entries=[ Spawn(BomberBotStaticLite, Point.TURRET_BOTTOM_LEFT), Spawn(TriggerBot, Point.LEFT), - Spawn(TriggerBot, Point.LEFT_LOWER) - if player_count > 1 - else None, - Spawn(TriggerBot, Point.LEFT_UPPER) - if player_count > 2 - else None, + ( + Spawn(TriggerBot, Point.LEFT_LOWER) + if player_count > 1 + else None + ), + ( + Spawn(TriggerBot, Point.LEFT_UPPER) + if player_count > 2 + else None + ), ] ), Wave( entries=[ Spawn(BrawlerBotLite, Point.TOP_RIGHT), - Spawn(BrawlerBot, Point.TOP_HALF_RIGHT) - if player_count > 1 - else None, + ( + Spawn(BrawlerBot, Point.TOP_HALF_RIGHT) + if player_count > 1 + else None + ), Spawn(BrawlerBotLite, Point.TOP_LEFT), - Spawn(BrawlerBotLite, Point.TOP_HALF_LEFT) - if player_count > 2 - else None, + ( + Spawn(BrawlerBotLite, Point.TOP_HALF_LEFT) + if player_count > 2 + else None + ), Spawn(BrawlerBot, Point.TOP), Spawn(BomberBotStaticLite, Point.TURRET_TOP_MIDDLE), ] @@ -416,12 +439,16 @@ class OnslaughtGame(bs.CoopGameActivity[Player, Team]): Spawn(TriggerBotStatic, Point.TURRET_BOTTOM_LEFT), Spawn(TriggerBotStatic, Point.TURRET_BOTTOM_RIGHT), Spawn(TriggerBot, Point.BOTTOM), - Spawn(TriggerBot, Point.BOTTOM_HALF_RIGHT) - if player_count > 1 - else None, - Spawn(TriggerBot, Point.BOTTOM_HALF_LEFT) - if player_count > 2 - else None, + ( + Spawn(TriggerBot, Point.BOTTOM_HALF_RIGHT) + if player_count > 1 + else None + ), + ( + Spawn(TriggerBot, Point.BOTTOM_HALF_LEFT) + if player_count > 2 + else None + ), ] ), Wave( @@ -429,12 +456,16 @@ class OnslaughtGame(bs.CoopGameActivity[Player, Team]): Spawn(BomberBotStaticLite, Point.TURRET_TOP_LEFT), Spawn(BomberBotStaticLite, Point.TURRET_TOP_RIGHT), Spawn(ChargerBot, Point.BOTTOM), - Spawn(ChargerBot, Point.BOTTOM_HALF_LEFT) - if player_count > 1 - else None, - Spawn(ChargerBot, Point.BOTTOM_HALF_RIGHT) - if player_count > 2 - else None, + ( + Spawn(ChargerBot, Point.BOTTOM_HALF_LEFT) + if player_count > 1 + else None + ), + ( + Spawn(ChargerBot, Point.BOTTOM_HALF_RIGHT) + if player_count > 2 + else None + ), ] ), ] @@ -446,44 +477,62 @@ class OnslaughtGame(bs.CoopGameActivity[Player, Team]): Wave( base_angle=-50, entries=[ - Spawn(BrawlerBot, spacing=12) - if player_count > 3 - else None, + ( + Spawn(BrawlerBot, spacing=12) + if player_count > 3 + else None + ), Spawn(BrawlerBot, spacing=12), Spawn(BomberBot, spacing=6), - Spawn(BomberBot, spacing=6) - if self._preset is Preset.PRO - else None, - Spawn(BomberBot, spacing=6) - if player_count > 1 - else None, + ( + Spawn(BomberBot, spacing=6) + if self._preset is Preset.PRO + else None + ), + ( + Spawn(BomberBot, spacing=6) + if player_count > 1 + else None + ), Spawn(BrawlerBot, spacing=12), - Spawn(BrawlerBot, spacing=12) - if player_count > 2 - else None, + ( + Spawn(BrawlerBot, spacing=12) + if player_count > 2 + else None + ), ], ), Wave( base_angle=180, entries=[ - Spawn(BrawlerBot, spacing=6) - if player_count > 3 - else None, - Spawn(BrawlerBot, spacing=6) - if self._preset is Preset.PRO - else None, + ( + Spawn(BrawlerBot, spacing=6) + if player_count > 3 + else None + ), + ( + Spawn(BrawlerBot, spacing=6) + if self._preset is Preset.PRO + else None + ), Spawn(BrawlerBot, spacing=6), Spawn(ChargerBot, spacing=45), - Spawn(ChargerBot, spacing=45) - if player_count > 1 - else None, + ( + Spawn(ChargerBot, spacing=45) + if player_count > 1 + else None + ), Spawn(BrawlerBot, spacing=6), - Spawn(BrawlerBot, spacing=6) - if self._preset is Preset.PRO - else None, - Spawn(BrawlerBot, spacing=6) - if player_count > 2 - else None, + ( + Spawn(BrawlerBot, spacing=6) + if self._preset is Preset.PRO + else None + ), + ( + Spawn(BrawlerBot, spacing=6) + if player_count > 2 + else None + ), ], ), Wave( @@ -492,15 +541,21 @@ class OnslaughtGame(bs.CoopGameActivity[Player, Team]): Spawn(ChargerBot, spacing=30), Spawn(TriggerBot, spacing=30), Spawn(TriggerBot, spacing=30), - Spawn(TriggerBot, spacing=30) - if self._preset is Preset.PRO - else None, - Spawn(TriggerBot, spacing=30) - if player_count > 1 - else None, - Spawn(TriggerBot, spacing=30) - if player_count > 3 - else None, + ( + Spawn(TriggerBot, spacing=30) + if self._preset is Preset.PRO + else None + ), + ( + Spawn(TriggerBot, spacing=30) + if player_count > 1 + else None + ), + ( + Spawn(TriggerBot, spacing=30) + if player_count > 3 + else None + ), Spawn(ChargerBot, spacing=30), ], ), @@ -508,16 +563,22 @@ class OnslaughtGame(bs.CoopGameActivity[Player, Team]): base_angle=90, entries=[ Spawn(StickyBot, spacing=50), - Spawn(StickyBot, spacing=50) - if self._preset is Preset.PRO - else None, + ( + Spawn(StickyBot, spacing=50) + if self._preset is Preset.PRO + else None + ), Spawn(StickyBot, spacing=50), - Spawn(StickyBot, spacing=50) - if player_count > 1 - else None, - Spawn(StickyBot, spacing=50) - if player_count > 3 - else None, + ( + Spawn(StickyBot, spacing=50) + if player_count > 1 + else None + ), + ( + Spawn(StickyBot, spacing=50) + if player_count > 3 + else None + ), ], ), Wave( @@ -525,14 +586,18 @@ class OnslaughtGame(bs.CoopGameActivity[Player, Team]): entries=[ Spawn(TriggerBot, spacing=72), Spawn(TriggerBot, spacing=72), - Spawn(TriggerBot, spacing=72) - if self._preset is Preset.PRO - else None, + ( + Spawn(TriggerBot, spacing=72) + if self._preset is Preset.PRO + else None + ), Spawn(TriggerBot, spacing=72), Spawn(TriggerBot, spacing=72), - Spawn(TriggerBot, spacing=36) - if player_count > 2 - else None, + ( + Spawn(TriggerBot, spacing=36) + if player_count > 2 + else None + ), ], ), Wave( @@ -540,15 +605,21 @@ class OnslaughtGame(bs.CoopGameActivity[Player, Team]): entries=[ Spawn(ChargerBotProShielded, spacing=50), Spawn(ChargerBotProShielded, spacing=50), - Spawn(ChargerBotProShielded, spacing=50) - if self._preset is Preset.PRO - else None, - Spawn(ChargerBotProShielded, spacing=50) - if player_count > 1 - else None, - Spawn(ChargerBotProShielded, spacing=50) - if player_count > 2 - else None, + ( + Spawn(ChargerBotProShielded, spacing=50) + if self._preset is Preset.PRO + else None + ), + ( + Spawn(ChargerBotProShielded, spacing=50) + if player_count > 1 + else None + ), + ( + Spawn(ChargerBotProShielded, spacing=50) + if player_count > 2 + else None + ), ], ), ] @@ -566,15 +637,21 @@ class OnslaughtGame(bs.CoopGameActivity[Player, Team]): self._waves = [ Wave( entries=[ - Spawn(BomberBotProStatic, Point.TURRET_TOP_MIDDLE_LEFT) - if hard - else None, + ( + Spawn( + BomberBotProStatic, Point.TURRET_TOP_MIDDLE_LEFT + ) + if hard + else None + ), Spawn( BomberBotProStatic, Point.TURRET_TOP_MIDDLE_RIGHT ), - Spawn(BomberBotProStatic, Point.TURRET_TOP_LEFT) - if player_count > 2 - else None, + ( + Spawn(BomberBotProStatic, Point.TURRET_TOP_LEFT) + if player_count > 2 + else None + ), Spawn(ExplodeyBot, Point.TOP_RIGHT), Delay(4.0), Spawn(ExplodeyBot, Point.TOP_LEFT), @@ -584,9 +661,11 @@ class OnslaughtGame(bs.CoopGameActivity[Player, Team]): entries=[ Spawn(ChargerBot, Point.LEFT), Spawn(ChargerBot, Point.RIGHT), - Spawn(ChargerBot, Point.RIGHT_UPPER_MORE) - if player_count > 2 - else None, + ( + Spawn(ChargerBot, Point.RIGHT_UPPER_MORE) + if player_count > 2 + else None + ), Spawn(BomberBotProStatic, Point.TURRET_TOP_LEFT), Spawn(BomberBotProStatic, Point.TURRET_TOP_RIGHT), ] @@ -594,29 +673,39 @@ class OnslaughtGame(bs.CoopGameActivity[Player, Team]): Wave( entries=[ Spawn(TriggerBotPro, Point.TOP_RIGHT), - Spawn(TriggerBotPro, Point.RIGHT_UPPER_MORE) - if player_count > 1 - else None, + ( + Spawn(TriggerBotPro, Point.RIGHT_UPPER_MORE) + if player_count > 1 + else None + ), Spawn(TriggerBotPro, Point.RIGHT_UPPER), - Spawn(TriggerBotPro, Point.RIGHT_LOWER) - if hard - else None, - Spawn(TriggerBotPro, Point.RIGHT_LOWER_MORE) - if player_count > 2 - else None, + ( + Spawn(TriggerBotPro, Point.RIGHT_LOWER) + if hard + else None + ), + ( + Spawn(TriggerBotPro, Point.RIGHT_LOWER_MORE) + if player_count > 2 + else None + ), Spawn(TriggerBotPro, Point.BOTTOM_RIGHT), ] ), Wave( entries=[ Spawn(ChargerBotProShielded, Point.BOTTOM_RIGHT), - Spawn(ChargerBotProShielded, Point.BOTTOM) - if player_count > 2 - else None, + ( + Spawn(ChargerBotProShielded, Point.BOTTOM) + if player_count > 2 + else None + ), Spawn(ChargerBotProShielded, Point.BOTTOM_LEFT), - Spawn(ChargerBotProShielded, Point.TOP) - if hard - else None, + ( + Spawn(ChargerBotProShielded, Point.TOP) + if hard + else None + ), Spawn(BomberBotProStatic, Point.TURRET_TOP_MIDDLE), ] ), @@ -643,12 +732,21 @@ class OnslaughtGame(bs.CoopGameActivity[Player, Team]): Spawn(BomberBotProStatic, Point.TURRET_TOP_RIGHT), Spawn(BomberBotProStatic, Point.TURRET_BOTTOM_LEFT), Spawn(BomberBotProStatic, Point.TURRET_BOTTOM_RIGHT), - Spawn(BomberBotProStatic, Point.TURRET_TOP_MIDDLE_LEFT) - if hard - else None, - Spawn(BomberBotProStatic, Point.TURRET_TOP_MIDDLE_RIGHT) - if hard - else None, + ( + Spawn( + BomberBotProStatic, Point.TURRET_TOP_MIDDLE_LEFT + ) + if hard + else None + ), + ( + Spawn( + BomberBotProStatic, + Point.TURRET_TOP_MIDDLE_RIGHT, + ) + if hard + else None + ), ] ), ] @@ -667,12 +765,14 @@ class OnslaughtGame(bs.CoopGameActivity[Player, Team]): # Spit out a few powerups and start dropping more shortly. self._drop_powerups( standard_points=True, - poweruptype='curse' - if self._preset in [Preset.UBER, Preset.UBER_EASY] - else ( - 'land_mines' - if self._preset in [Preset.ROOKIE, Preset.ROOKIE_EASY] - else None + poweruptype=( + 'curse' + if self._preset in [Preset.UBER, Preset.UBER_EASY] + else ( + 'land_mines' + if self._preset in [Preset.ROOKIE, Preset.ROOKIE_EASY] + else None + ) ), ) bs.timer(4.0, self._start_powerup_drops) diff --git a/dist/ba_data/python/bascenev1lib/game/race.py b/dist/ba_data/python/bascenev1lib/game/race.py index 4a35fa4..c555616 100644 --- a/dist/ba_data/python/bascenev1lib/game/race.py +++ b/dist/ba_data/python/bascenev1lib/game/race.py @@ -9,10 +9,9 @@ from __future__ import annotations import random import logging -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override from dataclasses import dataclass -from typing_extensions import override import bascenev1 as bs from bascenev1lib.actor.bomb import Bomb @@ -778,9 +777,11 @@ class RaceGame(bs.TeamGameActivity[Player, Team]): assert self._timer is not None if self._timer.has_started(): self._timer.stop( - endtime=None - if self._last_team_time is None - else (self._timer.getstarttime() + self._last_team_time) + endtime=( + None + if self._last_team_time is None + else (self._timer.getstarttime() + self._last_team_time) + ) ) results = bs.GameResults() diff --git a/dist/ba_data/python/bascenev1lib/game/runaround.py b/dist/ba_data/python/bascenev1lib/game/runaround.py index b970d35..8d0b993 100644 --- a/dist/ba_data/python/bascenev1lib/game/runaround.py +++ b/dist/ba_data/python/bascenev1lib/game/runaround.py @@ -14,9 +14,8 @@ import random import logging from enum import Enum from dataclasses import dataclass -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, cast, Sequence, override -from typing_extensions import override import bascenev1 as bs from bascenev1lib.actor.popuptext import PopupText @@ -45,7 +44,7 @@ from bascenev1lib.actor.spazbot import ( ) if TYPE_CHECKING: - from typing import Any, Sequence + from typing import Any class Preset(Enum): @@ -190,6 +189,7 @@ class RunaroundGame(bs.CoopGameActivity[Player, Team]): self._lives_text: bs.NodeActor | None = None self._flawless = True self._time_bonus_timer: bs.Timer | None = None + self._lives_hbtime: bs.Timer | None = None self._time_bonus_text: bs.NodeActor | None = None self._time_bonus_mult: float | None = None self._wave_text: bs.NodeActor | None = None @@ -279,9 +279,11 @@ class RunaroundGame(bs.CoopGameActivity[Player, Team]): Spacing(duration=1.0), Spawn(TriggerBot, path=3), Spacing(duration=1.0), - Spawn(TriggerBot, path=1) - if (player_count > 1 and hard) - else None, + ( + Spawn(TriggerBot, path=1) + if (player_count > 1 and hard) + else None + ), Spacing(duration=1.0), Spawn(TriggerBot, path=2) if player_count > 2 else None, Spacing(duration=1.0), @@ -320,17 +322,23 @@ class RunaroundGame(bs.CoopGameActivity[Player, Team]): Spacing(duration=1.5), Spawn(BomberBotProShielded, path=1) if hard else None, Spacing(duration=1.5) if hard else None, - Spawn(BomberBotProShielded, path=3) - if player_count > 1 - else None, + ( + Spawn(BomberBotProShielded, path=3) + if player_count > 1 + else None + ), Spacing(duration=1.5), - Spawn(BomberBotProShielded, path=2) - if player_count > 2 - else None, + ( + Spawn(BomberBotProShielded, path=2) + if player_count > 2 + else None + ), Spacing(duration=1.5), - Spawn(BomberBotProShielded, path=1) - if player_count > 3 - else None, + ( + Spawn(BomberBotProShielded, path=1) + if player_count > 3 + else None + ), ] ), ] @@ -352,9 +360,11 @@ class RunaroundGame(bs.CoopGameActivity[Player, Team]): BrawlerBotPro if hard else BrawlerBot, point=Point.BOTTOM_LEFT, ), - Spawn(BrawlerBotPro, point=Point.BOTTOM_RIGHT) - if player_count > 2 - else None, + ( + Spawn(BrawlerBotPro, point=Point.BOTTOM_RIGHT) + if player_count > 2 + else None + ), ] ), Wave( @@ -375,9 +385,11 @@ class RunaroundGame(bs.CoopGameActivity[Player, Team]): Spawn(BomberBotProShielded, path=3), Spawn(BomberBotProShielded, path=3), Spawn(ChargerBot, point=Point.BOTTOM_RIGHT), - Spawn(ChargerBot, point=Point.BOTTOM_LEFT) - if player_count > 2 - else None, + ( + Spawn(ChargerBot, point=Point.BOTTOM_LEFT) + if player_count > 2 + else None + ), ] ), Wave( @@ -388,12 +400,16 @@ class RunaroundGame(bs.CoopGameActivity[Player, Team]): Spawn(TriggerBotPro, path=1 if hard else 2), Spawn(TriggerBotPro, path=1 if hard else 2), Spawn(TriggerBotPro, path=1 if hard else 2), - Spawn(TriggerBotPro, path=1 if hard else 2) - if player_count > 1 - else None, - Spawn(TriggerBotPro, path=1 if hard else 2) - if player_count > 3 - else None, + ( + Spawn(TriggerBotPro, path=1 if hard else 2) + if player_count > 1 + else None + ), + ( + Spawn(TriggerBotPro, path=1 if hard else 2) + if player_count > 3 + else None + ), ] ), Wave( @@ -402,12 +418,20 @@ class RunaroundGame(bs.CoopGameActivity[Player, Team]): TriggerBotProShielded if hard else TriggerBotPro, point=Point.BOTTOM_LEFT, ), - Spawn(TriggerBotProShielded, point=Point.BOTTOM_RIGHT) - if hard - else None, - Spawn(TriggerBotProShielded, point=Point.BOTTOM_RIGHT) - if player_count > 2 - else None, + ( + Spawn( + TriggerBotProShielded, point=Point.BOTTOM_RIGHT + ) + if hard + else None + ), + ( + Spawn( + TriggerBotProShielded, point=Point.BOTTOM_RIGHT + ) + if player_count > 2 + else None + ), Spawn(BomberBot, path=3), Spawn(BomberBot, path=3), Spacing(duration=5.0), @@ -425,15 +449,19 @@ class RunaroundGame(bs.CoopGameActivity[Player, Team]): Spawn(StickyBot, point=Point.BOTTOM_RIGHT), Spawn(BomberBotProShielded, path=2), Spawn(BomberBotProShielded, path=2), - Spawn(StickyBot, point=Point.BOTTOM_RIGHT) - if player_count > 2 - else None, + ( + Spawn(StickyBot, point=Point.BOTTOM_RIGHT) + if player_count > 2 + else None + ), Spawn(BomberBotProShielded, path=2), Spawn(ExplodeyBot, point=Point.BOTTOM_LEFT), Spawn(BomberBotProShielded, path=2), - Spawn(BomberBotProShielded, path=2) - if player_count > 1 - else None, + ( + Spawn(BomberBotProShielded, path=2) + if player_count > 1 + else None + ), Spacing(duration=5.0), Spawn(StickyBot, point=Point.BOTTOM_LEFT), Spacing(duration=2.0), @@ -461,9 +489,7 @@ class RunaroundGame(bs.CoopGameActivity[Player, Team]): l_offs = ( -80 if uiscale is bs.UIScale.SMALL - else -40 - if uiscale is bs.UIScale.MEDIUM - else 0 + else -40 if uiscale is bs.UIScale.MEDIUM else 0 ) self._lives_bg = bs.NodeActor( @@ -525,6 +551,18 @@ class RunaroundGame(bs.CoopGameActivity[Player, Team]): if self._lives == 0: self._bots.stop_moving() self.continue_or_end_game() + + # Heartbeat behavior + if self._lives < 5: + hbtime = 0.39 + (0.21 * self._lives) + self._lives_hbtime = bs.Timer( + hbtime, lambda: self.heart_dyin(True, hbtime), repeat=True + ) + self.heart_dyin(True) + else: + self._lives_hbtime = None + self.heart_dyin(False) + assert self._lives_text is not None assert self._lives_text.node self._lives_text.node.text = str(self._lives) @@ -1366,3 +1404,43 @@ class RunaroundGame(bs.CoopGameActivity[Player, Team]): def _set_can_end_wave(self) -> None: self._can_end_wave = True + + def heart_dyin(self, status: bool, time: float = 1.22) -> None: + """Makes the UI heart beat at low health.""" + assert self._lives_bg is not None + if self._lives_bg.node.exists(): + return + heart = self._lives_bg.node + + # Make the heart beat intensely! + if status: + bs.animate_array( + heart, + 'scale', + 2, + { + 0: (90, 90), + time * 0.1: (105, 105), + time * 0.21: (88, 88), + time * 0.42: (90, 90), + time * 0.52: (105, 105), + time * 0.63: (88, 88), + time: (90, 90), + }, + ) + + # Neutralize heartbeat (Done did when dead.) + else: + # Ew; janky old scenev1 has a single 'Node' Python type so + # it thinks heart.scale could be a few different things + # (float, Sequence[float], etc.). So we have to force the + # issue with a cast(). This should go away with scenev2/etc. + bs.animate_array( + heart, + 'scale', + 2, + { + 0.0: cast(Sequence[float], heart.scale), + time: (90, 90), + }, + ) diff --git a/dist/ba_data/python/bascenev1lib/game/targetpractice.py b/dist/ba_data/python/bascenev1lib/game/targetpractice.py index fdff48f..18d9410 100644 --- a/dist/ba_data/python/bascenev1lib/game/targetpractice.py +++ b/dist/ba_data/python/bascenev1lib/game/targetpractice.py @@ -8,9 +8,8 @@ from __future__ import annotations import random -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override -from typing_extensions import override import bascenev1 as bs from bascenev1lib.actor.scoreboard import Scoreboard @@ -321,11 +320,15 @@ class Target(bs.Actor): bs.getsound( 'orchestraHit4' if streak > 3 - else 'orchestraHit3' - if streak > 2 - else 'orchestraHit2' - if streak > 1 - else 'orchestraHit' + else ( + 'orchestraHit3' + if streak > 2 + else ( + 'orchestraHit2' + if streak > 1 + else 'orchestraHit' + ) + ) ).play() elif dist <= self._r2 + self._rfudge: self._nodes[0].color = cdull diff --git a/dist/ba_data/python/bascenev1lib/game/thelaststand.py b/dist/ba_data/python/bascenev1lib/game/thelaststand.py index 7752c3f..15b7cb9 100644 --- a/dist/ba_data/python/bascenev1lib/game/thelaststand.py +++ b/dist/ba_data/python/bascenev1lib/game/thelaststand.py @@ -7,9 +7,8 @@ from __future__ import annotations import random import logging from dataclasses import dataclass -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override -from typing_extensions import override import bascenev1 as bs from bascenev1lib.actor.playerspaz import PlayerSpaz diff --git a/dist/ba_data/python/bascenev1lib/mainmenu.py b/dist/ba_data/python/bascenev1lib/mainmenu.py index 14d63a1..4e90293 100644 --- a/dist/ba_data/python/bascenev1lib/mainmenu.py +++ b/dist/ba_data/python/bascenev1lib/mainmenu.py @@ -8,9 +8,8 @@ from __future__ import annotations import time import random import weakref -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override -from typing_extensions import override import bascenev1 as bs import bauiv1 as bui @@ -134,8 +133,8 @@ class MainMenuActivity(bs.Activity[bs.Player, bs.Team]): text = bs.Lstr( value='${V} (${B}) (${D})', subs=[ - ('${V}', app.env.version), - ('${B}', str(app.env.build_number)), + ('${V}', app.env.engine_version), + ('${B}', str(app.env.engine_build_number)), ('${D}', bs.Lstr(resource='debugText')), ], ) @@ -143,12 +142,14 @@ class MainMenuActivity(bs.Activity[bs.Player, bs.Team]): text = bs.Lstr( value='${V} (${B})', subs=[ - ('${V}', app.env.version), - ('${B}', str(app.env.build_number)), + ('${V}', app.env.engine_version), + ('${B}', str(app.env.engine_build_number)), ], ) else: - text = bs.Lstr(value='${V}', subs=[('${V}', app.env.version)]) + text = bs.Lstr( + value='${V}', subs=[('${V}', app.env.engine_version)] + ) scale = 0.9 if (uiscale is bs.UIScale.SMALL or vr_mode) else 0.7 color = (1, 1, 1, 1) if vr_mode else (0.5, 0.6, 0.5, 0.7) self.version = bs.NodeActor( diff --git a/dist/ba_data/python/bascenev1lib/maps.py b/dist/ba_data/python/bascenev1lib/maps.py index d473114..04f5b36 100644 --- a/dist/ba_data/python/bascenev1lib/maps.py +++ b/dist/ba_data/python/bascenev1lib/maps.py @@ -5,9 +5,8 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override -from typing_extensions import override import bascenev1 as bs from bascenev1lib.gameutils import SharedObjects diff --git a/dist/ba_data/python/bascenev1lib/tutorial.py b/dist/ba_data/python/bascenev1lib/tutorial.py index 86aa641..119cf6e 100644 --- a/dist/ba_data/python/bascenev1lib/tutorial.py +++ b/dist/ba_data/python/bascenev1lib/tutorial.py @@ -17,9 +17,8 @@ from __future__ import annotations import math import logging from collections import deque -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override -from typing_extensions import override import bascenev1 as bs from bascenev1lib.actor.spaz import Spaz diff --git a/dist/ba_data/python/bauiv1/__init__.py b/dist/ba_data/python/bauiv1/__init__.py index 4f633f3..2e3144b 100644 --- a/dist/ba_data/python/bauiv1/__init__.py +++ b/dist/ba_data/python/bauiv1/__init__.py @@ -70,6 +70,11 @@ from babase import ( native_review_request_supported, NotFoundError, open_file_externally, + open_url, + overlay_web_browser_close, + overlay_web_browser_is_open, + overlay_web_browser_is_supported, + overlay_web_browser_open_url, Permission, Plugin, PluginSpec, @@ -106,7 +111,6 @@ from _bauiv1 import ( imagewidget, is_party_icon_visible, Mesh, - open_url, rowwidget, scrollwidget, set_party_icon_always_visible, @@ -191,6 +195,10 @@ __all__ = [ 'NotFoundError', 'open_file_externally', 'open_url', + 'overlay_web_browser_close', + 'overlay_web_browser_is_open', + 'overlay_web_browser_is_supported', + 'overlay_web_browser_open_url', 'Permission', 'Plugin', 'PluginSpec', diff --git a/dist/ba_data/python/bauiv1/_subsystem.py b/dist/ba_data/python/bauiv1/_subsystem.py index 34d7f5a..c9bd7f5 100644 --- a/dist/ba_data/python/bauiv1/_subsystem.py +++ b/dist/ba_data/python/bauiv1/_subsystem.py @@ -6,9 +6,8 @@ from __future__ import annotations import logging import inspect -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override -from typing_extensions import override import babase import _bauiv1 @@ -43,7 +42,10 @@ class UIV1Subsystem(babase.AppSubsystem): self._uiscale: babase.UIScale - interfacetype = env['ui_scale'] + interfacetype = babase.app.config.get('UI Scale', env['ui_scale']) + if interfacetype == 'auto': + interfacetype = env['ui_scale'] + if interfacetype == 'large': self._uiscale = babase.UIScale.LARGE elif interfacetype == 'medium': diff --git a/dist/ba_data/python/bauiv1/_uitypes.py b/dist/ba_data/python/bauiv1/_uitypes.py index 2f93f22..6431a55 100644 --- a/dist/ba_data/python/bauiv1/_uitypes.py +++ b/dist/ba_data/python/bauiv1/_uitypes.py @@ -7,9 +7,8 @@ from __future__ import annotations import os import weakref from dataclasses import dataclass -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override -from typing_extensions import override import babase import _bauiv1 @@ -164,9 +163,7 @@ class UIController: entrynew = ( self._dialog_stack[-1] if self._dialog_stack - else self._main_stack[-1] - if self._main_stack - else None + else self._main_stack[-1] if self._main_stack else None ) if entrynew is not None: entrynew.create() diff --git a/dist/ba_data/python/bauiv1/onscreenkeyboard.py b/dist/ba_data/python/bauiv1/onscreenkeyboard.py index e91972e..dc02071 100644 --- a/dist/ba_data/python/bauiv1/onscreenkeyboard.py +++ b/dist/ba_data/python/bauiv1/onscreenkeyboard.py @@ -41,15 +41,13 @@ class OnScreenKeyboardWindow(Window): scale=( 2.0 if uiscale is babase.UIScale.SMALL - else 1.5 - if uiscale is babase.UIScale.MEDIUM - else 1.0 + else 1.5 if uiscale is babase.UIScale.MEDIUM else 1.0 + ), + stack_offset=( + (0, 0) + if uiscale is babase.UIScale.SMALL + else (0, 0) if uiscale is babase.UIScale.MEDIUM else (0, 0) ), - stack_offset=(0, 0) - if uiscale is babase.UIScale.SMALL - else (0, 0) - if uiscale is babase.UIScale.MEDIUM - else (0, 0), ) ) self._cancel_button = _bauiv1.buttonwidget( @@ -300,9 +298,11 @@ class OnScreenKeyboardWindow(Window): chars = [c.upper() for c in chars] _bauiv1.buttonwidget( edit=self._shift_button, - color=self._key_color_lit - if self._mode == 'caps' - else self._key_color_dark, + color=( + self._key_color_lit + if self._mode == 'caps' + else self._key_color_dark + ), label=babase.charstr(babase.SpecialChar.SHIFT), on_activate_call=self._shift, ) diff --git a/dist/ba_data/python/bauiv1lib/account/link.py b/dist/ba_data/python/bauiv1lib/account/link.py index 3f1ceb7..58828ad 100644 --- a/dist/ba_data/python/bauiv1lib/account/link.py +++ b/dist/ba_data/python/bauiv1lib/account/link.py @@ -38,9 +38,7 @@ class AccountLinkWindow(bui.Window): base_scale = ( 1.65 if uiscale is bui.UIScale.SMALL - else 1.5 - if uiscale is bui.UIScale.MEDIUM - else 1.1 + else 1.5 if uiscale is bui.UIScale.MEDIUM else 1.1 ) super().__init__( root_widget=bui.containerwidget( @@ -48,9 +46,9 @@ class AccountLinkWindow(bui.Window): transition=transition, scale=base_scale, scale_origin_stack_offset=scale_origin, - stack_offset=(0, -10) - if uiscale is bui.UIScale.SMALL - else (0, 0), + stack_offset=( + (0, -10) if uiscale is bui.UIScale.SMALL else (0, 0) + ), ) ) self._cancel_button = bui.buttonwidget( @@ -128,10 +126,12 @@ class AccountLinkWindow(bui.Window): plus.run_v1_account_transactions() def _enter_code_press(self) -> None: - from bauiv1lib import promocode + from bauiv1lib.sendinfo import SendInfoWindow - promocode.PromoCodeWindow( - modal=True, origin_widget=self._enter_code_button + SendInfoWindow( + modal=True, + legacy_code_mode=True, + origin_widget=self._enter_code_button, ) bui.containerwidget( edit=self._root_widget, transition=self._transition_out @@ -159,9 +159,7 @@ class AccountLinkCodeWindow(bui.Window): scale=( 1.8 if uiscale is bui.UIScale.SMALL - else 1.35 - if uiscale is bui.UIScale.MEDIUM - else 1.0 + else 1.35 if uiscale is bui.UIScale.MEDIUM else 1.0 ), ) ) diff --git a/dist/ba_data/python/bauiv1lib/account/settings.py b/dist/ba_data/python/bauiv1lib/account/settings.py index f9eab7c..f92697b 100644 --- a/dist/ba_data/python/bauiv1lib/account/settings.py +++ b/dist/ba_data/python/bauiv1lib/account/settings.py @@ -74,9 +74,7 @@ class AccountSettingsWindow(bui.Window): self._height = ( 390 if uiscale is bui.UIScale.SMALL - else 430 - if uiscale is bui.UIScale.MEDIUM - else 490 + else 430 if uiscale is bui.UIScale.MEDIUM else 490 ) self._sign_in_button = None @@ -98,9 +96,10 @@ class AccountSettingsWindow(bui.Window): # Always want to show our web-based v2 login option. self._show_sign_in_buttons.append('V2Proxy') - # Legacy v1 device accounts are currently always available - # (though we need to start phasing them out at some point). - self._show_sign_in_buttons.append('Device') + # Legacy v1 device accounts available only if the user + # has explicitly enabled deprecated login types. + if bui.app.config.resolve('Show Deprecated Login Types'): + self._show_sign_in_buttons.append('Device') top_extra = 15 if uiscale is bui.UIScale.SMALL else 0 super().__init__( @@ -112,13 +111,11 @@ class AccountSettingsWindow(bui.Window): scale=( 2.09 if uiscale is bui.UIScale.SMALL - else 1.4 - if uiscale is bui.UIScale.MEDIUM - else 1.0 + else 1.4 if uiscale is bui.UIScale.MEDIUM else 1.0 + ), + stack_offset=( + (0, -19) if uiscale is bui.UIScale.SMALL else (0, 0) ), - stack_offset=(0, -19) - if uiscale is bui.UIScale.SMALL - else (0, 0), ) ) if uiscale is bui.UIScale.SMALL and app.ui_v1.use_toolbars: @@ -211,9 +208,6 @@ class AccountSettingsWindow(bui.Window): self._refresh_tickets_text() self._refresh_account_name_text() - def _get_sign_in_text(self) -> bui.Lstr: - return bui.Lstr(resource=self._r + '.signInText') - def _refresh(self) -> None: # pylint: disable=too-many-statements # pylint: disable=too-many-branches @@ -320,9 +314,14 @@ class AccountSettingsWindow(bui.Window): show_game_service_button = game_center_active game_service_button_space = 60.0 - show_what_is_v2 = self._v1_signed_in and v1_account_type == 'V2' + # Phasing this out. + show_what_is_v2 = False + # show_what_is_v2 = self._v1_signed_in and v1_account_type == 'V2' - show_linked_accounts_text = self._v1_signed_in + # Phasing this out (for V2 accounts at least). + show_linked_accounts_text = ( + self._v1_signed_in and v1_account_type != 'V2' + ) linked_accounts_text_space = 60.0 # Always show achievements except in the game-center case where @@ -365,7 +364,10 @@ class AccountSettingsWindow(bui.Window): show_unlink_accounts_button = show_link_accounts_button unlink_accounts_button_space = 90.0 - show_v2_link_info = self._v1_signed_in and not show_link_accounts_button + # Phasing this out. + # show_v2_link_info = self._v1_signed_in + # and not show_link_accounts_button + show_v2_link_info = False v2_link_info_space = 70.0 legacy_unlink_button_space = 120.0 @@ -374,7 +376,7 @@ class AccountSettingsWindow(bui.Window): 'Local', 'V2', ] - sign_out_button_space = 70.0 + sign_out_button_space = 80.0 # We can show cancel if we're either waiting on an adapter to # provide us with v2 credentials or waiting for those credentials @@ -613,7 +615,7 @@ class AccountSettingsWindow(bui.Window): autoselect=True, size=(button_width, 60), label=bui.Lstr( - value='${A}${B}', + value='${A} ${B}', subs=[ ( '${A}', @@ -658,7 +660,7 @@ class AccountSettingsWindow(bui.Window): # in all languages. Can revisit if not true. # https://developer.apple.com/forums/thread/725779 label=bui.Lstr( - value='${A}${B}', + value='${A} ${B}', subs=[ ( '${A}', @@ -699,39 +701,53 @@ class AccountSettingsWindow(bui.Window): label='', on_activate_call=self._v2_proxy_sign_in_press, ) + + v2labeltext: bui.Lstr | str = ( + bui.Lstr(resource=self._r + '.signInWithAnEmailAddressText') + if show_game_center_sign_in_button + or show_google_play_sign_in_button + or show_device_sign_in_button + else bui.Lstr(resource=self._r + '.signInText') + ) + v2infotext: bui.Lstr | str | None = None + bui.textwidget( parent=self._subcontainer, draw_controller=btn, h_align='center', v_align='center', size=(0, 0), - position=(self._sub_width * 0.5, v + 17), + position=( + self._sub_width * 0.5, + v + (17 if v2infotext is not None else 10), + ), text=bui.Lstr( - value='${A}${B}', + value='${A} ${B}', subs=[ ('${A}', bui.charstr(bui.SpecialChar.V2_LOGO)), ( '${B}', - bui.Lstr(resource=self._r + '.signInWithV2Text'), + v2labeltext, ), ], ), maxwidth=button_width * 0.8, color=(0.75, 1.0, 0.7), ) - bui.textwidget( - parent=self._subcontainer, - draw_controller=btn, - h_align='center', - v_align='center', - size=(0, 0), - position=(self._sub_width * 0.5, v - 4), - text=bui.Lstr(resource=self._r + '.signInWithV2InfoText'), - flatness=1.0, - scale=0.57, - maxwidth=button_width * 0.9, - color=(0.55, 0.8, 0.5), - ) + if v2infotext is not None: + bui.textwidget( + parent=self._subcontainer, + draw_controller=btn, + h_align='center', + v_align='center', + size=(0, 0), + position=(self._sub_width * 0.5, v - 4), + text=v2infotext, + flatness=1.0, + scale=0.57, + maxwidth=button_width * 0.9, + color=(0.55, 0.8, 0.5), + ) if first_selectable is None: first_selectable = btn if bui.app.ui_v1.use_toolbars: @@ -774,7 +790,7 @@ class AccountSettingsWindow(bui.Window): size=(0, 0), position=(self._sub_width * 0.5, v + 17), text=bui.Lstr( - value='${A}${B}', + value='${A} ${B}', subs=[ ('${A}', bui.charstr(bui.SpecialChar.LOCAL_ACCOUNT)), ( @@ -928,9 +944,9 @@ class AccountSettingsWindow(bui.Window): if gpgs_active else 'achievementsIcon' ), - icon_color=(0.8, 0.95, 0.7) - if gpgs_active - else (0.85, 0.8, 0.9), + icon_color=( + (0.8, 0.95, 0.7) if gpgs_active else (0.85, 0.8, 0.9) + ), on_activate_call=( self._on_custom_achievements_press if gpgs_active @@ -1566,6 +1582,14 @@ class AccountSettingsWindow(bui.Window): bui.apptimer(0.1, bui.WeakCall(self._update)) def _sign_in_press(self, login_type: str | LoginType) -> None: + from bauiv1lib.connectivity import wait_for_connectivity + + # If we're still waiting for our master-server connection, + # keep the user informed of this instead of rushing in and + # failing immediately. + wait_for_connectivity(on_connected=lambda: self._sign_in(login_type)) + + def _sign_in(self, login_type: str | LoginType) -> None: plus = bui.app.plus assert plus is not None @@ -1653,6 +1677,15 @@ class AccountSettingsWindow(bui.Window): bui.apptimer(0.1, bui.WeakCall(self._update)) def _v2_proxy_sign_in_press(self) -> None: + # pylint: disable=cyclic-import + from bauiv1lib.connectivity import wait_for_connectivity + + # If we're still waiting for our master-server connection, + # keep the user informed of this instead of rushing in and + # failing immediately. + wait_for_connectivity(on_connected=self._v2_proxy_sign_in) + + def _v2_proxy_sign_in(self) -> None: # pylint: disable=cyclic-import from bauiv1lib.account.v2proxy import V2ProxySignInWindow diff --git a/dist/ba_data/python/bauiv1lib/account/unlink.py b/dist/ba_data/python/bauiv1lib/account/unlink.py index aa1a993..ef80dad 100644 --- a/dist/ba_data/python/bauiv1lib/account/unlink.py +++ b/dist/ba_data/python/bauiv1lib/account/unlink.py @@ -39,9 +39,7 @@ class AccountUnlinkWindow(bui.Window): base_scale = ( 2.0 if uiscale is bui.UIScale.SMALL - else 1.6 - if uiscale is bui.UIScale.MEDIUM - else 1.1 + else 1.6 if uiscale is bui.UIScale.MEDIUM else 1.1 ) super().__init__( root_widget=bui.containerwidget( @@ -49,9 +47,9 @@ class AccountUnlinkWindow(bui.Window): transition=transition, scale=base_scale, scale_origin_stack_offset=scale_origin, - stack_offset=(0, -10) - if uiscale is bui.UIScale.SMALL - else (0, 0), + stack_offset=( + (0, -10) if uiscale is bui.UIScale.SMALL else (0, 0) + ), ) ) self._cancel_button = bui.buttonwidget( diff --git a/dist/ba_data/python/bauiv1lib/account/v2proxy.py b/dist/ba_data/python/bauiv1lib/account/v2proxy.py index 756ea61..d91f1f0 100644 --- a/dist/ba_data/python/bauiv1lib/account/v2proxy.py +++ b/dist/ba_data/python/bauiv1lib/account/v2proxy.py @@ -4,6 +4,7 @@ from __future__ import annotations +import time import logging from efro.error import CommunicationError @@ -21,6 +22,7 @@ class V2ProxySignInWindow(bui.Window): self._height = 550 self._proxyid: str | None = None self._proxykey: str | None = None + self._overlay_web_browser_open = False assert bui.app.classic is not None uiscale = bui.app.ui_v1.uiscale @@ -34,24 +36,44 @@ class V2ProxySignInWindow(bui.Window): scale=( 1.25 if uiscale is bui.UIScale.SMALL - else 1.05 - if uiscale is bui.UIScale.MEDIUM - else 0.9 + else 1.05 if uiscale is bui.UIScale.MEDIUM else 0.9 ), ) ) - self._loading_text = bui.textwidget( + self._state_text = bui.textwidget( parent=self._root_widget, - position=(self._width * 0.5, self._height * 0.5), + position=(self._width * 0.5, self._height * 0.6), h_align='center', v_align='center', size=(0, 0), + scale=1.4, maxwidth=0.9 * self._width, text=bui.Lstr( value='${A}...', subs=[('${A}', bui.Lstr(resource='loadingText'))], ), + color=(1, 1, 1), + ) + self._sub_state_text = bui.textwidget( + parent=self._root_widget, + position=(self._width * 0.5, self._height * 0.55), + h_align='center', + v_align='top', + scale=0.85, + size=(0, 0), + maxwidth=0.9 * self._width, + text='', + ) + self._sub_state_text2 = bui.textwidget( + parent=self._root_widget, + position=(self._width * 0.1, self._height * 0.3), + h_align='left', + v_align='top', + scale=0.7, + size=(0, 0), + maxwidth=0.9 * self._width, + text='', ) self._cancel_button = bui.buttonwidget( @@ -68,14 +90,83 @@ class V2ProxySignInWindow(bui.Window): edit=self._root_widget, cancel_button=self._cancel_button ) - self._update_timer: bui.AppTimer | None = None + self._message_in_flight = False + self._complete = False + self._connection_wait_timeout_time = time.monotonic() + 10.0 - # Ask the cloud for a proxy login id. - assert bui.app.plus is not None - bui.app.plus.cloud.send_message_cb( + self._update_timer = bui.AppTimer( + 0.371, bui.WeakCall(self._update), repeat=True + ) + bui.pushcall(bui.WeakCall(self._update)) + + def _update(self) -> None: + + plus = bui.app.plus + assert plus is not None + + # If we've opened an overlay web browser, all we do is kill + # ourselves when it closes. + if self._overlay_web_browser_open: + if not bui.overlay_web_browser_is_open(): + self._overlay_web_browser_open = False + self._done() + return + + if self._message_in_flight or self._complete: + return + + now = time.monotonic() + + # Spin for a moment if it looks like we have no server + # connection; it might still be getting on its feet. + if ( + not plus.cloud.connected + and now < self._connection_wait_timeout_time + ): + return + + plus.cloud.send_message_cb( bacommon.cloud.LoginProxyRequestMessage(), on_response=bui.WeakCall(self._on_proxy_request_response), ) + self._message_in_flight = True + + def _get_server_address(self) -> str: + plus = bui.app.plus + assert plus is not None + out = plus.get_master_server_address(version=2) + assert isinstance(out, str) + return out + + def _set_error_state(self, error_location: str) -> None: + msaddress = self._get_server_address() + addr = msaddress.removeprefix('https://') + bui.textwidget( + edit=self._state_text, + text=f'Unable to connect to {addr}.', + color=(1, 0, 0), + ) + support_email = 'support@froemling.net' + bui.textwidget( + edit=self._sub_state_text, + text=( + f'Usually this means your internet is down.\n' + f'Please contact {support_email} if this is not the case.' + ), + color=(1, 0, 0), + ) + bui.textwidget( + edit=self._sub_state_text2, + text=( + f'debug-info:\n' + f' error-location: {error_location}\n' + f' connectivity: {bui.app.net.connectivity_state}\n' + f' transport: {bui.app.net.transport_state}' + ), + color=(0.8, 0.2, 0.3), + flatness=1.0, + shadow=0.0, + ) def _on_proxy_request_response( self, response: bacommon.cloud.LoginProxyRequestResponse | Exception @@ -83,17 +174,60 @@ class V2ProxySignInWindow(bui.Window): plus = bui.app.plus assert plus is not None - # Something went wrong. Show an error message and that's it. - if isinstance(response, Exception): - bui.textwidget( - edit=self._loading_text, - text=bui.Lstr(resource='internal.unavailableNoConnectionText'), - color=(1, 0, 0), + if not self._message_in_flight: + logging.warning( + 'v2proxy got _on_proxy_request_response' + ' without _message_in_flight set; unexpected.' ) + self._message_in_flight = False + + # Something went wrong. Show an error message and schedule retry. + if isinstance(response, Exception): + self._set_error_state(f'response exc ({type(response).__name__})') + self._complete = True return + self._complete = True + + # Clear out stuff we use to show progress/errors. + self._sub_state_text.delete() + self._sub_state_text2.delete() + + # If we have overlay-web-browser functionality, bring up + # an inline sign-in dialog. + if bui.overlay_web_browser_is_supported(): + bui.textwidget( + edit=self._state_text, + text=bui.Lstr(resource='pleaseWaitText'), + ) + self._show_overlay_sign_in_ui(response) + self._overlay_web_browser_open = True + else: + # Otherwise just show link-button/qr-code for the sign-in. + self._state_text.delete() + self._show_standard_sign_in_ui(response) + + # In either case, start querying for results now. + self._proxyid = response.proxyid + self._proxykey = response.proxykey + bui.apptimer( + STATUS_CHECK_INTERVAL_SECONDS, bui.WeakCall(self._ask_for_status) + ) + + def _show_overlay_sign_in_ui( + self, response: bacommon.cloud.LoginProxyRequestResponse + ) -> None: + msaddress = self._get_server_address() + address = msaddress + response.url_overlay + bui.overlay_web_browser_open_url(address) + + def _show_standard_sign_in_ui( + self, response: bacommon.cloud.LoginProxyRequestResponse + ) -> None: + msaddress = self._get_server_address() + # Show link(s) the user can use to sign in. - address = plus.get_master_server_address(version=2) + response.url + address = msaddress + response.url address_pretty = address.removeprefix('https://') assert bui.app.classic is not None @@ -153,13 +287,6 @@ class V2ProxySignInWindow(bui.Window): texture=bui.get_qrcode_texture(address), ) - # Start querying for results. - self._proxyid = response.proxyid - self._proxykey = response.proxykey - bui.apptimer( - STATUS_CHECK_INTERVAL_SECONDS, bui.WeakCall(self._ask_for_status) - ) - def _ask_for_status(self) -> None: assert self._proxyid is not None assert self._proxykey is not None @@ -174,12 +301,11 @@ class V2ProxySignInWindow(bui.Window): def _got_status( self, response: bacommon.cloud.LoginProxyStateQueryResponse | Exception ) -> None: - # For now, if anything goes wrong on the server-side, just abort - # with a vague error message. Can be more verbose later if need be. if ( isinstance(response, bacommon.cloud.LoginProxyStateQueryResponse) and response.state is response.State.FAIL ): + logging.info('LoginProxy failed.') bui.getsound('error').play() bui.screenmessage(bui.Lstr(resource='errorText'), color=(1, 0, 0)) self._done() @@ -242,4 +368,9 @@ class V2ProxySignInWindow(bui.Window): # no-op if our underlying widget is dead or on its way out. if not self._root_widget or self._root_widget.transitioning_out: return + + # If we've got an inline browser up, tell it to close. + if self._overlay_web_browser_open: + bui.overlay_web_browser_close() + bui.containerwidget(edit=self._root_widget, transition='out_scale') diff --git a/dist/ba_data/python/bauiv1lib/account/viewer.py b/dist/ba_data/python/bauiv1lib/account/viewer.py index 9046a80..2dbb1de 100644 --- a/dist/ba_data/python/bauiv1lib/account/viewer.py +++ b/dist/ba_data/python/bauiv1lib/account/viewer.py @@ -4,10 +4,9 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override import logging -from typing_extensions import override import bauiv1 as bui from bauiv1lib.popup import PopupWindow, PopupMenuWindow @@ -43,9 +42,7 @@ class AccountViewerWindow(PopupWindow): scale = ( 2.6 if uiscale is bui.UIScale.SMALL - else 1.8 - if uiscale is bui.UIScale.MEDIUM - else 1.4 + else 1.8 if uiscale is bui.UIScale.MEDIUM else 1.4 ) self._transitioning_out = False @@ -53,9 +50,7 @@ class AccountViewerWindow(PopupWindow): self._height = ( 300 if uiscale is bui.UIScale.SMALL - else 400 - if uiscale is bui.UIScale.MEDIUM - else 450 + else 400 if uiscale is bui.UIScale.MEDIUM else 450 ) self._subcontainer: bui.Widget | None = None @@ -143,7 +138,7 @@ class AccountViewerWindow(PopupWindow): bui.app.classic.master_server_v1_get( 'bsAccountInfo', { - 'buildNumber': bui.app.env.build_number, + 'buildNumber': bui.app.env.engine_build_number, 'accountID': self._account_id, 'profileID': self._profile_id, }, @@ -186,9 +181,7 @@ class AccountViewerWindow(PopupWindow): scale=( 2.3 if uiscale is bui.UIScale.SMALL - else 1.65 - if uiscale is bui.UIScale.MEDIUM - else 1.23 + else 1.65 if uiscale is bui.UIScale.MEDIUM else 1.23 ), choices=choices, choices_display=choices_display, diff --git a/dist/ba_data/python/bauiv1lib/achievements.py b/dist/ba_data/python/bauiv1lib/achievements.py index df2345d..228e773 100644 --- a/dist/ba_data/python/bauiv1lib/achievements.py +++ b/dist/ba_data/python/bauiv1lib/achievements.py @@ -4,7 +4,7 @@ from __future__ import annotations -from typing_extensions import override +from typing import override from bauiv1lib.popup import PopupWindow import bauiv1 as bui @@ -23,18 +23,14 @@ class AchievementsWindow(PopupWindow): scale = ( 2.3 if uiscale is bui.UIScale.SMALL - else 1.65 - if uiscale is bui.UIScale.MEDIUM - else 1.23 + else 1.65 if uiscale is bui.UIScale.MEDIUM else 1.23 ) self._transitioning_out = False self._width = 450 self._height = ( 300 if uiscale is bui.UIScale.SMALL - else 370 - if uiscale is bui.UIScale.MEDIUM - else 450 + else 370 if uiscale is bui.UIScale.MEDIUM else 450 ) bg_color = (0.5, 0.4, 0.6) @@ -126,9 +122,11 @@ class AchievementsWindow(PopupWindow): bui.imagewidget( parent=self._subcontainer, - position=(sub_width * 0.10 + 1, sub_height - 20 - incr * i - 9) - if complete - else (sub_width * 0.10 - 4, sub_height - 20 - incr * i - 14), + position=( + (sub_width * 0.10 + 1, sub_height - 20 - incr * i - 9) + if complete + else (sub_width * 0.10 - 4, sub_height - 20 - incr * i - 14) + ), size=(18, 18) if complete else (27, 27), opacity=1.0 if complete else 0.3, color=ach.get_icon_color(complete)[:3], @@ -167,9 +165,11 @@ class AchievementsWindow(PopupWindow): flatness=1.0, shadow=0.0, color=(0.83, 0.8, 0.85) if complete else (0.8, 0.8, 0.8, 0.2), - text=ach.description_full_complete - if complete - else ach.description_full, + text=( + ach.description_full_complete + if complete + else ach.description_full + ), size=(0, 0), h_align='left', v_align='center', diff --git a/dist/ba_data/python/bauiv1lib/appinvite.py b/dist/ba_data/python/bauiv1lib/appinvite.py index 06305e3..4bcbbcb 100644 --- a/dist/ba_data/python/bauiv1lib/appinvite.py +++ b/dist/ba_data/python/bauiv1lib/appinvite.py @@ -31,9 +31,7 @@ class ShowFriendCodeWindow(bui.Window): scale=( 1.7 if uiscale is bui.UIScale.SMALL - else 1.35 - if uiscale is bui.UIScale.MEDIUM - else 1.0 + else 1.35 if uiscale is bui.UIScale.MEDIUM else 1.0 ), ) ) diff --git a/dist/ba_data/python/bauiv1lib/characterpicker.py b/dist/ba_data/python/bauiv1lib/characterpicker.py index 92194e2..5e6ad30 100644 --- a/dist/ba_data/python/bauiv1lib/characterpicker.py +++ b/dist/ba_data/python/bauiv1lib/characterpicker.py @@ -5,9 +5,7 @@ from __future__ import annotations import math -from typing import TYPE_CHECKING - -from typing_extensions import override +from typing import TYPE_CHECKING, override from bauiv1lib.popup import PopupWindow import bauiv1 as bui @@ -41,9 +39,7 @@ class CharacterPicker(PopupWindow): scale = ( 1.85 if uiscale is bui.UIScale.SMALL - else 1.65 - if uiscale is bui.UIScale.MEDIUM - else 1.23 + else 1.65 if uiscale is bui.UIScale.MEDIUM else 1.23 ) self._delegate = delegate diff --git a/dist/ba_data/python/bauiv1lib/colorpicker.py b/dist/ba_data/python/bauiv1lib/colorpicker.py index 3088743..2896427 100644 --- a/dist/ba_data/python/bauiv1lib/colorpicker.py +++ b/dist/ba_data/python/bauiv1lib/colorpicker.py @@ -4,9 +4,7 @@ from __future__ import annotations -from typing import TYPE_CHECKING - -from typing_extensions import override +from typing import TYPE_CHECKING, override from bauiv1lib.popup import PopupWindow import bauiv1 as bui @@ -43,9 +41,7 @@ class ColorPicker(PopupWindow): scale = ( 2.3 if uiscale is bui.UIScale.SMALL - else 1.65 - if uiscale is bui.UIScale.MEDIUM - else 1.23 + else 1.65 if uiscale is bui.UIScale.MEDIUM else 1.23 ) self._parent = parent self._position = position @@ -206,9 +202,7 @@ class ColorPickerExact(PopupWindow): scale = ( 2.3 if uiscale is bui.UIScale.SMALL - else 1.65 - if uiscale is bui.UIScale.MEDIUM - else 1.23 + else 1.65 if uiscale is bui.UIScale.MEDIUM else 1.23 ) self._delegate = delegate self._transitioning_out = False @@ -217,6 +211,8 @@ class ColorPickerExact(PopupWindow): self._last_press_time = bui.apptime() self._last_press_color_name: str | None = None self._last_press_increasing: bool | None = None + self._hex_timer: bui.AppTimer | None = None + self._hex_prev_text: str = '#FFFFFF' self._change_speed = 1.0 width = 180.0 height = 240.0 @@ -233,11 +229,26 @@ class ColorPickerExact(PopupWindow): ) self._swatch = bui.imagewidget( parent=self.root_widget, - position=(width * 0.5 - 50, height - 70), - size=(100, 70), - texture=bui.gettexture('buttonSquare'), + position=(width * 0.5 - 65 + 5, height - 95), + size=(130, 115), + texture=bui.gettexture('clayStroke'), color=(1, 0, 0), ) + self._hex_textbox = bui.textwidget( + parent=self.root_widget, + position=(width * 0.5 - 37.5 + 3, height - 51), + max_chars=9, + text='#FFFFFF', + autoselect=True, + size=(75, 30), + v_align='center', + editable=True, + maxwidth=70, + allow_clear_button=False, + force_internal_editing=True, + glow_type='uniform', + ) + x = 50 y = height - 90 self._label_r: bui.Widget @@ -292,6 +303,37 @@ class ColorPickerExact(PopupWindow): # color to the delegate, so start doing that. self._update_for_color() + # Update our HEX stuff! + self._update_for_hex() + self._hex_timer = bui.AppTimer(0.025, self._update_for_hex, repeat=True) + + def _update_for_hex(self) -> None: + """Update for any HEX or color change.""" + from typing import cast + + hextext = cast(str, bui.textwidget(query=self._hex_textbox)) + hexcolor: tuple + # Check if our current hex text doesn't match with our old one. + # Convert our current hex text into a color if possible. + if hextext != self._hex_prev_text: + try: + hexcolor = hex_to_color(hextext) + if len(hexcolor) == 4: + r, g, b, a = hexcolor + del a # unused + else: + r, g, b = hexcolor + # Replace the color! + for i, ch in enumerate((r, g, b)): + self._color[i] = max(0.0, min(1.0, ch)) + self._update_for_color() + # Usually, a ValueError will occur if the provided hex + # is incomplete, which occurs when in the midst of typing it. + except ValueError: + pass + # Store the current text for our next comparison. + self._hex_prev_text = hextext + # noinspection PyUnresolvedReferences def _update_for_color(self) -> None: if not self.root_widget: @@ -307,6 +349,16 @@ class ColorPickerExact(PopupWindow): if self._delegate is not None: self._delegate.color_picker_selected_color(self, self._color) + # Show the HEX code of this color. + r, g, b = self._color + hexcode = color_to_hex(r, g, b, None) + self._hex_prev_text = hexcode + bui.textwidget( + edit=self._hex_textbox, + text=hexcode, + color=color_overlay_func(r, g, b), + ) + def _color_change_press(self, color_name: str, increasing: bool) -> None: # If we get rapid-fire presses, eventually start moving faster. current_time = bui.apptime() @@ -335,6 +387,8 @@ class ColorPickerExact(PopupWindow): return self._tag def _transition_out(self) -> None: + # Kill our timer + self._hex_timer = None if not self._transitioning_out: self._transitioning_out = True if self._delegate is not None: @@ -346,3 +400,102 @@ class ColorPickerExact(PopupWindow): if not self._transitioning_out: bui.getsound('swish').play() self._transition_out() + + +def hex_to_color(hex_color: str) -> tuple: + """Transforms an RGB / RGBA hex code into an rgb1/rgba1 tuple. + + Args: + hex_color (str): The HEX color. + Raises: + ValueError: If the provided HEX color isn't 6 or 8 characters long. + Returns: + tuple: The color tuple divided by 255. + """ + # Remove the '#' from the string if provided. + if hex_color.startswith('#'): + hex_color = hex_color.lstrip('#') + # Check if this has a valid length. + hexlength = len(hex_color) + if not hexlength in [6, 8]: + raise ValueError(f'Invalid HEX color provided: "{hex_color}"') + + # Convert the hex bytes to their true byte form. + ar, ag, ab, aa = ( + (int.from_bytes(bytes.fromhex(hex_color[0:2]))), + (int.from_bytes(bytes.fromhex(hex_color[2:4]))), + (int.from_bytes(bytes.fromhex(hex_color[4:6]))), + ( + (int.from_bytes(bytes.fromhex(hex_color[6:8]))) + if hexlength == 8 + else None + ), + ) + # Divide all numbers by 255 and return. + nr, ng, nb, na = ( + x / 255 if x is not None else None for x in (ar, ag, ab, aa) + ) + return (nr, ng, nb, na) if aa is not None else (nr, ng, nb) + + +def color_to_hex(r: float, g: float, b: float, a: float | None = 1.0) -> str: + """Converts an rgb1 tuple to a HEX color code. + + Args: + r (float): Red. + g (float): Green. + b (float): Blue. + a (float, optional): Alpha. Defaults to 1.0. + + Returns: + str: The hexified rgba values. + """ + # Turn our rgb1 to rgb255 + nr, ng, nb, na = [ + int(min(255, x * 255)) if x is not None else x for x in [r, g, b, a] + ] + # Merge all values into their HEX representation. + hex_code = ( + f'#{nr:02x}{ng:02x}{nb:02x}{na:02x}' + if na is not None + else f'#{nr:02x}{ng:02x}{nb:02x}' + ) + return hex_code + + +def color_overlay_func( + r: float, g: float, b: float, a: float | None = None +) -> tuple: + """I could NOT come up with a better function name. + + Args: + r (float): Red. + g (float): Green. + b (float): Blue. + a (float | None, optional): Alpha. Defaults to None. + + Returns: + tuple: A brighter color if the provided one is dark, + and a darker one if it's darker. + """ + + # Calculate the relative luminance using the formula for sRGB + # https://www.w3.org/TR/WCAG20/#relativeluminancedef + def relative_luminance(color: float) -> Any: + if color <= 0.03928: + return color / 12.92 + return ((color + 0.055) / 1.055) ** 2.4 + + luminance = ( + 0.2126 * relative_luminance(r) + + 0.7152 * relative_luminance(g) + + 0.0722 * relative_luminance(b) + ) + # Set our color multiplier depending on the provided color's luminance. + luminant = 1.65 if luminance < 0.33 else 0.2 + # Multiply our given numbers, making sure + # they don't blend in the original bg. + avg = (0.7 - (r + g + b / 3)) + 0.15 + r, g, b = [max(avg, x * luminant) for x in (r, g, b)] + # Include our alpha and ship it! + return (r, g, b, a) if a is not None else (r, g, b) diff --git a/dist/ba_data/python/bauiv1lib/config.py b/dist/ba_data/python/bauiv1lib/config.py index 763a370..255dd6d 100644 --- a/dist/ba_data/python/bauiv1lib/config.py +++ b/dist/ba_data/python/bauiv1lib/config.py @@ -94,6 +94,8 @@ class ConfigNumberEdit: changesound: bool = True, textscale: float = 1.0, as_percent: bool = False, + fallback_value: float = 0.0, + f: int = 1, ): if displayname is None: displayname = configkey @@ -103,8 +105,12 @@ class ConfigNumberEdit: self._maxval = maxval self._increment = increment self._callback = callback - self._value = bui.app.config.resolve(configkey) + try: + self._value = bui.app.config.resolve(configkey) + except ValueError: + self._value = bui.app.config.get(configkey, fallback_value) self._as_percent = as_percent + self._f = f self.nametext = bui.textwidget( parent=parent, @@ -171,5 +177,5 @@ class ConfigNumberEdit: if self._as_percent: val = f'{round(self._value*100.0)}%' else: - val = f'{self._value:.1f}' + val = f'{self._value:.{self._f}f}' bui.textwidget(edit=self.valuetext, text=val) diff --git a/dist/ba_data/python/bauiv1lib/confirm.py b/dist/ba_data/python/bauiv1lib/confirm.py index 2882b59..69e3ccd 100644 --- a/dist/ba_data/python/bauiv1lib/confirm.py +++ b/dist/ba_data/python/bauiv1lib/confirm.py @@ -61,9 +61,7 @@ class ConfirmWindow: scale=( 2.1 if uiscale is bui.UIScale.SMALL - else 1.5 - if uiscale is bui.UIScale.MEDIUM - else 1.0 + else 1.5 if uiscale is bui.UIScale.MEDIUM else 1.0 ), scale_origin_stack_offset=scale_origin, ) @@ -187,8 +185,10 @@ class QuitWindow: resource=quit_resource, subs=[('${APP_NAME}', bui.Lstr(resource='titleText'))], ), - lambda: bui.quit(confirm=False, quit_type=self._quit_type) - if self._quit_type is not None - else bui.quit(confirm=False), + lambda: ( + bui.quit(confirm=False, quit_type=self._quit_type) + if self._quit_type is not None + else bui.quit(confirm=False) + ), origin_widget=origin_widget, ).root_widget diff --git a/dist/ba_data/python/bauiv1lib/connectivity.py b/dist/ba_data/python/bauiv1lib/connectivity.py new file mode 100644 index 0000000..a0707d5 --- /dev/null +++ b/dist/ba_data/python/bauiv1lib/connectivity.py @@ -0,0 +1,132 @@ +# Released under the MIT License. See LICENSE for details. +# +"""UI functionality related to master-server connectivity.""" + +from __future__ import annotations + +import time +from typing import TYPE_CHECKING + +import bauiv1 as bui + +if TYPE_CHECKING: + from typing import Callable, Any + + +def wait_for_connectivity( + on_connected: Callable[[], Any], + on_cancel: Callable[[], Any] | None = None, +) -> None: + """Wait for the engine to establish a master-server connection. + + If need be, shows a window to keep the user informed of connectivity + state and allows the user to cancel the operation. Note that canceling + does not prevent the engine from continuing its attempt to establish + connectivity; it simply cancels the operation that depends on it. + """ + plus = bui.app.plus + assert plus is not None + + # Quick-out: if we're already connected, don't bother with the UI. + if plus.cloud.connected: + on_connected() + return + + WaitForConnectivityWindow(on_connected=on_connected, on_cancel=on_cancel) + + +class WaitForConnectivityWindow(bui.Window): + """Window informing the user that the game is establishing connectivity.""" + + def __init__( + self, + on_connected: Callable[[], Any], + on_cancel: Callable[[], Any] | None, + ) -> None: + self._on_connected = on_connected + self._on_cancel = on_cancel + self._width = 650 + self._height = 300 + self._infos: list[str | bui.Lstr] = [ + 'This can take a few moments, especially on first launch.', + 'Make sure your internet connection is working.', + ] + self._last_info_switch_time = time.monotonic() + self._info_index = 0 + super().__init__( + root_widget=bui.containerwidget( + size=(self._width, self._height), + transition='in_scale', + parent=bui.get_special_widget('overlay_stack'), + ) + ) + bui.textwidget( + parent=self._root_widget, + position=(self._width * 0.5, self._height * 0.65), + size=(0, 0), + scale=1.2, + h_align='center', + v_align='center', + text='Locating nearest regional servers...', + maxwidth=self._width * 0.9, + ) + self._info_text = bui.textwidget( + parent=self._root_widget, + position=(self._width * 0.5, self._height * 0.45), + size=(0, 0), + color=(0.7, 0.6, 0.7), + flatness=1.0, + scale=0.8, + h_align='center', + v_align='center', + text=self._infos[0], + maxwidth=self._width * 0.9, + ) + cancel_button = bui.buttonwidget( + parent=self._root_widget, + autoselect=True, + position=(50, 30), + size=(150, 50), + label=bui.Lstr(resource='cancelText'), + on_activate_call=self._cancel, + ) + bui.containerwidget(edit=self._root_widget, cancel_button=cancel_button) + self._update_timer = bui.AppTimer( + 0.113, bui.WeakCall(self._update), repeat=True + ) + + def _update(self) -> None: + now = time.monotonic() + + plus = bui.app.plus + assert plus is not None + + if plus.cloud.connected: + self._connected() + return + + if now - self._last_info_switch_time > 5.0: + self._info_index = (self._info_index + 1) % len(self._infos) + bui.textwidget( + edit=self._info_text, text=self._infos[self._info_index] + ) + self._last_info_switch_time = now + + def _connected(self) -> None: + if not self._root_widget or self._root_widget.transitioning_out: + return + bui.containerwidget( + edit=self._root_widget, + transition=('out_scale'), + ) + bui.pushcall(self._on_connected) + + def _cancel(self) -> None: + if not self._root_widget or self._root_widget.transitioning_out: + return + bui.containerwidget( + edit=self._root_widget, + transition=('out_scale'), + ) + if self._on_cancel is not None: + bui.pushcall(self._on_cancel) diff --git a/dist/ba_data/python/bauiv1lib/coop/browser.py b/dist/ba_data/python/bauiv1lib/coop/browser.py index e9f9b4a..540f180 100644 --- a/dist/ba_data/python/bauiv1lib/coop/browser.py +++ b/dist/ba_data/python/bauiv1lib/coop/browser.py @@ -90,9 +90,7 @@ class CoopBrowserWindow(bui.Window): self._height = ( 657 if uiscale is bui.UIScale.SMALL - else 730 - if uiscale is bui.UIScale.MEDIUM - else 800 + else 730 if uiscale is bui.UIScale.MEDIUM else 800 ) app.ui_v1.set_main_menu_location('Coop Select') self._r = 'coopSelectWindow' @@ -104,6 +102,19 @@ class CoopBrowserWindow(bui.Window): 'campaignDifficulty', 'easy' ) + if ( + self._campaign_difficulty == 'hard' + and not app.classic.accounts.have_pro_options() + ): + plus.add_v1_account_transaction( + { + 'type': 'SET_MISC_VAL', + 'name': 'campaignDifficulty', + 'value': 'easy', + } + ) + self._campaign_difficulty = 'easy' + super().__init__( root_widget=bui.containerwidget( size=(self._width, self._height + top_extra), @@ -112,17 +123,13 @@ class CoopBrowserWindow(bui.Window): stack_offset=( (0, -15) if uiscale is bui.UIScale.SMALL - else (0, 0) - if uiscale is bui.UIScale.MEDIUM - else (0, 0) + else (0, 0) if uiscale is bui.UIScale.MEDIUM else (0, 0) ), transition=transition, scale=( 1.2 if uiscale is bui.UIScale.SMALL - else 0.8 - if uiscale is bui.UIScale.MEDIUM - else 0.75 + else 0.8 if uiscale is bui.UIScale.MEDIUM else 0.75 ), ) ) @@ -271,9 +278,11 @@ class CoopBrowserWindow(bui.Window): self._scrollwidget = bui.scrollwidget( parent=self._root_widget, highlight=False, - position=(65 + x_inset, 120) - if uiscale is bui.UIScale.SMALL and app.ui_v1.use_toolbars - else (65 + x_inset, 70), + position=( + (65 + x_inset, 120) + if uiscale is bui.UIScale.SMALL and app.ui_v1.use_toolbars + else (65 + x_inset, 70) + ), size=(self._scroll_width, self._scroll_height), simple_culling_v=10.0, claims_left_right=True, @@ -421,12 +430,14 @@ class CoopBrowserWindow(bui.Window): if tbtn.time_remaining_value_text is not None: bui.textwidget( edit=tbtn.time_remaining_value_text, - text=bui.timestring(tbtn.time_remaining, centi=False) - if ( - tbtn.has_time_remaining - and self._tourney_data_up_to_date - ) - else '-', + text=( + bui.timestring(tbtn.time_remaining, centi=False) + if ( + tbtn.has_time_remaining + and self._tourney_data_up_to_date + ) + else '-' + ), ) # Also adjust the ad icon visibility. @@ -447,9 +458,9 @@ class CoopBrowserWindow(bui.Window): try: bui.imagewidget( edit=self._hard_button_lock_image, - opacity=0.0 - if bui.app.classic.accounts.have_pro_options() - else 1.0, + opacity=( + 0.0 if bui.app.classic.accounts.have_pro_options() else 1.0 + ), ) except Exception: logging.exception('Error updating campaign lock.') @@ -559,12 +570,16 @@ class CoopBrowserWindow(bui.Window): enable_sound=False, on_activate_call=bui.Call(self._set_campaign_difficulty, 'easy'), on_select_call=bui.Call(self.sel_change, 'campaign', 'easyButton'), - color=sel_color - if self._campaign_difficulty == 'easy' - else un_sel_color, - textcolor=sel_textcolor - if self._campaign_difficulty == 'easy' - else un_sel_textcolor, + color=( + sel_color + if self._campaign_difficulty == 'easy' + else un_sel_color + ), + textcolor=( + sel_textcolor + if self._campaign_difficulty == 'easy' + else un_sel_textcolor + ), ) bui.widget(edit=self._easy_button, show_buffer_left=100) if self._selected_campaign_level == 'easyButton': @@ -585,12 +600,16 @@ class CoopBrowserWindow(bui.Window): enable_sound=False, on_activate_call=bui.Call(self._set_campaign_difficulty, 'hard'), on_select_call=bui.Call(self.sel_change, 'campaign', 'hardButton'), - color=sel_color_hard - if self._campaign_difficulty == 'hard' - else un_sel_color, - textcolor=sel_textcolor - if self._campaign_difficulty == 'hard' - else un_sel_textcolor, + color=( + sel_color_hard + if self._campaign_difficulty == 'hard' + else un_sel_color + ), + textcolor=( + sel_textcolor + if self._campaign_difficulty == 'hard' + else un_sel_textcolor + ), ) self._hard_button_lock_image = bui.imagewidget( parent=parent_widget, @@ -960,35 +979,43 @@ class CoopBrowserWindow(bui.Window): for i, tbutton in enumerate(self._tournament_buttons): bui.widget( 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 + 1 < len(self._tournament_buttons) - else custom_h_scroll, + 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 + 1 < len(self._tournament_buttons) + else custom_h_scroll + ), ) bui.widget( edit=tbutton.more_scores_button, - down_widget=self._tournament_buttons[ - (i + 1) - ].current_leader_name_text - if i + 1 < len(self._tournament_buttons) - else custom_h_scroll, + down_widget=( + self._tournament_buttons[(i + 1)].current_leader_name_text + if i + 1 < len(self._tournament_buttons) + else custom_h_scroll + ), ) bui.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, + up_widget=( + self._tournament_info_button + if i == 0 + else self._tournament_buttons[i - 1].more_scores_button + ), ) for btn in self._custom_buttons: try: bui.widget( edit=btn.get_button(), - up_widget=tournament_h_scroll - if self._tournament_buttons - else self._tournament_info_button, + up_widget=( + tournament_h_scroll + if self._tournament_buttons + else self._tournament_info_button + ), ) except Exception: logging.exception('Error wiring up custom buttons.') @@ -1042,8 +1069,9 @@ class CoopBrowserWindow(bui.Window): def _switch_to_score( self, - show_tab: StoreBrowserWindow.TabID - | None = StoreBrowserWindow.TabID.EXTRAS, + show_tab: ( + StoreBrowserWindow.TabID | None + ) = StoreBrowserWindow.TabID.EXTRAS, ) -> None: # pylint: disable=cyclic-import from bauiv1lib.account import show_sign_in_prompt diff --git a/dist/ba_data/python/bauiv1lib/coop/gamebutton.py b/dist/ba_data/python/bauiv1lib/coop/gamebutton.py index c24d390..5172e3a 100644 --- a/dist/ba_data/python/bauiv1lib/coop/gamebutton.py +++ b/dist/ba_data/python/bauiv1lib/coop/gamebutton.py @@ -148,9 +148,11 @@ class GameButton: draw_controller=btn, position=(xach, yach), size=(a_scale, a_scale), - color=tuple(ach.get_icon_color(a_complete)[:3]) - if a_complete - else (1.2, 1.2, 1.2), + color=( + tuple(ach.get_icon_color(a_complete)[:3]) + if a_complete + else (1.2, 1.2, 1.2) + ), texture=ach.get_icon_ui_texture(a_complete), ) imw2 = bui.imagewidget( @@ -307,8 +309,6 @@ class GameButton: opacity=( 1.0 if (a_complete and unlocked) - else 0.2 - if a_complete - else 0.0 + else 0.2 if a_complete else 0.0 ), ) diff --git a/dist/ba_data/python/bauiv1lib/coop/level.py b/dist/ba_data/python/bauiv1lib/coop/level.py index b3e6d47..e3bfcaa 100644 --- a/dist/ba_data/python/bauiv1lib/coop/level.py +++ b/dist/ba_data/python/bauiv1lib/coop/level.py @@ -23,9 +23,7 @@ class CoopLevelLockedWindow(bui.Window): scale=( 1.7 if uiscale is bui.UIScale.SMALL - else 1.3 - if uiscale is bui.UIScale.MEDIUM - else 1.0 + else 1.3 if uiscale is bui.UIScale.MEDIUM else 1.0 ), ) ) diff --git a/dist/ba_data/python/bauiv1lib/coop/tournamentbutton.py b/dist/ba_data/python/bauiv1lib/coop/tournamentbutton.py index 4b76637..0ee8a59 100644 --- a/dist/ba_data/python/bauiv1lib/coop/tournamentbutton.py +++ b/dist/ba_data/python/bauiv1lib/coop/tournamentbutton.py @@ -417,9 +417,7 @@ class TournamentButton: prize_y_offs = ( 34 if 'prizeRange3' in entry - else 20 - if 'prizeRange2' in entry - else 12 + else 20 if 'prizeRange2' in entry else 12 ) x_offs = 90 @@ -460,9 +458,11 @@ class TournamentButton: bui.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, + 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, @@ -480,9 +480,11 @@ class TournamentButton: bui.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, + 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, @@ -500,9 +502,11 @@ class TournamentButton: bui.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, + 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, @@ -667,9 +671,11 @@ class TournamentButton: 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)), + 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: diff --git a/dist/ba_data/python/bauiv1lib/creditslist.py b/dist/ba_data/python/bauiv1lib/creditslist.py index c087dfb..f25449f 100644 --- a/dist/ba_data/python/bauiv1lib/creditslist.py +++ b/dist/ba_data/python/bauiv1lib/creditslist.py @@ -51,13 +51,11 @@ class CreditsListWindow(bui.Window): scale=( 2.0 if uiscale is bui.UIScale.SMALL - else 1.3 - if uiscale is bui.UIScale.MEDIUM - else 1.0 + else 1.3 if uiscale is bui.UIScale.MEDIUM else 1.0 + ), + stack_offset=( + (0, -8) if uiscale is bui.UIScale.SMALL else (0, 0) ), - stack_offset=(0, -8) - if uiscale is bui.UIScale.SMALL - else (0, 0), ) ) diff --git a/dist/ba_data/python/bauiv1lib/debug.py b/dist/ba_data/python/bauiv1lib/debug.py index b397610..5bcf38f 100644 --- a/dist/ba_data/python/bauiv1lib/debug.py +++ b/dist/ba_data/python/bauiv1lib/debug.py @@ -25,9 +25,7 @@ class DebugWindow(bui.Window): self._height = height = ( 350 if uiscale is bui.UIScale.SMALL - else 420 - if uiscale is bui.UIScale.MEDIUM - else 520 + else 420 if uiscale is bui.UIScale.MEDIUM else 520 ) self._scroll_width = self._width - 100 @@ -50,13 +48,11 @@ class DebugWindow(bui.Window): scale=( 2.35 if uiscale is bui.UIScale.SMALL - else 1.55 - if uiscale is bui.UIScale.MEDIUM - else 1.0 + else 1.55 if uiscale is bui.UIScale.MEDIUM else 1.0 + ), + stack_offset=( + (0, -30) if uiscale is bui.UIScale.SMALL else (0, 0) ), - stack_offset=(0, -30) - if uiscale is bui.UIScale.SMALL - else (0, 0), ) ) diff --git a/dist/ba_data/python/bauiv1lib/discord.py b/dist/ba_data/python/bauiv1lib/discord.py index 2e2eb52..de891b2 100644 --- a/dist/ba_data/python/bauiv1lib/discord.py +++ b/dist/ba_data/python/bauiv1lib/discord.py @@ -45,9 +45,7 @@ class DiscordWindow(bui.Window): scale=( 1.6 if uiscale is bui.UIScale.SMALL - else 1.3 - if uiscale is bui.UIScale.MEDIUM - else 1.0 + else 1.3 if uiscale is bui.UIScale.MEDIUM else 1.0 ), stack_offset=(0, 5) if uiscale is bui.UIScale.SMALL else (0, 0), ) diff --git a/dist/ba_data/python/bauiv1lib/feedback.py b/dist/ba_data/python/bauiv1lib/feedback.py index 09568a3..cbab894 100644 --- a/dist/ba_data/python/bauiv1lib/feedback.py +++ b/dist/ba_data/python/bauiv1lib/feedback.py @@ -33,9 +33,7 @@ def ask_for_rating() -> bui.Widget | None: scale=( 1.6 if uiscale is bui.UIScale.SMALL - else 1.35 - if uiscale is bui.UIScale.MEDIUM - else 1.0 + else 1.35 if uiscale is bui.UIScale.MEDIUM else 1.0 ), ) v = height - 50 @@ -64,16 +62,9 @@ def ask_for_rating() -> bui.Widget | None: ) def do_rating() -> None: - if platform == 'android': - appname = bui.appname() - if subplatform == 'google': - url = f'market://details?id=net.froemling.{appname}' - else: - url = f'market://details?id=net.froemling.{appname}cb' - else: - url = 'macappstore://itunes.apple.com/app/id416482767?ls=1&mt=12' - - bui.open_url(url) + # This is not currently in use anywhere. + bui.screenmessage(bui.Lstr(resource='error')) + # bui.open_url(url) bui.containerwidget(edit=dlg, transition='out_left') bui.buttonwidget( diff --git a/dist/ba_data/python/bauiv1lib/fileselector.py b/dist/ba_data/python/bauiv1lib/fileselector.py index e9cf0ca..308b659 100644 --- a/dist/ba_data/python/bauiv1lib/fileselector.py +++ b/dist/ba_data/python/bauiv1lib/fileselector.py @@ -8,9 +8,7 @@ import os import time import logging from threading import Thread -from typing import TYPE_CHECKING - -from typing_extensions import override +from typing import TYPE_CHECKING, override import bauiv1 as bui @@ -57,13 +55,11 @@ class FileSelectorWindow(bui.Window): scale=( 2.23 if uiscale is bui.UIScale.SMALL - else 1.4 - if uiscale is bui.UIScale.MEDIUM - else 1.0 + else 1.4 if uiscale is bui.UIScale.MEDIUM else 1.0 + ), + stack_offset=( + (0, -35) if uiscale is bui.UIScale.SMALL else (0, 0) ), - stack_offset=(0, -35) - if uiscale is bui.UIScale.SMALL - else (0, 0), ) ) bui.textwidget( @@ -73,11 +69,15 @@ class FileSelectorWindow(bui.Window): color=bui.app.ui_v1.title_color, h_align='center', v_align='center', - text=bui.Lstr(resource=self._r + '.titleFolderText') - if (allow_folders and not valid_file_extensions) - else bui.Lstr(resource=self._r + '.titleFileText') - if not allow_folders - else bui.Lstr(resource=self._r + '.titleFileFolderText'), + text=( + bui.Lstr(resource=self._r + '.titleFolderText') + if (allow_folders and not valid_file_extensions) + else ( + bui.Lstr(resource=self._r + '.titleFileText') + if not allow_folders + else bui.Lstr(resource=self._r + '.titleFileFolderText') + ) + ), maxwidth=210, ) @@ -444,9 +444,11 @@ class FileSelectorWindow(bui.Window): position=(10 + folder_icon_size * 1.05, entry_height * 0.5), size=(0, 0), maxwidth=self._scroll_width * 0.93 - 50, - color=(1, 1, 1, 1) - if (is_valid_file_path or is_dir) - else (0.5, 0.5, 0.5, 1), + color=( + (1, 1, 1, 1) + if (is_valid_file_path or is_dir) + else (0.5, 0.5, 0.5, 1) + ), ) v -= entry_height diff --git a/dist/ba_data/python/bauiv1lib/gather/__init__.py b/dist/ba_data/python/bauiv1lib/gather/__init__.py index 61efe32..710e36c 100644 --- a/dist/ba_data/python/bauiv1lib/gather/__init__.py +++ b/dist/ba_data/python/bauiv1lib/gather/__init__.py @@ -99,9 +99,7 @@ class GatherWindow(bui.Window): self._height = ( 582 if uiscale is bui.UIScale.SMALL - else 680 - if uiscale is bui.UIScale.MEDIUM - else 800 + else 680 if uiscale is bui.UIScale.MEDIUM else 800 ) self._current_tab: GatherWindow.TabID | None = None extra_top = 20 if uiscale is bui.UIScale.SMALL else 0 @@ -116,15 +114,13 @@ class GatherWindow(bui.Window): scale=( 1.3 if uiscale is bui.UIScale.SMALL - else 0.97 - if uiscale is bui.UIScale.MEDIUM - else 0.8 + else 0.97 if uiscale is bui.UIScale.MEDIUM else 0.8 + ), + stack_offset=( + (0, -11) + if uiscale is bui.UIScale.SMALL + else (0, 0) if uiscale is bui.UIScale.MEDIUM else (0, 0) ), - stack_offset=(0, -11) - if uiscale is bui.UIScale.SMALL - else (0, 0) - if uiscale is bui.UIScale.MEDIUM - else (0, 0), ) ) @@ -165,9 +161,7 @@ class GatherWindow(bui.Window): scale=( 1.5 if not condensed - else 1.0 - if uiscale is bui.UIScale.MEDIUM - else 0.6 + else 1.0 if uiscale is bui.UIScale.MEDIUM else 0.6 ), h_align='center', v_align='center', diff --git a/dist/ba_data/python/bauiv1lib/gather/abouttab.py b/dist/ba_data/python/bauiv1lib/gather/abouttab.py index 1257c4b..086471e 100644 --- a/dist/ba_data/python/bauiv1lib/gather/abouttab.py +++ b/dist/ba_data/python/bauiv1lib/gather/abouttab.py @@ -4,9 +4,7 @@ from __future__ import annotations -from typing import TYPE_CHECKING - -from typing_extensions import override +from typing import TYPE_CHECKING, override from bauiv1lib.gather import GatherTab import bauiv1 as bui @@ -44,9 +42,7 @@ class AboutGatherTab(GatherTab): message_height = ( 210 if uiscale is bui.UIScale.SMALL - else 305 - if uiscale is bui.UIScale.MEDIUM - else 370 + else 305 if uiscale is bui.UIScale.MEDIUM else 370 ) # Let's not talk about sharing in vr-mode; its tricky to fit more # than one head in a VR-headset. diff --git a/dist/ba_data/python/bauiv1lib/gather/manualtab.py b/dist/ba_data/python/bauiv1lib/gather/manualtab.py index d7f3205..8c889ef 100644 --- a/dist/ba_data/python/bauiv1lib/gather/manualtab.py +++ b/dist/ba_data/python/bauiv1lib/gather/manualtab.py @@ -9,10 +9,9 @@ import logging from enum import Enum from threading import Thread from dataclasses import dataclass -from typing import TYPE_CHECKING, cast +from typing import TYPE_CHECKING, cast, override from bauiv1lib.gather import GatherTab -from typing_extensions import override import bauiv1 as bui import bascenev1 as bs @@ -48,7 +47,10 @@ class _HostLookupThread(Thread): try: import socket - result = socket.gethostbyname(self._name) + result = [ + item[-1][0] + for item in socket.getaddrinfo(self.name, self._port) + ][0] except Exception: result = None bui.pushcall( @@ -212,15 +214,19 @@ class ManualGatherTab(GatherTab): inactive_color = (0.5, 0.4, 0.5) bui.textwidget( edit=self._join_by_address_text, - color=active_color - if value is SubTabType.JOIN_BY_ADDRESS - else inactive_color, + color=( + active_color + if value is SubTabType.JOIN_BY_ADDRESS + else inactive_color + ), ) bui.textwidget( edit=self._favorites_text, - color=active_color - if value is SubTabType.FAVORITES - else inactive_color, + color=( + active_color + if value is SubTabType.FAVORITES + else inactive_color + ), ) # Clear anything existing in the old sub-tab. @@ -354,9 +360,7 @@ class ManualGatherTab(GatherTab): self._height = ( 578 if uiscale is bui.UIScale.SMALL - else 670 - if uiscale is bui.UIScale.MEDIUM - else 800 + else 670 if uiscale is bui.UIScale.MEDIUM else 800 ) self._scroll_width = self._width - 130 + 2 * x_inset @@ -375,16 +379,12 @@ class ManualGatherTab(GatherTab): b_height = ( 107 if uiscale is bui.UIScale.SMALL - else 142 - if uiscale is bui.UIScale.MEDIUM - else 190 + else 142 if uiscale is bui.UIScale.MEDIUM else 190 ) b_space_extra = ( 0 if uiscale is bui.UIScale.SMALL - else -2 - if uiscale is bui.UIScale.MEDIUM - else -5 + else -2 if uiscale is bui.UIScale.MEDIUM else -5 ) btnv = ( @@ -392,9 +392,7 @@ class ManualGatherTab(GatherTab): - ( 48 if uiscale is bui.UIScale.SMALL - else 45 - if uiscale is bui.UIScale.MEDIUM - else 40 + else 45 if uiscale is bui.UIScale.MEDIUM else 40 ) - b_height ) @@ -513,9 +511,7 @@ class ManualGatherTab(GatherTab): scale=( 1.8 if uiscale is bui.UIScale.SMALL - else 1.55 - if uiscale is bui.UIScale.MEDIUM - else 1.0 + else 1.55 if uiscale is bui.UIScale.MEDIUM else 1.0 ), size=(c_width, c_height), transition='in_scale', @@ -1059,7 +1055,7 @@ class ManualGatherTab(GatherTab): self._t_accessible_extra = t_accessible_extra bui.app.classic.master_server_v1_get( 'bsAccessCheck', - {'b': bui.app.env.build_number}, + {'b': bui.app.env.engine_build_number}, callback=bui.WeakCall(self._on_accessible_response), ) diff --git a/dist/ba_data/python/bauiv1lib/gather/nearbytab.py b/dist/ba_data/python/bauiv1lib/gather/nearbytab.py index 7393f6b..3301204 100644 --- a/dist/ba_data/python/bauiv1lib/gather/nearbytab.py +++ b/dist/ba_data/python/bauiv1lib/gather/nearbytab.py @@ -5,9 +5,8 @@ from __future__ import annotations import weakref -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override -from typing_extensions import override import bauiv1 as bui import bascenev1 as bs diff --git a/dist/ba_data/python/bauiv1lib/gather/privatetab.py b/dist/ba_data/python/bauiv1lib/gather/privatetab.py index 8b6cf7f..00bb4a2 100644 --- a/dist/ba_data/python/bauiv1lib/gather/privatetab.py +++ b/dist/ba_data/python/bauiv1lib/gather/privatetab.py @@ -11,9 +11,8 @@ import time import logging from enum import Enum from dataclasses import dataclass -from typing import TYPE_CHECKING, cast +from typing import TYPE_CHECKING, cast, override -from typing_extensions import override from efro.dataclassio import dataclass_from_dict, dataclass_to_dict from bacommon.net import ( PrivateHostingState, @@ -837,9 +836,7 @@ class PrivateGatherTab(GatherTab): color=( (0.6, 0.6, 0.6) if disabled - else (0.5, 1.0, 0.5) - if waiting - else None + else (0.5, 1.0, 0.5) if waiting else None ), enable_sound=False, label=btnlabel, @@ -992,8 +989,8 @@ class PrivateGatherTab(GatherTab): bui.getsound('error').play() return self._debug_server_comm('got valid connect response') - assert cresult.addr is not None and cresult.port is not None - bs.connect_to_party(cresult.addr, port=cresult.port) + assert cresult.address4 is not None and cresult.port is not None + bs.connect_to_party(cresult.address4, port=cresult.port) except Exception: self._debug_server_comm('got connect response error') bui.getsound('error').play() diff --git a/dist/ba_data/python/bauiv1lib/gather/publictab.py b/dist/ba_data/python/bauiv1lib/gather/publictab.py index 83b3b53..d8b2f14 100644 --- a/dist/ba_data/python/bauiv1lib/gather/publictab.py +++ b/dist/ba_data/python/bauiv1lib/gather/publictab.py @@ -11,9 +11,8 @@ import logging from threading import Thread from enum import Enum from dataclasses import dataclass -from typing import TYPE_CHECKING, cast +from typing import TYPE_CHECKING, cast, override -from typing_extensions import override from bauiv1lib.gather import GatherTab import bauiv1 as bui import bascenev1 as bs @@ -204,11 +203,11 @@ class UIRow: bui.textwidget( edit=self._ping_widget, text=str(int(party.ping)), - color=(0, 1, 0) - if party.ping <= ping_good - else (1, 1, 0) - if party.ping <= ping_med - else (1, 0, 0), + color=( + (0, 1, 0) + if party.ping <= ping_good + else (1, 1, 0) if party.ping <= ping_med else (1, 0, 0) + ), ) party.clean_display_index = index @@ -369,8 +368,8 @@ class PublicGatherTab(GatherTab): self._join_status_text: bui.Widget | None = None self._no_servers_found_text: bui.Widget | None = None self._host_max_party_size_value: bui.Widget | None = None - self._host_max_party_size_minus_button: (bui.Widget | None) = None - self._host_max_party_size_plus_button: (bui.Widget | None) = None + self._host_max_party_size_minus_button: bui.Widget | None = None + self._host_max_party_size_plus_button: bui.Widget | None = None self._host_status_text: bui.Widget | None = None self._signed_in = False self._ui_rows: list[UIRow] = [] @@ -800,9 +799,11 @@ class PublicGatherTab(GatherTab): parent=self._container, label=label, size=(400, 80), - on_activate_call=self._on_stop_advertising_press - if is_public_enabled - else self._on_start_advertizing_press, + on_activate_call=( + self._on_stop_advertising_press + if is_public_enabled + else self._on_start_advertizing_press + ), position=(c_width * 0.5 - 200, v), autoselect=True, up_widget=btn2, @@ -1365,7 +1366,7 @@ class PublicGatherTab(GatherTab): ) bui.app.classic.master_server_v1_get( 'bsAccessCheck', - {'b': bui.app.env.build_number}, + {'b': bui.app.env.engine_build_number}, callback=bui.WeakCall(self._on_public_party_accessible_response), ) diff --git a/dist/ba_data/python/bauiv1lib/getcurrency.py b/dist/ba_data/python/bauiv1lib/getcurrency.py index 6e355d4..7883f61 100644 --- a/dist/ba_data/python/bauiv1lib/getcurrency.py +++ b/dist/ba_data/python/bauiv1lib/getcurrency.py @@ -6,6 +6,8 @@ from __future__ import annotations from typing import TYPE_CHECKING +from efro.util import utc_now + import bauiv1 as bui if TYPE_CHECKING: @@ -72,13 +74,11 @@ class GetCurrencyWindow(bui.Window): scale=( 1.63 if uiscale is bui.UIScale.SMALL - else 1.2 - if uiscale is bui.UIScale.MEDIUM - else 1.0 + else 1.2 if uiscale is bui.UIScale.MEDIUM else 1.0 + ), + stack_offset=( + (0, -3) if uiscale is bui.UIScale.SMALL else (0, 0) ), - stack_offset=(0, -3) - if uiscale is bui.UIScale.SMALL - else (0, 0), ) ) @@ -387,7 +387,7 @@ class GetCurrencyWindow(bui.Window): else: v -= 20 - if True: # pylint: disable=using-constant-test + if bool(True): h_offs = 35 b_size_3 = (150, 120) cdb = _add_button( @@ -433,6 +433,8 @@ class GetCurrencyWindow(bui.Window): scale=1.0, ) tc_y_offs = 0 + else: + tc_y_offs = 0 h = self._width - (185 + x_inset) v = self._height - 95 + tc_y_offs @@ -543,24 +545,24 @@ class GetCurrencyWindow(bui.Window): plus = bui.app.plus assert plus is not None - # if we somehow get signed out, just die.. + # If we somehow get signed out, just die. if plus.get_v1_account_state() != 'signed_in': self._back() return self._ticket_count = plus.get_v1_account_ticket_count() - # update our incentivized ad button depending on whether ads are - # available + # Update our incentivized ad button depending on whether ads are + # available. if self._ad_button is not None: next_reward_ad_time = plus.get_v1_account_misc_read_val_2( 'nextRewardAdTime', None ) if next_reward_ad_time is not None: - next_reward_ad_time = datetime.datetime.utcfromtimestamp( - next_reward_ad_time + next_reward_ad_time = datetime.datetime.fromtimestamp( + next_reward_ad_time, datetime.UTC ) - now = datetime.datetime.utcnow() + now = utc_now() if plus.have_incentivized_ad() and ( next_reward_ad_time is None or next_reward_ad_time <= now ): @@ -588,8 +590,8 @@ class GetCurrencyWindow(bui.Window): sval = '' bui.textwidget(edit=self._ad_time_text, text=sval) - # if this is our first update, assign immediately; otherwise kick - # off a smooth transition if the value has changed + # If this is our first update, assign immediately; otherwise kick + # off a smooth transition if the value has changed. if self._smooth_ticket_count is None: self._smooth_ticket_count = float(self._ticket_count) self._smooth_update() # will set the text widget @@ -605,19 +607,19 @@ class GetCurrencyWindow(bui.Window): self._smooth_increase_speed = ( diff / 100.0 if diff >= 5000 - else diff / 50.0 - if diff >= 1500 - else diff / 30.0 - if diff >= 500 - else diff / 15.0 + else ( + diff / 50.0 + if diff >= 1500 + else diff / 30.0 if diff >= 500 else diff / 15.0 + ) ) def _disabled_press(self) -> None: plus = bui.app.plus assert plus is not None - # if we're on a platform without purchases, inform the user they - # can link their accounts and buy stuff elsewhere + # If we're on a platform without purchases, inform the user they + # can link their accounts and buy stuff elsewhere. app = bui.app assert app.classic is not None if ( @@ -654,8 +656,10 @@ class GetCurrencyWindow(bui.Window): return appinvite.handle_app_invites_press() return - # here we ping the server to ask if it's valid for us to - # purchase this.. (better to fail now than after we've paid locally) + + # Here we ping the server to ask if it's valid for us to + # purchase this.. (better to fail now than after we've paid + # locally). app = bui.app assert app.classic is not None bui.app.classic.master_server_v1_get( @@ -664,8 +668,8 @@ class GetCurrencyWindow(bui.Window): 'item': item, 'platform': app.classic.platform, 'subplatform': app.classic.subplatform, - 'version': app.env.version, - 'buildNumber': app.env.build_number, + 'version': app.env.engine_version, + 'buildNumber': app.env.engine_build_number, }, callback=bui.WeakCall(self._purchase_check_result, item), ) @@ -696,7 +700,7 @@ class GetCurrencyWindow(bui.Window): color=(1, 0, 0), ) - # actually start the purchase locally.. + # Actually start the purchase locally. def _do_purchase(self, item: str) -> None: plus = bui.app.plus assert plus is not None @@ -704,15 +708,15 @@ class GetCurrencyWindow(bui.Window): if item == 'ad': import datetime - # if ads are disabled until some time, error.. + # If ads are disabled until some time, error. next_reward_ad_time = plus.get_v1_account_misc_read_val_2( 'nextRewardAdTime', None ) if next_reward_ad_time is not None: - next_reward_ad_time = datetime.datetime.utcfromtimestamp( - next_reward_ad_time + next_reward_ad_time = datetime.datetime.fromtimestamp( + next_reward_ad_time, datetime.UTC ) - now = datetime.datetime.utcnow() + now = utc_now() if ( next_reward_ad_time is not None and next_reward_ad_time > now ) or self._ad_button_greyed: @@ -732,7 +736,7 @@ class GetCurrencyWindow(bui.Window): def _back(self) -> None: from bauiv1lib.store import browser - # no-op if our underlying widget is dead or on its way out. + # No-op if our underlying widget is dead or on its way out. if not self._root_widget or self._root_widget.transitioning_out: return diff --git a/dist/ba_data/python/bauiv1lib/getremote.py b/dist/ba_data/python/bauiv1lib/getremote.py index 0cea25a..ef772a4 100644 --- a/dist/ba_data/python/bauiv1lib/getremote.py +++ b/dist/ba_data/python/bauiv1lib/getremote.py @@ -4,7 +4,7 @@ from __future__ import annotations -from typing_extensions import override +from typing import override from bauiv1lib.popup import PopupWindow import bauiv1 as bui @@ -20,9 +20,7 @@ class GetBSRemoteWindow(PopupWindow): scale = ( 2.3 if uiscale is bui.UIScale.SMALL - else 1.65 - if uiscale is bui.UIScale.MEDIUM - else 1.23 + else 1.65 if uiscale is bui.UIScale.MEDIUM else 1.23 ) self._transitioning_out = False self._width = 570 diff --git a/dist/ba_data/python/bauiv1lib/helpui.py b/dist/ba_data/python/bauiv1lib/helpui.py index e1981aa..0a3de5f 100644 --- a/dist/ba_data/python/bauiv1lib/helpui.py +++ b/dist/ba_data/python/bauiv1lib/helpui.py @@ -41,9 +41,7 @@ class HelpWindow(bui.Window): height = ( 460 if uiscale is bui.UIScale.SMALL - else 530 - if uiscale is bui.UIScale.MEDIUM - else 600 + else 530 if uiscale is bui.UIScale.MEDIUM else 600 ) super().__init__( @@ -55,15 +53,13 @@ class HelpWindow(bui.Window): scale=( 1.77 if uiscale is bui.UIScale.SMALL - else 1.25 - if uiscale is bui.UIScale.MEDIUM - else 1.0 + else 1.25 if uiscale is bui.UIScale.MEDIUM else 1.0 + ), + stack_offset=( + (0, -30) + if uiscale is bui.UIScale.SMALL + else (0, 15) if uiscale is bui.UIScale.MEDIUM else (0, 0) ), - stack_offset=(0, -30) - if uiscale is bui.UIScale.SMALL - else (0, 15) - if uiscale is bui.UIScale.MEDIUM - else (0, 0), ) ) @@ -119,9 +115,11 @@ class HelpWindow(bui.Window): ), size=(140, 60), scale=0.7 if uiscale is bui.UIScale.SMALL else 0.8, - label=bui.Lstr(resource='backText') - if self._main_menu - else 'Close', + label=( + bui.Lstr(resource='backText') + if self._main_menu + else 'Close' + ), button_type='back' if self._main_menu else None, extra_touch_border_scale=2.0, autoselect=True, diff --git a/dist/ba_data/python/bauiv1lib/iconpicker.py b/dist/ba_data/python/bauiv1lib/iconpicker.py index 260bbde..968b9c1 100644 --- a/dist/ba_data/python/bauiv1lib/iconpicker.py +++ b/dist/ba_data/python/bauiv1lib/iconpicker.py @@ -5,9 +5,7 @@ from __future__ import annotations import math -from typing import TYPE_CHECKING - -from typing_extensions import override +from typing import TYPE_CHECKING, override from bauiv1lib.popup import PopupWindow import bauiv1 as bui @@ -40,9 +38,7 @@ class IconPicker(PopupWindow): scale = ( 1.85 if uiscale is bui.UIScale.SMALL - else 1.65 - if uiscale is bui.UIScale.MEDIUM - else 1.23 + else 1.65 if uiscale is bui.UIScale.MEDIUM else 1.23 ) self._delegate = delegate diff --git a/dist/ba_data/python/bauiv1lib/kiosk.py b/dist/ba_data/python/bauiv1lib/kiosk.py index ab1ca87..3543e5c 100644 --- a/dist/ba_data/python/bauiv1lib/kiosk.py +++ b/dist/ba_data/python/bauiv1lib/kiosk.py @@ -472,9 +472,9 @@ class KioskWindow(bui.Window): 'type': 'bs_hockey.HockeyGame', } ] - appconfig[ - 'Team Tournament Playlist Selection' - ] = 'Just Hockey' + appconfig['Team Tournament Playlist Selection'] = ( + 'Just Hockey' + ) bui.fade_screen( False, endcall=bui.Call( @@ -488,9 +488,11 @@ class KioskWindow(bui.Window): game = ( 'Easy:Onslaught Training' if mode == 'easy' - else 'Easy:Rookie Football' - if mode == 'medium' - else 'Easy:Uber Onslaught' + else ( + 'Easy:Rookie Football' + if mode == 'medium' + else 'Easy:Uber Onslaught' + ) ) cfg = bui.app.config cfg['Selected Coop Game'] = game diff --git a/dist/ba_data/python/bauiv1lib/league/rankwindow.py b/dist/ba_data/python/bauiv1lib/league/rankwindow.py index 725bb31..7e6d8d1 100644 --- a/dist/ba_data/python/bauiv1lib/league/rankwindow.py +++ b/dist/ba_data/python/bauiv1lib/league/rankwindow.py @@ -63,9 +63,7 @@ class LeagueRankWindow(bui.Window): self._height = ( 657 if uiscale is bui.UIScale.SMALL - else 710 - if uiscale is bui.UIScale.MEDIUM - else 800 + else 710 if uiscale is bui.UIScale.MEDIUM else 800 ) self._r = 'coopSelectWindow' self._rdict = bui.app.lang.get_resource(self._r) @@ -79,19 +77,17 @@ class LeagueRankWindow(bui.Window): super().__init__( root_widget=bui.containerwidget( size=(self._width, self._height + top_extra), - stack_offset=(0, -15) - if uiscale is bui.UIScale.SMALL - else (0, 10) - if uiscale is bui.UIScale.MEDIUM - else (0, 0), + stack_offset=( + (0, -15) + if uiscale is bui.UIScale.SMALL + else (0, 10) if uiscale is bui.UIScale.MEDIUM else (0, 0) + ), transition=transition, scale_origin_stack_offset=scale_origin, scale=( 1.2 if uiscale is bui.UIScale.SMALL - else 0.93 - if uiscale is bui.UIScale.MEDIUM - else 0.8 + else 0.93 if uiscale is bui.UIScale.MEDIUM else 0.8 ), ) ) @@ -210,9 +206,11 @@ class LeagueRankWindow(bui.Window): assert plus is not None txt = bui.Lstr( - resource='coopSelectWindow.activenessAllTimeInfoText' - if self._season == 'a' - else 'coopSelectWindow.activenessInfoText', + resource=( + 'coopSelectWindow.activenessAllTimeInfoText' + if self._season == 'a' + else 'coopSelectWindow.activenessInfoText' + ), subs=[ ( '${MAX}', @@ -853,9 +851,11 @@ class LeagueRankWindow(bui.Window): bui.textwidget( edit=self._league_title_text, - text='' - if self._season == 'a' - else bui.Lstr(resource='league.leagueText'), + text=( + '' + if self._season == 'a' + else bui.Lstr(resource='league.leagueText') + ), ) if data is None: @@ -910,9 +910,11 @@ class LeagueRankWindow(bui.Window): bui.textwidget(edit=self._season_ends_text, text=to_end_string) bui.textwidget( edit=self._trophy_counts_reset_text, - text=bui.Lstr(resource='league.trophyCountsResetText') - if self._is_current_season and show_season_end - else '', + text=( + bui.Lstr(resource='league.trophyCountsResetText') + if self._is_current_season and show_season_end + else '' + ), ) bui.textwidget(edit=self._league_text, text=lname, color=lcolor) @@ -932,21 +934,25 @@ class LeagueRankWindow(bui.Window): ) bui.textwidget( edit=self._to_ranked_text, - text=bui.Lstr(resource='coopSelectWindow.toRankedText').evaluate() - + '' - + extra_text - if do_percent - else '', + text=( + bui.Lstr(resource='coopSelectWindow.toRankedText').evaluate() + + '' + + extra_text + if do_percent + else '' + ), ) bui.textwidget( edit=self._your_power_ranking_text, - text=bui.Lstr( - resource='rankText', - fallback_resource='coopSelectWindow.yourPowerRankingText', - ) - if (not do_percent) - else '', + text=( + bui.Lstr( + resource='rankText', + fallback_resource='coopSelectWindow.yourPowerRankingText', + ) + if (not do_percent) + else '' + ), ) bui.textwidget( @@ -954,11 +960,11 @@ class LeagueRankWindow(bui.Window): position=(473, v - 70 - (170 if do_percent else 220)), text=status_text, big=(in_top or do_percent), - scale=3.0 - if (in_top or do_percent) - else 0.7 - if finished_season_unranked - else 1.0, + scale=( + 3.0 + if (in_top or do_percent) + else 0.7 if finished_season_unranked else 1.0 + ), ) if self._activity_mult_button is not None: @@ -992,9 +998,11 @@ class LeagueRankWindow(bui.Window): # pylint: disable=consider-using-f-string bui.textwidget( edit=self._pro_mult_text, - text=' -' - if (data is None or not have_pro) - else 'x ' + ('%.2f' % pro_mult), + text=( + ' -' + if (data is None or not have_pro) + else 'x ' + ('%.2f' % pro_mult) + ), ) bui.buttonwidget( edit=self._pro_mult_button, @@ -1019,9 +1027,11 @@ class LeagueRankWindow(bui.Window): bui.textwidget( edit=self._power_ranking_achievement_total_text, - text='-' - if data is None - else ('+ ' + pts_txt.replace('${NUMBER}', str(total_ach_value))), + text=( + '-' + if data is None + else ('+ ' + pts_txt.replace('${NUMBER}', str(total_ach_value))) + ), ) total_trophies_count = accounts.get_league_rank_points( @@ -1035,19 +1045,24 @@ class LeagueRankWindow(bui.Window): ) bui.textwidget( edit=self._power_ranking_trophies_total_text, - text='-' - if data is None - else ( - '+ ' + pts_txt.replace('${NUMBER}', str(total_trophies_value)) + text=( + '-' + if data is None + else ( + '+ ' + + pts_txt.replace('${NUMBER}', str(total_trophies_value)) + ) ), ) bui.textwidget( edit=self._power_ranking_total_text, - text='-' - if data is None - else eq_text.replace( - '${NUMBER}', str(accounts.get_league_rank_points(data)) + text=( + '-' + if data is None + else eq_text.replace( + '${NUMBER}', str(accounts.get_league_rank_points(data)) + ) ), ) for widget in self._power_ranking_score_widgets: diff --git a/dist/ba_data/python/bauiv1lib/mainmenu.py b/dist/ba_data/python/bauiv1lib/mainmenu.py index bec93e9..48242d9 100644 --- a/dist/ba_data/python/bauiv1lib/mainmenu.py +++ b/dist/ba_data/python/bauiv1lib/mainmenu.py @@ -43,9 +43,11 @@ class MainMenuWindow(bui.Window): super().__init__( root_widget=bui.containerwidget( transition=transition, - toolbar_visibility='menu_minimal_no_back' - if self._in_game - else 'menu_minimal_no_back', + toolbar_visibility=( + 'menu_minimal_no_back' + if self._in_game + else 'menu_minimal_no_back' + ), ) ) @@ -142,9 +144,11 @@ class MainMenuWindow(bui.Window): return ( 'storeCharacterXmas' if plus.get_v1_account_misc_read_val('xmas', False) - else 'storeCharacterEaster' - if plus.get_v1_account_misc_read_val('easter', False) - else 'storeCharacter' + else ( + 'storeCharacterEaster' + if plus.get_v1_account_misc_read_val('easter', False) + else 'storeCharacter' + ) ) def _check_refresh(self) -> None: @@ -344,9 +348,7 @@ class MainMenuWindow(bui.Window): icon_size = ( 55 if uiscale is bui.UIScale.SMALL - else 55 - if uiscale is bui.UIScale.MEDIUM - else 70 + else 55 if uiscale is bui.UIScale.MEDIUM else 70 ) bui.imagewidget( parent=self._root_widget, @@ -452,7 +454,7 @@ class MainMenuWindow(bui.Window): resource='watchWindow.playbackSpeedText', subs=[('${SPEED}', str(1.23))], ), - position=(h, v + v_offs + 7 * t_scale), + position=(h, v + v_offs + 15 * t_scale), h_align='center', v_align='center', size=(0, 0), @@ -526,6 +528,64 @@ class MainMenuWindow(bui.Window): autoselect=True, on_activate_call=bui.Call(self._pause_or_resume_replay), ) + btn = bui.buttonwidget( + parent=self._root_widget, + position=( + h - b_size * 1.5 - b_buffer_1 * 2, + v - b_size - b_buffer_2 + v_offs, + ), + button_type='square', + size=(b_size, b_size), + label='', + autoselect=True, + on_activate_call=bui.WeakCall(self._rewind_replay), + ) + bui.textwidget( + parent=self._root_widget, + draw_controller=btn, + # text='<<', + text=bui.charstr(bui.SpecialChar.REWIND_BUTTON), + position=( + h - b_size - b_buffer_1 * 2, + v - b_size * 0.5 - b_buffer_2 + 5 * t_scale + v_offs, + ), + h_align='center', + v_align='center', + size=(0, 0), + scale=2.0 * t_scale, + ) + btn = bui.buttonwidget( + parent=self._root_widget, + position=( + h + b_size * 0.5 + b_buffer_1 * 2, + v - b_size - b_buffer_2 + v_offs, + ), + button_type='square', + size=(b_size, b_size), + label='', + autoselect=True, + on_activate_call=bui.WeakCall(self._forward_replay), + ) + bui.textwidget( + parent=self._root_widget, + draw_controller=btn, + # text='>>', + text=bui.charstr(bui.SpecialChar.FAST_FORWARD_BUTTON), + position=( + h + b_size + b_buffer_1 * 2, + v - b_size * 0.5 - b_buffer_2 + 5 * t_scale + v_offs, + ), + h_align='center', + v_align='center', + size=(0, 0), + scale=2.0 * t_scale, + ) + + def _rewind_replay(self) -> None: + bs.seek_replay(-2 * pow(2, bs.get_replay_speed_exponent())) + + def _forward_replay(self) -> None: + bs.seek_replay(2 * pow(2, bs.get_replay_speed_exponent())) def _refresh_not_in_game( self, positions: list[tuple[float, float, float]] @@ -646,9 +706,11 @@ class MainMenuWindow(bui.Window): color=(0.45, 0.55, 0.45), textcolor=(0.7, 0.8, 0.7), label=bui.Lstr( - resource='modeArcadeText' - if bui.app.env.arcade - else 'modeDemoText' + resource=( + 'modeArcadeText' + if bui.app.env.arcade + else 'modeDemoText' + ) ), transition_delay=demo_menu_delay, on_activate_call=self._demo_menu_press, @@ -659,9 +721,7 @@ class MainMenuWindow(bui.Window): foof = ( -1 if uiscale is bui.UIScale.SMALL - else 1 - if uiscale is bui.UIScale.MEDIUM - else 3 + else 1 if uiscale is bui.UIScale.MEDIUM else 3 ) h, v, scale = positions[self._p_index] v = v + foof @@ -906,9 +966,7 @@ class MainMenuWindow(bui.Window): scale=( 2.15 if uiscale is bui.UIScale.SMALL - else 1.6 - if uiscale is bui.UIScale.MEDIUM - else 1.0 + else 1.6 if uiscale is bui.UIScale.MEDIUM else 1.0 ), ) h = 125.0 @@ -1398,8 +1456,9 @@ class MainMenuWindow(bui.Window): def _resume(self) -> None: assert bui.app.classic is not None bui.app.classic.resume() - if self._root_widget: - bui.containerwidget(edit=self._root_widget, transition='out_right') + # if self._root_widget: + # bui.containerwidget(edit=self._root_widget, + # transition='out_right') bui.app.ui_v1.clear_main_menu_window(transition='out_right') # If there's callbacks waiting for this window to go away, call them. diff --git a/dist/ba_data/python/bauiv1lib/party.py b/dist/ba_data/python/bauiv1lib/party.py index e531b86..3e52fd4 100644 --- a/dist/ba_data/python/bauiv1lib/party.py +++ b/dist/ba_data/python/bauiv1lib/party.py @@ -36,9 +36,7 @@ class PartyWindow(bui.Window): self._height = ( 365 if uiscale is bui.UIScale.SMALL - else 480 - if uiscale is bui.UIScale.MEDIUM - else 600 + else 480 if uiscale is bui.UIScale.MEDIUM else 600 ) self._display_old_msgs = True super().__init__( @@ -52,15 +50,15 @@ class PartyWindow(bui.Window): scale=( 2.0 if uiscale is bui.UIScale.SMALL - else 1.35 - if uiscale is bui.UIScale.MEDIUM - else 1.0 + else 1.35 if uiscale is bui.UIScale.MEDIUM else 1.0 + ), + stack_offset=( + (0, -10) + if uiscale is bui.UIScale.SMALL + else ( + (240, 0) if uiscale is bui.UIScale.MEDIUM else (330, 20) + ) ), - stack_offset=(0, -10) - if uiscale is bui.UIScale.SMALL - else (240, 0) - if uiscale is bui.UIScale.MEDIUM - else (330, 20), ) ) @@ -251,9 +249,7 @@ class PartyWindow(bui.Window): scale=( 2.3 if uiscale is bui.UIScale.SMALL - else 1.65 - if uiscale is bui.UIScale.MEDIUM - else 1.23 + else 1.65 if uiscale is bui.UIScale.MEDIUM else 1.23 ), choices=choices, choices_display=choices_display, @@ -312,9 +308,7 @@ class PartyWindow(bui.Window): columns = ( 1 if len(self._roster) == 1 - else 2 - if len(self._roster) == 2 - else 3 + else 2 if len(self._roster) == 2 else 3 ) rows = int(math.ceil(float(len(self._roster)) / columns)) c_width = (self._width * 0.9) / max(3, columns) @@ -572,9 +566,7 @@ class PartyWindow(bui.Window): scale=( 2.3 if uiscale is bui.UIScale.SMALL - else 1.65 - if uiscale is bui.UIScale.MEDIUM - else 1.23 + else 1.65 if uiscale is bui.UIScale.MEDIUM else 1.23 ), choices=['kick'], choices_display=[kick_str], diff --git a/dist/ba_data/python/bauiv1lib/partyqueue.py b/dist/ba_data/python/bauiv1lib/partyqueue.py index 26c97bd..147fabf 100644 --- a/dist/ba_data/python/bauiv1lib/partyqueue.py +++ b/dist/ba_data/python/bauiv1lib/partyqueue.py @@ -274,9 +274,7 @@ class PartyQueueWindow(bui.Window): scale=( 1.4 if uiscale is bui.UIScale.SMALL - else 1.2 - if uiscale is bui.UIScale.MEDIUM - else 1.0 + else 1.2 if uiscale is bui.UIScale.MEDIUM else 1.0 ), ) ) diff --git a/dist/ba_data/python/bauiv1lib/play.py b/dist/ba_data/python/bauiv1lib/play.py index ea52588..eda60ab 100644 --- a/dist/ba_data/python/bauiv1lib/play.py +++ b/dist/ba_data/python/bauiv1lib/play.py @@ -57,9 +57,7 @@ class PlayWindow(bui.Window): scale=( 1.6 if uiscale is bui.UIScale.SMALL - else 0.9 - if uiscale is bui.UIScale.MEDIUM - else 0.8 + else 0.9 if uiscale is bui.UIScale.MEDIUM else 0.8 ), stack_offset=(0, 0) if uiscale is bui.UIScale.SMALL else (0, 0), ) @@ -83,9 +81,11 @@ class PlayWindow(bui.Window): # (101 if main_menu else 61)), size=(0, 0), text=bui.Lstr( - resource=(self._r + '.titleText') - if self._is_main_menu - else 'playlistsText' + resource=( + (self._r + '.titleText') + if self._is_main_menu + else 'playlistsText' + ) ), scale=1.7, res_scale=2.0, @@ -494,18 +494,22 @@ class PlayWindow(bui.Window): bui.containerwidget( edit=self._root_widget, on_cancel_call=self._back, - selected_child=self._coop_button - if self._is_main_menu - else self._teams_button, + selected_child=( + self._coop_button + if self._is_main_menu + else self._teams_button + ), ) else: bui.buttonwidget(edit=back_button, on_activate_call=self._back) bui.containerwidget( edit=self._root_widget, cancel_button=back_button, - selected_child=self._coop_button - if self._is_main_menu - else self._teams_button, + selected_child=( + self._coop_button + if self._is_main_menu + else self._teams_button + ), ) self._restore_state() diff --git a/dist/ba_data/python/bauiv1lib/playlist/addgame.py b/dist/ba_data/python/bauiv1lib/playlist/addgame.py index c8acdb8..dce81ee 100644 --- a/dist/ba_data/python/bauiv1lib/playlist/addgame.py +++ b/dist/ba_data/python/bauiv1lib/playlist/addgame.py @@ -30,9 +30,7 @@ class PlaylistAddGameWindow(bui.Window): self._height = ( 346 if uiscale is bui.UIScale.SMALL - else 380 - if uiscale is bui.UIScale.MEDIUM - else 440 + else 380 if uiscale is bui.UIScale.MEDIUM else 440 ) top_extra = 30 if uiscale is bui.UIScale.SMALL else 20 self._scroll_width = 210 @@ -44,9 +42,7 @@ class PlaylistAddGameWindow(bui.Window): scale=( 2.17 if uiscale is bui.UIScale.SMALL - else 1.5 - if uiscale is bui.UIScale.MEDIUM - else 1.0 + else 1.5 if uiscale is bui.UIScale.MEDIUM else 1.0 ), stack_offset=(0, 1) if uiscale is bui.UIScale.SMALL else (0, 0), ) diff --git a/dist/ba_data/python/bauiv1lib/playlist/browser.py b/dist/ba_data/python/bauiv1lib/playlist/browser.py index 806ab7a..f8b1a73 100644 --- a/dist/ba_data/python/bauiv1lib/playlist/browser.py +++ b/dist/ba_data/python/bauiv1lib/playlist/browser.py @@ -67,9 +67,7 @@ class PlaylistBrowserWindow(bui.Window): self._height = ( 480 if uiscale is bui.UIScale.SMALL - else 510 - if uiscale is bui.UIScale.MEDIUM - else 580 + else 510 if uiscale is bui.UIScale.MEDIUM else 580 ) top_extra = 20 if uiscale is bui.UIScale.SMALL else 0 @@ -83,13 +81,11 @@ class PlaylistBrowserWindow(bui.Window): scale=( 1.69 if uiscale is bui.UIScale.SMALL - else 1.05 - if uiscale is bui.UIScale.MEDIUM - else 0.9 + else 1.05 if uiscale is bui.UIScale.MEDIUM else 0.9 + ), + stack_offset=( + (0, -26) if uiscale is bui.UIScale.SMALL else (0, 0) ), - stack_offset=(0, -26) - if uiscale is bui.UIScale.SMALL - else (0, 0), ) ) @@ -714,9 +710,9 @@ class PlaylistBrowserWindow(bui.Window): ) if self._selected_playlist != prev_sel: cfg = bui.app.config - cfg[ - self._pvars.config_name + ' Playlist Selection' - ] = self._selected_playlist + cfg[self._pvars.config_name + ' Playlist Selection'] = ( + self._selected_playlist + ) cfg.commit() self._save_state() diff --git a/dist/ba_data/python/bauiv1lib/playlist/customizebrowser.py b/dist/ba_data/python/bauiv1lib/playlist/customizebrowser.py index c5bd842..9880c4d 100644 --- a/dist/ba_data/python/bauiv1lib/playlist/customizebrowser.py +++ b/dist/ba_data/python/bauiv1lib/playlist/customizebrowser.py @@ -52,9 +52,7 @@ class PlaylistCustomizeBrowserWindow(bui.Window): self._height = ( 380.0 if uiscale is bui.UIScale.SMALL - else 420.0 - if uiscale is bui.UIScale.MEDIUM - else 500.0 + else 420.0 if uiscale is bui.UIScale.MEDIUM else 500.0 ) top_extra = 20.0 if uiscale is bui.UIScale.SMALL else 0.0 @@ -66,13 +64,11 @@ class PlaylistCustomizeBrowserWindow(bui.Window): scale=( 2.05 if uiscale is bui.UIScale.SMALL - else 1.5 - if uiscale is bui.UIScale.MEDIUM - else 1.0 + else 1.5 if uiscale is bui.UIScale.MEDIUM else 1.0 + ), + stack_offset=( + (0, -10) if uiscale is bui.UIScale.SMALL else (0, 0) ), - stack_offset=(0, -10) - if uiscale is bui.UIScale.SMALL - else (0, 0), ) ) @@ -118,9 +114,7 @@ class PlaylistCustomizeBrowserWindow(bui.Window): scl = ( 1.1 if uiscale is bui.UIScale.SMALL - else 1.27 - if uiscale is bui.UIScale.MEDIUM - else 1.57 + else 1.27 if uiscale is bui.UIScale.MEDIUM else 1.57 ) scl *= 0.63 v -= 65.0 * scl @@ -285,9 +279,11 @@ class PlaylistCustomizeBrowserWindow(bui.Window): bui.widget( edit=scrollwidget, left_widget=new_button, - right_widget=bui.get_special_widget('party_button') - if bui.app.ui_v1.use_toolbars - else None, + right_widget=( + bui.get_special_widget('party_button') + if bui.app.ui_v1.use_toolbars + else None + ), ) # make sure config exists @@ -329,9 +325,9 @@ class PlaylistCustomizeBrowserWindow(bui.Window): if self._selected_playlist_name is not None: cfg = bui.app.config - cfg[ - self._pvars.config_name + ' Playlist Selection' - ] = self._selected_playlist_name + cfg[self._pvars.config_name + ' Playlist Selection'] = ( + self._selected_playlist_name + ) cfg.commit() bui.containerwidget( @@ -408,9 +404,11 @@ class PlaylistCustomizeBrowserWindow(bui.Window): text=self._get_playlist_display_name(pname), h_align='left', v_align='center', - color=(0.6, 0.6, 0.7, 1.0) - if pname == '__default__' - else (0.85, 0.85, 0.85, 1), + color=( + (0.6, 0.6, 0.7, 1.0) + if pname == '__default__' + else (0.85, 0.85, 0.85, 1) + ), always_highlight=True, on_select_call=bui.Call(self._select, pname, index), on_activate_call=bui.Call(self._edit_button.activate), @@ -458,12 +456,12 @@ class PlaylistCustomizeBrowserWindow(bui.Window): # if we want and also lets us pass it to the game (since we reset # the whole python environment that's not actually easy). cfg = bui.app.config - cfg[ - self._pvars.config_name + ' Playlist Selection' - ] = self._selected_playlist_name - cfg[ - self._pvars.config_name + ' Playlist Randomize' - ] = self._do_randomize_val + cfg[self._pvars.config_name + ' Playlist Selection'] = ( + self._selected_playlist_name + ) + cfg[self._pvars.config_name + ' Playlist Randomize'] = ( + self._do_randomize_val + ) cfg.commit() def _new_playlist(self) -> None: @@ -536,12 +534,10 @@ class PlaylistCustomizeBrowserWindow(bui.Window): # (we don't use len()-1 here because the default list adds one) assert self._selected_playlist_index is not None - if self._selected_playlist_index > len( - bui.app.config[self._pvars.config_name + ' Playlists'] - ): - self._selected_playlist_index = len( - bui.app.config[self._pvars.config_name + ' Playlists'] - ) + self._selected_playlist_index = min( + self._selected_playlist_index, + len(bui.app.config[self._pvars.config_name + ' Playlists']), + ) self._refresh() def _import_playlist(self) -> None: diff --git a/dist/ba_data/python/bauiv1lib/playlist/edit.py b/dist/ba_data/python/bauiv1lib/playlist/edit.py index 11e0019..d01badb 100644 --- a/dist/ba_data/python/bauiv1lib/playlist/edit.py +++ b/dist/ba_data/python/bauiv1lib/playlist/edit.py @@ -36,9 +36,7 @@ class PlaylistEditWindow(bui.Window): self._height = ( 400 if uiscale is bui.UIScale.SMALL - else 470 - if uiscale is bui.UIScale.MEDIUM - else 540 + else 470 if uiscale is bui.UIScale.MEDIUM else 540 ) top_extra = 20 if uiscale is bui.UIScale.SMALL else 0 @@ -49,13 +47,11 @@ class PlaylistEditWindow(bui.Window): scale=( 2.0 if uiscale is bui.UIScale.SMALL - else 1.3 - if uiscale is bui.UIScale.MEDIUM - else 1.0 + else 1.3 if uiscale is bui.UIScale.MEDIUM else 1.0 + ), + stack_offset=( + (0, -16) if uiscale is bui.UIScale.SMALL else (0, 0) ), - stack_offset=(0, -16) - if uiscale is bui.UIScale.SMALL - else (0, 0), ) ) cancel_button = bui.buttonwidget( @@ -149,9 +145,7 @@ class PlaylistEditWindow(bui.Window): scl = ( 1.03 if uiscale is bui.UIScale.SMALL - else 1.36 - if uiscale is bui.UIScale.MEDIUM - else 1.74 + else 1.36 if uiscale is bui.UIScale.MEDIUM else 1.74 ) v -= 63.0 * scl diff --git a/dist/ba_data/python/bauiv1lib/playlist/editgame.py b/dist/ba_data/python/bauiv1lib/playlist/editgame.py index 491608f..c7dea64 100644 --- a/dist/ba_data/python/bauiv1lib/playlist/editgame.py +++ b/dist/ba_data/python/bauiv1lib/playlist/editgame.py @@ -108,9 +108,7 @@ class PlaylistEditGameWindow(bui.Window): height = ( 365 if uiscale is bui.UIScale.SMALL - else 460 - if uiscale is bui.UIScale.MEDIUM - else 550 + else 460 if uiscale is bui.UIScale.MEDIUM else 550 ) spacing = 52 y_extra = 15 @@ -129,13 +127,11 @@ class PlaylistEditGameWindow(bui.Window): scale=( 2.19 if uiscale is bui.UIScale.SMALL - else 1.35 - if uiscale is bui.UIScale.MEDIUM - else 1.0 + else 1.35 if uiscale is bui.UIScale.MEDIUM else 1.0 + ), + stack_offset=( + (0, -17) if uiscale is bui.UIScale.SMALL else (0, 0) ), - stack_offset=(0, -17) - if uiscale is bui.UIScale.SMALL - else (0, 0), ) ) @@ -143,9 +139,11 @@ class PlaylistEditGameWindow(bui.Window): parent=self._root_widget, position=(45 + x_inset, height - 82 + y_extra2), size=(180, 70) if is_add else (180, 65), - label=bui.Lstr(resource='backText') - if is_add - else bui.Lstr(resource='cancelText'), + label=( + bui.Lstr(resource='backText') + if is_add + else bui.Lstr(resource='cancelText') + ), button_type='back' if is_add else None, autoselect=True, scale=0.75, @@ -160,9 +158,11 @@ class PlaylistEditGameWindow(bui.Window): size=(200, 65), scale=0.75, text_scale=1.3, - label=bui.Lstr(resource=self._r + '.addGameText') - if is_add - else bui.Lstr(resource='doneText'), + label=( + bui.Lstr(resource=self._r + '.addGameText') + if is_add + else bui.Lstr(resource='doneText') + ), ) if bui.app.ui_v1.use_toolbars: @@ -447,9 +447,11 @@ class PlaylistEditGameWindow(bui.Window): parent=self._subcontainer, position=(h + 509 - 95, v), size=(0, 28), - text=bui.Lstr(resource='onText') - if value - else bui.Lstr(resource='offText'), + text=( + bui.Lstr(resource='onText') + if value + else bui.Lstr(resource='offText') + ), editable=False, color=(0.6, 1.0, 0.6, 1.0), maxwidth=mw2, @@ -566,9 +568,11 @@ class PlaylistEditGameWindow(bui.Window): ) -> None: bui.textwidget( edit=widget, - text=bui.Lstr(resource='onText') - if value - else bui.Lstr(resource='offText'), + text=( + bui.Lstr(resource='onText') + if value + else bui.Lstr(resource='offText') + ), ) self._settings[setting_name] = value diff --git a/dist/ba_data/python/bauiv1lib/playlist/mapselect.py b/dist/ba_data/python/bauiv1lib/playlist/mapselect.py index 77200be..8e2cc8e 100644 --- a/dist/ba_data/python/bauiv1lib/playlist/mapselect.py +++ b/dist/ba_data/python/bauiv1lib/playlist/mapselect.py @@ -49,9 +49,7 @@ class PlaylistMapSelectWindow(bui.Window): height = ( 400 if uiscale is bui.UIScale.SMALL - else 480 - if uiscale is bui.UIScale.MEDIUM - else 600 + else 480 if uiscale is bui.UIScale.MEDIUM else 600 ) top_extra = 20 if uiscale is bui.UIScale.SMALL else 0 @@ -62,13 +60,11 @@ class PlaylistMapSelectWindow(bui.Window): scale=( 2.17 if uiscale is bui.UIScale.SMALL - else 1.3 - if uiscale is bui.UIScale.MEDIUM - else 1.0 + else 1.3 if uiscale is bui.UIScale.MEDIUM else 1.0 + ), + stack_offset=( + (0, -27) if uiscale is bui.UIScale.SMALL else (0, 0) ), - stack_offset=(0, -27) - if uiscale is bui.UIScale.SMALL - else (0, 0), ) ) diff --git a/dist/ba_data/python/bauiv1lib/playlist/share.py b/dist/ba_data/python/bauiv1lib/playlist/share.py index 480aba5..2f8d7a2 100644 --- a/dist/ba_data/python/bauiv1lib/playlist/share.py +++ b/dist/ba_data/python/bauiv1lib/playlist/share.py @@ -5,18 +5,16 @@ from __future__ import annotations import time -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override -from typing_extensions import override - -from bauiv1lib.promocode import PromoCodeWindow +from bauiv1lib.sendinfo import SendInfoWindow import bauiv1 as bui if TYPE_CHECKING: from typing import Any, Callable -class SharePlaylistImportWindow(PromoCodeWindow): +class SharePlaylistImportWindow(SendInfoWindow): """Window for importing a shared playlist.""" def __init__( @@ -24,7 +22,9 @@ class SharePlaylistImportWindow(PromoCodeWindow): origin_widget: bui.Widget | None = None, on_success_callback: Callable[[], Any] | None = None, ): - PromoCodeWindow.__init__(self, modal=True, origin_widget=origin_widget) + SendInfoWindow.__init__( + self, modal=True, legacy_code_mode=True, origin_widget=origin_widget + ) self._on_success_callback = on_success_callback def _on_import_response(self, response: dict[str, Any] | None) -> None: @@ -93,9 +93,7 @@ class SharePlaylistResultsWindow(bui.Window): scale=( 1.8 if uiscale is bui.UIScale.SMALL - else 1.35 - if uiscale is bui.UIScale.MEDIUM - else 1.0 + else 1.35 if uiscale is bui.UIScale.MEDIUM else 1.0 ), ) ) diff --git a/dist/ba_data/python/bauiv1lib/playoptions.py b/dist/ba_data/python/bauiv1lib/playoptions.py index b2d42b2..a49045c 100644 --- a/dist/ba_data/python/bauiv1lib/playoptions.py +++ b/dist/ba_data/python/bauiv1lib/playoptions.py @@ -5,9 +5,8 @@ from __future__ import annotations import logging -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override -from typing_extensions import override import bascenev1 as bs import bauiv1 as bui @@ -33,6 +32,7 @@ class PlayOptionsWindow(PopupWindow): # pylint: disable=too-many-locals from bascenev1 import filter_playlist, get_map_class from bauiv1lib.playlist import PlaylistTypeVars + from bauiv1lib.config import ConfigNumberEdit self._r = 'gameListWindow' self._delegate = delegate @@ -52,7 +52,7 @@ class PlayOptionsWindow(PopupWindow): self._playlist = playlist self._width = 500.0 - self._height = 330.0 - 50.0 + self._height = 370.0 - 50.0 # In teams games, show the custom names/colors button. if self._sessiontype is bs.DualTeamSession: @@ -145,9 +145,7 @@ class PlayOptionsWindow(PopupWindow): scale = ( 1.69 if uiscale is bui.UIScale.SMALL - else 1.1 - if uiscale is bui.UIScale.MEDIUM - else 0.85 + else 1.1 if uiscale is bui.UIScale.MEDIUM else 0.85 ) # Creates our _root_widget. super().__init__( @@ -275,13 +273,40 @@ class PlayOptionsWindow(PopupWindow): texture=bui.gettexture('lock'), ) + y_offs = 50 if show_shuffle_check_box else 0 + + # Series Length + y_offs2 = 40 if self._sessiontype is bs.DualTeamSession else 0 + self._series_length_numedit = ConfigNumberEdit( + parent=self.root_widget, + position=(100, 200 + y_offs + y_offs2), + configkey=( + 'FFA' if self._sessiontype is bs.FreeForAllSession else 'Teams' + ) + + ' Series Length', + displayname=bui.Lstr( + resource=self._r + + ( + '.pointsToWinText' + if self._sessiontype is bs.FreeForAllSession + else '.seriesLengthText' + ) + ), + minval=1.0, + maxval=100.0 if self._sessiontype is bs.FreeForAllSession else 99.0, + increment=1.0 if self._sessiontype is bs.FreeForAllSession else 2.0, + fallback_value=( + 24 if self._sessiontype is bs.FreeForAllSession else 7 + ), + f=0, + ) + # Team names/colors. self._custom_colors_names_button: bui.Widget | None if self._sessiontype is bs.DualTeamSession: - y_offs = 50 if show_shuffle_check_box else 0 self._custom_colors_names_button = bui.buttonwidget( parent=self.root_widget, - position=(100, 200 + y_offs), + position=(100, 195 + y_offs), size=(290, 35), on_activate_call=bui.WeakCall(self._custom_colors_names_press), autoselect=True, @@ -304,9 +329,9 @@ class PlayOptionsWindow(PopupWindow): def _cb_callback(val: bool) -> None: self._do_randomize_val = val cfg = bui.app.config - cfg[ - self._pvars.config_name + ' Playlist Randomize' - ] = self._do_randomize_val + cfg[self._pvars.config_name + ' Playlist Randomize'] = ( + self._do_randomize_val + ) cfg.commit() if show_shuffle_check_box: diff --git a/dist/ba_data/python/bauiv1lib/popup.py b/dist/ba_data/python/bauiv1lib/popup.py index b53cfc9..ef830f8 100644 --- a/dist/ba_data/python/bauiv1lib/popup.py +++ b/dist/ba_data/python/bauiv1lib/popup.py @@ -5,9 +5,7 @@ from __future__ import annotations import weakref -from typing import TYPE_CHECKING - -from typing_extensions import override +from typing import TYPE_CHECKING, override import bauiv1 as bui @@ -129,8 +127,8 @@ class PopupMenuWindow(PopupWindow): if choices_display is None: choices_display = [] - # FIXME: For the moment we base our width on these strings so - # we need to flatten them. + # FIXME: For the moment we base our width on these strings so we + # need to flatten them. choices_display_fin: list[str] = [] for choice_display in choices_display: choices_display_fin.append(choice_display.evaluate()) @@ -154,9 +152,9 @@ class PopupMenuWindow(PopupWindow): else: self._height = 20 + len(choices) * 33 self._use_scroll = False - self._delegate = None # don't want this stuff called just yet.. + self._delegate = None # Don't want this stuff called just yet. - # extend width to fit our longest string (or our max-width) + # Extend width to fit our longest string (or our max-width). for index, choice in enumerate(choices): if len(choices_display_fin) == len(choices): choice_display_name = choices_display_fin[index] @@ -185,8 +183,8 @@ class PopupMenuWindow(PopupWindow): + 60, ) - # init parent class - this will rescale and reposition things as - # needed and create our root widget + # Init parent class - this will rescale and reposition things as + # needed and create our root widget. super().__init__( position, size=(self._width, self._height), scale=self._scale ) @@ -205,7 +203,7 @@ class PopupMenuWindow(PopupWindow): else: self._offset_widget = bui.containerwidget( parent=self.root_widget, - position=(30, 15), + position=(12, 12), size=(self._width - 40, self._height), background=False, ) @@ -223,12 +221,14 @@ class PopupMenuWindow(PopupWindow): size=(self._width - 40, 28), on_select_call=bui.Call(self._select, index), click_activate=True, - color=(0.5, 0.5, 0.5, 0.5) - if inactive - else ( - (0.5, 1, 0.5, 1) - if choice == self._current_choice - else (0.8, 0.8, 0.8, 1.0) + color=( + (0.5, 0.5, 0.5, 0.5) + if inactive + else ( + (0.5, 1, 0.5, 1) + if choice == self._current_choice + else (0.8, 0.8, 0.8, 1.0) + ) ), padding=0, maxwidth=maxwidth, @@ -236,6 +236,7 @@ class PopupMenuWindow(PopupWindow): on_activate_call=self._activate, v_align='center', selectable=(not inactive), + glow_type='uniform', ) if choice == self._current_choice: bui.containerwidget( @@ -318,9 +319,7 @@ class PopupMenu: scale = ( 2.3 if uiscale is bui.UIScale.SMALL - else 1.65 - if uiscale is bui.UIScale.MEDIUM - else 1.23 + else 1.65 if uiscale is bui.UIScale.MEDIUM else 1.23 ) if current_choice not in choices: current_choice = None diff --git a/dist/ba_data/python/bauiv1lib/profile/browser.py b/dist/ba_data/python/bauiv1lib/profile/browser.py index ef9db11..70d1cf3 100644 --- a/dist/ba_data/python/bauiv1lib/profile/browser.py +++ b/dist/ba_data/python/bauiv1lib/profile/browser.py @@ -38,9 +38,7 @@ class ProfileBrowserWindow(bui.Window): self._height = ( 360.0 if uiscale is bui.UIScale.SMALL - else 385.0 - if uiscale is bui.UIScale.MEDIUM - else 410.0 + else 385.0 if uiscale is bui.UIScale.MEDIUM else 410.0 ) # If we're being called up standalone, handle pause/resume ourself. @@ -74,13 +72,11 @@ class ProfileBrowserWindow(bui.Window): scale=( 2.2 if uiscale is bui.UIScale.SMALL - else 1.6 - if uiscale is bui.UIScale.MEDIUM - else 1.0 + else 1.6 if uiscale is bui.UIScale.MEDIUM else 1.0 + ), + stack_offset=( + (0, -14) if uiscale is bui.UIScale.SMALL else (0, 0) ), - stack_offset=(0, -14) - if uiscale is bui.UIScale.SMALL - else (0, 0), ) ) @@ -125,9 +121,7 @@ class ProfileBrowserWindow(bui.Window): scl = ( 1.055 if uiscale is bui.UIScale.SMALL - else 1.18 - if uiscale is bui.UIScale.MEDIUM - else 1.3 + else 1.18 if uiscale is bui.UIScale.MEDIUM else 1.3 ) v -= 70.0 * scl self._new_button = bui.buttonwidget( @@ -417,24 +411,11 @@ class ProfileBrowserWindow(bui.Window): char_index = spazzes.index('Spaz') assert isinstance(tval, str) - character = bui.buttonwidget( - parent=self._subcontainer, - position=(0, y_val), - size=(28, 28), - label='', - color=(1, 1, 1), - mask_texture=bui.gettexture('characterIconMask'), - tint_color=color, - tint2_color=_highlight, - texture=icon_textures[char_index], - tint_texture=icon_tint_textures[char_index], - selectable=False, - ) txtw = bui.textwidget( parent=self._subcontainer, - position=(35, y_val), + position=(5, y_val), size=((self._width - 210) / scl, 28), - text=bui.Lstr(value=tval), + text=bui.Lstr(value=f' {tval}'), h_align='left', v_align='center', on_select_call=bui.WeakCall(self._select, p_name, index), @@ -445,6 +426,17 @@ class ProfileBrowserWindow(bui.Window): on_activate_call=bui.Call(self._edit_button.activate), selectable=True, ) + character = bui.imagewidget( + parent=self._subcontainer, + position=(0, y_val), + size=(30, 30), + color=(1, 1, 1), + mask_texture=bui.gettexture('characterIconMask'), + tint_color=color, + tint2_color=_highlight, + texture=icon_textures[char_index], + tint_texture=icon_tint_textures[char_index], + ) if index == 0: bui.widget(edit=txtw, up_widget=self._back_button) if self._selected_profile is None: diff --git a/dist/ba_data/python/bauiv1lib/profile/edit.py b/dist/ba_data/python/bauiv1lib/profile/edit.py index 8456c60..7099e04 100644 --- a/dist/ba_data/python/bauiv1lib/profile/edit.py +++ b/dist/ba_data/python/bauiv1lib/profile/edit.py @@ -65,17 +65,13 @@ class EditProfileWindow(bui.Window): self._height = height = ( 350.0 if uiscale is bui.UIScale.SMALL - else 400.0 - if uiscale is bui.UIScale.MEDIUM - else 450.0 + else 400.0 if uiscale is bui.UIScale.MEDIUM else 450.0 ) spacing = 40 self._base_scale = ( 2.05 if uiscale is bui.UIScale.SMALL - else 1.5 - if uiscale is bui.UIScale.MEDIUM - else 1.0 + else 1.5 if uiscale is bui.UIScale.MEDIUM else 1.0 ) top_extra = 15 if uiscale is bui.UIScale.SMALL else 15 super().__init__( @@ -83,9 +79,9 @@ class EditProfileWindow(bui.Window): size=(width, height + top_extra), transition=transition, scale=self._base_scale, - stack_offset=(0, 15) - if uiscale is bui.UIScale.SMALL - else (0, 0), + stack_offset=( + (0, 15) if uiscale is bui.UIScale.SMALL else (0, 0) + ), ) ) cancel_button = btn = bui.buttonwidget( @@ -475,9 +471,11 @@ class EditProfileWindow(bui.Window): parent=self._root_widget, autoselect=True, position=(self._width * 0.5 + b_offs - b_size * 0.5, v - 50), - up_widget=self._upgrade_button - if self._upgrade_button is not None - else self._account_type_info_button, + up_widget=( + self._upgrade_button + if self._upgrade_button is not None + else self._account_type_info_button + ), size=(b_size, b_size), color=self._highlight, label='', @@ -803,6 +801,15 @@ class EditProfileWindow(bui.Window): bui.getsound('error').play() return False + # Make sure we're not renaming to another existing profile. + profiles: dict = bui.app.config.get('Player Profiles', {}) + if self._existing_profile != new_name and new_name in profiles.keys(): + bui.screenmessage( + bui.Lstr(resource='editProfileWindow.profileAlreadyExistsText') + ) + bui.getsound('error').play() + return False + if transition_out: bui.getsound('gunCocking').play() diff --git a/dist/ba_data/python/bauiv1lib/profile/upgrade.py b/dist/ba_data/python/bauiv1lib/profile/upgrade.py index b4f0243..a3968d9 100644 --- a/dist/ba_data/python/bauiv1lib/profile/upgrade.py +++ b/dist/ba_data/python/bauiv1lib/profile/upgrade.py @@ -39,9 +39,7 @@ class ProfileUpgradeWindow(bui.Window): self._base_scale = ( 2.05 if uiscale is bui.UIScale.SMALL - else 1.5 - if uiscale is bui.UIScale.MEDIUM - else 1.2 + else 1.5 if uiscale is bui.UIScale.MEDIUM else 1.2 ) self._upgrade_start_time: float | None = None self._name = edit_profile_window.getname() @@ -54,9 +52,9 @@ class ProfileUpgradeWindow(bui.Window): toolbar_visibility='menu_currency', transition=transition, scale=self._base_scale, - stack_offset=(0, 15) - if uiscale is bui.UIScale.SMALL - else (0, 0), + stack_offset=( + (0, 15) if uiscale is bui.UIScale.SMALL else (0, 0) + ), ) ) cancel_button = bui.buttonwidget( @@ -155,7 +153,7 @@ class ProfileUpgradeWindow(bui.Window): bui.app.classic.master_server_v1_get( 'bsGlobalProfileCheck', - {'name': self._name, 'b': bui.app.env.build_number}, + {'name': self._name, 'b': bui.app.env.engine_build_number}, callback=bui.WeakCall(self._profile_check_result), ) self._cost = plus.get_v1_account_misc_read_val( diff --git a/dist/ba_data/python/bauiv1lib/purchase.py b/dist/ba_data/python/bauiv1lib/purchase.py index 1a9c04f..56e03ce 100644 --- a/dist/ba_data/python/bauiv1lib/purchase.py +++ b/dist/ba_data/python/bauiv1lib/purchase.py @@ -48,13 +48,11 @@ class PurchaseWindow(bui.Window): scale=( 1.2 if uiscale is bui.UIScale.SMALL - else 1.1 - if uiscale is bui.UIScale.MEDIUM - else 1.0 + else 1.1 if uiscale is bui.UIScale.MEDIUM else 1.0 + ), + stack_offset=( + (0, -15) if uiscale is bui.UIScale.SMALL else (0, 0) ), - stack_offset=(0, -15) - if uiscale is bui.UIScale.SMALL - else (0, 0), ) ) self._is_double = False diff --git a/dist/ba_data/python/bauiv1lib/qrcode.py b/dist/ba_data/python/bauiv1lib/qrcode.py index 467bab7..d982702 100644 --- a/dist/ba_data/python/bauiv1lib/qrcode.py +++ b/dist/ba_data/python/bauiv1lib/qrcode.py @@ -3,7 +3,8 @@ """Provides functionality for displaying QR codes.""" from __future__ import annotations -from typing_extensions import override +from typing import override + import bauiv1 as bui from bauiv1lib.popup import PopupWindow @@ -19,9 +20,7 @@ class QRCodeWindow(PopupWindow): scale = ( 2.3 if uiscale is bui.UIScale.SMALL - else 1.65 - if uiscale is bui.UIScale.MEDIUM - else 1.23 + else 1.65 if uiscale is bui.UIScale.MEDIUM else 1.23 ) self._transitioning_out = False self._width = 450 diff --git a/dist/ba_data/python/bauiv1lib/report.py b/dist/ba_data/python/bauiv1lib/report.py index 1b00e11..a6256e9 100644 --- a/dist/ba_data/python/bauiv1lib/report.py +++ b/dist/ba_data/python/bauiv1lib/report.py @@ -29,9 +29,7 @@ class ReportPlayerWindow(bui.Window): scale=( 1.8 if uiscale is bui.UIScale.SMALL - else 1.35 - if uiscale is bui.UIScale.MEDIUM - else 1.0 + else 1.35 if uiscale is bui.UIScale.MEDIUM else 1.0 ), ) ) diff --git a/dist/ba_data/python/bauiv1lib/resourcetypeinfo.py b/dist/ba_data/python/bauiv1lib/resourcetypeinfo.py index 6c3fc58..a3e39eb 100644 --- a/dist/ba_data/python/bauiv1lib/resourcetypeinfo.py +++ b/dist/ba_data/python/bauiv1lib/resourcetypeinfo.py @@ -4,7 +4,7 @@ from __future__ import annotations -from typing_extensions import override +from typing import override from bauiv1lib.popup import PopupWindow import bauiv1 as bui @@ -19,9 +19,7 @@ class ResourceTypeInfoWindow(PopupWindow): scale = ( 2.3 if uiscale is bui.UIScale.SMALL - else 1.65 - if uiscale is bui.UIScale.MEDIUM - else 1.23 + else 1.65 if uiscale is bui.UIScale.MEDIUM else 1.23 ) self._transitioning_out = False self._width = 570 diff --git a/dist/ba_data/python/bauiv1lib/sendinfo.py b/dist/ba_data/python/bauiv1lib/sendinfo.py new file mode 100644 index 0000000..5915e4f --- /dev/null +++ b/dist/ba_data/python/bauiv1lib/sendinfo.py @@ -0,0 +1,292 @@ +# Released under the MIT License. See LICENSE for details. +# +"""UI functionality for entering promo codes.""" + +from __future__ import annotations + +import time +import logging +from typing import TYPE_CHECKING + +import bauiv1 as bui + +if TYPE_CHECKING: + from typing import Any + + +class SendInfoWindow(bui.Window): + """Window for sending info to the developer.""" + + def __init__( + self, + modal: bool = False, + legacy_code_mode: bool = False, + origin_widget: bui.Widget | None = None, + ): + self._legacy_code_mode = legacy_code_mode + + scale_origin: tuple[float, float] | None + if origin_widget is not None: + self._transition_out = 'out_scale' + scale_origin = origin_widget.get_screen_space_center() + transition = 'in_scale' + else: + self._transition_out = 'out_right' + scale_origin = None + transition = 'in_right' + + width = 450 if legacy_code_mode else 600 + height = 200 if legacy_code_mode else 300 + + self._modal = modal + self._r = 'promoCodeWindow' + + assert bui.app.classic is not None + uiscale = bui.app.ui_v1.uiscale + super().__init__( + root_widget=bui.containerwidget( + size=(width, height), + transition=transition, + toolbar_visibility='menu_minimal_no_back', + scale_origin_stack_offset=scale_origin, + scale=( + 2.0 + if uiscale is bui.UIScale.SMALL + else 1.5 if uiscale is bui.UIScale.MEDIUM else 1.0 + ), + ) + ) + + btn = bui.buttonwidget( + parent=self._root_widget, + scale=0.5, + position=(40, height - 40), + size=(60, 60), + label='', + on_activate_call=self._do_back, + autoselect=True, + color=(0.55, 0.5, 0.6), + icon=bui.gettexture('crossOut'), + iconscale=1.2, + ) + + v = height - 74 + + if legacy_code_mode: + v -= 20 + else: + v -= 20 + bui.textwidget( + parent=self._root_widget, + text=bui.Lstr(resource='sendInfoDescriptionText'), + maxwidth=width * 0.9, + position=(width * 0.5, v), + color=(0.7, 0.7, 0.7, 1.0), + size=(0, 0), + scale=0.8, + h_align='center', + v_align='center', + ) + v -= 20 + + # bui.textwidget( + # parent=self._root_widget, + # text=bui.Lstr( + # resource='supportEmailText', + # subs=[('${EMAIL}', 'support@froemling.net')], + # ), + # maxwidth=width * 0.9, + # position=(width * 0.5, v), + # color=(0.7, 0.7, 0.7, 1.0), + # size=(0, 0), + # scale=0.65, + # h_align='center', + # v_align='center', + # ) + v -= 80 + + bui.textwidget( + parent=self._root_widget, + text=bui.Lstr( + resource=( + self._r + '.codeText' + if legacy_code_mode + else 'descriptionText' + ) + ), + position=(22, v), + color=(0.8, 0.8, 0.8, 1.0), + size=(90, 30), + h_align='right', + maxwidth=100, + ) + v -= 8 + + self._text_field = bui.textwidget( + parent=self._root_widget, + position=(125, v), + size=(280 if legacy_code_mode else 380, 46), + text='', + h_align='left', + v_align='center', + max_chars=64, + color=(0.9, 0.9, 0.9, 1.0), + description=bui.Lstr( + resource=( + self._r + '.codeText' + if legacy_code_mode + else 'descriptionText' + ) + ), + editable=True, + padding=4, + on_return_press_call=self._activate_enter_button, + ) + bui.widget(edit=btn, down_widget=self._text_field) + + v -= 79 + b_width = 200 + self._enter_button = btn2 = bui.buttonwidget( + parent=self._root_widget, + position=(width * 0.5 - b_width * 0.5, v), + size=(b_width, 60), + scale=1.0, + label=bui.Lstr( + resource='submitText', fallback_resource=self._r + '.enterText' + ), + on_activate_call=self._do_enter, + ) + bui.containerwidget( + edit=self._root_widget, + cancel_button=btn, + start_button=btn2, + selected_child=self._text_field, + ) + + def _do_back(self) -> None: + # pylint: disable=cyclic-import + from bauiv1lib.settings.advanced import AdvancedSettingsWindow + + # no-op if our underlying widget is dead or on its way out. + if not self._root_widget or self._root_widget.transitioning_out: + return + + bui.containerwidget( + edit=self._root_widget, transition=self._transition_out + ) + if not self._modal: + assert bui.app.classic is not None + bui.app.ui_v1.set_main_menu_window( + AdvancedSettingsWindow(transition='in_left').get_root_widget(), + from_window=self._root_widget, + ) + + def _activate_enter_button(self) -> None: + self._enter_button.activate() + + def _do_enter(self) -> None: + # pylint: disable=cyclic-import + from bauiv1lib.settings.advanced import AdvancedSettingsWindow + + plus = bui.app.plus + assert plus is not None + + # no-op if our underlying widget is dead or on its way out. + if not self._root_widget or self._root_widget.transitioning_out: + return + + bui.containerwidget( + edit=self._root_widget, transition=self._transition_out + ) + if not self._modal: + assert bui.app.classic is not None + bui.app.ui_v1.set_main_menu_window( + AdvancedSettingsWindow(transition='in_left').get_root_widget(), + from_window=self._root_widget, + ) + + description: Any = bui.textwidget(query=self._text_field) + assert isinstance(description, str) + + # Used for things like unlocking shared playlists or linking + # accounts: talk directly to V1 server via transactions. + if self._legacy_code_mode: + if plus.get_v1_account_state() != 'signed_in': + bui.screenmessage( + bui.Lstr(resource='notSignedInErrorText'), color=(1, 0, 0) + ) + bui.getsound('error').play() + else: + plus.add_v1_account_transaction( + { + 'type': 'PROMO_CODE', + 'expire_time': time.time() + 5, + 'code': description, + } + ) + plus.run_v1_account_transactions() + else: + bui.app.create_async_task(_send_info(description)) + + +async def _send_info(description: str) -> None: + from bacommon.cloud import SendInfoMessage + + plus = bui.app.plus + assert plus is not None + + try: + # Don't allow *anything* if our V2 transport connection isn't up. + if not plus.cloud.connected: + bui.screenmessage( + bui.Lstr(resource='internal.unavailableNoConnectionText'), + color=(1, 0, 0), + ) + bui.getsound('error').play() + return + + # Ship to V2 server, with or without account info. + if plus.accounts.primary is not None: + with plus.accounts.primary: + response = await plus.cloud.send_message_async( + SendInfoMessage(description) + ) + else: + response = await plus.cloud.send_message_async( + SendInfoMessage(description) + ) + + # Support simple message printing from v2 server. + if response.message is not None: + bui.screenmessage(response.message, color=(0, 1, 0)) + + # If V2 handled it, we're done. + if response.handled: + return + + # Ok; V2 didn't handle it. Try V1 if we're signed in there. + if plus.get_v1_account_state() != 'signed_in': + bui.screenmessage( + bui.Lstr(resource='notSignedInErrorText'), color=(1, 0, 0) + ) + bui.getsound('error').play() + return + + # Push it along to v1 as an old style code. Allow v2 response to + # sub in its own code. + plus.add_v1_account_transaction( + { + 'type': 'PROMO_CODE', + 'expire_time': time.time() + 5, + 'code': ( + description + if response.legacy_code is None + else response.legacy_code + ), + } + ) + plus.run_v1_account_transactions() + except Exception: + logging.exception('Error sending promo code.') + bui.screenmessage('Error sending code (see log).', color=(1, 0, 0)) + bui.getsound('error').play() diff --git a/dist/ba_data/python/bauiv1lib/serverdialog.py b/dist/ba_data/python/bauiv1lib/serverdialog.py index acd8693..92ac7d5 100644 --- a/dist/ba_data/python/bauiv1lib/serverdialog.py +++ b/dist/ba_data/python/bauiv1lib/serverdialog.py @@ -51,9 +51,7 @@ class ServerDialogWindow(bui.Window): scale=( 1.8 if uiscale is bui.UIScale.SMALL - else 1.35 - if uiscale is bui.UIScale.MEDIUM - else 1.0 + else 1.35 if uiscale is bui.UIScale.MEDIUM else 1.0 ), ) ) @@ -114,9 +112,11 @@ class ServerDialogWindow(bui.Window): self._ok_button = bui.buttonwidget( parent=self._root_widget, position=( - (self._width - 182) - if (data.show_cancel or show_copy) - else (self._width * 0.5 - 80), + ( + (self._width - 182) + if (data.show_cancel or show_copy) + else (self._width * 0.5 - 80) + ), 30, ), size=(160, 60), diff --git a/dist/ba_data/python/bauiv1lib/settings/advanced.py b/dist/ba_data/python/bauiv1lib/settings/advanced.py index 3828537..521762a 100644 --- a/dist/ba_data/python/bauiv1lib/settings/advanced.py +++ b/dist/ba_data/python/bauiv1lib/settings/advanced.py @@ -52,9 +52,7 @@ class AdvancedSettingsWindow(bui.Window): self._height = ( 390.0 if uiscale is bui.UIScale.SMALL - else 450.0 - if uiscale is bui.UIScale.MEDIUM - else 520.0 + else 450.0 if uiscale is bui.UIScale.MEDIUM else 520.0 ) self._lang_status_text: bui.Widget | None = None @@ -71,13 +69,11 @@ class AdvancedSettingsWindow(bui.Window): scale=( 2.06 if uiscale is bui.UIScale.SMALL - else 1.4 - if uiscale is bui.UIScale.MEDIUM - else 1.0 + else 1.4 if uiscale is bui.UIScale.MEDIUM else 1.0 + ), + stack_offset=( + (0, -25) if uiscale is bui.UIScale.SMALL else (0, 0) ), - stack_offset=(0, -25) - if uiscale is bui.UIScale.SMALL - else (0, 0), ) ) @@ -94,7 +90,7 @@ class AdvancedSettingsWindow(bui.Window): self._scroll_width = self._width - (100 + 2 * x_inset) self._scroll_height = self._height - 115.0 self._sub_width = self._scroll_width * 0.95 - self._sub_height = 808.0 + self._sub_height = 870.0 if self._show_always_use_internal_keyboard: self._sub_height += 62 @@ -112,6 +108,7 @@ class AdvancedSettingsWindow(bui.Window): if self._do_net_test_button: self._sub_height += self._extra_button_spacing self._sub_height += self._spacing * 2.0 # plugins + self._sub_height += self._spacing * 2.0 # dev tools self._r = 'settingsWindowAdvanced' @@ -179,7 +176,7 @@ class AdvancedSettingsWindow(bui.Window): # Fetch the list of completed languages. bui.app.classic.master_server_v1_get( 'bsLangGetCompleted', - {'b': app.env.build_number}, + {'b': app.env.engine_build_number}, callback=bui.WeakCall(self._completed_langs_cb), ) @@ -193,39 +190,48 @@ class AdvancedSettingsWindow(bui.Window): from bauiv1lib.settings import nettesting as _unused4 from bauiv1lib import appinvite as _unused5 from bauiv1lib import account as _unused6 - from bauiv1lib import promocode as _unused7 + from bauiv1lib import sendinfo as _unused7 from bauiv1lib import debug as _unused8 from bauiv1lib.settings import plugins as _unused9 + from bauiv1lib.settings import devtools as _unused10 def _update_lang_status(self) -> None: if self._complete_langs_list is not None: up_to_date = bui.app.lang.language in self._complete_langs_list bui.textwidget( edit=self._lang_status_text, - text='' - if bui.app.lang.language == 'Test' - else bui.Lstr( - resource=f'{self._r}.translationNoUpdateNeededText' - ) - if up_to_date - else bui.Lstr( - resource=f'{self._r}.translationUpdateNeededText' + text=( + '' + if bui.app.lang.language == 'Test' + else ( + bui.Lstr( + resource=f'{self._r}.translationNoUpdateNeededText' + ) + if up_to_date + else bui.Lstr( + resource=f'{self._r}.translationUpdateNeededText' + ) + ) + ), + color=( + (0.2, 1.0, 0.2, 0.8) if up_to_date else (1.0, 0.2, 0.2, 0.8) ), - color=(0.2, 1.0, 0.2, 0.8) - if up_to_date - else (1.0, 0.2, 0.2, 0.8), ) else: bui.textwidget( edit=self._lang_status_text, - text=bui.Lstr(resource=f'{self._r}.translationFetchErrorText') - if self._complete_langs_error - else bui.Lstr( - resource=f'{self._r}.translationFetchingStatusText' + text=( + bui.Lstr(resource=f'{self._r}.translationFetchErrorText') + if self._complete_langs_error + else bui.Lstr( + resource=f'{self._r}.translationFetchingStatusText' + ) + ), + color=( + (1.0, 0.5, 0.2) + if self._complete_langs_error + else (0.7, 0.7, 0.7) ), - color=(1.0, 0.5, 0.2) - if self._complete_langs_error - else (0.7, 0.7, 0.7), ) def _rebuild(self) -> None: @@ -282,33 +288,16 @@ class AdvancedSettingsWindow(bui.Window): this_button_width = 410 - self._promo_code_button = bui.buttonwidget( - parent=self._subcontainer, - position=(self._sub_width / 2 - this_button_width / 2, v - 14), - size=(this_button_width, 60), - autoselect=True, - label=bui.Lstr(resource=f'{self._r}.enterPromoCodeText'), - text_scale=1.0, - on_activate_call=self._on_promo_code_press, - ) - if self._back_button is not None: - bui.widget( - edit=self._promo_code_button, - up_widget=self._back_button, - left_widget=self._back_button, - ) - v -= self._extra_button_spacing * 0.8 - assert bui.app.classic is not None bui.textwidget( parent=self._subcontainer, - position=(200, v + 10), + position=(70, v + 10), size=(0, 0), text=bui.Lstr(resource=f'{self._r}.languageText'), maxwidth=150, - scale=0.95, + scale=1.2, color=bui.app.ui_v1.title_color, - h_align='right', + h_align='left', v_align='center', ) @@ -387,7 +376,7 @@ class AdvancedSettingsWindow(bui.Window): bui.textwidget( parent=self._subcontainer, - position=(self._sub_width * 0.5, v + 10), + position=(90, v + 10), size=(0, 0), text=bui.Lstr( resource=f'{self._r}.helpTranslateText', @@ -398,7 +387,7 @@ class AdvancedSettingsWindow(bui.Window): flatness=1.0, scale=0.65, color=(0.4, 0.9, 0.4, 0.8), - h_align='center', + h_align='left', v_align='center', ) v -= self._spacing * 1.9 @@ -429,7 +418,7 @@ class AdvancedSettingsWindow(bui.Window): maxwidth=400.0, ) self._update_lang_status() - v -= 40 + v -= 50 lang_inform = plus.get_v1_account_misc_val('langInform', False) @@ -476,19 +465,6 @@ class AdvancedSettingsWindow(bui.Window): maxwidth=430, ) - v -= 42 - self._show_dev_console_button_check_box = ConfigCheckBox( - parent=self._subcontainer, - position=(50, v), - size=(self._sub_width - 100, 30), - configkey='Show Dev Console Button', - displayname=bui.Lstr( - resource=f'{self._r}.showDevConsoleButtonText' - ), - scale=1.0, - maxwidth=430, - ) - v -= 42 self._show_demos_when_idle_check_box = ConfigCheckBox( parent=self._subcontainer, @@ -500,6 +476,19 @@ class AdvancedSettingsWindow(bui.Window): maxwidth=430, ) + v -= 42 + self._show_deprecated_login_types_check_box = ConfigCheckBox( + parent=self._subcontainer, + position=(50, v), + size=(self._sub_width - 100, 30), + configkey='Show Deprecated Login Types', + displayname=bui.Lstr( + resource=f'{self._r}.showDeprecatedLoginTypesText' + ), + scale=1.0, + maxwidth=430, + ) + v -= 42 self._disable_camera_shake_check_box = ConfigCheckBox( parent=self._subcontainer, @@ -575,6 +564,19 @@ class AdvancedSettingsWindow(bui.Window): bui.open_url, 'https://ballistica.net/wiki/modding-guide' ), ) + + v -= self._spacing * 2.0 + + self._dev_tools_button = bui.buttonwidget( + parent=self._subcontainer, + position=(self._sub_width / 2 - this_button_width / 2, v - 10), + size=(this_button_width, 60), + autoselect=True, + label=bui.Lstr(resource=f'{self._r}.devToolsText'), + text_scale=1.0, + on_activate_call=self._on_dev_tools_button_press, + ) + if self._show_always_use_internal_keyboard: assert self._always_use_internal_keyboard_check_box is not None bui.widget( @@ -668,6 +670,17 @@ class AdvancedSettingsWindow(bui.Window): on_activate_call=self._on_benchmark_press, ) + v -= 100 + self._send_info_button = bui.buttonwidget( + parent=self._subcontainer, + position=(self._sub_width / 2 - this_button_width / 2, v - 14), + size=(this_button_width, 60), + autoselect=True, + label=bui.Lstr(resource=f'{self._r}.sendInfoText'), + text_scale=1.0, + on_activate_call=self._on_send_info_press, + ) + for child in self._subcontainer.get_children(): bui.widget(edit=child, show_buffer_bottom=30, show_buffer_top=20) @@ -720,14 +733,6 @@ class AdvancedSettingsWindow(bui.Window): if not self._root_widget or self._root_widget.transitioning_out: return - # Net-testing requires a signed in v1 account. - if plus.get_v1_account_state() != 'signed_in': - bui.screenmessage( - bui.Lstr(resource='notSignedInErrorText'), color=(1, 0, 0) - ) - bui.getsound('error').play() - return - self._save_state() bui.containerwidget(edit=self._root_widget, transition='out_left') assert bui.app.classic is not None @@ -763,9 +768,26 @@ class AdvancedSettingsWindow(bui.Window): from_window=self._root_widget, ) - def _on_promo_code_press(self) -> None: - from bauiv1lib.promocode import PromoCodeWindow - from bauiv1lib.account import show_sign_in_prompt + def _on_dev_tools_button_press(self) -> None: + # pylint: disable=cyclic-import + from bauiv1lib.settings.devtools import DevToolsWindow + + # no-op if our underlying widget is dead or on its way out. + if not self._root_widget or self._root_widget.transitioning_out: + return + + self._save_state() + bui.containerwidget(edit=self._root_widget, transition='out_left') + assert bui.app.classic is not None + bui.app.ui_v1.set_main_menu_window( + DevToolsWindow( + origin_widget=self._dev_tools_button + ).get_root_widget(), + from_window=self._root_widget, + ) + + def _on_send_info_press(self) -> None: + from bauiv1lib.sendinfo import SendInfoWindow # no-op if our underlying widget is dead or on its way out. if not self._root_widget or self._root_widget.transitioning_out: @@ -774,17 +796,12 @@ class AdvancedSettingsWindow(bui.Window): plus = bui.app.plus assert plus is not None - # We have to be logged in for promo-codes to work. - if plus.get_v1_account_state() != 'signed_in': - show_sign_in_prompt() - return - self._save_state() bui.containerwidget(edit=self._root_widget, transition='out_left') assert bui.app.classic is not None bui.app.ui_v1.set_main_menu_window( - PromoCodeWindow( - origin_widget=self._promo_code_button + SendInfoWindow( + origin_widget=self._send_info_button ).get_root_widget(), from_window=self._root_widget, ) @@ -806,6 +823,7 @@ class AdvancedSettingsWindow(bui.Window): def _save_state(self) -> None: # pylint: disable=too-many-branches + # pylint: disable=too-many-statements try: sel = self._root_widget.get_selected_child() if sel == self._scrollwidget: @@ -814,14 +832,16 @@ class AdvancedSettingsWindow(bui.Window): sel_name = 'VRTest' elif sel == self._net_test_button: sel_name = 'NetTest' - elif sel == self._promo_code_button: - sel_name = 'PromoCode' + elif sel == self._send_info_button: + sel_name = 'SendInfo' elif sel == self._benchmarks_button: sel_name = 'Benchmarks' elif sel == self._kick_idle_players_check_box.widget: sel_name = 'KickIdlePlayers' elif sel == self._show_demos_when_idle_check_box.widget: sel_name = 'ShowDemosWhenIdle' + elif sel == self._show_deprecated_login_types_check_box.widget: + sel_name = 'ShowDeprecatedLoginTypes' elif sel == self._show_game_ping_check_box.widget: sel_name = 'ShowPing' elif sel == self._disable_camera_shake_check_box.widget: @@ -848,12 +868,12 @@ class AdvancedSettingsWindow(bui.Window): sel_name = 'ShowUserMods' elif sel == self._plugins_button: sel_name = 'Plugins' + elif sel == self._dev_tools_button: + sel_name = 'DevTools' elif sel == self._modding_guide_button: sel_name = 'ModdingGuide' elif sel == self._language_inform_checkbox: sel_name = 'LangInform' - elif sel == self._show_dev_console_button_check_box.widget: - sel_name = 'ShowDevConsole' else: raise ValueError(f'unrecognized selection \'{sel}\'') elif sel == self._back_button: @@ -883,14 +903,16 @@ class AdvancedSettingsWindow(bui.Window): sel = self._vr_test_button elif sel_name == 'NetTest': sel = self._net_test_button - elif sel_name == 'PromoCode': - sel = self._promo_code_button + elif sel_name == 'SendInfo': + sel = self._send_info_button elif sel_name == 'Benchmarks': sel = self._benchmarks_button elif sel_name == 'KickIdlePlayers': sel = self._kick_idle_players_check_box.widget elif sel_name == 'ShowDemosWhenIdle': sel = self._show_demos_when_idle_check_box.widget + elif sel_name == 'ShowDeprecatedLoginTypes': + sel = self._show_deprecated_login_types_check_box.widget elif sel_name == 'ShowPing': sel = self._show_game_ping_check_box.widget elif sel_name == 'DisableCameraShake': @@ -915,12 +937,12 @@ class AdvancedSettingsWindow(bui.Window): sel = self._show_user_mods_button elif sel_name == 'Plugins': sel = self._plugins_button + elif sel_name == 'DevTools': + sel = self._dev_tools_button elif sel_name == 'ModdingGuide': sel = self._modding_guide_button elif sel_name == 'LangInform': sel = self._language_inform_checkbox - elif sel_name == 'ShowDevConsole': - sel = self._show_dev_console_button_check_box.widget else: sel = None if sel is not None: diff --git a/dist/ba_data/python/bauiv1lib/settings/allsettings.py b/dist/ba_data/python/bauiv1lib/settings/allsettings.py index 98aedda..6e6ed9d 100644 --- a/dist/ba_data/python/bauiv1lib/settings/allsettings.py +++ b/dist/ba_data/python/bauiv1lib/settings/allsettings.py @@ -56,13 +56,11 @@ class AllSettingsWindow(bui.Window): scale=( 1.75 if uiscale is bui.UIScale.SMALL - else 1.35 - if uiscale is bui.UIScale.MEDIUM - else 1.0 + else 1.35 if uiscale is bui.UIScale.MEDIUM else 1.0 + ), + stack_offset=( + (0, -8) if uiscale is bui.UIScale.SMALL else (0, 0) ), - stack_offset=(0, -8) - if uiscale is bui.UIScale.SMALL - else (0, 0), ) ) diff --git a/dist/ba_data/python/bauiv1lib/settings/audio.py b/dist/ba_data/python/bauiv1lib/settings/audio.py index fc39b71..27ed2e4 100644 --- a/dist/ba_data/python/bauiv1lib/settings/audio.py +++ b/dist/ba_data/python/bauiv1lib/settings/audio.py @@ -63,9 +63,7 @@ class AudioSettingsWindow(bui.Window): base_scale = ( 2.05 if uiscale is bui.UIScale.SMALL - else 1.6 - if uiscale is bui.UIScale.MEDIUM - else 1.0 + else 1.6 if uiscale is bui.UIScale.MEDIUM else 1.0 ) popup_menu_scale = base_scale * 1.2 @@ -75,9 +73,9 @@ class AudioSettingsWindow(bui.Window): transition=transition, scale=base_scale, scale_origin_stack_offset=scale_origin, - stack_offset=(0, -20) - if uiscale is bui.UIScale.SMALL - else (0, 0), + stack_offset=( + (0, -20) if uiscale is bui.UIScale.SMALL else (0, 0) + ), ) ) diff --git a/dist/ba_data/python/bauiv1lib/settings/controls.py b/dist/ba_data/python/bauiv1lib/settings/controls.py index 108657f..8c28836 100644 --- a/dist/ba_data/python/bauiv1lib/settings/controls.py +++ b/dist/ba_data/python/bauiv1lib/settings/controls.py @@ -124,9 +124,7 @@ class ControlsSettingsWindow(bui.Window): scale=( smallscale if uiscale is bui.UIScale.SMALL - else 1.5 - if uiscale is bui.UIScale.MEDIUM - else 1.0 + else 1.5 if uiscale is bui.UIScale.MEDIUM else 1.0 ), ) ) diff --git a/dist/ba_data/python/bauiv1lib/settings/devtools.py b/dist/ba_data/python/bauiv1lib/settings/devtools.py new file mode 100644 index 0000000..2db6e9c --- /dev/null +++ b/dist/ba_data/python/bauiv1lib/settings/devtools.py @@ -0,0 +1,228 @@ +# Released under the MIT License. See LICENSE for details. +# +"""UI functionality for Modding Tools.""" + +from __future__ import annotations + +import babase +import bauiv1 as bui +from bauiv1lib.popup import PopupMenu +from bauiv1lib.confirm import ConfirmWindow +from bauiv1lib.config import ConfigCheckBox + + +class DevToolsWindow(bui.Window): + """Window for accessing modding tools.""" + + def __init__( + self, + transition: str = 'in_right', + origin_widget: bui.Widget | None = None, + ): + + app = bui.app + assert app.classic is not None + + # If they provided an origin-widget, scale up from that. + scale_origin: tuple[float, float] | None + if origin_widget is not None: + self._transition_out = 'out_scale' + scale_origin = origin_widget.get_screen_space_center() + transition = 'in_scale' + else: + self._transition_out = 'out_right' + scale_origin = None + + uiscale = app.ui_v1.uiscale + self._width = 970.0 if uiscale is bui.UIScale.SMALL else 670.0 + x_inset = 150 if uiscale is bui.UIScale.SMALL else 0 + self._height = ( + 390.0 + if uiscale is bui.UIScale.SMALL + else 450.0 if uiscale is bui.UIScale.MEDIUM else 520.0 + ) + + self._spacing = 32 + top_extra = 10 if uiscale is bui.UIScale.SMALL else 0 + + self._scroll_width = self._width - (100 + 2 * x_inset) + self._scroll_height = self._height - 115.0 + self._sub_width = self._scroll_width * 0.95 + self._sub_height = 350.0 + + super().__init__( + root_widget=bui.containerwidget( + size=(self._width, self._height + top_extra), + transition=transition, + toolbar_visibility='menu_minimal', + scale_origin_stack_offset=scale_origin, + scale=( + 2.06 + if uiscale is bui.UIScale.SMALL + else 1.4 if uiscale is bui.UIScale.MEDIUM else 1.0 + ), + stack_offset=( + (0, -25) if uiscale is bui.UIScale.SMALL else (0, 0) + ), + ) + ) + + self._r = 'settingsDevTools' + + if app.ui_v1.use_toolbars and uiscale is bui.UIScale.SMALL: + bui.containerwidget( + edit=self._root_widget, on_cancel_call=self._do_back + ) + self._back_button = None + else: + self._back_button = bui.buttonwidget( + parent=self._root_widget, + position=(53 + x_inset, self._height - 60), + size=(140, 60), + scale=0.8, + autoselect=True, + label=bui.Lstr(resource='backText'), + button_type='back', + on_activate_call=self._do_back, + ) + bui.containerwidget( + edit=self._root_widget, cancel_button=self._back_button + ) + + self._title_text = bui.textwidget( + parent=self._root_widget, + position=(self._width * 0.5, self._height - 48), + size=(0, 25), + maxwidth=self._width - 200, + text=bui.Lstr(resource='settingsWindowAdvanced.devToolsText'), + color=app.ui_v1.title_color, + h_align='center', + v_align='center', + ) + + if self._back_button is not None: + bui.buttonwidget( + edit=self._back_button, + button_type='backSmall', + size=(60, 60), + label=bui.charstr(bui.SpecialChar.BACK), + ) + + self._scrollwidget = bui.scrollwidget( + parent=self._root_widget, + position=(50 + x_inset, 50), + simple_culling_v=20.0, + highlight=False, + size=(self._scroll_width, self._scroll_height), + selection_loops_to_parent=True, + ) + bui.widget(edit=self._scrollwidget, right_widget=self._scrollwidget) + self._subcontainer = bui.containerwidget( + parent=self._scrollwidget, + size=(self._sub_width, self._sub_height), + background=False, + selection_loops_to_parent=True, + ) + + v = self._sub_height - 35 + this_button_width = 410 + + v -= self._spacing * 2.5 + self._show_dev_console_button_check_box = ConfigCheckBox( + parent=self._subcontainer, + position=(90, v + 40), + size=(self._sub_width - 100, 30), + configkey='Show Dev Console Button', + displayname=bui.Lstr( + resource='settingsWindowAdvanced.showDevConsoleButtonText' + ), + scale=1.0, + maxwidth=400, + ) + if self._back_button is not None: + bui.widget( + edit=self._show_dev_console_button_check_box.widget, + up_widget=self._back_button, + ) + + v -= self._spacing * 1.2 + self._create_user_system_scripts_button = bui.buttonwidget( + parent=self._subcontainer, + position=(self._sub_width / 2 - this_button_width / 2, v - 10), + size=(this_button_width, 60), + autoselect=True, + label=bui.Lstr(resource='userSystemScriptsCreateText'), + text_scale=1.0, + on_activate_call=babase.modutils.create_user_system_scripts, + ) + + v -= self._spacing * 2.5 + self._delete_user_system_scripts_button = bui.buttonwidget( + parent=self._subcontainer, + position=(self._sub_width / 2 - this_button_width / 2, v - 10), + size=(this_button_width, 60), + autoselect=True, + label=bui.Lstr(resource='userSystemScriptsDeleteText'), + text_scale=1.0, + on_activate_call=lambda: ConfirmWindow( + action=babase.modutils.delete_user_system_scripts, + ), + ) + + v -= self._spacing * 2.5 + bui.textwidget( + parent=self._subcontainer, + position=(170, v + 10), + size=(0, 0), + text=bui.Lstr(resource='uiScaleText'), + color=app.ui_v1.title_color, + h_align='center', + v_align='center', + ) + + PopupMenu( + parent=self._subcontainer, + position=(230, v - 20), + button_size=(200.0, 60.0), + width=100.0, + choices=[ + 'auto', + 'small', + 'medium', + 'large', + ], + choices_display=[ + bui.Lstr(resource='autoText'), + bui.Lstr(resource='sizeSmallText'), + bui.Lstr(resource='sizeMediumText'), + bui.Lstr(resource='sizeLargeText'), + ], + current_choice=app.config.get('UI Scale', 'auto'), + on_value_change_call=self._set_uiscale, + ) + + def _set_uiscale(self, val: str) -> None: + cfg = bui.app.config + cfg['UI Scale'] = val + cfg.apply_and_commit() + if bui.app.ui_v1.uiscale.name != val.upper(): + bui.screenmessage( + bui.Lstr(resource='settingsWindowAdvanced.mustRestartText'), + color=(1.0, 0.5, 0.0), + ) + + def _do_back(self) -> None: + from bauiv1lib.settings.advanced import AdvancedSettingsWindow + + # no-op if our underlying widget is dead or on its way out. + if not self._root_widget or self._root_widget.transitioning_out: + return + + bui.containerwidget( + edit=self._root_widget, transition=self._transition_out + ) + assert bui.app.classic is not None + bui.app.ui_v1.set_main_menu_window( + AdvancedSettingsWindow(transition='in_left').get_root_widget(), + from_window=self._root_widget, + ) diff --git a/dist/ba_data/python/bauiv1lib/settings/gamepad.py b/dist/ba_data/python/bauiv1lib/settings/gamepad.py index a63847d..4661f15 100644 --- a/dist/ba_data/python/bauiv1lib/settings/gamepad.py +++ b/dist/ba_data/python/bauiv1lib/settings/gamepad.py @@ -1,5 +1,6 @@ # Released under the MIT License. See LICENSE for details. # +# pylint: disable=too-many-lines """Settings UI functionality related to gamepads.""" from __future__ import annotations @@ -7,16 +8,20 @@ from __future__ import annotations import logging from typing import TYPE_CHECKING +from bauiv1lib.popup import PopupMenuWindow import bascenev1 as bs import bauiv1 as bui if TYPE_CHECKING: from typing import Any, Callable + from bauiv1lib.popup import PopupWindow class GamepadSettingsWindow(bui.Window): """Window for configuring a gamepad.""" + # pylint: disable=too-many-public-methods + def __init__( self, gamepad: bs.InputDevice, @@ -34,7 +39,6 @@ class GamepadSettingsWindow(bui.Window): self._name = self._input.name self._r = 'configGamepadWindow' - self._settings = settings self._transition_out = transition_out # We're a secondary gamepad if supplied with settings. @@ -53,23 +57,90 @@ class GamepadSettingsWindow(bui.Window): scale=( 1.63 if uiscale is bui.UIScale.SMALL - else 1.35 - if uiscale is bui.UIScale.MEDIUM - else 1.0 + else 1.35 if uiscale is bui.UIScale.MEDIUM else 1.0 + ), + stack_offset=( + (-20, -16) if uiscale is bui.UIScale.SMALL else (0, 0) ), - stack_offset=(-20, -16) - if uiscale is bui.UIScale.SMALL - else (0, 0), transition=transition, ) ) + self._settings: dict[str, int] = {} + if not self._is_secondary: + self._get_config_mapping() # Don't ask to config joysticks while we're in here. self._rebuild_ui() - def _rebuild_ui(self) -> None: + def _get_config_mapping(self, default: bool = False) -> None: + for button in [ + 'buttonJump', + 'buttonJump_B', + 'buttonPunch', + 'buttonPunch_B', + 'buttonBomb', + 'buttonBomb_B', + 'buttonPickUp', + 'buttonPickUp_B', + 'buttonStart', + 'buttonStart_B', + 'buttonStart2', + 'buttonStart2_B', + 'buttonUp', + 'buttonUp_B', + 'buttonDown', + 'buttonDown_B', + 'buttonLeft', + 'buttonLeft_B', + 'buttonRight', + 'buttonRight_B', + 'buttonRun1', + 'buttonRun1_B', + 'buttonRun2', + 'buttonRun2_B', + 'triggerRun1', + 'triggerRun1_B', + 'triggerRun2', + 'triggerRun2_B', + 'buttonIgnored', + 'buttonIgnored_B', + 'buttonIgnored2', + 'buttonIgnored2_B', + 'buttonIgnored3', + 'buttonIgnored3_B', + 'buttonIgnored4', + 'buttonIgnored4_B', + 'buttonVRReorient', + 'buttonVRReorient_B', + 'analogStickDeadZone', + 'analogStickDeadZone_B', + 'dpad', + 'dpad_B', + 'unassignedButtonsRun', + 'unassignedButtonsRun_B', + 'startButtonActivatesDefaultWidget', + 'startButtonActivatesDefaultWidget_B', + 'uiOnly', + 'uiOnly_B', + 'ignoreCompletely', + 'ignoreCompletely_B', + 'autoRecalibrateAnalogStick', + 'autoRecalibrateAnalogStick_B', + 'analogStickLR', + 'analogStickLR_B', + 'analogStickUD', + 'analogStickUD_B', + 'enableSecondary', + ]: + assert bui.app.classic is not None + val = bui.app.classic.get_input_device_mapped_value( + self._input, button, default + ) + if val != -1: + self._settings[button] = val + + def _rebuild_ui(self, is_reset: bool = False) -> None: # pylint: disable=too-many-statements - # pylint: disable=too-many-locals assert bui.app.classic is not None @@ -79,77 +150,6 @@ class GamepadSettingsWindow(bui.Window): self._textwidgets: dict[str, bui.Widget] = {} - # If we were supplied with settings, we're a secondary joystick and - # just operate on that. in the other (normal) case we make our own. - if not self._is_secondary: - # Fill our temp config with present values (for our primary and - # secondary controls). - self._settings = {} - for skey in [ - 'buttonJump', - 'buttonJump_B', - 'buttonPunch', - 'buttonPunch_B', - 'buttonBomb', - 'buttonBomb_B', - 'buttonPickUp', - 'buttonPickUp_B', - 'buttonStart', - 'buttonStart_B', - 'buttonStart2', - 'buttonStart2_B', - 'buttonUp', - 'buttonUp_B', - 'buttonDown', - 'buttonDown_B', - 'buttonLeft', - 'buttonLeft_B', - 'buttonRight', - 'buttonRight_B', - 'buttonRun1', - 'buttonRun1_B', - 'buttonRun2', - 'buttonRun2_B', - 'triggerRun1', - 'triggerRun1_B', - 'triggerRun2', - 'triggerRun2_B', - 'buttonIgnored', - 'buttonIgnored_B', - 'buttonIgnored2', - 'buttonIgnored2_B', - 'buttonIgnored3', - 'buttonIgnored3_B', - 'buttonIgnored4', - 'buttonIgnored4_B', - 'buttonVRReorient', - 'buttonVRReorient_B', - 'analogStickDeadZone', - 'analogStickDeadZone_B', - 'dpad', - 'dpad_B', - 'unassignedButtonsRun', - 'unassignedButtonsRun_B', - 'startButtonActivatesDefaultWidget', - 'startButtonActivatesDefaultWidget_B', - 'uiOnly', - 'uiOnly_B', - 'ignoreCompletely', - 'ignoreCompletely_B', - 'autoRecalibrateAnalogStick', - 'autoRecalibrateAnalogStick_B', - 'analogStickLR', - 'analogStickLR_B', - 'analogStickUD', - 'analogStickUD_B', - 'enableSecondary', - ]: - val = bui.app.classic.get_input_device_mapped_value( - self._input, skey - ) - if val != -1: - self._settings[skey] = val - back_button: bui.Widget | None if self._is_secondary: @@ -369,22 +369,27 @@ class GamepadSettingsWindow(bui.Window): scale=1.0, ) - self._advanced_button = bui.buttonwidget( + self._more_button = bui.buttonwidget( parent=self._root_widget, autoselect=True, - label=bui.Lstr(resource=self._r + '.advancedText'), + label='...', text_scale=0.9, color=(0.45, 0.4, 0.5), textcolor=(0.65, 0.6, 0.7), position=(self._width - 300, 30), size=(130, 40), - on_activate_call=self._do_advanced, + on_activate_call=self._do_more, ) try: if cancel_button is not None and save_button is not None: bui.widget(edit=cancel_button, right_widget=save_button) bui.widget(edit=save_button, left_widget=cancel_button) + if is_reset: + bui.containerwidget( + edit=self._root_widget, + selected_child=self._more_button, + ) except Exception: logging.exception('Error wiring up gamepad config window.') @@ -394,7 +399,7 @@ class GamepadSettingsWindow(bui.Window): def get_advanced_button(self) -> bui.Widget: """(internal)""" - return self._advanced_button + return self._more_button def get_is_secondary(self) -> bool: """(internal)""" @@ -543,16 +548,12 @@ class GamepadSettingsWindow(bui.Window): sval1 = ( self._settings['analogStickLR' + self._ext] if 'analogStickLR' + self._ext in self._settings - else 5 - if self._is_secondary - else None + else 5 if self._is_secondary else None ) sval2 = ( self._settings['analogStickUD' + self._ext] if 'analogStickUD' + self._ext in self._settings - else 6 - if self._is_secondary - else None + else 6 if self._is_secondary else None ) assert isinstance(sval1, (int, type(None))) assert isinstance(sval2, (int, type(None))) @@ -595,9 +596,7 @@ class GamepadSettingsWindow(bui.Window): dpadnum = ( self._settings['dpad' + self._ext] if 'dpad' + self._ext in self._settings - else 2 - if self._is_secondary - else None + else 2 if self._is_secondary else None ) assert isinstance(dpadnum, (int, type(None))) if dpadnum is not None: @@ -809,6 +808,84 @@ class GamepadSettingsWindow(bui.Window): from_window=self._root_widget, ) + def _reset(self) -> None: + from bauiv1lib.confirm import ConfirmWindow + + assert bui.app.classic is not None + + # efro note: I think it's ok to reset without a confirm here + # because the user can see pretty clearly what changes and can + # cancel out of the settings window without saving if they want. + if bool(False): + ConfirmWindow( + # TODO: Implement a translation string for this! + 'Are you sure you want to reset your button mapping?\n' + 'This will also reset your advanced mappings\n' + 'and secondary controller button mappings.', + self._do_reset, + width=490, + height=150, + ) + else: + self._do_reset() + + def _do_reset(self) -> None: + """Resets the input's mapping settings.""" + from babase import InputDeviceNotFoundError + + self._settings = {} + # Unplugging the controller while performing a + # mapping reset makes things go bonkers a little. + try: + self._get_config_mapping(default=True) + except InputDeviceNotFoundError: + pass + + self._rebuild_ui(is_reset=True) + bui.getsound('gunCocking').play() + + def _do_more(self) -> None: + """Show a burger menu with extra settings.""" + # pylint: disable=cyclic-import + choices: list[str] = [ + 'advanced', + 'reset', + ] + choices_display: list[bui.Lstr] = [ + bui.Lstr(resource=self._r + '.advancedText'), + bui.Lstr(resource='settingsWindowAdvanced.resetText'), + ] + + uiscale = bui.app.ui_v1.uiscale + PopupMenuWindow( + position=self._more_button.get_screen_space_center(), + scale=( + 2.3 + if uiscale is bui.UIScale.SMALL + else 1.65 if uiscale is bui.UIScale.MEDIUM else 1.23 + ), + width=150, + choices=choices, + choices_display=choices_display, + current_choice='advanced', + delegate=self, + ) + + def popup_menu_selected_choice( + self, popup_window: PopupMenuWindow, choice: str + ) -> None: + """Called when a choice is selected in the popup.""" + del popup_window # unused + if choice == 'reset': + self._reset() + elif choice == 'advanced': + self._do_advanced() + else: + print(f'invalid choice: {choice}') + + def popup_menu_closing(self, popup_window: PopupWindow) -> None: + """Called when the popup is closing.""" + def _save(self) -> None: classic = bui.app.classic assert classic is not None @@ -844,7 +921,7 @@ class GamepadSettingsWindow(bui.Window): 'controllerConfig', { 'ua': classic.legacy_user_agent_string, - 'b': bui.app.env.build_number, + 'b': bui.app.env.engine_build_number, 'name': self._name, 'inputMapHash': inputhash, 'config': dst2, @@ -893,9 +970,7 @@ class AwaitGamepadInputWindow(bui.Window): scale=( 2.0 if uiscale is bui.UIScale.SMALL - else 1.9 - if uiscale is bui.UIScale.MEDIUM - else 1.0 + else 1.9 if uiscale is bui.UIScale.MEDIUM else 1.0 ), size=(width, height), transition='in_scale', diff --git a/dist/ba_data/python/bauiv1lib/settings/gamepadadvanced.py b/dist/ba_data/python/bauiv1lib/settings/gamepadadvanced.py index d44300f..4249cbb 100644 --- a/dist/ba_data/python/bauiv1lib/settings/gamepadadvanced.py +++ b/dist/ba_data/python/bauiv1lib/settings/gamepadadvanced.py @@ -42,13 +42,11 @@ class GamepadAdvancedSettingsWindow(bui.Window): * ( 1.85 if uiscale is bui.UIScale.SMALL - else 1.35 - if uiscale is bui.UIScale.MEDIUM - else 1.0 + else 1.35 if uiscale is bui.UIScale.MEDIUM else 1.0 + ), + stack_offset=( + (0, -25) if uiscale is bui.UIScale.SMALL else (0, 0) ), - stack_offset=(0, -25) - if uiscale is bui.UIScale.SMALL - else (0, 0), scale_origin_stack_offset=(advb.get_screen_space_center()), ) ) diff --git a/dist/ba_data/python/bauiv1lib/settings/gamepadselect.py b/dist/ba_data/python/bauiv1lib/settings/gamepadselect.py index d0dba0a..416d48f 100644 --- a/dist/ba_data/python/bauiv1lib/settings/gamepadselect.py +++ b/dist/ba_data/python/bauiv1lib/settings/gamepadselect.py @@ -45,9 +45,7 @@ def gamepad_configure_callback(event: dict[str, Any]) -> None: scale=( 1.7 if uiscale is bui.UIScale.SMALL - else 1.4 - if uiscale is bui.UIScale.MEDIUM - else 1.0 + else 1.4 if uiscale is bui.UIScale.MEDIUM else 1.0 ), size=(width, height), transition='in_right', @@ -122,9 +120,7 @@ class GamepadSelectWindow(bui.Window): scale=( 2.3 if uiscale is bui.UIScale.SMALL - else 1.5 - if uiscale is bui.UIScale.MEDIUM - else 1.0 + else 1.5 if uiscale is bui.UIScale.MEDIUM else 1.0 ), size=(width, height), transition='in_right', diff --git a/dist/ba_data/python/bauiv1lib/settings/graphics.py b/dist/ba_data/python/bauiv1lib/settings/graphics.py index f441826..3eaf429 100644 --- a/dist/ba_data/python/bauiv1lib/settings/graphics.py +++ b/dist/ba_data/python/bauiv1lib/settings/graphics.py @@ -75,9 +75,7 @@ class GraphicsSettingsWindow(bui.Window): base_scale = ( 2.0 if uiscale is bui.UIScale.SMALL - else 1.5 - if uiscale is bui.UIScale.MEDIUM - else 1.0 + else 1.5 if uiscale is bui.UIScale.MEDIUM else 1.0 ) popup_menu_scale = base_scale * 1.2 v = height - 50 @@ -88,9 +86,9 @@ class GraphicsSettingsWindow(bui.Window): transition=transition, scale_origin_stack_offset=scale_origin, scale=base_scale, - stack_offset=(0, -30) - if uiscale is bui.UIScale.SMALL - else (0, 0), + stack_offset=( + (0, -30) if uiscale is bui.UIScale.SMALL else (0, 0) + ), ) ) @@ -174,9 +172,11 @@ class GraphicsSettingsWindow(bui.Window): width=150, scale=popup_menu_scale, choices=['Auto', 'Higher', 'High', 'Medium', 'Low'], - choices_disabled=['Higher', 'High'] - if bui.get_max_graphics_quality() == 'Medium' - else [], + choices_disabled=( + ['Higher', 'High'] + if bui.get_max_graphics_quality() == 'Medium' + else [] + ), choices_display=[ bui.Lstr(resource='autoText'), bui.Lstr(resource=self._r + '.higherText'), diff --git a/dist/ba_data/python/bauiv1lib/settings/keyboard.py b/dist/ba_data/python/bauiv1lib/settings/keyboard.py index 1e56437..7023250 100644 --- a/dist/ba_data/python/bauiv1lib/settings/keyboard.py +++ b/dist/ba_data/python/bauiv1lib/settings/keyboard.py @@ -6,11 +6,13 @@ from __future__ import annotations from typing import TYPE_CHECKING +from bauiv1lib.popup import PopupMenuWindow import bauiv1 as bui import bascenev1 as bs if TYPE_CHECKING: from typing import Any + from bauiv1lib.popup import PopupWindow class ConfigKeyboardWindow(bui.Window): @@ -39,25 +41,19 @@ class ConfigKeyboardWindow(bui.Window): scale=( 1.6 if uiscale is bui.UIScale.SMALL - else 1.3 - if uiscale is bui.UIScale.MEDIUM - else 1.0 + else 1.3 if uiscale is bui.UIScale.MEDIUM else 1.0 ), stack_offset=(0, 5) if uiscale is bui.UIScale.SMALL else (0, 0), transition=transition, ) ) + self._settings: dict[str, int] = {} + self._get_config_mapping() + self._rebuild_ui() - def _rebuild_ui(self) -> None: - assert bui.app.classic is not None - - for widget in self._root_widget.get_children(): - widget.delete() - - # Fill our temp config with present values. - self._settings: dict[str, int] = {} + def _get_config_mapping(self, default: bool = False) -> None: for button in [ 'buttonJump', 'buttonPunch', @@ -70,12 +66,20 @@ class ConfigKeyboardWindow(bui.Window): 'buttonLeft', 'buttonRight', ]: - self._settings[ - button - ] = bui.app.classic.get_input_device_mapped_value( - self._input, button + assert bui.app.classic is not None + self._settings[button] = ( + bui.app.classic.get_input_device_mapped_value( + self._input, button, default + ) ) + def _rebuild_ui(self, is_reset: bool = False) -> None: + assert bui.app.classic is not None + + for widget in self._root_widget.get_children(): + widget.delete() + + # b_off = 0 if self._unique_id != '#1' else 9 cancel_button = bui.buttonwidget( parent=self._root_widget, autoselect=True, @@ -101,9 +105,6 @@ class ConfigKeyboardWindow(bui.Window): start_button=save_button, ) - bui.widget(edit=cancel_button, right_widget=save_button) - bui.widget(edit=save_button, left_widget=cancel_button) - v = self._height - 74.0 bui.textwidget( parent=self._root_widget, @@ -213,6 +214,24 @@ class ConfigKeyboardWindow(bui.Window): scale=1.0, ) + self._more_button = bui.buttonwidget( + parent=self._root_widget, + autoselect=True, + label='...', + text_scale=0.9, + color=(0.45, 0.4, 0.5), + textcolor=(0.65, 0.6, 0.7), + position=(self._width * 0.5 - 65, 30), + size=(130, 40), + on_activate_call=self._do_more, + ) + + if is_reset: + bui.containerwidget( + edit=self._root_widget, + selected_child=self._more_button, + ) + def _pretty_button_name(self, button_name: str) -> bui.Lstr: button_id = self._settings[button_name] if button_id == -1: @@ -282,6 +301,70 @@ class ConfigKeyboardWindow(bui.Window): from_window=self._root_widget, ) + def _reset(self) -> None: + from bauiv1lib.confirm import ConfirmWindow + + assert bui.app.classic is not None + + # efro note: I think it's ok to reset without a confirm here + # because the user can see pretty clearly what changes and can + # cancel out of the keyboard settings edit if they want. + if bool(False): + ConfirmWindow( + # TODO: Implement a translation string for this! + 'Are you sure you want to reset your button mapping?', + self._do_reset, + width=480, + height=95, + ) + else: + self._do_reset() + + def _do_reset(self) -> None: + """Resets the input's mapping settings.""" + self._settings = {} + self._get_config_mapping(default=True) + self._rebuild_ui(is_reset=True) + bui.getsound('gunCocking').play() + + def _do_more(self) -> None: + """Show a burger menu with extra settings.""" + # pylint: disable=cyclic-import + choices: list[str] = [ + 'reset', + ] + choices_display: list[bui.Lstr] = [ + bui.Lstr(resource='settingsWindowAdvanced.resetText'), + ] + + uiscale = bui.app.ui_v1.uiscale + PopupMenuWindow( + position=self._more_button.get_screen_space_center(), + scale=( + 2.3 + if uiscale is bui.UIScale.SMALL + else 1.65 if uiscale is bui.UIScale.MEDIUM else 1.23 + ), + width=150, + choices=choices, + choices_display=choices_display, + current_choice='reset', + delegate=self, + ) + + def popup_menu_selected_choice( + self, popup_window: PopupMenuWindow, choice: str + ) -> None: + """Called when a choice is selected in the popup.""" + del popup_window # unused + if choice == 'reset': + self._reset() + else: + print(f'invalid choice: {choice}') + + def popup_menu_closing(self, popup_window: PopupWindow) -> None: + """Called when the popup is closing.""" + def _save(self) -> None: from bauiv1lib.settings.controls import ControlsSettingsWindow @@ -316,7 +399,7 @@ class ConfigKeyboardWindow(bui.Window): { 'ua': bui.app.classic.legacy_user_agent_string, 'name': self._name, - 'b': bui.app.env.build_number, + 'b': bui.app.env.engine_build_number, 'config': dst2, 'v': 2, }, @@ -347,9 +430,7 @@ class AwaitKeyboardInputWindow(bui.Window): scale=( 2.0 if uiscale is bui.UIScale.SMALL - else 1.5 - if uiscale is bui.UIScale.MEDIUM - else 1.0 + else 1.5 if uiscale is bui.UIScale.MEDIUM else 1.0 ), ) ) diff --git a/dist/ba_data/python/bauiv1lib/settings/nettesting.py b/dist/ba_data/python/bauiv1lib/settings/nettesting.py index e4e6e99..2e7f7e8 100644 --- a/dist/ba_data/python/bauiv1lib/settings/nettesting.py +++ b/dist/ba_data/python/bauiv1lib/settings/nettesting.py @@ -37,9 +37,7 @@ class NetTestingWindow(bui.Window): scale=( 1.56 if uiscale is bui.UIScale.SMALL - else 1.2 - if uiscale is bui.UIScale.MEDIUM - else 0.8 + else 1.2 if uiscale is bui.UIScale.MEDIUM else 0.8 ), stack_offset=(0.0, -7 if uiscale is bui.UIScale.SMALL else 0.0), transition=transition, @@ -164,6 +162,7 @@ class NetTestingWindow(bui.Window): def _run_diagnostics(weakwin: weakref.ref[NetTestingWindow]) -> None: # pylint: disable=too-many-statements + # pylint: disable=too-many-branches from efro.util import utc_now @@ -250,8 +249,11 @@ def _run_diagnostics(weakwin: weakref.ref[NetTestingWindow]) -> None: curv1addr = plus.get_master_server_address(version=1) _print(f'\nUsing V1 address: {curv1addr}') - _print('\nRunning V1 transaction...') - _print_test_results(_test_v1_transaction) + if plus.get_v1_account_state() == 'signed_in': + _print('\nRunning V1 transaction...') + _print_test_results(_test_v1_transaction) + else: + _print('\nSkipping V1 transaction (Not signed into V1).') # V2 ping baseaddr = plus.get_master_server_address(version=2) diff --git a/dist/ba_data/python/bauiv1lib/settings/plugins.py b/dist/ba_data/python/bauiv1lib/settings/plugins.py index b5f9332..bcece4e 100644 --- a/dist/ba_data/python/bauiv1lib/settings/plugins.py +++ b/dist/ba_data/python/bauiv1lib/settings/plugins.py @@ -58,9 +58,7 @@ class PluginWindow(bui.Window): self._height = ( 390.0 if uiscale is bui.UIScale.SMALL - else 450.0 - if uiscale is bui.UIScale.MEDIUM - else 520.0 + else 450.0 if uiscale is bui.UIScale.MEDIUM else 520.0 ) top_extra = 10 if uiscale is bui.UIScale.SMALL else 0 super().__init__( @@ -72,13 +70,11 @@ class PluginWindow(bui.Window): scale=( 2.06 if uiscale is bui.UIScale.SMALL - else 1.4 - if uiscale is bui.UIScale.MEDIUM - else 1.0 + else 1.4 if uiscale is bui.UIScale.MEDIUM else 1.0 + ), + stack_offset=( + (0, -25) if uiscale is bui.UIScale.SMALL else (0, 0) ), - stack_offset=(0, -25) - if uiscale is bui.UIScale.SMALL - else (0, 0), ) ) @@ -252,9 +248,7 @@ class PluginWindow(bui.Window): scale=( 2.3 if uiscale is bui.UIScale.SMALL - else 1.65 - if uiscale is bui.UIScale.MEDIUM - else 1.23 + else 1.65 if uiscale is bui.UIScale.MEDIUM else 1.23 ), choices=[c.value for c in Category], choices_display=[bui.Lstr(resource=c.resource) for c in Category], @@ -345,7 +339,6 @@ class PluginWindow(bui.Window): show = not enabled else: assert_never(self._category) - # show = False if not show: continue @@ -365,9 +358,11 @@ class PluginWindow(bui.Window): textcolor=( (0.8, 0.3, 0.3) if (plugspec.attempted_load and plugspec.plugin is None) - else (0.6, 0.6, 0.6) - if plugspec.plugin is None - else (0, 1, 0) + else ( + (0.6, 0.6, 0.6) + if plugspec.plugin is None + else (0, 1, 0) + ) ), ) # noinspection PyUnresolvedReferences diff --git a/dist/ba_data/python/bauiv1lib/settings/pluginsettings.py b/dist/ba_data/python/bauiv1lib/settings/pluginsettings.py index 1474f5c..d59554f 100644 --- a/dist/ba_data/python/bauiv1lib/settings/pluginsettings.py +++ b/dist/ba_data/python/bauiv1lib/settings/pluginsettings.py @@ -22,9 +22,7 @@ class PluginSettingsWindow(bui.Window): height = ( 365.0 if uiscale is bui.UIScale.SMALL - else 300.0 - if uiscale is bui.UIScale.MEDIUM - else 370.0 + else 300.0 if uiscale is bui.UIScale.MEDIUM else 370.0 ) top_extra = 10 if uiscale is bui.UIScale.SMALL else 0 @@ -37,13 +35,11 @@ class PluginSettingsWindow(bui.Window): scale=( 2.06 if uiscale is bui.UIScale.SMALL - else 1.4 - if uiscale is bui.UIScale.MEDIUM - else 1.0 + else 1.4 if uiscale is bui.UIScale.MEDIUM else 1.0 + ), + stack_offset=( + (0, -25) if uiscale is bui.UIScale.SMALL else (0, 0) ), - stack_offset=(0, -25) - if uiscale is bui.UIScale.SMALL - else (0, 0), ) ) diff --git a/dist/ba_data/python/bauiv1lib/settings/remoteapp.py b/dist/ba_data/python/bauiv1lib/settings/remoteapp.py index 3542f99..80fc3c1 100644 --- a/dist/ba_data/python/bauiv1lib/settings/remoteapp.py +++ b/dist/ba_data/python/bauiv1lib/settings/remoteapp.py @@ -24,13 +24,11 @@ class RemoteAppSettingsWindow(bui.Window): scale=( 1.85 if uiscale is bui.UIScale.SMALL - else 1.3 - if uiscale is bui.UIScale.MEDIUM - else 1.0 + else 1.3 if uiscale is bui.UIScale.MEDIUM else 1.0 + ), + stack_offset=( + (-10, 0) if uiscale is bui.UIScale.SMALL else (0, 0) ), - stack_offset=(-10, 0) - if uiscale is bui.UIScale.SMALL - else (0, 0), ) ) btn = bui.buttonwidget( @@ -87,9 +85,6 @@ class RemoteAppSettingsWindow(bui.Window): ) v -= 90 - # hmm the itms:// version doesnt bounce through safari but is kinda - # apple-specific-ish - # Update: now we just show link to the remote webpage. bui.textwidget( parent=self._root_widget, diff --git a/dist/ba_data/python/bauiv1lib/settings/testing.py b/dist/ba_data/python/bauiv1lib/settings/testing.py index 30f11e2..5af029b 100644 --- a/dist/ba_data/python/bauiv1lib/settings/testing.py +++ b/dist/ba_data/python/bauiv1lib/settings/testing.py @@ -36,13 +36,11 @@ class TestingWindow(bui.Window): scale=( 2.5 if uiscale is bui.UIScale.SMALL - else 1.2 - if uiscale is bui.UIScale.MEDIUM - else 1.0 + else 1.2 if uiscale is bui.UIScale.MEDIUM else 1.0 + ), + stack_offset=( + (0, -28) if uiscale is bui.UIScale.SMALL else (0, 0) ), - stack_offset=(0, -28) - if uiscale is bui.UIScale.SMALL - else (0, 0), ) ) self._back_button = btn = bui.buttonwidget( @@ -114,9 +112,9 @@ class TestingWindow(bui.Window): # If we haven't yet, record the default value for this name so # we can reset if we want.. if entry_name not in bui.app.classic.value_test_defaults: - bui.app.classic.value_test_defaults[ - entry_name - ] = bui.app.classic.value_test(entry_name) + bui.app.classic.value_test_defaults[entry_name] = ( + bui.app.classic.value_test(entry_name) + ) bui.textwidget( parent=self._subcontainer, diff --git a/dist/ba_data/python/bauiv1lib/settings/touchscreen.py b/dist/ba_data/python/bauiv1lib/settings/touchscreen.py index d77a16a..b3e1d2b 100644 --- a/dist/ba_data/python/bauiv1lib/settings/touchscreen.py +++ b/dist/ba_data/python/bauiv1lib/settings/touchscreen.py @@ -35,9 +35,7 @@ class TouchscreenSettingsWindow(bui.Window): scale=( 1.9 if uiscale is bui.UIScale.SMALL - else 1.55 - if uiscale is bui.UIScale.MEDIUM - else 1.2 + else 1.55 if uiscale is bui.UIScale.MEDIUM else 1.2 ), ) ) diff --git a/dist/ba_data/python/bauiv1lib/soundtrack/browser.py b/dist/ba_data/python/bauiv1lib/soundtrack/browser.py index a3b561b..c25b888 100644 --- a/dist/ba_data/python/bauiv1lib/soundtrack/browser.py +++ b/dist/ba_data/python/bauiv1lib/soundtrack/browser.py @@ -43,9 +43,7 @@ class SoundtrackBrowserWindow(bui.Window): self._height = ( 340 if uiscale is bui.UIScale.SMALL - else 370 - if uiscale is bui.UIScale.MEDIUM - else 440 + else 370 if uiscale is bui.UIScale.MEDIUM else 440 ) spacing = 40.0 v = self._height - 40.0 @@ -60,13 +58,11 @@ class SoundtrackBrowserWindow(bui.Window): scale=( 2.3 if uiscale is bui.UIScale.SMALL - else 1.6 - if uiscale is bui.UIScale.MEDIUM - else 1.0 + else 1.6 if uiscale is bui.UIScale.MEDIUM else 1.0 + ), + stack_offset=( + (0, -18) if uiscale is bui.UIScale.SMALL else (0, 0) ), - stack_offset=(0, -18) - if uiscale is bui.UIScale.SMALL - else (0, 0), ) ) @@ -110,9 +106,7 @@ class SoundtrackBrowserWindow(bui.Window): scl = ( 1.0 if uiscale is bui.UIScale.SMALL - else 1.13 - if uiscale is bui.UIScale.MEDIUM - else 1.4 + else 1.13 if uiscale is bui.UIScale.MEDIUM else 1.4 ) v -= 60.0 * scl self._new_button = btn = bui.buttonwidget( @@ -245,9 +239,11 @@ class SoundtrackBrowserWindow(bui.Window): bui.widget( edit=self._scrollwidget, left_widget=self._new_button, - right_widget=bui.get_special_widget('party_button') - if bui.app.ui_v1.use_toolbars - else self._scrollwidget, + right_widget=( + bui.get_special_widget('party_button') + if bui.app.ui_v1.use_toolbars + else self._scrollwidget + ), ) self._col = bui.columnwidget(parent=scrollwidget, border=2, margin=0) @@ -286,8 +282,9 @@ class SoundtrackBrowserWindow(bui.Window): bui.getsound('shieldDown').play() assert self._selected_soundtrack_index is not None assert self._soundtracks is not None - if self._selected_soundtrack_index >= len(self._soundtracks): - self._selected_soundtrack_index = len(self._soundtracks) + self._selected_soundtrack_index = min( + self._selected_soundtrack_index, len(self._soundtracks) + ) self._refresh() def _delete_soundtrack(self) -> None: @@ -392,7 +389,7 @@ class SoundtrackBrowserWindow(bui.Window): def _back(self) -> None: # pylint: disable=cyclic-import - from bauiv1lib.settings import audio + from bauiv1lib.settings.audio import AudioSettingsWindow # no-op if our underlying widget is dead or on its way out. if not self._root_widget or self._root_widget.transitioning_out: @@ -404,7 +401,7 @@ class SoundtrackBrowserWindow(bui.Window): ) assert bui.app.classic is not None bui.app.ui_v1.set_main_menu_window( - audio.AudioSettingsWindow(transition='in_left').get_root_widget(), + AudioSettingsWindow(transition='in_left').get_root_widget(), from_window=self._root_widget, ) @@ -552,7 +549,10 @@ class SoundtrackBrowserWindow(bui.Window): return self._save_state() bui.containerwidget(edit=self._root_widget, transition='out_left') - SoundtrackEditWindow(existing_soundtrack=None) + bui.app.ui_v1.set_main_menu_window( + SoundtrackEditWindow(existing_soundtrack=None).get_root_widget(), + from_window=self._root_widget, + ) def _create_done(self, new_soundtrack: str) -> None: if new_soundtrack is not None: diff --git a/dist/ba_data/python/bauiv1lib/soundtrack/edit.py b/dist/ba_data/python/bauiv1lib/soundtrack/edit.py index 8c3887e..4ede4d8 100644 --- a/dist/ba_data/python/bauiv1lib/soundtrack/edit.py +++ b/dist/ba_data/python/bauiv1lib/soundtrack/edit.py @@ -35,9 +35,7 @@ class SoundtrackEditWindow(bui.Window): self._height = ( 395 if uiscale is bui.UIScale.SMALL - else 450 - if uiscale is bui.UIScale.MEDIUM - else 560 + else 450 if uiscale is bui.UIScale.MEDIUM else 560 ) super().__init__( root_widget=bui.containerwidget( @@ -46,15 +44,13 @@ class SoundtrackEditWindow(bui.Window): scale=( 2.08 if uiscale is bui.UIScale.SMALL - else 1.5 - if uiscale is bui.UIScale.MEDIUM - else 1.0 + else 1.5 if uiscale is bui.UIScale.MEDIUM else 1.0 + ), + stack_offset=( + (0, -48) + if uiscale is bui.UIScale.SMALL + else (0, 15) if uiscale is bui.UIScale.MEDIUM else (0, 0) ), - stack_offset=(0, -48) - if uiscale is bui.UIScale.SMALL - else (0, 15) - if uiscale is bui.UIScale.MEDIUM - else (0, 0), ) ) cancel_button = bui.buttonwidget( @@ -266,13 +262,11 @@ class SoundtrackEditWindow(bui.Window): icon=( self._file_tex if icon_type == 'file' - else self._folder_tex - if icon_type == 'folder' - else None + else self._folder_tex if icon_type == 'folder' else None + ), + icon_color=( + (1.1, 0.8, 0.2) if icon_type == 'folder' else (1, 1, 1) ), - icon_color=(1.1, 0.8, 0.2) - if icon_type == 'folder' - else (1, 1, 1), left_widget=self._text_field, iconscale=0.7, autoselect=True, @@ -314,9 +308,11 @@ class SoundtrackEditWindow(bui.Window): label=bui.Lstr(resource=self._r + '.testText'), text_scale=0.6, on_activate_call=bui.Call(self._test, bs.MusicType(song_type)), - up_widget=prev_test_button - if prev_test_button is not None - else self._text_field, + up_widget=( + prev_test_button + if prev_test_button is not None + else self._text_field + ), ) if prev_test_button is not None: bui.widget(edit=prev_test_button, down_widget=btn) @@ -427,7 +423,7 @@ class SoundtrackEditWindow(bui.Window): return None def _cancel(self) -> None: - from bauiv1lib.soundtrack import browser as stb + from bauiv1lib.soundtrack.browser import SoundtrackBrowserWindow # no-op if our underlying widget is dead or on its way out. if not self._root_widget or self._root_widget.transitioning_out: @@ -440,12 +436,12 @@ class SoundtrackEditWindow(bui.Window): music.set_music_play_mode(bui.app.classic.MusicPlayMode.REGULAR) bui.containerwidget(edit=self._root_widget, transition='out_right') bui.app.ui_v1.set_main_menu_window( - stb.SoundtrackBrowserWindow(transition='in_left').get_root_widget(), + SoundtrackBrowserWindow(transition='in_left').get_root_widget(), from_window=self._root_widget, ) def _do_it(self) -> None: - from bauiv1lib.soundtrack import browser as stb + from bauiv1lib.soundtrack.browser import SoundtrackBrowserWindow # no-op if our underlying widget is dead or on its way out. if not self._root_widget or self._root_widget.transitioning_out: @@ -499,7 +495,7 @@ class SoundtrackEditWindow(bui.Window): ) bui.app.ui_v1.set_main_menu_window( - stb.SoundtrackBrowserWindow(transition='in_left').get_root_widget(), + SoundtrackBrowserWindow(transition='in_left').get_root_widget(), from_window=self._root_widget, ) diff --git a/dist/ba_data/python/bauiv1lib/soundtrack/entrytypeselect.py b/dist/ba_data/python/bauiv1lib/soundtrack/entrytypeselect.py index 583855a..c1a7d2e 100644 --- a/dist/ba_data/python/bauiv1lib/soundtrack/entrytypeselect.py +++ b/dist/ba_data/python/bauiv1lib/soundtrack/entrytypeselect.py @@ -63,9 +63,7 @@ class SoundtrackEntryTypeSelectWindow(bui.Window): scale=( 1.7 if uiscale is bui.UIScale.SMALL - else 1.4 - if uiscale is bui.UIScale.MEDIUM - else 1.0 + else 1.4 if uiscale is bui.UIScale.MEDIUM else 1.0 ), ), cleanupcheck=False, diff --git a/dist/ba_data/python/bauiv1lib/specialoffer.py b/dist/ba_data/python/bauiv1lib/specialoffer.py index 6e4a463..24ae04d 100644 --- a/dist/ba_data/python/bauiv1lib/specialoffer.py +++ b/dist/ba_data/python/bauiv1lib/specialoffer.py @@ -86,13 +86,11 @@ class SpecialOfferWindow(bui.Window): scale=( 1.2 if uiscale is bui.UIScale.SMALL - else 1.15 - if uiscale is bui.UIScale.MEDIUM - else 1.0 + else 1.15 if uiscale is bui.UIScale.MEDIUM else 1.0 + ), + stack_offset=( + (0, -15) if uiscale is bui.UIScale.SMALL else (0, 0) ), - stack_offset=(0, -15) - if uiscale is bui.UIScale.SMALL - else (0, 0), ) ) self._is_bundle_sale = False @@ -310,9 +308,11 @@ class SpecialOfferWindow(bui.Window): self._cancel_button = bui.buttonwidget( parent=self._root_widget, - position=(50, 40) - if self._is_bundle_sale - else (self._width * 0.5 - 75, 40), + position=( + (50, 40) + if self._is_bundle_sale + else (self._width * 0.5 - 75, 40) + ), size=(150, 60), scale=1.0, on_activate_call=self._cancel, @@ -322,9 +322,11 @@ class SpecialOfferWindow(bui.Window): self._cancel_countdown_text = bui.textwidget( parent=self._root_widget, text='', - position=(50 + 150 + 20, 40 + 27) - if self._is_bundle_sale - else (self._width * 0.5 - 75 + 150 + 20, 40 + 27), + position=( + (50 + 150 + 20, 40 + 27) + if self._is_bundle_sale + else (self._width * 0.5 - 75 + 150 + 20, 40 + 27) + ), scale=1.1, size=(0, 0), h_align='left', @@ -349,12 +351,14 @@ class SpecialOfferWindow(bui.Window): bui.containerwidget( edit=self._root_widget, cancel_button=self._cancel_button, - start_button=self._purchase_button - if self._is_bundle_sale - else None, - selected_child=self._purchase_button - if self._is_bundle_sale - else display['button'], + start_button=( + self._purchase_button if self._is_bundle_sale else None + ), + selected_child=( + self._purchase_button + if self._is_bundle_sale + else display['button'] + ), ) def _stop_flashing(self) -> None: @@ -373,12 +377,12 @@ class SpecialOfferWindow(bui.Window): def _update_cancel_button_graphics(self) -> None: bui.buttonwidget( edit=self._cancel_button, - color=(0.5, 0.5, 0.5) - if self._cancel_delay > 0 - else (0.7, 0.4, 0.34), - textcolor=(0.5, 0.5, 0.5) - if self._cancel_delay > 0 - else (0.9, 0.9, 1.0), + color=( + (0.5, 0.5, 0.5) if self._cancel_delay > 0 else (0.7, 0.4, 0.34) + ), + textcolor=( + (0.5, 0.5, 0.5) if self._cancel_delay > 0 else (0.9, 0.9, 1.0) + ), ) bui.textwidget( edit=self._cancel_countdown_text, diff --git a/dist/ba_data/python/bauiv1lib/store/browser.py b/dist/ba_data/python/bauiv1lib/store/browser.py index 555e5e1..901fc9f 100644 --- a/dist/ba_data/python/bauiv1lib/store/browser.py +++ b/dist/ba_data/python/bauiv1lib/store/browser.py @@ -15,6 +15,7 @@ from enum import Enum from threading import Thread from typing import TYPE_CHECKING +from efro.util import utc_now from efro.error import CommunicationError import bacommon.cloud import bauiv1 as bui @@ -81,9 +82,7 @@ class StoreBrowserWindow(bui.Window): self._height = ( 578 if uiscale is bui.UIScale.SMALL - else 645 - if uiscale is bui.UIScale.MEDIUM - else 800 + else 645 if uiscale is bui.UIScale.MEDIUM else 800 ) self._current_tab: StoreBrowserWindow.TabID | None = None extra_top = 30 if uiscale is bui.UIScale.SMALL else 0 @@ -100,17 +99,13 @@ class StoreBrowserWindow(bui.Window): scale=( 1.3 if uiscale is bui.UIScale.SMALL - else 0.9 - if uiscale is bui.UIScale.MEDIUM - else 0.8 + else 0.9 if uiscale is bui.UIScale.MEDIUM else 0.8 ), scale_origin_stack_offset=scale_origin, stack_offset=( (0, -5) if uiscale is bui.UIScale.SMALL - else (0, 0) - if uiscale is bui.UIScale.MEDIUM - else (0, 0) + else (0, 0) if uiscale is bui.UIScale.MEDIUM else (0, 0) ), ) ) @@ -566,8 +561,8 @@ class StoreBrowserWindow(bui.Window): 'item': item, 'platform': app.classic.platform, 'subplatform': app.classic.subplatform, - 'version': app.env.version, - 'buildNumber': app.env.build_number, + 'version': app.env.engine_version, + 'buildNumber': app.env.engine_build_number, 'purchaseType': 'ticket' if is_ticket_purchase else 'real', }, callback=bui.WeakCall( @@ -687,8 +682,10 @@ class StoreBrowserWindow(bui.Window): # Look at the current set of sales; filter any with time remaining. for sale_item, sale_info in list(sales_raw.items()): to_end = ( - datetime.datetime.utcfromtimestamp(sale_info['e']) - - datetime.datetime.utcnow() + datetime.datetime.fromtimestamp( + sale_info['e'], datetime.UTC + ) + - utc_now() ).total_seconds() if to_end > 0: sales[sale_item] = { @@ -905,27 +902,23 @@ class StoreBrowserWindow(bui.Window): dummy_name = 'icons.foo' else: dummy_name = '' - section[ - 'button_size' - ] = cstore.get_store_item_display_size(dummy_name) + section['button_size'] = ( + cstore.get_store_item_display_size(dummy_name) + ) section['v_spacing'] = ( -25 if ( self._tab == 'extras' and uiscale is bui.UIScale.SMALL ) - else -17 - if self._tab == 'characters' - else 0 + else -17 if self._tab == 'characters' else 0 ) if 'title' not in section: section['title'] = '' section['x_offs'] = ( 130 if self._tab == 'extras' - else 270 - if self._tab == 'maps' - else 0 + else 270 if self._tab == 'maps' else 0 ) section['y_offs'] = ( 20 @@ -934,14 +927,14 @@ class StoreBrowserWindow(bui.Window): and uiscale is bui.UIScale.SMALL and bui.app.config.get('Merch Link') ) - else 55 - if ( - self._tab == 'extras' - and uiscale is bui.UIScale.SMALL + else ( + 55 + if ( + self._tab == 'extras' + and uiscale is bui.UIScale.SMALL + ) + else -20 if self._tab == 'icons' else 0 ) - else -20 - if self._tab == 'icons' - else 0 ) def instantiate( diff --git a/dist/ba_data/python/bauiv1lib/store/button.py b/dist/ba_data/python/bauiv1lib/store/button.py index c53e051..f205035 100644 --- a/dist/ba_data/python/bauiv1lib/store/button.py +++ b/dist/ba_data/python/bauiv1lib/store/button.py @@ -6,6 +6,8 @@ from __future__ import annotations import logging from typing import TYPE_CHECKING +from efro.util import utc_now + import bauiv1 as bui if TYPE_CHECKING: @@ -278,8 +280,10 @@ class StoreButton: for sale_item, sale_info in list(sales_raw.items()): if not plus.get_purchased(sale_item): to_end = ( - datetime.datetime.utcfromtimestamp(sale_info['e']) - - datetime.datetime.utcnow() + datetime.datetime.fromtimestamp( + sale_info['e'], datetime.UTC + ) + - utc_now() ).total_seconds() if to_end > 0: sale_times.append(to_end) diff --git a/dist/ba_data/python/bauiv1lib/store/item.py b/dist/ba_data/python/bauiv1lib/store/item.py index 3a80146..ee779cb 100644 --- a/dist/ba_data/python/bauiv1lib/store/item.py +++ b/dist/ba_data/python/bauiv1lib/store/item.py @@ -83,16 +83,20 @@ def instantiate_store_item_display( tint_color = ( item_info['color'] if 'color' in item_info - else character.default_color - if character.default_color is not None - else (1, 1, 1) + else ( + character.default_color + if character.default_color is not None + else (1, 1, 1) + ) ) tint2_color = ( item_info['highlight'] if 'highlight' in item_info - else character.default_highlight - if character.default_highlight is not None - else (1, 1, 1) + else ( + character.default_highlight + if character.default_highlight is not None + else (1, 1, 1) + ) ) icon_tex = character.icon_texture tint_tex = character.icon_mask_texture diff --git a/dist/ba_data/python/bauiv1lib/teamnamescolors.py b/dist/ba_data/python/bauiv1lib/teamnamescolors.py index 3fe9545..dc03060 100644 --- a/dist/ba_data/python/bauiv1lib/teamnamescolors.py +++ b/dist/ba_data/python/bauiv1lib/teamnamescolors.py @@ -4,9 +4,7 @@ from __future__ import annotations -from typing import TYPE_CHECKING, cast - -from typing_extensions import override +from typing import TYPE_CHECKING, cast, override from bauiv1lib.popup import PopupWindow from bauiv1lib.colorpicker import ColorPicker @@ -33,9 +31,7 @@ class TeamNamesColorsWindow(PopupWindow): scale = ( 1.69 if uiscale is bui.UIScale.SMALL - else 1.1 - if uiscale is bui.UIScale.MEDIUM - else 0.85 + else 1.1 if uiscale is bui.UIScale.MEDIUM else 0.85 ) super().__init__( position=scale_origin, size=(self._width, self._height), scale=scale diff --git a/dist/ba_data/python/bauiv1lib/tournamententry.py b/dist/ba_data/python/bauiv1lib/tournamententry.py index 90996bc..4595d34 100644 --- a/dist/ba_data/python/bauiv1lib/tournamententry.py +++ b/dist/ba_data/python/bauiv1lib/tournamententry.py @@ -5,16 +5,13 @@ from __future__ import annotations import logging -from typing import TYPE_CHECKING - -from typing_extensions import override +from typing import TYPE_CHECKING, override from bauiv1lib.popup import PopupWindow import bauiv1 as bui if TYPE_CHECKING: from typing import Any, Callable - import bascenev1 as bs @@ -32,6 +29,7 @@ class TournamentEntryWindow(PopupWindow): on_close_call: Callable[[], Any] | None = None, ): # Needs some tidying. + # pylint: disable=too-many-locals # pylint: disable=too-many-branches # pylint: disable=too-many-statements @@ -73,20 +71,28 @@ class TournamentEntryWindow(PopupWindow): scale = ( 2.3 if uiscale is bui.UIScale.SMALL - else 1.65 - if uiscale is bui.UIScale.MEDIUM - else 1.23 + else 1.65 if uiscale is bui.UIScale.MEDIUM else 1.23 ) self._delegate = delegate self._transitioning_out = False self._tournament_activity = tournament_activity - self._width = 340 - self._height = 225 + self._width: float = 340.0 + self._height: float = 225.0 bg_color = (0.5, 0.4, 0.6) + # Show the practice button as long as we're not + # restarting while on a paid tournament run. + self._do_practice = ( + self._tournament_activity is None + and bui.app.config.get('tournament_practice_enabled', False) + ) + + off_p = 0 if not self._do_practice else 48 + self._height += off_p * 0.933 + # Creates our root_widget. super().__init__( position=position, @@ -109,7 +115,7 @@ class TournamentEntryWindow(PopupWindow): self._cancel_button = bui.buttonwidget( parent=self.root_widget, - position=(20, self._height - 34), + position=(40, self._height - 34), size=(60, 60), scale=0.5, label='', @@ -134,15 +140,15 @@ class TournamentEntryWindow(PopupWindow): btn = self._pay_with_tickets_button = bui.buttonwidget( parent=self.root_widget, - position=(30 + x_offs, 60), + position=(30 + x_offs, 60 + off_p), autoselect=True, button_type='square', size=(120, 120), label='', on_activate_call=self._on_pay_with_tickets_press, ) - self._ticket_img_pos = (50 + x_offs, 94) - self._ticket_img_pos_free = (50 + x_offs, 80) + self._ticket_img_pos = (50 + x_offs, 94 + off_p) + self._ticket_img_pos_free = (50 + x_offs, 80 + off_p) self._ticket_img = bui.imagewidget( parent=self.root_widget, draw_controller=btn, @@ -150,8 +156,8 @@ class TournamentEntryWindow(PopupWindow): position=self._ticket_img_pos, texture=bui.gettexture('tickets'), ) - self._ticket_cost_text_position = (87 + x_offs, 88) - self._ticket_cost_text_position_free = (87 + x_offs, 120) + self._ticket_cost_text_position = (87 + x_offs, 88 + off_p) + self._ticket_cost_text_position_free = (87 + x_offs, 120 + off_p) self._ticket_cost_text = bui.textwidget( parent=self.root_widget, draw_controller=btn, @@ -167,7 +173,7 @@ class TournamentEntryWindow(PopupWindow): self._free_plays_remaining_text = bui.textwidget( parent=self.root_widget, draw_controller=btn, - position=(87 + x_offs, 78), + position=(87 + x_offs, 78 + off_p), size=(0, 0), h_align='center', v_align='center', @@ -180,7 +186,7 @@ class TournamentEntryWindow(PopupWindow): if self._do_ad_btn: btn = self._pay_with_ad_btn = bui.buttonwidget( parent=self.root_widget, - position=(190, 60), + position=(190, 60 + off_p), autoselect=True, button_type='square', size=(120, 120), @@ -191,21 +197,23 @@ class TournamentEntryWindow(PopupWindow): parent=self.root_widget, draw_controller=btn, size=(80, 80), - position=(210, 94), + position=(210, 94 + off_p), texture=bui.gettexture('tv'), ) - self._ad_text_position = (251, 88) - self._ad_text_position_remaining = (251, 92) + self._ad_text_position = (251, 88 + off_p) + self._ad_text_position_remaining = (251, 92 + off_p) have_ad_tries_remaining = ( self._tournament_info['adTriesRemaining'] is not None ) self._ad_text = bui.textwidget( parent=self.root_widget, draw_controller=btn, - position=self._ad_text_position_remaining - if have_ad_tries_remaining - else self._ad_text_position, + position=( + self._ad_text_position_remaining + if have_ad_tries_remaining + else self._ad_text_position + ), size=(0, 0), h_align='center', v_align='center', @@ -224,7 +232,7 @@ class TournamentEntryWindow(PopupWindow): self._ad_plays_remaining_text = bui.textwidget( parent=self.root_widget, draw_controller=btn, - position=(251, 78), + position=(251, 78 + off_p), size=(0, 0), h_align='center', v_align='center', @@ -236,7 +244,7 @@ class TournamentEntryWindow(PopupWindow): bui.textwidget( parent=self.root_widget, - position=(self._width * 0.5, 120), + position=(self._width * 0.5, 120 + off_p), size=(0, 0), h_align='center', v_align='center', @@ -250,13 +258,26 @@ class TournamentEntryWindow(PopupWindow): else: self._pay_with_ad_btn = None + btn_size = (150, 45) + btn_pos = (self._width / 2 - btn_size[0] / 2, self._width / 2 - 110) + self._practice_button = None + if self._do_practice: + self._practice_button = bui.buttonwidget( + parent=self.root_widget, + position=btn_pos, + autoselect=True, + size=btn_size, + label=bui.Lstr(resource='practiceText'), + on_activate_call=self._on_practice_press, + ) + self._get_tickets_button: bui.Widget | None = None self._ticket_count_text: bui.Widget | None = None if not bui.app.ui_v1.use_toolbars: if bui.app.classic.allow_ticket_purchases: self._get_tickets_button = bui.buttonwidget( parent=self.root_widget, - position=(self._width - 190 + 125, self._height - 34), + position=(self._width - 190 + 105, self._height - 34), autoselect=True, scale=0.5, size=(120, 60), @@ -372,6 +393,8 @@ class TournamentEntryWindow(PopupWindow): sel = self.root_widget.get_selected_child() if sel == self._pay_with_ad_btn: sel_name = 'Ad' + elif sel == self._practice_button: + sel_name = 'Practice' else: sel_name = 'Tickets' cfg = bui.app.config @@ -382,6 +405,8 @@ class TournamentEntryWindow(PopupWindow): sel_name = bui.app.config.get('Tournament Pay Selection', 'Tickets') if sel_name == 'Ad' and self._pay_with_ad_btn is not None: sel = self._pay_with_ad_btn + elif sel_name == 'Practice' and self._practice_button is not None: + sel = self._practice_button else: sel = self._pay_with_tickets_button bui.containerwidget(edit=self.root_widget, selected_child=sel) @@ -407,9 +432,11 @@ class TournamentEntryWindow(PopupWindow): ): plus.tournament_query( args={ - 'source': 'entry window' - if self._tournament_activity is None - else 'retry entry window' + 'source': ( + 'entry window' + if self._tournament_activity is None + else 'retry entry window' + ) }, callback=bui.WeakCall(self._on_tournament_query_response), ) @@ -448,35 +475,43 @@ class TournamentEntryWindow(PopupWindow): subs=[ ( '${COUNT}', - str(self._purchase_price) - if self._purchase_price is not None - else '?', + ( + str(self._purchase_price) + if self._purchase_price is not None + else '?' + ), ) ], ) ), - position=self._ticket_cost_text_position_free - if self._purchase_price == 0 - else self._ticket_cost_text_position, + position=( + self._ticket_cost_text_position_free + if self._purchase_price == 0 + else self._ticket_cost_text_position + ), scale=1.0 if self._purchase_price == 0 else 0.6, ) bui.textwidget( edit=self._free_plays_remaining_text, - text='' - if ( - self._tournament_info['freeTriesRemaining'] in [None, 0] - or self._purchase_price != 0 - ) - else '' + str(self._tournament_info['freeTriesRemaining']), + text=( + '' + if ( + self._tournament_info['freeTriesRemaining'] in [None, 0] + or self._purchase_price != 0 + ) + else '' + str(self._tournament_info['freeTriesRemaining']) + ), ) bui.imagewidget( edit=self._ticket_img, opacity=0.2 if self._purchase_price == 0 else 1.0, - position=self._ticket_img_pos_free - if self._purchase_price == 0 - else self._ticket_img_pos, + position=( + self._ticket_img_pos_free + if self._purchase_price == 0 + else self._ticket_img_pos + ), ) if self._do_ad_btn: @@ -487,9 +522,11 @@ class TournamentEntryWindow(PopupWindow): ) bui.textwidget( edit=self._ad_text, - position=self._ad_text_position_remaining - if have_ad_tries_remaining - else self._ad_text_position, + position=( + self._ad_text_position_remaining + if have_ad_tries_remaining + else self._ad_text_position + ), color=(0, 1, 0) if enabled else (0.5, 0.5, 0.5), ) bui.imagewidget( @@ -525,29 +562,37 @@ class TournamentEntryWindow(PopupWindow): text=bui.charstr(bui.SpecialChar.TICKET) + t_str, ) - def _launch(self) -> None: + def _launch(self, practice: bool = False) -> None: assert bui.app.classic is not None if self._launched: return self._launched = True launched = False - # If they gave us an existing activity, just restart it. - if self._tournament_activity is not None: + # If they gave us an existing, non-consistent + # practice activity, just restart it. + if ( + self._tournament_activity is not None + and not practice == self._tournament_activity.session.submit_score + ): try: - bui.apptimer(0.1, bui.getsound('cashRegister').play) + if not practice: + bui.apptimer(0.1, bui.getsound('cashRegister').play) + bui.screenmessage( + bui.Lstr( + translate=( + 'serverResponses', + 'Entering tournament...', + ) + ), + color=(0, 1, 0), + ) + bui.apptimer(0 if practice else 0.3, self._transition_out) + launched = True with self._tournament_activity.context: self._tournament_activity.end( {'outcome': 'restart'}, force=True ) - bui.apptimer(0.3, self._transition_out) - launched = True - bui.screenmessage( - bui.Lstr( - translate=('serverResponses', 'Entering tournament...') - ), - color=(0, 1, 0), - ) # We can hit exceptions here if _tournament_activity ends before # our restart attempt happens. @@ -560,27 +605,31 @@ class TournamentEntryWindow(PopupWindow): # If we had no existing activity (or were unable to restart it) # launch a new session. if not launched: - bui.apptimer(0.1, bui.getsound('cashRegister').play) - bui.apptimer( - 1.0, - lambda: bui.app.classic.launch_coop_game( - self._tournament_info['game'], - args={ - 'min_players': self._tournament_info['minPlayers'], - 'max_players': self._tournament_info['maxPlayers'], - 'tournament_id': self._tournament_id, - }, + if not practice: + bui.apptimer(0.1, bui.getsound('cashRegister').play) + bui.screenmessage( + bui.Lstr( + translate=('serverResponses', 'Entering tournament...') + ), + color=(0, 1, 0), ) - if bui.app.classic is not None - else None, - ) - bui.apptimer(0.7, self._transition_out) - bui.screenmessage( - bui.Lstr( - translate=('serverResponses', 'Entering tournament...') + bui.apptimer( + 0 if practice else 1.0, + lambda: ( + bui.app.classic.launch_coop_game( + self._tournament_info['game'], + args={ + 'min_players': self._tournament_info['minPlayers'], + 'max_players': self._tournament_info['maxPlayers'], + 'tournament_id': self._tournament_id, + 'submit_score': not practice, + }, + ) + if bui.app.classic is not None + else None ), - color=(0, 1, 0), ) + bui.apptimer(0 if practice else 1.25, self._transition_out) def _on_pay_with_tickets_press(self) -> None: from bauiv1lib import getcurrency @@ -677,6 +726,25 @@ class TournamentEntryWindow(PopupWindow): on_completion_call=bui.WeakCall(self._on_ad_complete), ) + def _on_practice_press(self) -> None: + plus = bui.app.plus + assert plus is not None + + # If we're already entering, ignore. + if self._entering: + return + + # Deny if it looks like the tourney has ended. + if self._seconds_remaining == 0: + bui.screenmessage( + bui.Lstr(resource='tournamentEndedText'), color=(1, 0, 0) + ) + bui.getsound('error').play() + return + + self._entering = True + self._launch(practice=True) + def _on_ad_complete(self, actually_showed: bool) -> None: plus = bui.app.plus assert plus is not None diff --git a/dist/ba_data/python/bauiv1lib/tournamentscores.py b/dist/ba_data/python/bauiv1lib/tournamentscores.py index ebd44de..e4be2db 100644 --- a/dist/ba_data/python/bauiv1lib/tournamentscores.py +++ b/dist/ba_data/python/bauiv1lib/tournamentscores.py @@ -4,9 +4,7 @@ from __future__ import annotations -from typing import TYPE_CHECKING - -from typing_extensions import override +from typing import TYPE_CHECKING, override from bauiv1lib.popup import PopupWindow import bauiv1 as bui @@ -48,9 +46,7 @@ class TournamentScoresWindow(PopupWindow): scale = ( 2.3 if uiscale is bui.UIScale.SMALL - else 1.65 - if uiscale is bui.UIScale.MEDIUM - else 1.23 + else 1.65 if uiscale is bui.UIScale.MEDIUM else 1.23 ) self._transitioning_out = False @@ -58,9 +54,7 @@ class TournamentScoresWindow(PopupWindow): self._height = ( 300 if uiscale is bui.UIScale.SMALL - else 370 - if uiscale is bui.UIScale.MEDIUM - else 450 + else 370 if uiscale is bui.UIScale.MEDIUM else 450 ) bg_color = (0.5, 0.4, 0.6) diff --git a/dist/ba_data/python/bauiv1lib/trophies.py b/dist/ba_data/python/bauiv1lib/trophies.py index e605ad7..a7982f6 100644 --- a/dist/ba_data/python/bauiv1lib/trophies.py +++ b/dist/ba_data/python/bauiv1lib/trophies.py @@ -4,9 +4,7 @@ from __future__ import annotations -from typing import TYPE_CHECKING - -from typing_extensions import override +from typing import TYPE_CHECKING, override from bauiv1lib import popup import bauiv1 as bui @@ -31,9 +29,7 @@ class TrophiesWindow(popup.PopupWindow): scale = ( 2.3 if uiscale is bui.UIScale.SMALL - else 1.65 - if uiscale is bui.UIScale.MEDIUM - else 1.23 + else 1.65 if uiscale is bui.UIScale.MEDIUM else 1.23 ) self._transitioning_out = False self._width = 300 @@ -179,9 +175,9 @@ class TrophiesWindow(popup.PopupWindow): scale=0.4, flatness=1.0, shadow=0.0, - color=(0.63, 0.6, 0.75) - if (t_count > 0) - else (0.6, 0.6, 0.6, 0.4), + color=( + (0.63, 0.6, 0.75) if (t_count > 0) else (0.6, 0.6, 0.6, 0.4) + ), text=txt, size=(0, 0), h_align='center', @@ -193,9 +189,9 @@ class TrophiesWindow(popup.PopupWindow): parent=self._subcontainer, position=(sub_width * 0.88, sub_height - 20 - incr * i), maxwidth=sub_width * 0.3, - color=(0.7, 0.8, 1.0) - if (t_count > 0) - else (0.9, 0.9, 1.0, 0.3), + color=( + (0.7, 0.8, 1.0) if (t_count > 0) else (0.9, 0.9, 1.0, 0.3) + ), flatness=1.0, shadow=0.0, scale=0.5, diff --git a/dist/ba_data/python/bauiv1lib/url.py b/dist/ba_data/python/bauiv1lib/url.py index 2e9b833..bb19912 100644 --- a/dist/ba_data/python/bauiv1lib/url.py +++ b/dist/ba_data/python/bauiv1lib/url.py @@ -27,9 +27,7 @@ class ShowURLWindow(bui.Window): scale=( 1.25 if uiscale is bui.UIScale.SMALL - else 1.25 - if uiscale is bui.UIScale.MEDIUM - else 1.25 + else 1.25 if uiscale is bui.UIScale.MEDIUM else 1.25 ), ) ) diff --git a/dist/ba_data/python/bauiv1lib/v2upgrade.py b/dist/ba_data/python/bauiv1lib/v2upgrade.py index de39822..1ce5b3b 100644 --- a/dist/ba_data/python/bauiv1lib/v2upgrade.py +++ b/dist/ba_data/python/bauiv1lib/v2upgrade.py @@ -28,9 +28,7 @@ class V2UpgradeWindow(bui.Window): scale=( 1.25 if uiscale is bui.UIScale.SMALL - else 1.25 - if uiscale is bui.UIScale.MEDIUM - else 1.25 + else 1.25 if uiscale is bui.UIScale.MEDIUM else 1.25 ), ) ) diff --git a/dist/ba_data/python/bauiv1lib/watch.py b/dist/ba_data/python/bauiv1lib/watch.py index b963612..0757c68 100644 --- a/dist/ba_data/python/bauiv1lib/watch.py +++ b/dist/ba_data/python/bauiv1lib/watch.py @@ -60,9 +60,7 @@ class WatchWindow(bui.Window): self._height = ( 578 if uiscale is bui.UIScale.SMALL - else 670 - if uiscale is bui.UIScale.MEDIUM - else 800 + else 670 if uiscale is bui.UIScale.MEDIUM else 800 ) self._current_tab: WatchWindow.TabID | None = None extra_top = 20 if uiscale is bui.UIScale.SMALL else 0 @@ -76,15 +74,13 @@ class WatchWindow(bui.Window): scale=( 1.3 if uiscale is bui.UIScale.SMALL - else 0.97 - if uiscale is bui.UIScale.MEDIUM - else 0.8 + else 0.97 if uiscale is bui.UIScale.MEDIUM else 0.8 + ), + stack_offset=( + (0, -10) + if uiscale is bui.UIScale.SMALL + else (0, 15) if uiscale is bui.UIScale.MEDIUM else (0, 0) ), - stack_offset=(0, -10) - if uiscale is bui.UIScale.SMALL - else (0, 15) - if uiscale is bui.UIScale.MEDIUM - else (0, 0), ) ) @@ -249,16 +245,12 @@ class WatchWindow(bui.Window): b_height = ( 107 if uiscale is bui.UIScale.SMALL - else 142 - if uiscale is bui.UIScale.MEDIUM - else 190 + else 142 if uiscale is bui.UIScale.MEDIUM else 190 ) b_space_extra = ( 0 if uiscale is bui.UIScale.SMALL - else -2 - if uiscale is bui.UIScale.MEDIUM - else -5 + else -2 if uiscale is bui.UIScale.MEDIUM else -5 ) b_color = (0.6, 0.53, 0.63) @@ -268,9 +260,7 @@ class WatchWindow(bui.Window): - ( 48 if uiscale is bui.UIScale.SMALL - else 45 - if uiscale is bui.UIScale.MEDIUM - else 40 + else 45 if uiscale is bui.UIScale.MEDIUM else 40 ) - b_height ) @@ -393,9 +383,7 @@ class WatchWindow(bui.Window): scale=( 1.8 if uiscale is bui.UIScale.SMALL - else 1.55 - if uiscale is bui.UIScale.MEDIUM - else 1.0 + else 1.55 if uiscale is bui.UIScale.MEDIUM else 1.0 ), size=(c_width, c_height), transition='in_scale', @@ -581,9 +569,9 @@ class WatchWindow(bui.Window): parent=self._columnwidget, size=(self._my_replays_scroll_width / t_scale, 30), selectable=True, - color=(1.0, 1, 0.4) - if name == '__lastReplay.brp' - else (1, 1, 1), + color=( + (1.0, 1, 0.4) if name == '__lastReplay.brp' else (1, 1, 1) + ), always_highlight=True, on_select_call=bui.Call(self._on_my_replay_select, name), on_activate_call=self._my_replays_watch_replay_button.activate, diff --git a/dist/ba_data/python/efro/cloudshell.py b/dist/ba_data/python/efro/cloudshell.py index 3965d07..2d6b63e 100644 --- a/dist/ba_data/python/efro/cloudshell.py +++ b/dist/ba_data/python/efro/cloudshell.py @@ -32,6 +32,7 @@ class HostConfig: user: str = 'ubuntu' port: int = 22 mosh_port: int | None = None + mosh_port_2: int | None = None mosh_server_path: str | None = None mosh_shell: str = 'sh' workspaces_root: str = '/home/${USER}/cloudshell_workspaces' diff --git a/dist/ba_data/python/efro/dataclassio/__init__.py b/dist/ba_data/python/efro/dataclassio/__init__.py index eae9c82..5c76cc7 100644 --- a/dist/ba_data/python/efro/dataclassio/__init__.py +++ b/dist/ba_data/python/efro/dataclassio/__init__.py @@ -32,6 +32,7 @@ from efro.dataclassio._api import ( dataclass_from_dict, dataclass_from_json, dataclass_validate, + dataclass_hash, ) __all__ = [ @@ -47,6 +48,7 @@ __all__ = [ 'dataclass_to_dict', 'dataclass_to_json', 'dataclass_validate', + 'dataclass_hash', 'ioprep', 'ioprepped', 'is_ioprepped_dataclass', diff --git a/dist/ba_data/python/efro/dataclassio/_api.py b/dist/ba_data/python/efro/dataclassio/_api.py index 0bd5f89..ae0554e 100644 --- a/dist/ba_data/python/efro/dataclassio/_api.py +++ b/dist/ba_data/python/efro/dataclassio/_api.py @@ -10,6 +10,7 @@ data formats in a nondestructive manner. from __future__ import annotations +import json from enum import Enum from typing import TYPE_CHECKING, TypeVar @@ -79,7 +80,6 @@ def dataclass_to_json( By default, keys are sorted for pretty output and not otherwise, but this can be overridden by supplying a value for the 'sort_keys' arg. """ - import json jdict = dataclass_to_dict( obj=obj, coerce_to_float=coerce_to_float, codec=Codec.JSON @@ -142,11 +142,10 @@ def dataclass_from_json( allow_unknown_attrs: bool = True, discard_unknown_attrs: bool = False, ) -> T: - """Utility function; return a dataclass instance given a json string. + """Return a dataclass instance given a json string. Basically dataclass_from_dict(json.loads(...)) """ - import json return dataclass_from_dict( cls=cls, @@ -167,3 +166,27 @@ def dataclass_validate( _Outputter( obj, create=False, codec=codec, coerce_to_float=coerce_to_float ).run() + + +def dataclass_hash(obj: Any, coerce_to_float: bool = True) -> str: + """Calculate a hash for the provided dataclass. + + Basically this emits json for the dataclass (with keys sorted + to keep things deterministic) and hashes the resulting string. + """ + import hashlib + from base64 import urlsafe_b64encode + + json_dict = dataclass_to_dict( + obj, codec=Codec.JSON, coerce_to_float=coerce_to_float + ) + + # Need to sort keys to keep things deterministic. + json_str = json.dumps(json_dict, separators=(',', ':'), sort_keys=True) + + sha = hashlib.sha256() + sha.update(json_str.encode()) + + # Go with urlsafe base64 instead of the usual hex to save some + # space, and kill those ugly padding chars at the end. + return urlsafe_b64encode(sha.digest()).decode().strip('=') diff --git a/dist/ba_data/python/efro/dataclassio/_base.py b/dist/ba_data/python/efro/dataclassio/_base.py index d2edc2f..3c38975 100644 --- a/dist/ba_data/python/efro/dataclassio/_base.py +++ b/dist/ba_data/python/efro/dataclassio/_base.py @@ -81,11 +81,6 @@ class IOMultiType(Generic[EnumT]): See tests/test_efro/test_dataclassio.py for examples. """ - # Dataclasses inheriting from an IOMultiType will store a type-id - # with this key in their serialized data. This value can be - # overridden in IOMultiType subclasses as desired. - ID_STORAGE_NAME = '_dciotype' - @classmethod def get_type(cls, type_id: EnumT) -> type[Self]: """Return a specific subclass given a type-id.""" @@ -103,6 +98,18 @@ class IOMultiType(Generic[EnumT]): assert issubclass(out, Enum) return out + @classmethod + def get_type_id_storage_name(cls) -> str: + """Return the key used to store type id in serialized data. + + The default is an obscure value so that it does not conflict + with members of individual type attrs, but in some cases one + might prefer to serialize it to something simpler like 'type' + by overriding this call. One just needs to make sure that no + encompassed types serialize anything to 'type' themself. + """ + return '_dciotype' + class IOAttrs: """For specifying io behavior in annotations. @@ -332,7 +339,7 @@ def _get_multitype_type( raise ValueError( f"Found a {type(val)} at '{fieldpath}'; expected a dict." ) - storename = cls.ID_STORAGE_NAME + storename = cls.get_type_id_storage_name() id_val = val.get(storename) if id_val is None: raise ValueError( diff --git a/dist/ba_data/python/efro/dataclassio/_inputter.py b/dist/ba_data/python/efro/dataclassio/_inputter.py index 0e19cba..41588e9 100644 --- a/dist/ba_data/python/efro/dataclassio/_inputter.py +++ b/dist/ba_data/python/efro/dataclassio/_inputter.py @@ -73,7 +73,7 @@ class _Inputter: if issubclass(self._cls, IOMultiType) and not dataclasses.is_dataclass( self._cls ): - type_id_val = values.get(self._cls.ID_STORAGE_NAME) + type_id_val = values.get(self._cls.get_type_id_storage_name()) if type_id_val is None: raise ValueError( f'No type id value present for multi-type object:' @@ -193,6 +193,9 @@ class _Inputter: if issubclass(origin, datetime.datetime): return self._datetime_from_input(cls, fieldpath, value, ioattrs) + if issubclass(origin, datetime.timedelta): + return self._timedelta_from_input(cls, fieldpath, value, ioattrs) + if origin is bytes: return self._bytes_from_input(origin, fieldpath, value) @@ -234,6 +237,7 @@ class _Inputter: associated values, and nested dataclasses should be passed as dicts. """ # pylint: disable=too-many-locals + # pylint: disable=too-many-statements # pylint: disable=too-many-branches if not isinstance(values, dict): raise TypeError( @@ -252,8 +256,8 @@ class _Inputter: fields = dataclasses.fields(cls) fields_by_name = {f.name: f for f in fields} - # Preprocess all fields to convert Annotated[] to contained types - # and IOAttrs. + # Preprocess all fields to convert Annotated[] to contained + # types and IOAttrs. parsed_field_annotations = { f.name: _parse_annotated(prep.annotations[f.name]) for f in fields } @@ -262,12 +266,25 @@ class _Inputter: # type attr. Ignore that while parsing since we already have a # definite type and it will just pollute extra-attrs otherwise. if issubclass(cls, IOMultiType): - type_id_store_name = cls.ID_STORAGE_NAME + type_id_store_name = cls.get_type_id_storage_name() + + # However we do want to make sure the class we're loading + # doesn't itself use this same name, as this could lead to + # tricky breakage. We can't verify this for types at prep + # time because IOMultiTypes are lazy-loaded, so this is + # the best we can do. + if type_id_store_name in fields_by_name: + raise RuntimeError( + f"{cls} contains a '{type_id_store_name}' field" + ' which clashes with the type-id-storage-name of' + ' the IOMultiType it inherits from.' + ) + else: type_id_store_name = None - # Go through all data in the input, converting it to either dataclass - # args or extra data. + # Go through all data in the input, converting it to either + # dataclass args or extra data. args: dict[str, Any] = {} for rawkey, value in values.items(): @@ -284,8 +301,8 @@ class _Inputter: if self._discard_unknown_attrs: continue - # Treat this like 'Any' data; ensure that it is valid - # raw json. + # Treat this like 'Any' data; ensure that it is + # valid raw json. if not _is_valid_for_codec(value, self._codec): raise TypeError( f'Unknown attr \'{key}\'' @@ -534,42 +551,6 @@ class _Inputter: for i in value ) - def _datetime_from_input( - self, cls: type, fieldpath: str, value: Any, ioattrs: IOAttrs | None - ) -> Any: - # For firestore we expect a datetime object. - if self._codec is Codec.FIRESTORE: - # Don't compare exact type here, as firestore can give us - # a subclass with extended precision. - if not isinstance(value, datetime.datetime): - raise TypeError( - f'Invalid input value for "{fieldpath}" on' - f' "{cls.__name__}";' - f' expected a datetime, got a {type(value).__name__}' - ) - check_utc(value) - return value - - assert self._codec is Codec.JSON - - # We expect a list of 7 ints. - if type(value) is not list: - raise TypeError( - f'Invalid input value for "{fieldpath}" on "{cls.__name__}";' - f' expected a list, got a {type(value).__name__}' - ) - if len(value) != 7 or not all(isinstance(x, int) for x in value): - raise ValueError( - f'Invalid input value for "{fieldpath}" on "{cls.__name__}";' - f' expected a list of 7 ints, got {[type(v) for v in value]}.' - ) - out = datetime.datetime( # type: ignore - *value, tzinfo=datetime.timezone.utc - ) - if ioattrs is not None: - ioattrs.validate_datetime(out, fieldpath) - return out - def _tuple_from_input( self, cls: type, @@ -620,3 +601,59 @@ class _Inputter: assert len(out) == len(childanntypes) return tuple(out) + + def _datetime_from_input( + self, cls: type, fieldpath: str, value: Any, ioattrs: IOAttrs | None + ) -> Any: + # For firestore we expect a datetime object. + if self._codec is Codec.FIRESTORE: + # Don't compare exact type here, as firestore can give us + # a subclass with extended precision. + if not isinstance(value, datetime.datetime): + raise TypeError( + f'Invalid input value for "{fieldpath}" on' + f' "{cls.__name__}";' + f' expected a datetime, got a {type(value).__name__}' + ) + check_utc(value) + return value + + assert self._codec is Codec.JSON + + # We expect a list of 7 ints. + if type(value) is not list: + raise TypeError( + f'Invalid input value for "{fieldpath}" on "{cls.__name__}";' + f' expected a list, got a {type(value).__name__}' + ) + if len(value) != 7 or not all(isinstance(x, int) for x in value): + raise ValueError( + f'Invalid input value for "{fieldpath}" on "{cls.__name__}";' + f' expected a list of 7 ints, got {[type(v) for v in value]}.' + ) + out = datetime.datetime( # type: ignore + *value, tzinfo=datetime.timezone.utc + ) + if ioattrs is not None: + ioattrs.validate_datetime(out, fieldpath) + return out + + def _timedelta_from_input( + self, cls: type, fieldpath: str, value: Any, ioattrs: IOAttrs | None + ) -> Any: + del ioattrs # Unused. + # We expect a list of 3 ints. + if type(value) is not list: + raise TypeError( + f'Invalid input value for "{fieldpath}" on "{cls.__name__}";' + f' expected a list, got a {type(value).__name__}' + ) + if len(value) != 3 or not all(isinstance(x, int) for x in value): + raise ValueError( + f'Invalid input value for "{fieldpath}" on "{cls.__name__}";' + f' expected a list of 3 ints, got {[type(v) for v in value]}.' + ) + out = datetime.timedelta( + days=value[0], seconds=value[1], microseconds=value[2] + ) + return out diff --git a/dist/ba_data/python/efro/dataclassio/_outputter.py b/dist/ba_data/python/efro/dataclassio/_outputter.py index 216b11d..56417dd 100644 --- a/dist/ba_data/python/efro/dataclassio/_outputter.py +++ b/dist/ba_data/python/efro/dataclassio/_outputter.py @@ -12,6 +12,7 @@ from enum import Enum import dataclasses import typing import types +import json import datetime from typing import TYPE_CHECKING, cast, Any @@ -160,7 +161,15 @@ class _Outputter: assert obj.get_type(type_id) is type(obj) if self._create: assert out is not None - out[obj.ID_STORAGE_NAME] = type_id.value + storagename = obj.get_type_id_storage_name() + if any(f.name == storagename for f in fields): + raise RuntimeError( + f'dataclassio: {type(obj)} contains a' + f" '{storagename}' field which clashes with" + f' the type-id-storage-name of the IOMulticlass' + f' it inherits from.' + ) + out[storagename] = type_id.value return out @@ -330,19 +339,54 @@ class _Outputter: f' data type(s) not supported by the' f' specified codec ({self._codec.name}).' ) - return list(value) if self._create else None + # We output json-friendly values so this becomes a list. + # We need to sort the list so our output is + # deterministic and can be meaningfully compared with + # others, across processes, etc. + # + # Since we don't know what types we've got here, we + # guarantee sortability by dumping each value to a json + # string (itself with keys sorted) and using that as the + # value's sorting key. Not efficient but it works. A + # good reason to avoid set[Any] though. Perhaps we + # should just disallow it altogether. + return ( + sorted(value, key=lambda v: json.dumps(v, sort_keys=True)) + if self._create + else None + ) # We contain elements of some specified type. assert len(childanntypes) == 1 if self._create: - # Note: we output json-friendly values so this becomes - # a list. - return [ - self._process_value( - cls, fieldpath, childanntypes[0], x, ioattrs - ) - for x in value - ] + # We output json-friendly values so this becomes a list. + # We need to sort the list so our output is + # deterministic and can be meaningfully compared with + # others, across processes, etc. + # + # In this case we have a single concrete type, and for + # most incarnations of that (str, int, etc.) we can just + # sort our final output. For more complex cases, + # however, such as optional values or dataclasses, we + # need to convert everything to a json string (itself + # with keys sorted) and sort based on those strings. + # This is probably a good reason to avoid sets + # containing dataclasses or optional values. Perhaps we + # should just disallow those. + return sorted( + ( + self._process_value( + cls, fieldpath, childanntypes[0], x, ioattrs + ) + for x in value + ), + key=( + None + if childanntypes[0] in [str, int, float, bool] + else lambda v: json.dumps(v, sort_keys=True) + ), + ) + for x in value: self._process_value( cls, fieldpath, childanntypes[0], x, ioattrs @@ -410,6 +454,17 @@ class _Outputter: if self._create else None ) + if issubclass(origin, datetime.timedelta): + if not isinstance(value, origin): + raise TypeError( + f'Expected a {origin} for {fieldpath};' + f' found a {type(value)}.' + ) + return ( + [value.days, value.seconds, value.microseconds] + if self._create + else None + ) if origin is bytes: return self._process_bytes(cls, fieldpath, value) diff --git a/dist/ba_data/python/efro/dataclassio/_prep.py b/dist/ba_data/python/efro/dataclassio/_prep.py index d7f8e82..8321dbd 100644 --- a/dist/ba_data/python/efro/dataclassio/_prep.py +++ b/dist/ba_data/python/efro/dataclassio/_prep.py @@ -4,6 +4,7 @@ # Note: We do lots of comparing of exact types here which is normally # frowned upon (stuff like isinstance() is usually encouraged). +# # pylint: disable=unidiomatic-typecheck from __future__ import annotations @@ -142,8 +143,10 @@ class PrepSession: return existing_data # Sanity check. - # Note that we now support recursive types via the PREP_SESSION_ATTR, - # so we theoretically shouldn't run into this this. + # + # Note that we now support recursive types via the + # PREP_SESSION_ATTR, so we theoretically shouldn't run into this + # this. if recursion_level > MAX_RECURSION: raise RuntimeError('Max recursion exceeded.') @@ -152,20 +155,21 @@ class PrepSession: if not isinstance(cls_any, type) or not dataclasses.is_dataclass(cls): raise TypeError(f'Passed arg {cls} is not a dataclass type.') - # Add a pointer to the prep-session while doing the prep. - # This way we can ignore types that we're already in the process - # of prepping and can support recursive types. + # Add a pointer to the prep-session while doing the prep. This + # way we can ignore types that we're already in the process of + # prepping and can support recursive types. existing_prep = getattr(cls, PREP_SESSION_ATTR, None) if existing_prep is not None: if existing_prep is self: return None - # We shouldn't need to support failed preps - # or preps from multiple threads at once. + # We shouldn't need to support failed preps or preps from + # multiple threads at once. raise RuntimeError('Found existing in-progress prep.') setattr(cls, PREP_SESSION_ATTR, self) # Generate a warning on non-explicit preps; we prefer prep to - # happen explicitly at runtime so errors can be detected early on. + # happen explicitly at runtime so errors can be detected early + # on. if not self.explicit: logging.warning( 'efro.dataclassio: implicitly prepping dataclass: %s.' @@ -201,8 +205,8 @@ class PrepSession: all_storage_names: set[str] = set() storage_names_to_attr_names: dict[str, str] = {} - # Ok; we've resolved actual types for this dataclass. - # now recurse through them, verifying that we support all contained + # Ok; we've resolved actual types for this dataclass. now + # recurse through them, verifying that we support all contained # types and prepping any contained dataclass types. for attrname, anntype in resolved_annotations.items(): anntype, ioattrs = _parse_annotated(anntype) @@ -235,7 +239,8 @@ class PrepSession: recursion_level=recursion_level + 1, ) - # Success! Store our resolved stuff with the class and we're done. + # Success! Store our resolved stuff with the class and we're + # done. prepdata = PrepData( annotations=resolved_annotations, storage_names_to_attr_names=storage_names_to_attr_names, @@ -282,8 +287,8 @@ class PrepSession: if anntype is typing.Any: return - # Everything below this point assumes the annotation type resolves - # to a concrete type. + # Everything below this point assumes the annotation type + # resolves to a concrete type. if not isinstance(origin, type): raise TypeError( f'Unsupported type found for \'{attrname}\' on {cls}:' @@ -292,8 +297,8 @@ class PrepSession: # If a soft_default value/factory was passed, we do some basic # type checking on the top-level value here. We also run full - # recursive validation on values later during inputting, but this - # should catch at least some errors early on, which can be + # recursive validation on values later during inputting, but + # this should catch at least some errors early on, which can be # useful since soft_defaults are not static type checked. if ioattrs is not None: have_soft_default = False @@ -319,11 +324,13 @@ class PrepSession: if origin in SIMPLE_TYPES: return - # For sets and lists, check out their single contained type (if any). + # For sets and lists, check out their single contained type (if + # any). if origin in (list, set): childtypes = typing.get_args(anntype) if len(childtypes) == 0: - # This is equivalent to Any; nothing else needs checking. + # This is equivalent to Any; nothing else needs + # checking. return if len(childtypes) > 1: raise TypeError( @@ -346,7 +353,8 @@ class PrepSession: # For key types we support Any, str, int, # and Enums with uniform str/int values. if not childtypes or childtypes[0] is typing.Any: - # 'Any' needs no further checks (just checked per-instance). + # 'Any' needs no further checks (just checked + # per-instance). pass elif childtypes[0] in (str, int): # str and int are all good as keys. @@ -362,7 +370,8 @@ class PrepSession: # For value types we support any of our normal types. if not childtypes or _get_origin(childtypes[1]) is typing.Any: - # 'Any' needs no further checks (just checked per-instance). + # 'Any' needs no further checks (just checked + # per-instance). pass else: self.prep_type( @@ -374,9 +383,9 @@ class PrepSession: ) return - # For Tuples, simply check individual member types. - # (and, for now, explicitly disallow zero member types or usage - # of ellipsis) + # For Tuples, simply check individual member types. (and, for + # now, explicitly disallow zero member types or usage of + # ellipsis) if origin is tuple: childtypes = typing.get_args(anntype) if not childtypes: @@ -405,11 +414,16 @@ class PrepSession: self.prep_enum(origin) return - # We allow datetime objects (and google's extended subclass of them - # used in firestore, which is why we don't look for exact type here). + # We allow datetime objects (and google's extended subclass of + # them used in firestore, which is why we don't look for exact + # type here). if issubclass(origin, datetime.datetime): return + # We support datetime.timedelta. + if issubclass(origin, datetime.timedelta): + return + if dataclasses.is_dataclass(origin): self.prep_dataclass(origin, recursion_level=recursion_level + 1) return diff --git a/dist/ba_data/python/efro/dataclassio/extras.py b/dist/ba_data/python/efro/dataclassio/extras.py index c54b0c0..b92788f 100644 --- a/dist/ba_data/python/efro/dataclassio/extras.py +++ b/dist/ba_data/python/efro/dataclassio/extras.py @@ -5,9 +5,7 @@ from __future__ import annotations import dataclasses -from typing import TYPE_CHECKING - -from typing_extensions import override +from typing import TYPE_CHECKING, override if TYPE_CHECKING: from typing import Any diff --git a/dist/ba_data/python/efro/debug.py b/dist/ba_data/python/efro/debug.py index 1f9b8b9..024efcc 100644 --- a/dist/ba_data/python/efro/debug.py +++ b/dist/ba_data/python/efro/debug.py @@ -14,12 +14,17 @@ from __future__ import annotations import gc import sys +import time import types +import weakref +import threading from typing import TYPE_CHECKING if TYPE_CHECKING: from typing import Any, TextIO + from logging import Logger + ABS_MAX_LEVEL = 10 # NOTE: In general we want this toolset to allow us to explore @@ -344,3 +349,182 @@ def _printrefs( expand_ids=expand_ids, file=file, ) + + +class DeadlockDumper: + """Dumps thread states if still around after timeout seconds. + + This uses low level Python functionality so should still fire + even in the case of deadlock. + """ + + # faulthandler has a single traceback-dump-later state, so only + # one of us can control it at a time. + lock = threading.Lock() + watch_in_progress = False + + def __init__(self, timeout: float) -> None: + import faulthandler + + cls = type(self) + + with cls.lock: + if cls.watch_in_progress: + print( + 'Existing DeadlockDumper found; new one will be a no-op.', + file=sys.stderr, + ) + self.active = False + return + + # Ok; no watch is in progress; we can be the active one. + cls.watch_in_progress = True + self.active = True + faulthandler.dump_traceback_later(timeout=timeout) + + def __del__(self) -> None: + import faulthandler + + cls = type(self) + + # If we made it to here, call off the dump. But only if we are + # the active dump. + if self.active: + faulthandler.cancel_dump_traceback_later() + cls.watch_in_progress = False + + +class DeadlockWatcher: + """Individual watcher for deadlock conditions. + + Use the enable_deadlock_watchers() to enable this system. + + Next, create these in contexts where they will be torn down after + some operation completes. If any is not torn down within the + timeout period, a traceback of all threads will be dumped. + + Note that the checker thread runs a cycle every ~5 seconds, so + something stuck needs to remain stuck for 5 seconds or so to be + caught for sure. + """ + + watchers_lock: threading.Lock | None = None + watchers: list[weakref.ref[DeadlockWatcher]] | None = None + + def __init__( + self, + timeout: float = 10.0, + logger: Logger | None = None, + logextra: dict | None = None, + ) -> None: + from efro.util import caller_source_location + + # pylint: disable=not-context-manager + cls = type(self) + if cls.watchers_lock is None or cls.watchers is None: + print( + 'DeadlockWatcher created without watchers enabled.', + file=sys.stderr, + ) + return + + # All we do is record when we were made and how long till we + # expire. + self.create_time = time.monotonic() + self.timeout = timeout + self.noted_expire = False + self.logger = logger + self.logextra = logextra + self.caller_source_loc = caller_source_location() + curthread = threading.current_thread() + self.thread_id = ( + '' + if curthread.ident is None + else hex(curthread.ident).removeprefix('0x') + ) + + with cls.watchers_lock: + cls.watchers.append(weakref.ref(self)) + + @classmethod + def enable_deadlock_watchers(cls) -> None: + """Spins up deadlock-watcher functionality. + + Must be explicitly called before any DeadlockWatchers are + created. + """ + assert cls.watchers_lock is None + cls.watchers_lock = threading.Lock() + assert cls.watchers is None + cls.watchers = [] + + threading.Thread( + target=cls._deadlock_watcher_thread_main, daemon=True + ).start() + + @classmethod + def _deadlock_watcher_thread_main(cls) -> None: + # pylint: disable=not-context-manager + # pylint: disable=not-an-iterable + assert cls.watchers_lock is not None and cls.watchers is not None + + # Spin in a loop checking our watchers periodically and dumping + # state if any have timed out. The trick here is that we don't + # explicitly dump state, but rather we set up a "dead man's + # switch" that does so after some amount of time if we don't + # explicitly cancel it. This way we'll hopefully get state dumps + # even for things like total GIL deadlocks. + while True: + + # Set a dead man's switch for this pass. + dumper = DeadlockDumper(timeout=5.171) + + # Sleep most of the way through it but give ourselves time + # to turn it off if we're still responsive. + time.sleep(4.129) + now = time.monotonic() + + found_fresh_expired = False + + # If any watcher is still alive but expired, sleep past the + # timeout to force the dumper to do its thing. + with cls.watchers_lock: + + for wref in cls.watchers: + w = wref() + if ( + w is not None + and now - w.create_time > w.timeout + and not w.noted_expire + ): + # If they supplied a logger, let them know they + # should check stderr for a dump. + if w.logger is not None: + w.logger.error( + 'DeadlockWatcher at %s in thread %s' + ' with time %.2f expired;' + ' check stderr for stack traces.', + w.caller_source_loc, + w.thread_id, + w.timeout, + extra=w.logextra, + ) + found_fresh_expired = True + w.noted_expire = True + + # Important to clear this ref; otherwise we can keep + # a random watcher alive until our next time through. + w = None + + # Prune dead watchers and reset for the next pass. + cls.watchers = [w for w in cls.watchers if w() is not None] + + if found_fresh_expired: + # Push us over the dumper time limit which give us a + # lovely dump. Technically we could just do an immediate + # dump here instead, but that would give us two paths to + # maintain instead of this single one. + time.sleep(2.0) + + # Call off the dump if it hasn't happened yet. + del dumper diff --git a/dist/ba_data/python/efro/error.py b/dist/ba_data/python/efro/error.py index f0e561b..5bf8bee 100644 --- a/dist/ba_data/python/efro/error.py +++ b/dist/ba_data/python/efro/error.py @@ -3,11 +3,9 @@ """Common errors and related functionality.""" from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, override import errno -from typing_extensions import override - if TYPE_CHECKING: from typing import Any diff --git a/dist/ba_data/python/efro/log.py b/dist/ba_data/python/efro/log.py index e33e10a..3360d5e 100644 --- a/dist/ba_data/python/efro/log.py +++ b/dist/ba_data/python/efro/log.py @@ -12,10 +12,9 @@ import itertools from enum import Enum from collections import deque from dataclasses import dataclass, field -from typing import TYPE_CHECKING, Annotated +from typing import TYPE_CHECKING, Annotated, override from threading import Thread, current_thread, Lock -from typing_extensions import override from efro.util import utc_now from efro.call import tpartial from efro.terminal import Clr @@ -151,6 +150,7 @@ class LogHandler(logging.Handler): self._cache = deque[tuple[int, LogEntry]]() self._cache_index_offset = 0 self._cache_lock = Lock() + # self._report_blocking_io_on_echo_error = False self._printed_callback_error = False self._thread_bootstrapped = False self._thread = Thread(target=self._log_thread_main, daemon=True) @@ -365,13 +365,32 @@ class LogHandler(logging.Handler): # thread because the delay can throw off command line prompts or # make tight debugging harder. if self._echofile is not None: + # try: + # if self._report_blocking_io_on_echo_error: + # premsg = ( + # 'WARNING: BlockingIOError ON LOG ECHO OUTPUT;' + # ' YOU ARE PROBABLY MISSING LOGS\n' + # ) + # self._report_blocking_io_on_echo_error = False + # else: + # premsg = '' ends = LEVELNO_COLOR_CODES.get(record.levelno) namepre = f'{Clr.WHT}{record.name}:{Clr.RST} ' if ends is not None: - self._echofile.write(f'{namepre}{ends[0]}{msg}{ends[1]}\n') + self._echofile.write( + f'{namepre}{ends[0]}' + f'{msg}{ends[1]}\n' + # f'{namepre}{ends[0]}' f'{premsg}{msg}{ends[1]}\n' + ) else: self._echofile.write(f'{namepre}{msg}\n') self._echofile.flush() + # except BlockingIOError: + # # Ran into this when doing a bunch of logging; assuming + # # this is asyncio's doing?.. For now trying to survive + # # the error but telling the user something is probably + # # missing in their output. + # self._report_blocking_io_on_echo_error = True if __debug__: echotime = time.monotonic() @@ -388,6 +407,7 @@ class LogHandler(logging.Handler): ) if __debug__: + # pylint: disable=used-before-assignment # Make noise if we're taking a significant amount of time here. # Limit the noise to once every so often though; otherwise we # could get a feedback loop where every log emit results in a @@ -604,9 +624,23 @@ class FileLogEcho: self._name = name self._handler = handler + # Think this was a result of setting non-blocking stdin somehow; + # probably not needed. + # self._report_blocking_io_error = False + def write(self, output: Any) -> None: """Override standard write call.""" + # try: + # if self._report_blocking_io_error: + # self._report_blocking_io_error = False + # self._original.write( + # 'WARNING: BlockingIOError ENCOUNTERED;' + # ' OUTPUT IS PROBABLY MISSING' + # ) + self._original.write(output) + # except BlockingIOError: + # self._report_blocking_io_error = True self._handler.file_write(self._name, output) def flush(self) -> None: diff --git a/dist/ba_data/python/efro/message/_protocol.py b/dist/ba_data/python/efro/message/_protocol.py index 57dd80a..b9b0144 100644 --- a/dist/ba_data/python/efro/message/_protocol.py +++ b/dist/ba_data/python/efro/message/_protocol.py @@ -386,7 +386,6 @@ class MessageProtocol: f'\n' f'from typing import TYPE_CHECKING{ovld}{ovld2}\n' f'\n' - # f'from typing_extensions import override\n' f'{import_lines}' f'\n' f'if TYPE_CHECKING:\n' diff --git a/dist/ba_data/python/efro/util.py b/dist/ba_data/python/efro/util.py index 5fc5b3e..5034b1b 100644 --- a/dist/ba_data/python/efro/util.py +++ b/dist/ba_data/python/efro/util.py @@ -12,16 +12,6 @@ import functools from enum import Enum from typing import TYPE_CHECKING, cast, TypeVar, Generic -_pytz_utc: Any - -# We don't *require* pytz, but we want to support it for tzinfos if available. -try: - import pytz - - _pytz_utc = pytz.utc -except ModuleNotFoundError: - _pytz_utc = None # pylint: disable=invalid-name - if TYPE_CHECKING: import asyncio from efro.call import Call as Call # 'as Call' so we re-export. @@ -112,29 +102,35 @@ def enum_by_value(cls: type[EnumT], value: Any) -> EnumT: def check_utc(value: datetime.datetime) -> None: """Ensure a datetime value is timezone-aware utc.""" - if value.tzinfo is not datetime.timezone.utc and ( - _pytz_utc is None or value.tzinfo is not _pytz_utc - ): + if value.tzinfo is not datetime.UTC: raise ValueError( - 'datetime value does not have timezone set as' - ' datetime.timezone.utc' + 'datetime value does not have timezone set as datetime.UTC' ) def utc_now() -> datetime.datetime: - """Get offset-aware current utc time. + """Get timezone-aware current utc time. - This should be used for all datetimes getting sent over the network, - used with the entity system, etc. - (datetime.utcnow() gives a utc time value, but it is not timezone-aware - which makes it less safe to use) + Just a shortcut for datetime.datetime.now(datetime.UTC). + Avoid datetime.datetime.utcnow() which is deprecated and gives naive + times. """ - return datetime.datetime.now(datetime.timezone.utc) + return datetime.datetime.now(datetime.UTC) + + +def utc_now_naive() -> datetime.datetime: + """Get naive utc time. + + This can be used to replace datetime.utcnow(), which is now deprecated. + Most all code should migrate to use timezone-aware times instead of + this. + """ + return datetime.datetime.now(datetime.UTC).replace(tzinfo=None) def utc_today() -> datetime.datetime: """Get offset-aware midnight in the utc time zone.""" - now = datetime.datetime.now(datetime.timezone.utc) + now = datetime.datetime.now(datetime.UTC) return datetime.datetime( year=now.year, month=now.month, day=now.day, tzinfo=now.tzinfo ) @@ -142,7 +138,7 @@ def utc_today() -> datetime.datetime: def utc_this_hour() -> datetime.datetime: """Get offset-aware beginning of the current hour in the utc time zone.""" - now = datetime.datetime.now(datetime.timezone.utc) + now = datetime.datetime.now(datetime.UTC) return datetime.datetime( year=now.year, month=now.month, @@ -154,7 +150,7 @@ def utc_this_hour() -> datetime.datetime: def utc_this_minute() -> datetime.datetime: """Get offset-aware beginning of current minute in the utc time zone.""" - now = datetime.datetime.now(datetime.timezone.utc) + now = datetime.datetime.now(datetime.UTC) return datetime.datetime( year=now.year, month=now.month, @@ -236,7 +232,7 @@ class DirtyBit: auto_dirty_seconds: float | None = None, min_update_interval: float | None = None, ): - curtime = time.time() + curtime = time.monotonic() self._retry_interval = retry_interval self._auto_dirty_seconds = auto_dirty_seconds self._min_update_interval = min_update_interval @@ -268,11 +264,13 @@ class DirtyBit: # If we're freshly clean, set our next auto-dirty time (if we have # one). if self._dirty and not value and self._auto_dirty_seconds is not None: - self._next_auto_dirty_time = time.time() + self._auto_dirty_seconds + self._next_auto_dirty_time = ( + time.monotonic() + self._auto_dirty_seconds + ) # If we're freshly dirty, schedule an immediate update. if not self._dirty and value: - self._next_update_time = time.time() + self._next_update_time = time.monotonic() # If they want to enforce a minimum update interval, # push out the next update time if it hasn't been long enough. @@ -295,7 +293,7 @@ class DirtyBit: Takes into account the amount of time passed since the target was marked dirty or since should_update last returned True. """ - curtime = time.time() + curtime = time.monotonic() # Auto-dirty ourself if we're into that. if ( @@ -713,6 +711,27 @@ def compact_id(num: int) -> str: ) +def caller_source_location() -> str: + """Returns source file name and line of the code calling us. + + Example: 'mymodule.py:23' + """ + try: + import inspect + + frame = inspect.currentframe() + for _i in range(2): + if frame is None: + raise RuntimeError() + frame = frame.f_back + if frame is None: + raise RuntimeError() + fname = os.path.basename(frame.f_code.co_filename) + return f'{fname}:{frame.f_lineno}' + except Exception: + return '' + + def unchanging_hostname() -> str: """Return an unchanging name for the local device. @@ -871,3 +890,11 @@ def ago_str( timedelta_str(now - timeval, maxparts=maxparts, decimals=decimals) + ' ago' ) + + +def split_list(input_list: list[T], max_length: int) -> list[list[T]]: + """Split a single list into smaller lists.""" + return [ + input_list[i : i + max_length] + for i in range(0, len(input_list), max_length) + ]